claude-security-check 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +54 -2
  4. package/bin/cli.mjs +70 -0
  5. package/examples/hmac-empty.mjs +12 -0
  6. package/examples/hono-mount.mjs +20 -0
  7. package/package.json +30 -4
  8. package/skill/SKILL.md +52 -0
  9. package/skill/incidents/INDEX.md +50 -0
  10. package/skill/incidents/_template.md +24 -0
  11. package/skill/incidents/access-sharing/internal-share-org-team-auto-attached.md +22 -0
  12. package/skill/incidents/authz-idor/copied-admin-wiring.md +24 -0
  13. package/skill/incidents/authz-idor/fail-closed-on-empty-admin-table.md +23 -0
  14. package/skill/incidents/authz-idor/ownership-check-inside-branch.md +25 -0
  15. package/skill/incidents/client-side-action/liff-send-posts-to-wrong-chat.md +25 -0
  16. package/skill/incidents/db-privileges/security-definer-open-to-anon.md +27 -0
  17. package/skill/incidents/external/2026-10-arcadia-traversal-config-secret.md +31 -0
  18. package/skill/incidents/fail-open/idp-client-id-unset.md +23 -0
  19. package/skill/incidents/fail-open/webhook-hmac-empty-secret.md +26 -0
  20. package/skill/incidents/logging-forensics/webhook-body-console-log-pii.md +23 -0
  21. package/skill/incidents/process/break-guard-to-see-red.md +22 -0
  22. package/skill/incidents/unauth-route/internal-route-and-subpath-bypass.md +32 -0
  23. package/skill/incidents/vibe-coding/2023-2026-secrets-on-public-github.md +31 -0
  24. package/skill/incidents/vibe-coding/2024-03-huggingface-cli-hallucinated-package.md +30 -0
  25. package/skill/incidents/vibe-coding/2025-03-cursor-saas-attacked-after-launch.md +30 -0
  26. package/skill/incidents/vibe-coding/2025-03-lovable-rls-missing-cve-2025-48757.md +32 -0
  27. package/skill/incidents/vibe-coding/2025-07-amazon-q-extension-malicious-prompt.md +28 -0
  28. package/skill/incidents/vibe-coding/2025-07-base44-private-app-register-by-app-id.md +31 -0
  29. package/skill/incidents/vibe-coding/2025-07-gemini-cli-lost-files.md +28 -0
  30. package/skill/incidents/vibe-coding/2025-07-replit-agent-deleted-production-db.md +32 -0
  31. package/skill/incidents/vibe-coding/2025-2026-vibe-coded-apps-public-by-default.md +29 -0
  32. package/skill/incidents/vibe-coding/2026-01-moltbook-supabase-key-rls-off.md +31 -0
  33. package/skill/incidents/vibe-coding/gas-llm-invoice-sidebar.md +60 -0
  34. package/skill/patterns/vibe-coding.md +129 -0
  35. package/skill/procedure/01-scope.md +17 -0
  36. package/skill/procedure/02-surface-map.md +35 -0
  37. package/skill/procedure/03-auth-checklist.md +32 -0
  38. package/skill/procedure/04-forensics-readiness.md +51 -0
  39. package/skill/procedure/05-report.md +16 -0
  40. package/skill/procedure/06-vibe-coding.md +36 -0
@@ -0,0 +1,17 @@
1
+ # 01. 範囲と前提
2
+
3
+ ## 対象を確かめる
4
+ - リポジトリの場所・リモート・ブランチを `git -C <repo> remote -v` / `git -C <repo> branch --show-current` で実測する(推測で書かない)。
5
+ - 本番と検証環境のクラウドのプロジェクト ID は、Terraform の変数ファイル(`*.tfvars` の `project_id` など)から取る。
6
+ - worktree(`git worktree list`)は対象から外す。同じコードを 2 回数えることになる。
7
+
8
+ ## 先に読む(そのリポジトリの前提・過去事故)
9
+ - `AGENTS.md` / `CLAUDE.md`(ルートと `src/` `infra/` `supabase/` などの配下)
10
+ - `.claude/rules/` などのルール文書のうち、認証・M2M・公開エンドポイント・DB 権限・ログ・測り方の罠に関するもの
11
+ - チームのヒヤリハット記録があれば、点検する機能の語で grep する
12
+ - このスキルの `incidents/INDEX.md`
13
+
14
+ ## 範囲外にすること(報告に書いておく)
15
+ - `.env*` は開かない(秘密)。開いていないことを報告に書く。
16
+ - 各 route の全数精読をしていないなら「全数は未確認」と書く。
17
+ - 本番への書き込みを伴う確認(実際に攻撃してみる等)はしない。到達性は検証環境で確かめる。
@@ -0,0 +1,35 @@
1
+ # 02. 構成と外部公開面の地図
2
+
3
+ 目的は「どこから入れるか」を一枚にすること。脆弱性探しはまだしない。
4
+ 例は Google Cloud(Cloud Run)+ Terraform。他のクラウドでも見る観点は同じです。
5
+
6
+ ## 2-1. インフラ(Terraform を読む+本番で確かめる)
7
+ | 見るもの | 場所の例 | 意味 |
8
+ |---|---|---|
9
+ | Cloud Run の `ingress` | `infra/cloud_run*.tf` | `INGRESS_TRAFFIC_ALL` =インターネットから直接届く。**「サーバー間だから認証不要」は成立しない**(incidents/unauth-route/) |
10
+ | `allUsers` の `roles/run.invoker` | IAM の定義 | 付いていれば認証は全部アプリ層の責任 |
11
+ | Scheduler / Tasks の OIDC | `cloud_scheduler.tf` `cloud_tasks.tf` | audience とサービスアカウント。アプリ側で検証しているかは別に見る |
12
+ | CI のデプロイ権限(Workload Identity 等) | `workload_identity.tf` | リポジトリ・ブランチ条件、デプロイ用の権限がプロジェクト全体に付いていないか |
13
+ | Secret の一覧と配り先 | `secrets.tf` と各サービスの `secret_key_ref` | 名前だけ控える。**同じ鍵が何サービスに配られているか**(共用の鍵は漏れたときの範囲が広い) |
14
+ | 前段(WAF/CDN) | `dig +short <host>` / `curl -sI https://<host>` の `server` ヘッダ | クラウドのフロントに直結していれば WAF は無い |
15
+
16
+ ## 2-2. 認証の入口を種類で分ける
17
+ 1. 人(管理者): どの IdP・どのドメイン制限・どこで判定しているか(middleware / proxy / route 内 / RLS)
18
+ 2. 人(顧客): OTP・ソーシャルログイン・LINE LIFF など。トークンを誰がどう検証するか(audience / client_id の照合)
19
+ 3. M2M: 共有キーか署名か OIDC か。**actor(誰の代理か)は自己申告か検証済みか**
20
+ 4. Webhook: 署名 / トークン / IP 制限のどれか。**鍵が未設定のときの挙動**
21
+ 5. ジョブ: OIDC をアプリで検証しているか、IAM だけか
22
+ 6. 開発用の抜け道(`NODE_ENV === "development"` のときだけ通るトークン等)が本番で閉じているか
23
+
24
+ ## 2-3. 公開エンドポイントの棚卸し
25
+ - Next.js: `src/app/**/api/**/route.ts` を全列挙し、`middleware.ts` / `proxy.ts` の公開パターン・matcher と突き合わせる。
26
+ **前方一致の公開パターン**は配下の将来のルートまで無認証にする。
27
+ - Hono 等: `app.route(` の数で数えない。**全 endpoint に展開してから** middleware のパターンと 1 本ずつ照合する
28
+ (`app.use("/x")` と `app.use("/x/*")` は別。マウント側のファイルや factory 内の `route.use` も見る)。
29
+ - 無認証で残すものは「ルート内で何を検証しているか」を 1 行で書けるかで判定する。
30
+ - 自分の棚卸しを**別の資料(OpenAPI の記述等)と突き合わせて**検証する。自前の検査スクリプトは型注釈の `{` などで取りこぼす。
31
+
32
+ ## 2-4. DB
33
+ - RLS の使い方(テナント分離か、特権ロール以外の締め出しだけか)
34
+ - 特権クライアント(Supabase の service_role 等)の生成箇所。正規のヘルパー以外の**直書き**も grep する
35
+ - `SECURITY DEFINER` 関数の anon 実行可否(Supabase は `REVOKE ... FROM PUBLIC` だけでは anon が残る)
@@ -0,0 +1,32 @@
1
+ # 03. 認証・認可の観点表
2
+
3
+ 右端に事例があるものは、**実際に起きた事例**から作った観点です。事例を開くと、何が起きたか・どう見つけたかが分かります。
4
+ 新しい事例を足したら、ここにも観点を 1 行足す。
5
+
6
+ | # | 観点 | 確かめ方 | 事例 |
7
+ |---|---|---|---|
8
+ | A1 | ingress が公開なのに「内部用だから」と認証の無いルートが無いか | 公開ルートの棚卸し(02 §2-3)で「内部」「internal」「サーバー間」を grep | unauth-route/internal-route-and-subpath-bypass |
9
+ | A2 | ミドルウェアが bare パスだけに掛かり、サブパスが素通りしていないか(Hono は `/x` と `/x/*` の 2 行) | `app.use(` と `app.route(` を対で照合 | 同上 |
10
+ | A3 | 公開判定が前方一致で、将来のサブルートまで無認証になっていないか | 公開パターン(`startsWith` で判定している配列など)を列挙し、配下の route 全部に自前の認証があるか | (観点のみ) |
11
+ | A4 | 同じハンドラを 2 か所にマウントして「影の経路」ができていないか | 同じ factory を複数の path で `route` していないか | unauth-route/internal-route-and-subpath-bypass |
12
+ | F1 | 受信検証の鍵・許可リスト・client_id が**未設定のときに通る**作りになっていないか(`?? ""`、`if (cfg && ...)`) | `grep -rnE '(SECRET\|TOKEN\|KEY\|CHANNEL_ID)[^\n]*\?\? ""'` と `if \(options\?\.` を、**受信の検証に使っているもの**に絞る | fail-open/webhook-hmac-empty-secret, fail-open/idp-client-id-unset |
13
+ | F2 | レート制限・外部判定が障害時に fail-open で、公開 OTP 等の守りが外れないか | レート制限の実装の catch 節 | (観点のみ) |
14
+ | Z1 | 他エンティティの ID を受け取る書込 API が、**ID の有無に関わらず**所有権チェックを先頭で通すか | body から `*.id` を受ける route を列挙し、検証が `if (!id)` 分岐の中だけにないか | authz-idor/ownership-check-inside-branch |
15
+ | Z2 | 認可テーブルを別アプリの配線からコピーしていないか(同じ形の名前 `*_admins` でも役割が違う) | アプリごとの管理者テーブルの役割表と、API 側の照合先を突き合わせる | authz-idor/copied-admin-wiring |
16
+ | Z3 | fail-closed の照合を足すとき、照合先テーブルが空でないか・埋める手段があるか(人と bot で基準が食い違わないか) | 本番の件数(`count(*)` のみ) | authz-idor/fail-closed-on-empty-admin-table |
17
+ | Z4 | M2M の共有鍵が何サービスで共用されているか・actor が自己申告か | Secret の配り先表(02 §2-1) | (観点のみ) |
18
+ | D1 | 状態を変える `SECURITY DEFINER` 関数が anon/authenticated に開いていないか(`FROM PUBLIC` だけでは anon が残る) | `has_function_privilege('anon', oid, 'EXECUTE')` を本番で**読み取りで**数える | db-privileges/security-definer-open-to-anon |
19
+ | C1 | 顧客の画面(LIFF 等)から、利用者の権限で外部へ送信・投稿していないか | 送信 API の呼び出し・ラッパの存在を grep | client-side-action/liff-send-posts-to-wrong-chat |
20
+ | E1 | 登録の有無を返す公開 API に per-IP + per-email のレート制限があるか(reCAPTCHA だけにしない) | 公開の auth 系 route ごとに「レート制限・失敗ログ・障害時の挙動」を表にする | (観点のみ) |
21
+ | L1 | Webhook・外部 API のログが redact を通っているか(`console.*` 直呼びで個人情報を生出力していないか) | `console\.(log\|error)` を webhook / 外部クライアント配下で grep | logging-forensics/webhook-body-console-log-pii |
22
+ | T1 | 認可のテストで RED を見るとき、ガード自体を `if (true)` 等に壊していないか | `grep -rc 'if (true)'` = 0 を確認 | process/break-guard-to-see-red |
23
+
24
+ ## AI に書かせたコードによくあるミス
25
+ V1〜 の型は [patterns/vibe-coding.md](../patterns/vibe-coding.md) にまとめています。点検の進め方は [06-vibe-coding.md](06-vibe-coding.md) です。
26
+
27
+ ## 外部の事件から来た観点
28
+ | # | 観点 | 事例 |
29
+ |---|---|---|
30
+ | X1 | パス操作(`../`)でサーバ上のファイルを返す口が無いか(ファイル名・パスを受け取って読む/配信するルート) | external/2026-10-arcadia-traversal-config-secret |
31
+ | X2 | 設定ファイル・リポジトリ・ビルド成果物にサービス間の鍵が平文で入っていないか(一つ漏れると横に広がる) | 同上 |
32
+ | X3 | DB の異常負荷より前に、探索(404 の山)やログイン連打で気づけるか | 同上 → 04 の検知 |
@@ -0,0 +1,51 @@
1
+ # 04. 攻撃者の行動をログで追えるか
2
+
3
+ 「事件が起きたとき、何時・どこから・何をされたかを分単位で並べられるか」を点検する。
4
+ 手本: external/2026-10-arcadia-traversal-config-secret(公表資料が分単位の時系列を出している)。
5
+ コマンドは Google Cloud(Cloud Run + Cloud Logging)の例です。
6
+
7
+ ## 4-1. 測るコマンド(すべて読み取り。`--account` を毎回付ける)
8
+ ```bash
9
+ A=<your-account@example.com>; P=<project_id>
10
+ gcloud logging buckets list --project=$P --account=$A --format="table(name.basename(),retentionDays,locked)"
11
+ gcloud logging sinks list --project=$P --account=$A --format="table(name,destination,filter)"
12
+ gcloud logging sinks describe _Default --project=$P --account=$A --format="value(exclusions[].name)"
13
+ gcloud projects get-iam-policy $P --account=$A --format=json \
14
+ | python3 -c "import json,sys;print(json.load(sys.stdin).get('auditConfigs','(none)'))"
15
+ # ステータス別件数(値は出さない)
16
+ gcloud logging read 'logName:"run.googleapis.com%2Frequests"' --project=$P --account=$A --freshness=24h --limit=20000 \
17
+ --format="value(httpRequest.status)" | awk '{c[substr($1,1,1)"xx"]++;n++}END{for(k in c)print k,c[k];print "total",n}'
18
+ # 1 件のフィールド名だけ
19
+ gcloud logging read 'logName:"run.googleapis.com%2Frequests"' --project=$P --account=$A --freshness=24h --limit=1 --format=json \
20
+ | python3 -c "import json,sys;e=json.load(sys.stdin)[0];print(sorted(e.get('httpRequest',{}).keys()))"
21
+ # 最古のログ(=実効の保存期間)
22
+ gcloud logging read 'logName:"run.googleapis.com%2Frequests"' --project=$P --account=$A --freshness=400d --order=asc --limit=1 --format="value(timestamp)"
23
+ ```
24
+ - `gcloud logging exclusions` というサブコマンドは無い(Google Cloud SDK 583.0.0 で実測)。除外は `_Default` sink の `exclusions` で見る。
25
+ - 件数が `--limit` に達したら「打ち切り」と書く。
26
+ - **2xx が 4xx より少ない**ときは、成功ログを除外・サンプリングしていないかを `sinks describe` で確かめる。
27
+
28
+ ## 4-2. 判定表(各行を 追える / 一部 / 追えない で埋める)
29
+ | 攻撃者の行動 | 残る場所 | 追えない典型原因 |
30
+ |---|---|---|
31
+ | パス探索(404 の山) | リクエストログ | 保存期間切れ |
32
+ | トラバーサル・SQLi の試行 | リクエストログの URL(クエリ含む) | POST 本文は残らない |
33
+ | ログイン連打 | リクエストログ(4xx)+ IdP 側ログ | 狙われたアカウントは本文に入るので残らない |
34
+ | **攻撃が成功した瞬間(2xx)** | リクエストログ | **コスト対策で 2xx を除外・サンプリングしている** |
35
+ | ログイン後に見た・持ち出した | アプリの構造化ログ(userId 付き)・DB 監査 | アクセスログにユーザー ID が無い |
36
+ | 盗んだ鍵の使用 | リクエストログ+M2M の呼び出し元ログ | 共用鍵だと正規の呼び出しと区別できない |
37
+ | 秘密を誰が読んだか | Data Access 監査ログ | `auditConfigs` が無い=無効 |
38
+ | 設定変更・デプロイ・IAM 変更 | Admin Activity 監査ログ(`_Required` バケット・400 日・ロック済み) | -- |
39
+ | 攻撃中に気づく | ログベース指標+アラート | 5xx しか見ていない |
40
+ | 1 か月以上前 | 長期保管(別プロジェクトの BigQuery/GCS) | `_Default` バケットの既定 30 日のみ |
41
+ | 証拠を消されない | ロック済みバケット・別プロジェクト | 同じプロジェクト内・未ロック |
42
+
43
+ ## 4-3. 対策の優先度(目安。プロジェクトごとに見直す)
44
+ 1. 2xx を含むリクエストログを全部残す(除外をやめる、または捨てずに安い保存先へ回す)
45
+ 2. 1 年以上・別プロジェクト・保存期間ロック(BigQuery なら IP 単位の時系列が SQL 一本で出る)
46
+ 3. Data Access 監査ログ(Secret Manager・IAM)
47
+ 4. 検知アラート(同じ IP の 4xx 急増/ログイン失敗の連打/1 ユーザーの異常な 200 件数/管理者権限の付与/秘密の読み取り)
48
+ 5. アプリのセキュリティイベントログ(認証の成否・権限付与・401/403・レート制限・M2M の呼び出し元・一括出力。userId・role・IP・trace ID 付き。redact を通す)
49
+ 6. DB 側の記録(マネージド DB のプラン別の保存期間・pgaudit 等)
50
+ 7. 前段の WAF(ロードバランサ+Cloud Armor、Cloudflare 等)
51
+ 8. 「疑わしい IP の時系列を出す」クエリを手順書に保存しておく
@@ -0,0 +1,16 @@
1
+ # 05. 報告の型
2
+
3
+ 1. **結論 1〜3 行**(できる/できない、いちばん大きな穴)
4
+ 2. 手本の事件の各段階 × 自社システムの表(追える/一部/追えない)
5
+ 3. 実測した事実の表(プロジェクト別・数値・日時)
6
+ 4. 穴の一覧(# / 穴 / 根拠ラベル `実測`・`コード上`・`未確認` / 何が困るか)
7
+ 5. 対策(優先度・何が残るようになるか・手間と費用の目安。見積もっていなければそう書く)
8
+ 6. 気になった点(疑問の段階。断定しない)
9
+ 7. 次の一手(候補を 2〜3)
10
+ 8. 調査方法と、やっていないこと(編集なし・`.env` 未読・全数は未確認 など)
11
+
12
+ ## 出し先
13
+ - まず会話の中で文章として返す。メモに残すならリポジトリの外(チームに共有されない場所)に置く。
14
+ - HTML や共有ページにするなら、作る**前に**公開範囲を決める(SKILL.md §0-5)。
15
+ - 読み手が知らなそうな概念(ingress・OIDC・RLS・fail-open 等)は、初出で 1 行説明する。
16
+ - 新しく分かった事例は incidents/ に足してから報告を閉じる。
@@ -0,0 +1,36 @@
1
+ # 06. AI に書かせたコードの点検
2
+
3
+ 貼られたコード・リポジトリ・差分を読み、[patterns/vibe-coding.md](../patterns/vibe-coding.md) の型に当てはめて穴を探す。
4
+ 本番の点検(01〜04)と違い、クラウドには触れずにコードだけで進められる。§0 の安全ルールはそのまま守る。
5
+
6
+ ## 6-1. 先に確かめること(分からなければ聞く)
7
+ | 聞くこと | なぜ |
8
+ |---|---|
9
+ | 誰が使うか(自分だけ・社内・外部の顧客) | 「自分しか使わない」前提が崩れる範囲が決まる |
10
+ | どこで動くか(ブラウザ・サーバー・GAS・CLI・拡張機能) | ブラウザで動くコードの値と鍵は、利用者に見える |
11
+ | 何のデータを扱うか(個人情報・お金・取引先の文書・鍵) | 重大度の基準になる |
12
+ | 外から入るものは何か(ファイル・LLM の出力・Webhook・フォーム・URL) | §6-2 の入口になる |
13
+ | 本番で使われているか、いつから | 報告の急ぎ度。分からなければ `未確認` と書く |
14
+
15
+ ## 6-2. 入口から出口までを表にする
16
+ 外から入った値が、どこを通ってどこへ出るかを 1 行ずつ書く。穴はたいてい出口にある。
17
+ | 入口 | 通る処理 | 出口 | 出口での扱い |
18
+ |---|---|---|---|
19
+ | 例: 利用者が選んだ PDF | LLM で会社名・金額を読む | 画面(`innerHTML`)・シート(`appendRow`)・Drive | エスケープの有無・数式の扱い・共有範囲 |
20
+
21
+ 出口の種類: 画面の HTML/DB・シート・CSV/シェル・ファイルパス/外部 API(送信・投稿・決済)/ログ/共有フォルダ。
22
+
23
+ ## 6-3. 型を当てる
24
+ - patterns の各節の grep を、対象のディレクトリに流す。貼られたコードならファイルに保存してから流す。
25
+ - grep で当たった行は、前後を読んで本当に外部入力が届くかを確かめる。届かなければ外す。
26
+ - grep に当たらない型(V4 の「人の確認が無い」、V17 の共有範囲など)は、§6-2 の表を見て判断する。
27
+
28
+ ## 6-4. 確かめられるものは再現する
29
+ - 画面の組み立て・文字列処理のように、その部分だけ取り出して node 等で動かせるものは動かして `実測` にする。
30
+ - 本番・共有のシートやフォルダで試さない。試すならテスト用のコピーで行う。
31
+ - 再現していないものは `コード上`、親フォルダの共有範囲のように見ていないものは `未確認` と書く。
32
+
33
+ ## 6-5. 報告と記録
34
+ - 報告の型は 05 と同じ。穴の表は「# / 穴 / 根拠ラベル / 何が困るか / 型の番号」にする。
35
+ - 直し方は書いてよいが、利用者の指示が無ければコードは直さない(§0-1)。
36
+ - 新しい型や例が出たら、patterns の [型を足す](../patterns/vibe-coding.md#型を足す) の手順で足す。