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

Google Apps Script security: токени, права доступу, webhook secret і audit log для Google Sheets automation
Google Apps Script security: токени, права доступу, webhook secret і audit log для Google Sheets automation

Безпека в 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, а не копіювати токени в приклади коду.

PropertiesService зберігає API token поза кодом Apps Script і не показує секрет у клієнтському JS
PropertiesService зберігає API token поза кодом Apps Script і не показує секрет у клієнтському JS
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.

Webhook security flow: doPost, X-Auth-Token, перевірка secret і reject unauthorized request
Webhook security flow: doPost, X-Auth-Token, перевірка secret і reject unauthorized request
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.

Audit log з маскуванням персональних даних: email, phone, event і actor без повного PII
Audit log з маскуванням персональних даних: email, phone, event і actor без повного PII
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-таблицю. Не всі, хто редагує бізнес-рядки, мають бачити секрети або технічні налаштування.

Ролі editor viewer admin і least privilege для Google Sheets security
Ролі editor viewer admin і least privilege для Google Sheets security

Практична модель:

РольДоступ
admindeployment, 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.