import Image from 'next/image';
ある週末に自分のエージェント設定を見直していて、少し背筋が寒くなりました。半年前、まだ挙動が読めなかった頃に「とりあえず全部許可」で通したパーミッションが、そのまま残っていたのです。実行ログをたどると、そのうちのいくつかは一度も使われていませんでした。使っていない権限が、静かに開いたままになっていたわけです。
承認ダイアログが多すぎる「承認疲れ」はよく語られます。けれど私が肝を冷やしたのは、その逆でした。前もって全部許可してしまったせいで、もう誰も(自分すら)その範囲を見直さなくなっていた。プロンプトが出ないことが、油断そのものになっていたのです。
ここで組み立てたいのは、Antigravity 2.2.1 の統一パーミッションで一度広く付与した権限を、行動ログを根拠に安全へ絞り込む仕組みです。鍵は、権限管理を一度きりの設定ではなく「付与 → 観測 → 回収」という回り続けるループとして設計することです。
「承認疲れ」の逆側にある失敗
統一パーミッションは、散らばっていた承認ダイアログや MCP ごとの許可リストを1枚のポリシーに束ねてくれます。設定を集約する話はAntigravity 統一パーミッションで許可設定を1枚に束ねる方法 で扱いました。ただ、束ねた先で新しい落とし穴が生まれます。
集約すると、付与は一度で済みます。一度で済むからこそ、見直されなくなります。個人開発では特にそうで、レビューしてくれる同僚がいないぶん、「昔の自分が広めに開けた穴」を誰も塞ぎません。これは攻撃を受けたときの被害範囲(ブラストラジウス)を、必要もないのに広げ続けている状態です。
私はこの失敗を、承認疲れの鏡像だと考えています。承認疲れが「プロンプトが多すぎて内容を読まなくなる」なら、こちらは「プロンプトが出ないので存在を忘れる」。対処法も鏡像で、前者は設定の集約、後者は実使用にもとづく回収 です。
絞り込みを「付与 → 観測 → 回収」のループにする
クラウドの権限管理には、付与した権限のうち一定期間使われていないものを「未使用」として洗い出す考え方があります。同じ発想を手元のエージェント運用に持ち込みます。
フェーズ やること 判断材料
付与 挙動が読めない初期は、やや広めに許可する 作業を止めないことを優先
観測 行動ログに、実際に使った操作・パスを記録し続ける ツール名・対象パス・時刻
回収 付与と実使用を突き合わせ、未使用の許可を候補として提示する 観測期間・実行回数のしきい値
大事なのは、回収を自動適用しないことです。回収フェーズが出すのはあくまで「提案」であり、最後は人が確認して締める。これは行動ログの信頼性が前提になるので、改ざんを検知できる監査ログ の設計と組み合わせて初めて成り立ちます。
データモデル:ポリシー(付与)と行動ログ(実使用)
まず2つの入力を型で定義します。付与ポリシーは「どの操作を、どのスコープまで許すか」の一覧。行動ログは「実際に何をしたか」の時系列です。
// permission-types.ts
// 操作の種類。Antigravity の統一パーミッションを手元で扱うための最小モデル
export type Verb = "read" | "write" | "exec" | "net" ;
// 付与された1件の許可。scope は glob(例: "src/**", "https://api.github.com/**")
export interface Grant {
id : string ; // 例: "write:src"
verb : Verb ;
scope : string ; // glob パターン
grantedAt : string ; // ISO8601
}
// エージェントが実際に行った1アクション(監査ログの1行)
export interface Action {
ts : string ; // ISO8601
verb : Verb ;
target : string ; // 実際に触れたパス or URL
runId : string ; // 実行を識別するID
}
export interface Policy {
grants : Grant [];
}
scope を glob にしておくのは、あとで「広すぎる許可を狭める」提案を出すためです。write:src/** と付与しておいて、実際には src/config/ 以下しか触っていなければ、狭める余地が見えます。
実装:付与と実使用を突き合わせる
観測期間ぶんの行動ログを読み込み、各付与が実際に使われたか、使われたなら実使用がどのパスに集中したかを集計します。glob 照合には、Node で広く使われている picomatch を使います。
// reconcile.ts
import picomatch from "picomatch" ;
import type { Grant, Action, Policy } from "./permission-types" ;
export interface GrantUsage {
grant : Grant ;
hitCount : number ; // この付与に一致した実アクション数
usedTargets : string []; // 実際に触れた対象(重複排除)
}
// 付与ごとに、観測期間内の実使用を集計する
export function reconcile ( policy : Policy , actions : Action []) : GrantUsage [] {
return policy.grants. map (( grant ) => {
const match = picomatch (grant.scope);
const hits = actions. filter (
( a ) => a.verb === grant.verb && match (a.target)
);
// 実際に触れた対象を重複排除して控えておく(スコープ縮小の材料)
const usedTargets = [ ...new Set (hits. map (( a ) => a.target))];
return { grant, hitCount: hits. length , usedTargets };
});
}
ここでの意図は「照合をポリシー側の glob に委ねる」ことです。行動ログ側は生のパスを素直に記録し、判定ロジックは付与ポリシーの scope に一本化します。判定を1か所に集めておくと、あとで照合規則を変えても行動ログを作り直さずに済みます。
未使用の回収と、広すぎるスコープの縮小
集計結果から2種類の提案を作ります。1つは「一度も使われなかった付与=回収候補」。もう1つは「使われてはいるが、実使用がもっと狭いパスに収まっている付与=縮小候補」です。
// suggest.ts
import type { GrantUsage } from "./reconcile" ;
export interface Suggestion {
grantId : string ;
kind : "revoke" | "narrow" ;
reason : string ;
proposedScope ?: string ; // narrow のとき、狭めた後の scope
}
// 実使用パスの共通接頭辞ディレクトリを求める(縮小提案の素)
function commonDir ( paths : string []) : string | null {
if (paths. length === 0 ) return null ;
const split = paths. map (( p ) => p. split ( "/" ));
const head = split[ 0 ];
const out : string [] = [];
for ( let i = 0 ; i < head. length ; i ++ ) {
if (split. every (( s ) => s[i] === head[i])) out. push (head[i]);
else break ;
}
// 末尾のファイル名部分は落として、ディレクトリだけを残す
return out. length > 1 ? out. slice ( 0 , - 1 ). join ( "/" ) : null ;
}
export function suggest ( usages : GrantUsage []) : Suggestion [] {
const out : Suggestion [] = [];
for ( const u of usages) {
if (u.hitCount === 0 ) {
out. push ({
grantId: u.grant.id,
kind: "revoke" ,
reason: "観測期間内に一度も行使されませんでした" ,
});
continue ;
}
const dir = commonDir (u.usedTargets);
// 付与済み scope より実使用が明らかに狭いときだけ縮小を提案
if (dir && ! u.grant.scope. startsWith (dir)) {
out. push ({
grantId: u.grant.id,
kind: "narrow" ,
reason: `実使用が ${ dir }/ 配下に収まっています` ,
proposedScope: `${ dir }/**` ,
});
}
}
return out;
}
commonDir は実際に触れたパスの共通接頭辞ディレクトリを取り、そこまでスコープを狭める案を出します。ファイル名部分を落としてディレクトリで止めているのは、翌週に同じディレクトリ内の別ファイルを触っても壊れないようにするためです。狭めすぎは、回収の意味を失わせるほどの再申請を招きます。
早すぎる絞り込みを防ぐガード
このループの一番の危険は、観測が足りないうちに絞ってしまうことです。「たまたま今月使わなかっただけ」の権限を回収すると、来月エージェントが止まります。だから回収には必ず2つのしきい値を噛ませます。
// guard.ts
import type { GrantUsage } from "./reconcile" ;
import type { Suggestion } from "./suggest" ;
export interface GuardConfig {
minRuns : number ; // 最低これだけの実行を観測してから判断
minWindowDays : number ; // 最低これだけの期間を観測してから判断
}
export function applyGuard (
suggestions : Suggestion [],
observedRuns : number ,
windowDays : number ,
cfg : GuardConfig
) : { ready : boolean ; suggestions : Suggestion []; note : string } {
if (observedRuns < cfg.minRuns || windowDays < cfg.minWindowDays) {
return {
ready: false ,
suggestions: [],
note: `観測不足(${ observedRuns }回 / ${ windowDays }日)。しきい値(${ cfg . minRuns }回 / ${ cfg . minWindowDays }日)到達まで保留します` ,
};
}
return { ready: true , suggestions, note: "観測十分。提案を提示します" };
}
私は個人運用では minRuns を 100、minWindowDays を 21 から始めました。スケジュール実行が主なので、実行回数のほうが先に埋まります。逆に手動で不定期に使うだけの環境なら、日数側を厚めにすることを推奨します。ここは運用リズムに合わせて決める値で、正解が1つあるわけではありません。
観測不足のあいだは、絞り込まずにそのまま回します。作業を止めないことのほうが、この段階では優先です。バージョンを固定して再現性のある観測を積む考え方は、スケジュール実行の文脈で別途整理しています。
週次で回す:実際の削減例
私自身の運用では、この回収ループを週次の点検に組み込んでいます。Dolice で複数サイトを個人運用しているぶん、エージェントに開けた穴も増えがちで、定期的に締め直さないとすぐに広がります。ある月の実測が次のとおりでした。
項目 回収前 回収後
付与の総数 23 14
未使用の付与(revoke 候補) 9 0
広すぎるスコープ(narrow 候補) 5 1
観測: 実行回数 / 期間 140回 / 28日
23個のうち9個、割合にして約39%が、28日140回の実行で一度も行使されていませんでした。多くは、初期に「使うかもしれない」で開けた net 系と exec 系です。回収してもエージェントは止まらず、その後2週間の再観測でも再申請は1件だけでした。数字にしてみると、自分がどれだけ広めに開けていたかがはっきりして、少し気恥ずかしくなります。
提案を人の目で確認する工程は残します。回収候補の中に1つ、月末バッチでしか使わない権限が紛れていたことがあり、観測期間をまたいで初めて拾えました。自動適用にしていたら、その月末に静かに壊れていたはずです。より厳密な権限境界の設計そのものはAntigravity Agent に書き込み権限を安全に渡す Permission Boundary の設計 が参考になります。
まとめ
まずは、いま自分のエージェントに付与している権限を1枚に書き出し、直近の行動ログと突き合わせてみてください。「一度も使っていない許可」が1つでも見つかったら、この回収ループを回す価値があります。付与を減らすことは、機能を減らすことではありません。使っていない鍵を、静かに1本ずつ返していく作業です。
権限管理は派手さのない領域ですが、個人開発で長く安全に自動化を回すほど、この地味な締め直しが効いてきます。私自身もまだ観測しきい値を手探りで調整している途中です。同じように複数の自動処理を抱えている方と、ちょうどよい塩梅を探っていけたら嬉しく思います。