本文へ移動
戻る
解説2026-10-14 更新読了 7分

「自分」で実行する Web アプリで、Google が守ってくれる範囲

Web アプリを「自分」として実行する設定で、広く公開したとします。 開いた人に設置者の Drive を覗かれることはありません。ただ、Google が止めてくれるのは入口までで、入口の先はコードの書き方しだいです。

「自分」で実行する Web アプリは、誰が開いても、デプロイした人の権限で動きます。設置者が承認した権限を、開いた人がアプリ越しに借りている形です。

そう聞くと不安になりますが、開いた人が設置者のアカウントを直接触れるわけではありません。触れるのは、アプリが用意した関数を通したときだけです。気をつけるのは、その関数がどこまでできてしまうかです。

Google が受け持っているところ

サーバー側のコード(.gs)は、画面には送られません。中に書いたスプレッドシートやフォルダの ID も、戻り値や HTML に入れない限り、開いた人からは見えません。

画面の HTML は、サンドボックスの iframe の中で動きます。開いた人のブラウザで動くのは、doGet が返した HTML とスクリプトだけです。

ID が知られても、それだけでファイルが開けるわけではありません。Drive のファイルは、共有の設定で許された人しか開けないからです。危ないのは、「リンクを知っている全員」に共有しているファイルです。こちらは ID がわかれば開けてしまいます。

公開している関数は、誰でも好きな引数で呼べる

google.script.run から呼べるのは、トップレベルで宣言した関数のうち、名前が _ で終わらないものすべてです。公式のガイドには、画面で一度も呼んでいない関数でも、ソースを調べた人には名前がわかる、と書かれています。

呼ぶ側は、ブラウザの開発者ツールから、好きな引数を付けて呼べます。画面のボタンを管理者にだけ出していても、関数そのものは誰からでも呼べる状態です。

そして呼ばれた関数は、設置者の権限で動きます。画面から来た値を信じて動く関数は、設置者の権限を誰にでも貸し出す窓口になります。

受け取った ID を、そのまま使わない

添付ファイルを消す関数を、こう書いたとします。

function deleteAttachment(fileId) {
  DriveApp.getFileById(fileId).setTrashed(true);
}

画面からは、添付ファイルの ID しか渡さないつもりです。ところが呼ぶ人は、設置者の Drive にあるどのファイルの ID でも渡せます。ファイルをゴミ箱に入れる DriveApp の操作は、drive(Drive 全体を読み書きする)スコープで承認されています。設置者の権限の範囲内なので、Google はこれを止めません。

画面には、アプリの中だけで通じる番号を渡します。Drive の ID は、サーバー側の表にだけ控えておきます。

function deleteAttachment(pageId, attachmentId) {
  const row = findAttachment_(pageId, attachmentId); // 自分の表から、そのページの添付を探す
  if (!row) throw new Error('添付が見つかりません');
  DriveApp.getFileById(row.driveId).setTrashed(true);
  removeAttachmentRow_(row);
}

findAttachment_() と removeAttachmentRow_() は、添付の目録を読み書きする自分の関数に置き換えてください。表にない組み合わせは、Drive に触る前に止まります。名前が _ で終わっているので、この2つは画面から直接は呼べません。

ページを見てよい人かどうかも、同じ関数の中で確かめます。目録に載っていても、見る権利のないページの添付は消させません。

エディタ用とトリガー用の関数も、窓口になる

エディタから1回だけ実行してもらう設定用の関数や、トリガーから動かす関数も、名前が _ で終わらなければ画面から呼べます。画面で使っていないので、うっかり見落としやすいところです。

毎朝お知らせのメールを送る関数を、誰かが画面から何度も呼んだとします。そのたびにメールが送られ、設置者の1日の送信枠が減っていきます。

時間主導型トリガーから動いたときは、イベントの triggerUid に、そのトリガーの ID が入っています。登録したトリガーの ID と一致したときだけ動くようにしておけば、画面から呼ばれても何もしません。

function sendDailyDigest(e) {
  const trigger = ScriptApp.getProjectTriggers()
    .find((t) => t.getHandlerFunction() === 'sendDailyDigest');
  if (!trigger || !e || String(e.triggerUid) !== trigger.getUniqueId()) return; // トリガー以外から呼ばれた

  sendDigest(); // 本来の処理
}

'sendDailyDigest' はこの関数自身の名前、sendDigest() は本来の処理に置き換えて使います。トリガーの ID は画面には渡していないので、呼ぶ側は当てられません。

ただ、triggerUid は公式には「イベントを起こしたトリガーの ID」としか説明されていません。秘密の鍵として扱える保証はないので、メールを送るような処理には「1日1回まで」のような歯止めも足しておくと安心です。送った日付を PropertiesService に控えておき、同じ日なら何もせずに終わらせます。

エディタ用の関数は、設置者が実行したときだけ動くようにします。「自分」で実行する Web アプリでは、設置者本人以外が開いても Session.getActiveUser().getEmail() は基本的に空です。エディタで設置者が実行したときはアドレスが取れるので、それを設置者のアドレスと比べます。見分け方は誰が開いているかを見分ける記事にまとめています。

戻り値に入れたものは、すべて見える

戻り値は、画面に表示していない項目も含めて、そのまま開いた人のブラウザに届きます。開発者ツールを開けば読めます。

表の行をそのまま返すと、表示に使っていない列まで渡ります。Drive の ID、メールアドレス、表の行番号のようなものです。画面に返すときは、返す項目を決めた形に詰め直してください。

function attachmentView_(row) {
  return { id: row.id, name: row.name, size: row.size }; // driveId は入れない
}

戻り値を作る関数を1つ決めておくと、あとから列が増えても、勝手に画面へ出ていくことはありません。

呼ばれるたびに、設置者の枠が減る

呼ばれた関数が使う枠は、すべて設置者のものです。連打されると、設置者が自分で使う分まで減ります。

枠 個人のアカウント Workspace
メールを送れる宛先の数 1日100件 1日1500件
トリガーで動く合計 1日90分 1日6時間
1回の実行 6分 6分

メールを送る関数や重い処理には、回数の上限を付けておくと安心です。CacheService に呼んだ回数を数えておき、一定の時間内に決めた回数を超えたら断る、という形で作れます。

公開する前に、窓口を数える

コードの中から、トップレベルにあって名前が _ で終わらない関数を全部書き出します。それがそのまま、誰でも呼べる窓口の一覧です。

1つずつ、誰が呼んでよいか、受け取った値を何と突き合わせているか、何を返しているかを確かめます。画面で使っていない関数が出てきたら、名前の末尾に _ を付けるか、中で呼んだ人を確かめてください。