じゃあどうやって安全に使い倒すか。現場でやってる事故防止の仕組みと、これからモカの基幹システム(moz)をAIに開放するときの設計の話。専門用語は出てきたら都度説明します。
「あったら便利」じゃなく「無いと終わる」レベルで重要。今日30分使って、全員と土台を握りたい。先にその理由を5つだけ。
クリエイターの個人情報、契約金額、クライアントとのNDA案件、内部のSlackログ。これらは SaaSの機能比較表に出てこない、見えない資産。一度漏れたら関係性が一発で壊れる業界にいる。信頼を構築するのに3年、失うのに3秒。
一個でも外に出たら、ニュースになるレベル。
人間が「うっかり」やらかす。
1人の操作ミス、1ファイルの誤送信。被害は局所的。
AIエージェントが「自走で」やらかす。
1コマンドで テーブル50個を消す、APIキー1個で 全クライアントデータを取得する。被害が 非局所。
暴走するAIに対して、人間の「気をつけて」では追いつかない。仕組みで止める時代に入った。
この資料を作る過程で自分の環境を点検したら、~/.claude/settings.local.json に API Key と WordPressパスワードが平文で2件 出てきた。ローテーション済み。経営者でこれ。誰にでも起こる という前提で対策しないと意味がない。
基幹システム(取引先・案件・財務・クリエイター65万件)を、AIから自然言語で操作可能にする計画が今動いている(後でデモする)。AIが基幹DBに触れる時代に入る前に、「正しい縛り方」を全員で握っておきたい。後から「あの時やっとけば」では遅い。
ハーネス = AIを動かしている外側の仕組み(Claude Codeそのもの)。AIに「危険なコマンドは慎重に」とお願いするより、ハーネス側で物理的にブロックする方が確実です。ATMに引き出し上限があるのと同じ発想。
Claude Codeには PreToolUse Hook(コマンド実行直前に自動チェックを挟む機能)があります。私の環境では、以下のコマンドは正規表現で問答無用にブロック:
rm -rf / 系(パソコンの中身を全消去)DROP DATABASE / DROP TABLE / TRUNCATE(本番DBの破壊)git push --force ... main(mainブランチの履歴上書き)AIが「実行します」と判断しても、フックが {"decision": "block"} を返すと実行はキャンセルされる。プロンプトは破れるが、フックは破れない。
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r '.tool_input.command');
if echo \"$CMD\" | grep -qiE '(rm\\s+-rf\\s+/|drop\\s+database|drop\\s+table|truncate\\s+table|--force\\s+push.*main)'; then
echo '{\"decision\":\"block\",\"reason\":\"危険なコマンドを検出\"}'
else
echo '{\"decision\":\"approve\"}'
fi"
}]
}]
}
}
defaultMode: auto で許可プロンプトを最小化。Claudeが止まらないので生産性は最大化。
Auto Modeで自由に動いても、本当に危険なものだけは正規表現で叩き落とす。速度と安全の両立。
自分でhookを書くのは面倒なので、Claude Code自身に書かせます。下のプロンプトをコピペすれば、上で説明した危険コマンドブロックhookが ~/.claude/settings.json に自動で追加されます。
配布ファイル: ~/Downloads/install_harness_hook_prompt.txt
私の ~/.claude/settings.json に、危険なBashコマンドを実行直前に
自動ブロックする PreToolUse hookを追加してください。
【ブロック対象】
1. ルート直下を全消去する rm -rf 系
- rm -rf / または rm -rf /var, /etc, /Users 等の絶対パス削除
2. データベース破壊系
- DROP DATABASE / DROP TABLE / TRUNCATE TABLE
3. Gitのmain履歴上書き
- git push --force ... main
- git push --force-with-lease ... main
【実装ルール】
- 既存の settings.json は壊さず、hooks セクションだけマージする
- matcher は "Bash"
- ブロック時: {"decision":"block","reason":"危険なコマンドを検出しました"}
- 通過時: {"decision":"approve"}
- 正規表現マッチには jq + grep -qiE を使う
【完了後にやること】
1. 追加後の hooks セクションを見せる
2. ブロックされるコマンドの例で本当に止まるか実演する
(例: echo "rm -rf /" を挟んで安全に検証)
3. 誤ブロックがないかも確認
実行してください。
シークレット = APIキー・パスワード・トークン等の認証情報。本来は暗号化された場所か環境変数に置くべきものが、設定ファイルやシェル履歴に そのまま生の文字列で残ってないか を全員でチェックします。たぶん1人1個は出ます。私自身、調査して2件見つけて冷や汗かきました。
配布ファイル: ~/Downloads/secret_scan_prompt.txt
私の環境にAPIキー・パスワード・トークン・秘密鍵が
平文で残っていないかをチェックしてください。
【点検する場所】
- ~/.claude/settings.json
- ~/.claude/settings.local.json
- ~/.zshrc / ~/.bashrc / ~/.bash_profile
- ~/.zsh_history(最後の3000行)
【検出パターン】
- Anthropic API Key (sk-ant-api...)
- OpenAI / Google / AWS / GitHub / Slack のAPIキー
- WordPress App Password(4文字×6スペース区切り)
- 秘密鍵(-----BEGIN ... PRIVATE KEY-----)
- password= / passwd= の平文
【報告ルール】
- 実際の値は絶対に全文出力しない(先頭8文字+...でマスク)
- ファイルパス:行番号:種類:マスク値 の形式
- 各ヒットに修復アクション併記
- ヒットゼロなら「クリーンです」と1行で報告
シークレットスキャン結果: 2件ヒット
1. ~/.claude/settings.local.json:24
種類: Anthropic API Key
値: sk-ant-a...(マスク)
対応: console.anthropic.com で revoke
→ 環境変数 ANTHROPIC_API_KEY に退避
2. ~/.zshrc:42
種類: WordPress App Password
値: tgln EOX...(マスク)
対応: WP管理画面で再発行 → ~/.netrc に退避
Claude Codeが「このコマンド許可しますか?」と聞いてくる時、コマンドにシークレットが含まれた状態で許可ボタンを押すと、そのまま settings.local.json の許可リストに焼き付く。次から聞かれず便利だけど、鍵も一緒に保存される。
ターミナルで curl -u user:pass ... や export KEY=... をワンライナーで流すと、~/.zsh_history に 永続的に残る。Gitに上げてないからセーフ、ではない。
いま、モカの基幹システム(取引先・案件・財務・クリエイター65万件)を MCP(AIアプリから外部システムを操作するための共通プロトコル)で公開する計画を進めてます。便利な反面、AIが暴走したらデータが消える。だから設計時点で防御を4層重ねます。
[Claude Code / Claude Desktop]
│ stdio
▼
[ブリッジプロセス (Node.js)]
│ HTTP SSE + Bearer PAT
▼
[Heroku: moz-mocha Laravel]
│ /mcp エンドポイント
│ Sanctum認証 → ability → is_admin → 実行
▼
[McpToolService]
│
├─ PostgreSQL(DB操作)
├─ Redis Queue(bulk処理)
└─ mcp_audit_logs(全書き込み記録)
ユーザーごとに PAT(Personal Access Token = 個人専用トークン)を発行。トークンには ability(権限ラベル)を付けて、できる操作を限定します。
| 層 | 仕組み | 役割 |
|---|---|---|
| 1. 認証 | Sanctum PAT(90日有効・ハッシュ保存) | 誰のリクエストか特定 |
| 2. 認可(権限) | ability制 resource:action |
例: projects:write = 案件の書き込み権 |
| 3. 認可(管理者) | destructive操作は is_admin=true 必須 |
トークン漏洩時の被害を局所化 |
PATが万が一漏れても、abilityが付いてなければ何もできない。さらに削除系は 管理者ユーザーしか実行できない。abilityチェックを通過しても is_admin=false なら403。
削除・失注マーク・入金済みマークは、いわゆる destructive 操作(戻せない操作)。AIに一発で実行させず、dry-run → 人間確認 → 本番実行を強制します。
AIのループ誤動作で取引先テーブルが空になる事故を、構造的に 防ぐ。
| 操作分類 | 上限 | 狙い |
|---|---|---|
| SAFE 読み取り | 300 req/分 | サーバー負荷防止 |
| NORMAL 書き込み | 60 req/分 | 大量誤登録の防止 |
| DESTRUCTIVE | 20 req/分(固定窓) | 削除ループの早期遮断 |
| bulk_create(一括) | 5 req/分 | キュー詰まり防止 |
「誰が・いつ・どのツールを・どの引数で・dry-runか実行か」を mcp_audit_logs テーブルに全件記録。事故が起きた時に巻き戻せます。
CREATE TABLE mcp_audit_logs (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT REFERENCES users(id),
tool_name VARCHAR(100) NOT NULL, -- 例: projects.create
params JSONB, -- 引数(PAT・パスワードは除外)
result JSONB,
dry_run BOOLEAN DEFAULT FALSE,
confirmed BOOLEAN DEFAULT FALSE,
ip VARCHAR(45),
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
ScheduleCalculatorService 経由必須、ステータス変更メモは既存ルールを継承、クリエイター指標更新はCSVインポートのみ。バリデーションをバイパスする抜け道は作らない。設計書を眺める会じゃ意味がない。共有会のために、最小スコープ(読み取り3ツール)で実装してきました。約3時間の作業。これから皆さんの前で動かします。
| ファイル | 役割 | 規模 |
|---|---|---|
app/Services/McpToolService.php | 3ツールのビジネスロジック | 約140行 |
app/Http/Controllers/McpController.php | JSON-RPC 2.0ハンドラ + ability check | 約100行 |
app/Console/Commands/McpIssueToken.php | PAT発行 artisanコマンド | 約60行 |
routes/api.php | POST /api/mcp 追加 | +3行 |
app/Models/User.php | HasApiTokens trait追加 | +2行 |
composer.json | laravel/sanctum 追加 | +1依存 |
合計 約300行 のコードと、Sanctumライブラリ1個。これだけで基幹DBがAIから操作可能になる。
$ docker compose exec app php artisan mcp:issue-token \
--user=1 \
--abilities=clients:read,projects:read,financials:read
============================================================
MCP PAT を発行しました
============================================================
User : 岡崎耕平 (id=1, is_admin=true)
Abilities : clients:read, projects:read, financials:read
Expires : 2026-08-20 11:11
Token (平文・1回限り表示):
1|reXabZH4GZ2iZuvuYWoOSh0xzua1zxNI3uMQCHHgb...
「moz経由で、今月の売上目標と実績、達成率を教えて」
[financials.get_summary を呼んだ]
{
"year_month": "2026-05",
"revenue_target": 19600000,
"revenue_actual": 25270000,
"achievement_rate_pct": 128.9,
"gross_profit_actual": 7220000,
"gross_profit_rate_pct": 28.6
}
→ 今月は目標1,960万に対し実績2,527万、
達成率128.9%です(粗利率28.6%)。
「優先度Sの取引先を見せて」
[clients.list を priority=S で呼んだ]
{
"total": 1,
"clients": [{
"id": 254,
"company_name": "ナッシュ株式会社",
"type": "client",
"priority": "S",
"status": "brief_received",
"next_follow_up_at": "2026-03-26",
"user": { "id": 3, "name": "栗原龍司" }
}]
}
「鍵に権限ラベルをつける」設計が本当に効くか、その場で見せます。financials:read のみの制限トークンを発行し直して、わざと clients.list を呼ばせます。
financials.get_summary
→ { "achievement_rate_pct": 128.9, ... }
正常に返却
clients.list
→ {
"error": {
"code": -32003,
"message": "Forbidden:
このトークンには
clients:read の権限が
ありません"
}
}
これが「PAT × Ability × is_admin の3層」の実物。トークンが漏れても、abilityがなければ何もできない。
mcp_audit_logs テーブル)— 設計書あり、Phase 2で実装明日から自分のClaude Code環境に効く話。
PreToolUse hookで危険コマンドを正規表現ブロック。プロンプトは破れるがフックは破れない。
月1で配布プロンプトを流すだけ。許可ボタンとシェル履歴という2大漏洩源を定期点検。
destructive操作だけ管理者必須にすれば、トークン漏洩時の被害が「読み取り+一部書き込み」で止まる。
削除系はAIに一発で実行させない。「23件が消えます」を人間に見せてから実行する。