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
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+ - AI に書かせたコード(バイブコーディング)の点検を追加
5
+ - `procedure/06-vibe-coding.md`: コードだけで進める点検の手順
6
+ - `patterns/vibe-coding.md`: よくあるミスの型 V1〜V26(入力の信用・鍵・認証認可・失敗の扱い・公開範囲・依存とコスト・AI エージェントの動かし方)と grep
7
+ - 事例: 公表された他社の事例 10 件(Lovable・Moltbook・Base44・Replit・Gemini CLI・Amazon Q・存在しないパッケージ・公開リポジトリの鍵・公開アプリの調査・手書き 0 行の SaaS)と、匿名の社内ツール 1 件
8
+ - `install --force` で、手元の `incidents/INDEX.md` と同梱の索引が違うときは、同梱の索引を `incidents/INDEX.upstream.md` として置くようにした(0.1.0 から上げると新しい事例が索引に載らなかったため)
9
+
10
+ ## 0.1.0
11
+ - 初版。手順 5 本・観点表 18 項目・事例 12 件(他社 1・匿名の実例 11)・再現スクリプト 2 本
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kanketsu-jp
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,55 @@
1
- # Temporary Holding Version
1
+ # claude-security-check
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Claude Code の Skill です。本番システムを**読み取りだけで**点検し、次の 2 つに実測で答えます。
4
+
5
+ - どこから入れるか(外部公開面・認証の入口・認可の穴)
6
+ - 入られたら、何時・どこから・何をされたかを後から並べられるか(ログの保存・除外・監査ログ・検知)
7
+ - AI に書かせたコード(バイブコーディング)に、よくある危ないところが無いか(本番に触れず、コードだけで点検)
8
+
9
+ 観点表の各行は、実際に起きたインシデント・ヒヤリハットと、他社の公表事件から作っています。
10
+ 事例は `incidents/` にカテゴリ別のディレクトリで足していけます。
11
+
12
+ ## インストール
13
+
14
+ ```bash
15
+ npx claude-security-check install # ~/.claude/skills/security-check へ
16
+ npx claude-security-check install --project # ./.claude/skills/security-check へ
17
+ ```
18
+
19
+ Claude Code で `/security-check` と打つと開きます(`disable-model-invocation: true` なので、自動では読み込まれません)。
20
+
21
+ 更新するときは `--force` を付けます。`SKILL.md`・`procedure/`・`patterns/` は上書きし、自分で足した事例と `incidents/INDEX.md` は残します。
22
+
23
+ **自分の組織の事例を足したディレクトリは、公開リポジトリに置かないでください。** 未対策の穴の一覧は、攻撃者にとっての地図になります。
24
+
25
+ ## 中身
26
+
27
+ ```
28
+ skill/
29
+ ├─ SKILL.md 点検の安全ルールと 6 段階の手順
30
+ ├─ procedure/
31
+ │ ├─ 01-scope.md 範囲と、先に読む前提
32
+ │ ├─ 02-surface-map.md 構成と外部公開面の地図
33
+ │ ├─ 03-auth-checklist.md 認証・認可の観点表
34
+ │ ├─ 04-forensics-readiness.md 攻撃者の行動をログで追えるか(読み取りの測定コマンドと判定表)
35
+ │ ├─ 05-report.md 報告の型と、出し先の決め方
36
+ │ └─ 06-vibe-coding.md AI に書かせたコードの点検(コードだけで進める)
37
+ ├─ patterns/
38
+ │ └─ vibe-coding.md AI に書かせたコードによくあるミス(V1〜V26・grep つき)
39
+ └─ incidents/
40
+ ├─ INDEX.md / _template.md
41
+ └─ <カテゴリ>/<事例>.md 他社 11 件・匿名の実例 12 件
42
+ examples/
43
+ ├─ hono-mount.mjs app.use の bare パスとサブパスの違いを再現
44
+ └─ hmac-empty.mjs 空の鍵の HMAC 検証が通ることを再現
45
+ ```
46
+
47
+ ## 前提
48
+
49
+ - Claude Code(Skill が使えるバージョン)
50
+ - 測定コマンドの例は Google Cloud(Cloud Run + Cloud Logging)と `gcloud` です。観点は他のクラウドでも同じです。
51
+ - `examples/` を動かすには Node.js 18.17 以上(`hono-mount.mjs` は `npm i hono` が要ります)。
52
+
53
+ ## License
54
+
55
+ MIT
package/bin/cli.mjs ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ import { cpSync, existsSync, mkdirSync, readdirSync, statSync, copyFileSync, readFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join, relative, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const SKILL_NAME = "security-check";
8
+ const SRC = resolve(dirname(fileURLToPath(import.meta.url)), "..", "skill");
9
+
10
+ const USAGE = `usage: claude-security-check <command> [options]
11
+
12
+ commands:
13
+ install スキルを ~/.claude/skills/${SKILL_NAME} にコピーする
14
+ path 同梱しているスキルの場所を表示する
15
+
16
+ options (install):
17
+ --project ./.claude/skills/${SKILL_NAME} にコピーする(プロジェクト単位)
18
+ --dir <p> コピー先の skills ディレクトリを指定する
19
+ --force 既にあるとき、SKILL.md と procedure/ を上書きし、無い事例だけ足す
20
+ (incidents/ の既存ファイルと INDEX.md は上書きしない)`;
21
+
22
+ function walk(dir) {
23
+ return readdirSync(dir).flatMap((name) => {
24
+ const p = join(dir, name);
25
+ return statSync(p).isDirectory() ? walk(p) : [p];
26
+ });
27
+ }
28
+
29
+ function install(args) {
30
+ const i = args.indexOf("--dir");
31
+ const base = i >= 0 ? resolve(args[i + 1] ?? "") :
32
+ args.includes("--project") ? resolve(".claude", "skills") : join(homedir(), ".claude", "skills");
33
+ const dest = join(base, SKILL_NAME);
34
+
35
+ if (!existsSync(dest)) {
36
+ mkdirSync(base, { recursive: true });
37
+ cpSync(SRC, dest, { recursive: true });
38
+ console.log(`installed: ${dest}`);
39
+ return;
40
+ }
41
+ if (!args.includes("--force")) {
42
+ console.error(`already exists: ${dest}\n上書きするなら --force(自分で足した事例と INDEX.md は残ります)`);
43
+ process.exit(1);
44
+ }
45
+ let updated = 0, added = 0, kept = 0, indexNote = false;
46
+ for (const file of walk(SRC)) {
47
+ const rel = relative(SRC, file);
48
+ const target = join(dest, rel);
49
+ const isIncident = rel.startsWith("incidents") && !rel.endsWith("_template.md");
50
+ if (isIncident && existsSync(target)) {
51
+ kept++;
52
+ // INDEX.md は利用者の追記を残しつつ、同梱の新しい索引を横に置いて差分を見られるようにする
53
+ if (rel === join("incidents", "INDEX.md") && readFileSync(file, "utf8") !== readFileSync(target, "utf8")) {
54
+ copyFileSync(file, join(dirname(target), "INDEX.upstream.md"));
55
+ indexNote = true;
56
+ }
57
+ continue;
58
+ }
59
+ mkdirSync(dirname(target), { recursive: true });
60
+ existsSync(target) ? updated++ : added++;
61
+ copyFileSync(file, target);
62
+ }
63
+ console.log(`updated: ${dest} (上書き ${updated} / 追加 ${added} / 残した事例 ${kept})`);
64
+ if (indexNote) console.log(`incidents/INDEX.md は残しました。同梱の新しい索引は incidents/INDEX.upstream.md にあります(差分を INDEX.md に写してください)`);
65
+ }
66
+
67
+ const [cmd, ...rest] = process.argv.slice(2);
68
+ if (cmd === "install") install(rest);
69
+ else if (cmd === "path") console.log(SRC);
70
+ else { console.log(USAGE); process.exit(cmd ? 1 : 0); }
@@ -0,0 +1,12 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ const secret = process.env.WEBHOOK_SECRET ?? ""; // 未設定なら空文字
3
+ const body = JSON.stringify({ orderId: 1, status: "shipped" });
4
+
5
+ // 攻撃者側: 空の鍵で署名を作る
6
+ const forged = createHmac("sha256", "").update(body).digest("hex");
7
+
8
+ // サーバ側: 受け取った署名を検証する
9
+ const expected = createHmac("sha256", secret).update(body).digest("hex");
10
+ const ok = timingSafeEqual(Buffer.from(forged), Buffer.from(expected));
11
+ console.log("WEBHOOK_SECRET set?", process.env.WEBHOOK_SECRET !== undefined);
12
+ console.log("forged signature accepted?", ok);
@@ -0,0 +1,20 @@
1
+ import { Hono } from "hono";
2
+ const auth = async (c, next) => c.req.header("x-api-key") === "secret" ? next() : c.text("unauthorized", 401);
3
+
4
+ const app = new Hono();
5
+ app.use("/api/visit-logs", auth); // bare パスだけに掛けた
6
+ app.get("/api/visit-logs", (c) => c.json({ list: [] }));
7
+ app.get("/api/visit-logs/lookup", (c) => c.json({ name: "山田 太郎" }));
8
+
9
+ const fixed = new Hono();
10
+ fixed.use("/api/visit-logs", auth);
11
+ fixed.use("/api/visit-logs/*", auth); // サブパスにも掛ける
12
+ fixed.get("/api/visit-logs", (c) => c.json({ list: [] }));
13
+ fixed.get("/api/visit-logs/lookup", (c) => c.json({ name: "山田 太郎" }));
14
+
15
+ for (const [label, a] of [["bare のみ", app], ["bare + /*", fixed]]) {
16
+ for (const p of ["/api/visit-logs", "/api/visit-logs/lookup"]) {
17
+ const r = await a.request(p);
18
+ console.log(label.padEnd(10), p.padEnd(24), r.status);
19
+ }
20
+ }
package/package.json CHANGED
@@ -1,6 +1,32 @@
1
1
  {
2
2
  "name": "claude-security-check",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Claude Code の Skill: 本番システムを読み取りだけで点検し、認可の穴と「攻撃されたら後から追えるか」を実測で答える手順と、実際の事例集",
5
+ "type": "module",
6
+ "bin": {
7
+ "claude-security-check": "bin/cli.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "skill",
12
+ "examples",
13
+ "README.md",
14
+ "LICENSE",
15
+ "CHANGELOG.md"
16
+ ],
17
+ "engines": {
18
+ "node": ">=18.17"
19
+ },
20
+ "keywords": [
21
+ "claude-code",
22
+ "claude-skill",
23
+ "security",
24
+ "forensics",
25
+ "authorization",
26
+ "cloud-run",
27
+ "supabase",
28
+ "hono"
29
+ ],
30
+ "license": "MIT",
31
+ "author": "kanketsu-jp"
32
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: security-check
3
+ description: 本番システムのセキュリティ点検を、読み取りだけで行う。構成と外部公開面の地図づくり、認証・認可の観点チェック、攻撃者の行動をログで後から追えるかの点検をして、実測とコード上を分けて報告する。実際に起きたインシデント・ヒヤリハット・他社の公表事件を incidents/ にカテゴリ別で貯め、点検のたびに照らす。「セキュリティチェックして」「この事件みたいなことがうちで起きたら追える?」「認証の穴を洗って」「インシデント事例を追加して」「AI に書かせたこのコードは大丈夫?」「バイブコーディングの危険を点検して」と言われたときに使う。
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # security-check
8
+
9
+ 本番システムを**壊さずに**点検し、「どこから入れるか」「入られたら後から追えるか」を実測で答えるための手順と事例集です。
10
+ AI に書かせたコードの危ないところも、本番に触れずにコードだけで点検できます(§1 の下)。
11
+ 観点表の各行は、実際に起きた事例から作っています。事例は `incidents/` にカテゴリ別で足していけます。
12
+
13
+ ## 0. 絶対に守ること(点検の安全)
14
+
15
+ 1. **読むだけ。** 編集・commit・push・本番の設定変更・デプロイをしない(利用者の明示の指示があるまで)。
16
+ 本番クラウドは**設定のメタデータと件数**だけ読む(バケット一覧・sink 一覧・IAM ポリシー・ステータス別の件数)。
17
+ 2. **秘密の値・個人情報を出さない。** 出してよいのは Secret の**名前**、件数、フィールド名まで。
18
+ IP・メール・氏名はログから引いても報告に載せない(必要なら伏せ字)。`.env*` は開かない。
19
+ 3. **アカウントを取り違えない。** 複数の組織・アカウントを持つ環境では、`--account` / `--project` を**コマンドごとに**付ける。
20
+ `gcloud config set account` や `gh auth switch` で既定を変えない(同じ PC の別セッションまで巻き込む)。
21
+ 4. **実測とコード上を書き分ける。** 各断定に `実測` / `コード上` / `未確認` を付ける。
22
+ 「0 件」は「異常が無い 0」と「見ていない 0」を区別して書く。
23
+ 5. **報告の置き場の公開範囲を先に決める。** 穴の一覧は攻撃者にとっての地図です。
24
+ 社内の共有サービスには「全社チームが自動で付いて外せない」ものがある(incidents/access-sharing/)。
25
+ 作る前に、誰が読めるかを確かめる。
26
+
27
+ ## 1. 手順(この順で進める)
28
+
29
+ | # | やること | 詳細 |
30
+ |---|---|---|
31
+ | 1 | 範囲と前提を読む(AGENTS.md / CLAUDE.md / 認証・M2M・公開エンドポイントに関するルール文書) | [procedure/01-scope.md](procedure/01-scope.md) |
32
+ | 2 | 構成と外部公開面の地図(ingress・invoker・認証の入口・公開エンドポイント・DB・Secret) | [procedure/02-surface-map.md](procedure/02-surface-map.md) |
33
+ | 3 | 認証・認可の観点チェック(事例から作った観点表) | [procedure/03-auth-checklist.md](procedure/03-auth-checklist.md) |
34
+ | 4 | 攻撃者の行動をログで追えるか(保存期間・除外・監査ログ・WAF・検知) | [procedure/04-forensics-readiness.md](procedure/04-forensics-readiness.md) |
35
+ | 5 | **incidents/ を照らす**(同じ型が今のコードに無いか) | [incidents/INDEX.md](incidents/INDEX.md) |
36
+ | 6 | 報告(結論→表→穴→対策の優先度→次の一手) | [procedure/05-report.md](procedure/05-report.md) |
37
+
38
+ **AI に書かせたコード**(貼られたコード・小さなツール・差分)を点検するときは、上の 1〜4 の代わりに [procedure/06-vibe-coding.md](procedure/06-vibe-coding.md) で進め、[patterns/vibe-coding.md](patterns/vibe-coding.md) の型(V1〜)に当てはめる。報告は 6 と同じ。
39
+
40
+ 広い読み取り(全 route の棚卸し等)はサブエージェントへ**1 リポジトリ 1 体**で並列に振り、
41
+ 「読み取り専用」と明記して結論だけ受け取る。サブエージェントの「気になった点」は疑問として扱い、
42
+ 報告前に自分で根拠の行を開いて確かめる。
43
+
44
+ ## 2. 事例を足す
45
+
46
+ - 新しく踏んだ・見つけた・他社で公表された → **その場で 1 件足す**。型は [incidents/_template.md](incidents/_template.md)。
47
+ - 置き場: `incidents/<カテゴリ>/<slug>.md`(自社の事例は `YYYY-MM-DD-<system>-<slug>.md` にすると並べやすい)。
48
+ カテゴリが無ければディレクトリを作り、INDEX のカテゴリ表にも足す。
49
+ - **INDEX.md の一覧とキーワードに 1 行足す**(索引に無いものは引かれない)。
50
+ - リポジトリのルール文書に詳しい記録があるものは**要約+出典パス**にとどめる(一つの事実は一か所)。
51
+ - 「起こりそう」は書かない。**実際に起きた・実測で見つけた・公表された**ものだけ。
52
+ - 🚨 自社の事例を書いたこのディレクトリを公開リポジトリやパッケージに載せない。未対策の穴が含まれる。
@@ -0,0 +1,50 @@
1
+ # 事例の索引(インシデント・ヒヤリハット・他社事例)
2
+
3
+ 点検の手順 5 で全行を照らす。足したら**この表とキーワードに 1 行足す**(索引に無いものは引かれない)。
4
+ grep の例: `grep -rli '<語>' ~/.claude/skills/security-check/incidents/`
5
+
6
+ 同梱の事例は、他社の公表事件 11 件(うち AI に書かせたコード・AI エージェントの事例 10 件)と、ある組織で実際に起きて**直した**事例 11 件、AI に書かせた社内ツールのコード点検 1 件です(組織名・システム名・日付は伏せています)。
7
+ 自分の組織の事例を足したら、このディレクトリは公開しないでください。
8
+
9
+ ## カテゴリ(= ディレクトリ)
10
+ | ディレクトリ | 何を入れるか | 観点表(procedure/03)の # |
11
+ |---|---|---|
12
+ | `external/` | 他社の公表事件 | X1〜X3 |
13
+ | `unauth-route/` | 認証の掛かっていない口・公開判定の誤り | A1〜A4 |
14
+ | `fail-open/` | 設定や依存が欠けたときに開いてしまう作り | F1・F2 |
15
+ | `authz-idor/` | 認可の対象・基準の誤り(IDOR・管理者判定) | Z1〜Z4 |
16
+ | `db-privileges/` | DB の権限・RLS・関数の実行権 | D1 |
17
+ | `client-side-action/` | 利用者の端末から利用者の権限で外部へ書き込む動作 | C1 |
18
+ | `logging-forensics/` | ログの漏えい・欠落・保存・検知 | L1・04 |
19
+ | `access-sharing/` | 資料・成果物の公開範囲 | SKILL §0-5 |
20
+ | `process/` | 作り方・テスト・レビューの手順が原因のもの | T1 |
21
+ | `vibe-coding/` | AI に書かせたコードによくあるミス(LLM 出力の信用・ブラウザのキー・失敗の握りつぶし) | V1〜V26([patterns/vibe-coding.md](../patterns/vibe-coding.md)) |
22
+
23
+ 列挙・総当たり(E1)のように、まだ事例が無い観点は 03 にだけ載せています。事例ができたらディレクトリを作ってください。
24
+
25
+ ## 一覧
26
+ | 組織 | 種別 | 重大度 | 事例 | キーワード |
27
+ |---|---|---|---|---|
28
+ | 他社 | 他社事例 | HIGH | [Moltbook: 「1 行も書いていない」サービスで本番 DB が公開鍵だけで読み書きできた](vibe-coding/2026-01-moltbook-supabase-key-rls-off.md) | Supabase, RLS, JS バンドル, APIトークン, バイブコーディング |
29
+ | 他社 | 他社事例 | HIGH | [AI で作られた公開アプリの調査(5,600 件で 2,000 超の穴・38 万件のうち約 5,000 件に機微な情報)](vibe-coding/2025-2026-vibe-coded-apps-public-by-default.md) | 既定で公開, RLS, service_role, 検索エンジン |
30
+ | 他社 | 他社事例 | CRITICAL | [Base44: 公開の app_id だけで他社の非公開アプリに登録できた](vibe-coding/2025-07-base44-private-app-register-by-app-id.md) | 認証の口, app_id, OTP, SSO, Swagger |
31
+ | 他社 | 他社事例 | HIGH | [Replit: コード凍結中に AI エージェントが本番 DB を消した](vibe-coding/2025-07-replit-agent-deleted-production-db.md) | AI エージェント, 本番 DB, 開発と本番の分離, 偽の報告 |
32
+ | 他社 | 他社事例 | MEDIUM | [Gemini CLI: フォルダ整理でファイルが全部見えなくなった](vibe-coding/2025-07-gemini-cli-lost-files.md) | AI エージェント, ファイル操作, サンドボックス |
33
+ | 他社 | 他社事例 | HIGH | [Amazon Q の VS Code 拡張に環境を消させるプロンプトが入って配布](vibe-coding/2025-07-amazon-q-extension-malicious-prompt.md) | AI 開発ツール, 供給網, プロンプト, 拡張機能 |
34
+ | 他社 | 他社事例 | CRITICAL | [Lovable: 生成アプリの 1 割で公開鍵だけで DB を読み書き(CVE-2025-48757)](vibe-coding/2025-03-lovable-rls-missing-cve-2025-48757.md) | Supabase, RLS, anon キー, PostgREST |
35
+ | 他社 | 他社事例 | HIGH | [「手書き 0 行」を公言した SaaS が公開後すぐに攻撃された](vibe-coding/2025-03-cursor-saas-attacked-after-launch.md) | APIキー, 課金の回避, 有料API |
36
+ | 他社 | 他社事例 | HIGH | [AI が勧める存在しないパッケージを登録したら 3 か月で 3 万回入れられた](vibe-coding/2024-03-huggingface-cli-hallucinated-package.md) | パッケージの幻覚, slopsquatting, 供給網 |
37
+ | 他社 | 他社事例 | HIGH | [公開リポジトリの鍵は数分で拾われる・AI 補助のコミットは漏えい率が約 2 倍](vibe-coding/2023-2026-secrets-on-public-github.md) | APIキー, GitHub, 直書き, .env, 学習データ |
38
+ | 匿名 | ヒヤリ | HIGH | [AI に書かせた請求書ツール(GAS): LLM 出力を innerHTML・シートへ素で入れ、API キーをブラウザで使う](vibe-coding/gas-llm-invoice-sidebar.md) | バイブコーディング, LLM 出力, innerHTML, 数式インジェクション, APIキー, Apps Script |
39
+ | 他社 | 他社事例 | CRITICAL | [アルカディア: トラバーサル→設定ファイルの鍵→横展開](external/2026-10-arcadia-traversal-config-secret.md) | トラバーサル, 設定ファイル, シークレット, 検知遅れ |
40
+ | 匿名 | インシデント | HIGH | [LIFF からの送信が無関係なトークに本人名義で出た](client-side-action/liff-send-posts-to-wrong-chat.md) | LIFF, sendMessages, 誤送信 |
41
+ | 匿名 | インシデント相当 | HIGH | [「サーバー間だから認証不要」+サブパス素通りで氏名が引けた](unauth-route/internal-route-and-subpath-bypass.md) | ingress, app.use, サブパス, 連番 |
42
+ | 匿名 | インシデント相当 | HIGH | [HMAC 鍵が未設定で空文字検証(誰でも署名を作れる)](fail-open/webhook-hmac-empty-secret.md) | HMAC, 空鍵, ?? "" |
43
+ | 匿名 | ヒヤリ | HIGH | [RED を見るために認可を if (true) に壊した](process/break-guard-to-see-red.md) | RED, if (true), テスト |
44
+ | 匿名 | インシデント相当 | CRITICAL | [店舗作成 API の IDOR(所有権チェックが分岐の中だけ)](authz-idor/ownership-check-inside-branch.md) | IDOR, 所有権, service_role |
45
+ | 匿名 | インシデント相当 | CRITICAL | [SECURITY DEFINER が anon に開いていた(ルール化の後に再発)](db-privileges/security-definer-open-to-anon.md) | SECURITY DEFINER, anon, REVOKE |
46
+ | 匿名 | ヒヤリ | MEDIUM | [別アプリの認可の配線をコピーして管理者テーブルを取り違え](authz-idor/copied-admin-wiring.md) | 管理者テーブル, コピー配線 |
47
+ | 匿名 | ヒヤリ | MEDIUM | [LINE チャネル ID が未設定で LIFF の検証が素通り](fail-open/idp-client-id-unset.md) | LIFF, client_id, fail-open |
48
+ | 匿名 | インシデント | MEDIUM | [空の管理者テーブルで fail-closed → bot が全員 403](authz-idor/fail-closed-on-empty-admin-table.md) | fail-closed, 403 |
49
+ | 匿名 | インシデント相当 | LOW〜MED | [Webhook の本文を console.log でそのまま出力(個人情報)](logging-forensics/webhook-body-console-log-pii.md) | ログ, 個人情報, redact |
50
+ | 匿名 | ヒヤリ | LOW〜MED | [穴の一覧を置いた社内共有に全社チームが付いて外せない](access-sharing/internal-share-org-team-auto-attached.md) | 共有, 公開範囲 |
@@ -0,0 +1,24 @@
1
+ # <一行の見出し:何が起きたか>
2
+
3
+ | 項目 | 値 |
4
+ |---|---|
5
+ | 日付 | YYYY-MM-DD |
6
+ | 組織 | 自社 / 他社(社名・公表されている場合のみ) |
7
+ | システム | 例: 管理画面の BFF(Hono・Cloud Run) |
8
+ | 種別 | インシデント(本番で起きた) / ヒヤリハット(本番前・実害なし・点検で発見) / 他社事例 |
9
+ | 重大度 | CRITICAL / HIGH / MEDIUM / LOW |
10
+ | 見つけ方 | 例: 定期監査・コミット監視のレビュー・利用者の報告・点検 |
11
+ | 出典 | リポジトリのルール文書(パス:節)・公表資料の URL |
12
+ | キーワード | INDEX と同じ語を 3〜6 個 |
13
+
14
+ ## 何が起きたか
15
+ (事実だけ。実測した数値・ステータスコードがあれば書く)
16
+
17
+ ## なぜ起きたか
18
+ (作り込みの型。「誰が悪いか」ではなく「どんな前提が崩れたか」)
19
+
20
+ ## どう直したか
21
+ (直していなければ「未対応」と書く)
22
+
23
+ ## 点検で見ること
24
+ (同じ型を見つけるための grep・コマンド・観点。procedure/03-auth-checklist.md の # と対応させる)
@@ -0,0 +1,22 @@
1
+ # 点検結果(穴の一覧)を社内の共有サービスに置いたら、全社向けのチームが自動で付いて外せなかった
2
+
3
+ | 項目 | 値 |
4
+ |---|---|
5
+ | 組織 | 自社(匿名化) |
6
+ | システム | 社内の静的ページ共有サービス |
7
+ | 種別 | ヒヤリハット(外部公開はしていない。閲覧できるようになったのは社内の数名) |
8
+ | 重大度 | LOW〜MEDIUM(穴の一覧は攻撃者の地図。閲覧者を絞る意図が通らなかった) |
9
+ | 見つけ方 | 作成した後の閲覧権限の確認 |
10
+ | キーワード | 共有, 公開範囲, 全社チーム, 閲覧権限, 報告の置き場 |
11
+
12
+ ## 何が起きたか
13
+ 依頼に応じて非公開のページとして作ったが、既定で全社向けのチームが閲覧できる設定になった。「自分だけに絞る」の指示で外そうとしたが、「全社アクセス用のチームは外せません」と拒否された。
14
+
15
+ ## なぜ起きたか
16
+ 作る前に、共有サービスの既定の閲覧範囲と、それを外せるかを確かめなかった。
17
+
18
+ ## どう直したか
19
+ 本人だけが見られる置き場(Basic 認証を掛ける・本人限定の場所に移す)を選び直す。
20
+
21
+ ## 点検で見ること
22
+ - SKILL.md §0-5。セキュリティの報告は**作る前に**置き場の閲覧範囲を決める
@@ -0,0 +1,24 @@
1
+ # 別アプリの認可の配線をコピーして、照合する管理者テーブルを取り違えかけた
2
+
3
+ | 項目 | 値 |
4
+ |---|---|
5
+ | 組織 | 自社(匿名化) |
6
+ | システム | 複数の小さな管理アプリが同じ BFF を使う構成 |
7
+ | 種別 | ヒヤリハット(作業中に発見) |
8
+ | 重大度 | MEDIUM(人と bot で認可の基準が食い違う。逆向きなら穴) |
9
+ | 見つけ方 | 移設作業のレビュー |
10
+ | キーワード | admin テーブル, コピー配線, actor, M2M |
11
+
12
+ ## 何が起きたか
13
+ アプリ B の管理 API を移すとき、アプリ A の「管理者テーブル `a_admins` で actor を判定する」ミドルウェアをそのまま使った。
14
+ アプリ B で通行を決めているのは `b_admins` なので、そちらにしか居ない管理者が 403 になる。
15
+
16
+ ## なぜ起きたか
17
+ 同じ形の名前(`*_admins`)でも役割が違った。アプリ B では通行の可否を決める表、アプリ A では LINE ID の置き場(判定はドメイン一致)。
18
+ しかもアプリ A ではそのミドルウェアが**唯一のゲート**なので、外すと穴になる。前提が逆だった。
19
+
20
+ ## どう直したか
21
+ アプリごとの管理者テーブルの役割表をルール文書に置き、配線する前に表で確かめる運用にした。
22
+
23
+ ## 点検で見ること
24
+ - 03 の Z2。アプリごとに「誰を管理者とみなすか」を表にし、人(UI)と bot(M2M)で同じ基準か
@@ -0,0 +1,23 @@
1
+ # 空の管理者テーブルで fail-closed の照合を足し、bot 経由の操作が全員 403 になった
2
+
3
+ | 項目 | 値 |
4
+ |---|---|
5
+ | 組織 | 自社(匿名化) |
6
+ | システム | 社内 bot から呼ばれる M2M の書き込み API |
7
+ | 種別 | インシデント(本番・可用性側) |
8
+ | 重大度 | MEDIUM(守りを足して業務が止まった。人の UI は無事なので気づきにくい) |
9
+ | 見つけ方 | 利用者からの報告(bot 経由の操作が 403) |
10
+ | キーワード | fail-closed, 管理者テーブル, 403, M2M, 空テーブル |
11
+
12
+ ## 何が起きたか
13
+ 管理者テーブルとの照合(fail-closed)を足したが、本番も検証環境もテーブルは空で、埋める管理画面も無かった。
14
+ 本番に 1 行を手で入れても、他のメンバーは 403 のままだった。
15
+
16
+ ## なぜ起きたか
17
+ 照合先にデータが入っているかを確かめずに fail-closed にした。人はドメイン制限、bot は管理者テーブルと、基準が食い違っていた。
18
+
19
+ ## どう直したか
20
+ テーブルが空ならドメインでの許可に戻し、1 件でも入れば厳格に照合する。照合を足すなら、埋める手段とセットにする。
21
+
22
+ ## 点検で見ること
23
+ - 03 の Z3。セキュリティを強める提案をするときは「止まる人がいないか」を本番の件数(count のみ)で先に確かめる
@@ -0,0 +1,25 @@
1
+ # 店舗作成 API が他社の company.id をそのまま受け取り、他社に店舗を紐付けられた(IDOR)
2
+
3
+ | 項目 | 値 |
4
+ |---|---|
5
+ | 組織 | 自社(匿名化) |
6
+ | システム | Next.js の route handler + Supabase(service_role で書き込み) |
7
+ | 種別 | インシデント相当(本番コードに存在・監査で発見) |
8
+ | 重大度 | CRITICAL(紐付けを経由して、被害企業の個人情報が RLS 上は正規に閲覧できる状態になる) |
9
+ | 見つけ方 | 定期のセキュリティ監査 |
10
+ | キーワード | IDOR, 所有権, company.id, service_role, 分岐の中だけの検証 |
11
+
12
+ ## 何が起きたか
13
+ 所有権のチェックが「`company.id` が無いときに既存の会社を探して使い回す」分岐の中にしか無かった。
14
+ クライアントが最初から `company.id` を送ると分岐を通らず、他社の ID がそのまま service_role の INSERT に使われた。
15
+
16
+ ## なぜ起きたか
17
+ - body を zod で検証せずに分解していた
18
+ - ID を決める処理と所有権のチェックを同じ分岐に置き、ID が無いときしか検証が効かなかった
19
+ - 後続の書き込みが service_role(RLS の外)だった
20
+
21
+ ## どう直したか
22
+ `id` があれば必ず先頭で所有権を検証する。
23
+
24
+ ## 点検で見ること
25
+ - 03 の Z1。body から他エンティティの ID を受ける書込 route を列挙し、検証の位置を見る
@@ -0,0 +1,25 @@
1
+ # 顧客画面(LIFF)からの送信で、無関係な人とのトークに本人の発言としてメッセージが出た
2
+
3
+ | 項目 | 値 |
4
+ |---|---|
5
+ | 組織 | 自社(匿名化) |
6
+ | システム | LINE LIFF で動くアンケートの完了画面 |
7
+ | 種別 | インシデント(本番・実機) |
8
+ | 重大度 | HIGH(第三者とのトークへ本人名義で投稿・情報の誤送信) |
9
+ | 見つけ方 | 実機テスト(他人とのトークで QR を読み取った) |
10
+ | 出典 | https://developers.line.biz/ja/reference/liff/#send-messages |
11
+ | キーワード | LIFF, sendMessages, 誤送信, getContext, utou, push |
12
+
13
+ ## 何が起きたか
14
+ アンケートの完了時に `liff.sendMessages()` でお礼のメッセージを送っていた。このメソッドは、**LIFF を開いたトークに本人の発言として**投稿する。
15
+ 他人とのトークで QR を開けば、そのトークに出る。さらにサーバ側の push と重なり、2 通届いていた。
16
+
17
+ ## なぜ起きたか
18
+ 「公式アカウントとの 1:1 トークで開く」ことを前提にしていた。`liff.getContext().type === "utou"` は他人との 1:1 でも同じ値なので、判定に使えない。
19
+
20
+ ## どう直したか
21
+ 顧客画面からは送らない。すべてサーバからの push にし、冪等キーで 1 回答 1 通にした。
22
+ クライアントのコードに送信 API が無いことを pre-push と CI で検査する。ラッパ関数も置かない。
23
+
24
+ ## 点検で見ること
25
+ - 03 の C1。クライアント側のコードで「利用者の権限で外部へ書き込む」API(送信・投稿・共有)を grep
@@ -0,0 +1,27 @@
1
+ # SECURITY DEFINER 関数が anon に開いていた(REVOKE FROM PUBLIC だけでは anon が残る)
2
+
3
+ | 項目 | 値 |
4
+ |---|---|
5
+ | 組織 | 自社(匿名化) |
6
+ | システム | Supabase(PostgreSQL の関数を RPC で公開) |
7
+ | 種別 | インシデント相当(本番。監査で 3 件 → ルール化した後に新しい関数で再発) |
8
+ | 重大度 | CRITICAL(請求の根拠になる価格のスナップショットを、anon が書き換えられた) |
9
+ | 見つけ方 | Supabase の security advisor / 本番での権限の読み取りクエリ |
10
+ | 出典 | https://supabase.com/docs/guides/troubleshooting/how-can-i-revoke-execution-of-a-postgresql-function-2GYb0A / https://supabase.com/docs/guides/troubleshooting/custom-role-inherits-privileges-that-were-not-explicitly-granted-ddaa1c |
11
+ | キーワード | SECURITY DEFINER, RPC, anon, REVOKE, ALTER DEFAULT PRIVILEGES, /rest/v1/rpc |
12
+
13
+ ## 何が起きたか
14
+ - 監査: 注文の確定・プロフィール作成・検索の関数が anon/authenticated に EXECUTE で開いており、関数の中に呼び出し元の検証が無かった
15
+ - 再発: ルールを書いた後に作った関数が、また anon に開いたままデプロイされた。本番で anon が実行できる関数は 13 件、そのうち 3 件は中で検証していなかった。ルールにあった仕組みの説明が不正確だったのが原因
16
+
17
+ ## なぜ起きたか
18
+ Supabase は既定の権限設定で、`public` スキーマに作った関数の EXECUTE を anon / authenticated に**個別に**付ける。
19
+ `REVOKE ... FROM PUBLIC` は PUBLIC の分しか外さないので、`has_function_privilege('anon', …)` は true のまま残る。
20
+ 「正規の呼び出しは service_role だから GRANT は無害」も誤りで、誰でも `POST /rest/v1/rpc/<fn>` で直接呼べる。
21
+
22
+ ## どう直したか
23
+ `REVOKE ... FROM PUBLIC, anon, authenticated` を必須にした。anon が実行できる関数の台帳を置き、台帳に無いものが増えたら CI で落とす。
24
+
25
+ ## 点検で見ること
26
+ - 03 の D1。本番で anon が実行できる SECURITY DEFINER を**読み取りで数え**、台帳と突き合わせる
27
+ - DB に直結する構成(Drizzle 等)なら、同じ観点を「接続ユーザーの権限」で見る
@@ -0,0 +1,31 @@
1
+ # 他社: ディレクトリトラバーサルで設定ファイルを取られ、中のサービス間の鍵で侵入が広がった
2
+
3
+ | 項目 | 値 |
4
+ |---|---|
5
+ | 日付 | 2026-10-11〜15(公表 2026-10-19、報道 2026-10-20) |
6
+ | 組織 | 他社(株式会社アルカディア。防災情報配信サービス等) |
7
+ | 種別 | 他社事例 |
8
+ | 重大度 | CRITICAL(メールアドレス最大約 313.6 万件などが漏えいした可能性、約 142 万通の不正メール送信。いずれも同社の公表値) |
9
+ | 見つけ方 | アクセス開始から約 17 時間後、DB の異常負荷を社内の監視が検知 |
10
+ | 出典 | https://www.itmedia.co.jp/news/article/2610/20/2000002426/ / 同社「不正アクセスの経過と当社の対処について」(2026-10-19) |
11
+ | キーワード | トラバーサル, 設定ファイル, シークレット, 横展開, 検知遅れ, アクセスログ, 時系列 |
12
+
13
+ ## 何が起きたか(公表資料の時系列・抜粋)
14
+ - 06:26 アクセス開始 → 06:54 ログイン用 JS 取得 → 07:31 モバイル用パス探索 → 09:23 ログイン API に気づき試行
15
+ - 10:52 トラバーサルのチェック → 11:04 セッション固定のチェック → 15:49 SQLi のチェック
16
+ - 15:51〜16:26 JS 大量取得・ログイン大量試行・パス総当たり
17
+ - **16:39 config.ini の取得に成功**(メディアサーバのディレクトリトラバーサル)
18
+ - その後、設定ファイル内の**サービス間ログイン用シークレットキー**でアカウント ID を総当たり→侵入→WordPress プラグイン追加・バックドア設置
19
+ - 23:40 DB の異常負荷で検知。以降、遮断・パスワード変更・メール配信停止・復旧に 4 日
20
+
21
+ ## なぜ起きたか
22
+ - ファイルパスを受け取る口にトラバーサルの脆弱性があった
23
+ - サービス間の鍵を設定ファイルに平文で置いていた(1 つ漏れて横に広がった)
24
+ - 探索・連打の段階で気づく仕組みが無かった(気づいたのは DB 負荷=被害の後)
25
+
26
+ ## 学び(自分のシステムに当てはめる)
27
+ - 公表できる分単位の時系列は、**アクセスログが全部残っていたから**作れた。成功リクエスト(2xx)を捨てていると「取得に成功」の行が残らない。
28
+ - 共用の鍵は、漏れたときの範囲(何サービスに届くか)を先に把握しておく。
29
+
30
+ ## 点検で見ること
31
+ - 03 の X1〜X3、04 の判定表を全部埋める
@@ -0,0 +1,23 @@
1
+ # LINE のチャネル ID が未設定だと、どのチャネルのトークンでも LIFF の検証を通っていた
2
+
3
+ | 項目 | 値 |
4
+ |---|---|
5
+ | 組織 | 自社(匿名化) |
6
+ | システム | LINE LIFF のアクセストークン検証 |
7
+ | 種別 | ヒヤリハット(設定は入っていたため実害なし) |
8
+ | 重大度 | MEDIUM(設定ミス 1 回で、他社の LINE アプリのトークンでなりすましできる) |
9
+ | 見つけ方 | 定期のセキュリティ監査 |
10
+ | キーワード | LIFF, client_id, channelId, fail-open, IdP, audience |
11
+
12
+ ## 何が起きたか
13
+ `if (options?.channelId && verifyData.client_id !== options.channelId)` の形だったため、
14
+ チャネル ID の環境変数が空だと条件全体が偽になり、照合が飛ばされていた。
15
+
16
+ ## なぜ起きたか
17
+ 「設定値があるか」と「照合」を 1 つの条件式に混ぜた。同じファイル群の Origin 検証は「両方無ければ拒否」の fail-closed で、作りが食い違っていた。
18
+
19
+ ## どう直したか
20
+ 設定が無ければ先に拒否し、そのあとで照合する 2 段に分けた。
21
+
22
+ ## 点検で見ること
23
+ - 03 の F1。IdP の audience / client_id / redirect_uri の検証を全部開き、設定が空のときの分岐を確かめる
@@ -0,0 +1,26 @@
1
+ # Webhook の HMAC 鍵が未設定のとき空文字で検証していた(空の鍵は誰でも知っているので、誰でも署名を作れる)
2
+
3
+ | 項目 | 値 |
4
+ |---|---|
5
+ | 組織 | 自社(匿名化) |
6
+ | システム | 外部システムからの Webhook 受信口 |
7
+ | 種別 | インシデント相当(鍵が未設定のまま検証環境にデプロイ済み) |
8
+ | 重大度 | HIGH |
9
+ | 見つけ方 | コミット監視のセキュリティレビュー(作った本人は気づいていない) |
10
+ | キーワード | HMAC, 空鍵, ?? "", fail-open, Webhook, requireEnv |
11
+
12
+ ## 何が起きたか
13
+ `webhookSecret: process.env.WEBHOOK_SECRET ?? ""` と書き、仕様にもコミットにも「未設定なら絶対に一致しないので全部 401」と書いていた。
14
+ 実際には空文字でも HMAC は計算でき、空の鍵は攻撃者も知っている。つまり誰でも正しい署名を作れる。
15
+ ガードの 1 行を消して確かめると、**空文字の署名で 200** が返った。
16
+
17
+ ## なぜ起きたか
18
+ 「たまたま失敗するはず」を守りとして数えた。
19
+
20
+ ## どう直したか
21
+ 秘密を `string | null` で持ち、ミドルウェアの最初の文で `if (!secret) return 401`。
22
+ 受信検証に使う他の鍵は、未設定なら起動時に落ちる `requireEnv` にそろえた。
23
+
24
+ ## 点検で見ること
25
+ - 03 の F1。`?? ""` の grep は 30 件出て危険なのは 1 件だった → **受信の検証に使っているか**で切り分ける
26
+ - 再現: このパッケージの `examples/hmac-empty.mjs`