Google Apps Script security: токени, права доступу і безпечні webhook-автоматизації
Google Apps Script security часто згадують тільки після того, як автоматизація вже працює. Таблиця приймає заявки, Web App отримує webhook-и, Telegram-бот відправляє повідомлення, менеджери бачать статуси. Але токен лежить у коді, Sheet відкритий за посиланням, а в логах зберігаються телефони й email клієнтів.

Безпека в Google Workspace automation - це не enterprise-розкіш. Це базова гігієна бізнес-даних. Якщо автоматизація працює з заявками, платежами, клієнтськими контактами або внутрішніми коментарями, у ТЗ мають бути правила для токенів, webhook-ів, доступів і audit log.
Чому автоматизація без доступів стає ризиком
Apps Script зручний саме тому, що швидко з'єднує Google Sheets, Gmail, Drive, Forms, Telegram і зовнішні API. Але ця швидкість легко створює небезпечні shortcuts. Якщо Web App приймає зовнішні події, базову архітектуру краще звірити з офіційним гайдом Apps Script Web Apps і не запускати webhook без secret-перевірки.
Типові помилки:
- API token записаний прямо в коді;
- секрет випадково вставили в клієнтський HTML або JS;
- Web App приймає будь-який
POSTбез secret key; - таблиця з персональними даними має доступ
Anyone with the link; - у всіх учасників editor-права;
- логи містять повні телефони, email і тексти заявок;
- немає зрозумілого audit trail, хто і що змінив.
Ризик не тільки в "хакерах". Частіше проблема побутова: посилання на Sheet переслали не тій людині, токен потрапив у скриншот, старий підрядник лишився editor-ом, а webhook endpoint знайшли в історії браузера.
Де зберігати токени
Токени не повинні лежати в коді. Для Apps Script мінімально прийнятний варіант - PropertiesService. Для складніших систем краще використовувати окремий secret manager, але в простому Google Workspace процесі ScriptProperties вже прибирає найгіршу помилку. Деталі варто перевіряти в офіційній довідці PropertiesService, а не копіювати токени в приклади коду.

function getSecret(name) {
const value = PropertiesService
.getScriptProperties()
.getProperty(name);
if (!value) {
throw new Error(`Missing secret: ${name}`);
}
return value;
}
function sendTelegramMessage(chatId, text) {
const token = getSecret("TG_BOT_TOKEN");
return UrlFetchApp.fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
method: "post",
contentType: "application/json",
payload: JSON.stringify({ chat_id: chatId, text })
});
}Не виводьте секрети в Logger.log(), не пишіть їх у Sheet і не повертайте в HTML. Якщо токен треба змінити, оновлюйте property і перевіряйте, що старий токен більше не використовується.
Webhook secret і X-Auth-Token
Якщо Apps Script Web App приймає webhook-и, він має відрізняти легітимний запит від випадкового або чужого. Найпростіший варіант - перевіряти secret у header, наприклад X-Auth-Token.

function doPost(e) {
try {
assertWebhookToken(e);
const payload = JSON.parse(e.postData.contents || "{}");
writeAuditLog({
event: "webhook_received",
actor: "site-form",
status: "ok",
payload
});
return jsonResponse({ ok: true });
} catch (error) {
writeAuditLog({
event: "webhook_rejected",
actor: "unknown",
status: "blocked",
error: String(error.message || error)
});
return jsonResponse({ ok: false, error: "Unauthorized" }, 403);
}
}
function assertWebhookToken(e) {
const expected = getSecret("WEBHOOK_SECRET");
const headers = e.headers || {};
const received = headers["X-Auth-Token"] || headers["x-auth-token"];
if (!received || received !== expected) {
throw new Error("Invalid webhook token");
}
}У деяких Apps Script deployment-сценаріях доступ до headers може бути обмеженим або відрізнятися залежно від proxy. Тоді secret можна передавати в payload, але це слабший варіант. У будь-якому випадку Web App без перевірки secret не має приймати production-дані.
Маскування персональних даних у логах
Логи потрібні, але вони не повинні ставати другою базою персональних даних. Для розбору інцидентів зазвичай достатньо маскованого email, частини телефону, типу події, статусу і короткого error message.

function maskEmail(email) {
const value = String(email || "").trim();
const [name, domain] = value.split("@");
if (!name || !domain) return "";
return `${name[0]}***@${domain}`;
}
function maskPhone(phone) {
const digits = String(phone || "").replace(/\D/g, "");
if (digits.length < 7) return "";
return `+${digits.slice(0, 5)}****${digits.slice(-2)}`;
}
function writeAuditLog(event) {
const sheet = SpreadsheetApp.getActive().getSheetByName("AuditLog");
const payload = event.payload || {};
sheet.appendRow([
new Date().toISOString(),
event.event,
event.actor || "",
maskEmail(payload.email),
maskPhone(payload.phone),
event.status,
String(event.error || "").slice(0, 300)
]);
}Якщо вам потрібні повні дані, вони мають жити в основній таблиці з контрольованим доступом, а не в логах, які потім копіюють у звіти або діагностику.
Ролі editor/viewer/admin
Google Sheets security починається з доступів. Не всі, хто дивиться звіт, мають редагувати production-таблицю. Не всі, хто редагує бізнес-рядки, мають бачити секрети або технічні налаштування.

Практична модель:
| Роль | Доступ |
|---|---|
admin | deployment, script properties, access review |
editor | робочі бізнес-таблиці без секретів |
viewer | звіти й read-only перегляд |
automation account | окремий Google account для сценаріїв |
Принцип least privilege означає: дати людині рівно той доступ, який потрібен для роботи. Якщо менеджеру треба оновлювати статус заявки, йому не потрібен доступ до script properties. Якщо клієнт дивиться dashboard, йому не потрібен editor.
Security checklist перед запуском
Перед production запуском Apps Script automation перевірте:
- секрети зберігаються в
PropertiesServiceабо secret manager; - у коді немає токенів, паролів і приватних URL;
- Web App перевіряє
X-Auth-Tokenабо інший secret; - Sheet не відкритий як
Anyone with the link, якщо містить PII; - editor-права мають тільки ті, хто реально редагує дані;
- є окремий automation account або чітко призначений owner;
- audit log пише події, але маскує email і phone;
- помилки не повертають секрети у відповідь webhook-а;
- старі підрядники й тестові акаунти прибрані з access list;
- є план secret rotation, якщо токен потрапив назовні.
Цей checklist варто додавати прямо в ТЗ. Безпека не має бути фінальним пунктом після "коли все запрацює".
Як один публічний Sheet може стати витоком
У типовому сценарії таблиця починається як зручний робочий інструмент: заявки, телефони, статуси оплат, коментарі менеджерів. Потім її відкривають за посиланням для підрядника, копіюють у звіт, показують у демонстрації або залишають доступ старому акаунту.
Проблема в тому, що Google Sheets виглядає як "просто таблиця", але фактично це база клієнтських даних. Якщо вона доступна публічно або занадто широкому колу людей, витік може статися без жодної складної атаки. Тому security потрібно закладати ще на етапі CRM автоматизації і дедуплікації лідів та черг, retry і audit log.
Правильний підхід простий: production Sheet з PII не має бути публічним. Для звітів робіть окремий read-only dashboard без зайвих персональних полів. Для логів використовуйте маскування. Для автоматизацій - окремий account і мінімальні права.
Практичний підсумок
Google Apps Script security - це набір невеликих, але обов'язкових правил. Токени не лежать у коді. Webhook перевіряє secret. Sheet не відкритий публічно з персональними даними. Ролі розділені. Логи допомагають розбирати помилки, але не дублюють всю приватну інформацію.
Якщо автоматизація працює з клієнтськими даними, security має бути частиною ТЗ з першого дня. Це не сповільнює запуск. Навпаки, зменшує шанс, що швидко зібраний Apps Script потім доведеться терміново переробляти через токен у коді, відкритий webhook або публічний Sheet.
Останні статті

OpenAI API cost control: як рахувати токени, ставити ліміти і не отримати несподіваний рахунок
OpenAI API cost control - це не страх перед LLM, а нормальна фінансова гігієна автоматизації. Якщо модель допомагає обробляти заявки, писати summary, класифікувати зверн…

Надійність автоматизації бізнес-процесів: черги, повтори і idempotency без великої інфраструктури
Надійність автоматизації бізнес-процесів починається не з Kubernetes, RabbitMQ або складної cloud-архітектури. У малому бізнесі вона часто починається з простішого питан…

RAG для бізнесу: як зробити внутрішній чат-бот по регламентах, FAQ і Google Docs
RAG для бізнесу потрібен не для того, щоб зробити ще одного "розумного чат-бота". Його нормальна задача простіша: дати співробітнику відповідь на основі внутрі…

CRM автоматизація: як прибирати дублікати лідів і ставити пріоритет менеджеру
CRM автоматизація часто починається не з купівлі великої CRM, а з менш романтичної задачі: прибрати дублікати лідів і зрозуміти, які заявки справді потребують швидкої ре…

AI помічник для продажів: summary клієнта перед дзвінком
Менеджер рідко провалює перший дзвінок через те, що не вміє говорити з клієнтом. Частіше проблема простіша: перед очима є тільки ім'я, телефон і короткий коментар із…

Автоматизація обробки замовлень: форма, Google Sheets, Telegram і статуси
Коли замовлення приходять із сайту, Google Forms, Telegram і дзвінків, хаос починається не в коді. Він починається в момент, коли незрозуміло, хто взяв заявку, чи підтве…