shield-harness 0.3.0 → 0.5.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.
@@ -14,10 +14,15 @@ const {
14
14
  sha256,
15
15
  appendEvidence,
16
16
  } = require("./lib/sh-utils");
17
+ const {
18
+ readSettingsFile: readAutoModeSection,
19
+ } = require("./lib/automode-detect");
17
20
 
18
21
  const HOOK_NAME = "sh-config-guard";
19
22
  const SETTINGS_FILE = path.join(".claude", "settings.json");
20
23
  const CONFIG_HASH_FILE = path.join(".claude", "logs", "config-hash.json");
24
+ const POLICIES_DIR = path.join(".claude", "policies");
25
+ const SETTINGS_LOCAL = path.join(".claude", "settings.local.json");
21
26
 
22
27
  // ---------------------------------------------------------------------------
23
28
  // Config Analysis
@@ -63,10 +68,91 @@ function saveConfigSnapshot(snapshot) {
63
68
  fs.writeFileSync(CONFIG_HASH_FILE, JSON.stringify(snapshot, null, 2));
64
69
  }
65
70
 
71
+ /**
72
+ * Count items in a YAML list section.
73
+ * Matches a section header like "deny_read:" followed by indented list items.
74
+ * @param {string} content - YAML file content
75
+ * @param {string} sectionName - Name of the YAML section to count
76
+ * @returns {number} Number of list items in the section
77
+ */
78
+ function countYamlListItems(content, sectionName) {
79
+ const regex = new RegExp(sectionName + ":\\n((?:\\s+-\\s+.+\\n)*)", "m");
80
+ const match = content.match(regex);
81
+ if (!match) return 0;
82
+ return match[1].split("\n").filter((l) => l.trim().startsWith("- ")).length;
83
+ }
84
+
85
+ /**
86
+ * Count host: entries in network_policies section.
87
+ * @param {string} content - YAML file content
88
+ * @returns {number} Number of network endpoint entries
89
+ */
90
+ function countNetworkEndpoints(content) {
91
+ const matches = content.match(/^\s+[-\s]*host:\s/gm);
92
+ return matches ? matches.length : 0;
93
+ }
94
+
95
+ /**
96
+ * Scan .claude/policies/ for YAML files and compute SHA-256 hash of each.
97
+ * @returns {Object.<string, string>} Map of file path to SHA-256 hash
98
+ */
99
+ function extractPolicyHashes() {
100
+ const hashes = {};
101
+ if (!fs.existsSync(POLICIES_DIR)) return hashes;
102
+ try {
103
+ const files = fs
104
+ .readdirSync(POLICIES_DIR)
105
+ .filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
106
+ for (const file of files) {
107
+ const filePath = path.join(POLICIES_DIR, file);
108
+ try {
109
+ const content = fs.readFileSync(filePath, "utf8");
110
+ hashes[filePath] = sha256(content);
111
+ } catch {
112
+ /* skip unreadable */
113
+ }
114
+ }
115
+ } catch {
116
+ /* dir read error */
117
+ }
118
+ return hashes;
119
+ }
120
+
121
+ /**
122
+ * Extract security-critical counts from each YAML policy file.
123
+ * @returns {Object.<string, { deny_read_count: number, deny_write_count: number, read_write_count: number, network_endpoint_count: number }>}
124
+ */
125
+ function extractPolicyMetrics() {
126
+ const metrics = {};
127
+ if (!fs.existsSync(POLICIES_DIR)) return metrics;
128
+ try {
129
+ const files = fs
130
+ .readdirSync(POLICIES_DIR)
131
+ .filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
132
+ for (const file of files) {
133
+ const filePath = path.join(POLICIES_DIR, file);
134
+ try {
135
+ const content = fs.readFileSync(filePath, "utf8");
136
+ metrics[filePath] = {
137
+ deny_read_count: countYamlListItems(content, "deny_read"),
138
+ deny_write_count: countYamlListItems(content, "deny_write"),
139
+ read_write_count: countYamlListItems(content, "read_write"),
140
+ network_endpoint_count: countNetworkEndpoints(content),
141
+ };
142
+ } catch {
143
+ /* skip */
144
+ }
145
+ }
146
+ } catch {
147
+ /* dir read error */
148
+ }
149
+ return metrics;
150
+ }
151
+
66
152
  /**
67
153
  * Extract security-critical fields from settings.
68
154
  * @param {Object} settings
69
- * @returns {{ deny_rules: string[], hook_count: number, hook_events: string[], hook_commands: string[], sandbox: boolean, unsandboxed: boolean, disableAllHooks: boolean }}
155
+ * @returns {{ deny_rules: string[], hook_count: number, hook_events: string[], hook_commands: string[], sandbox: boolean, unsandboxed: boolean, disableAllHooks: boolean, policy_hashes: Object, policy_metrics: Object }}
70
156
  */
71
157
  function extractSecurityFields(settings) {
72
158
  const denyRules = (settings.permissions && settings.permissions.deny) || [];
@@ -89,7 +175,7 @@ function extractSecurityFields(settings) {
89
175
  }
90
176
  }
91
177
 
92
- return {
178
+ const result = {
93
179
  deny_rules: denyRules,
94
180
  hook_count: hookCount,
95
181
  hook_events: hookEvents,
@@ -100,7 +186,35 @@ function extractSecurityFields(settings) {
100
186
  : true,
101
187
  unsandboxed: Boolean(settings.allowUnsandboxedCommands),
102
188
  disableAllHooks: Boolean(settings.disableAllHooks),
189
+ policy_hashes: extractPolicyHashes(),
190
+ policy_metrics: extractPolicyMetrics(),
191
+ soft_deny_count: 0,
192
+ soft_allow_count: 0,
103
193
  };
194
+
195
+ // Auto Mode: read autoMode from both settings files (Phase 7, ADR-038)
196
+ try {
197
+ const mainAutoMode = readAutoModeSection(SETTINGS_FILE);
198
+ const localAutoMode = readAutoModeSection(SETTINGS_LOCAL);
199
+ const softDeny = [
200
+ ...new Set([
201
+ ...(mainAutoMode ? mainAutoMode.soft_deny : []),
202
+ ...(localAutoMode ? localAutoMode.soft_deny : []),
203
+ ]),
204
+ ];
205
+ const softAllow = [
206
+ ...new Set([
207
+ ...(mainAutoMode ? mainAutoMode.soft_allow : []),
208
+ ...(localAutoMode ? localAutoMode.soft_allow : []),
209
+ ]),
210
+ ];
211
+ result.soft_deny_count = softDeny.length;
212
+ result.soft_allow_count = softAllow.length;
213
+ } catch {
214
+ // Auto Mode detection failure is non-blocking for config-guard
215
+ }
216
+
217
+ return result;
104
218
  }
105
219
 
106
220
  /**
@@ -156,6 +270,70 @@ function detectDangerousMutations(stored, current) {
156
270
  reasons.push("disableAllHooks set to true");
157
271
  }
158
272
 
273
+ // Check 6: OpenShell policy file tampering (ADR-037 GA Phase)
274
+ const storedHashes = stored.policy_hashes || {};
275
+ const currentHashes = current.policy_hashes || {};
276
+ const storedMetrics = stored.policy_metrics || {};
277
+ const currentMetrics = current.policy_metrics || {};
278
+
279
+ for (const [filePath, storedHash] of Object.entries(storedHashes)) {
280
+ if (!(filePath in currentHashes)) {
281
+ // Policy file was deleted
282
+ reasons.push(`OpenShell policy file removed: "${filePath}"`);
283
+ continue;
284
+ }
285
+ if (currentHashes[filePath] !== storedHash) {
286
+ // Policy file changed — check for weakening
287
+ const sm = storedMetrics[filePath] || {};
288
+ const cm = currentMetrics[filePath] || {};
289
+
290
+ if (
291
+ sm.deny_read_count > 0 &&
292
+ (cm.deny_read_count || 0) < sm.deny_read_count
293
+ ) {
294
+ reasons.push(
295
+ `OpenShell policy weakened: deny_read reduced (${sm.deny_read_count} → ${cm.deny_read_count || 0}) in "${filePath}"`,
296
+ );
297
+ }
298
+ if (
299
+ sm.deny_write_count > 0 &&
300
+ (cm.deny_write_count || 0) < sm.deny_write_count
301
+ ) {
302
+ reasons.push(
303
+ `OpenShell policy weakened: deny_write reduced (${sm.deny_write_count} → ${cm.deny_write_count || 0}) in "${filePath}"`,
304
+ );
305
+ }
306
+ if ((cm.network_endpoint_count || 0) > (sm.network_endpoint_count || 0)) {
307
+ reasons.push(
308
+ `OpenShell policy weakened: network endpoints expanded (${sm.network_endpoint_count || 0} → ${cm.network_endpoint_count}) in "${filePath}"`,
309
+ );
310
+ }
311
+ if ((cm.read_write_count || 0) > (sm.read_write_count || 0)) {
312
+ reasons.push(
313
+ `OpenShell policy weakened: read_write paths expanded (${sm.read_write_count || 0} → ${cm.read_write_count}) in "${filePath}"`,
314
+ );
315
+ }
316
+ }
317
+ }
318
+
319
+ // Check 7: Auto Mode soft_deny addition (CRITICAL — all default protections lost)
320
+ const storedSoftDeny = stored.soft_deny_count || 0;
321
+ const currentSoftDeny = current.soft_deny_count || 0;
322
+ if (currentSoftDeny > storedSoftDeny) {
323
+ reasons.push(
324
+ `Auto Mode soft_deny added (${storedSoftDeny} → ${currentSoftDeny}) — ALL default protections would be lost`,
325
+ );
326
+ }
327
+
328
+ // Check 8: Auto Mode soft_allow expansion
329
+ const storedSoftAllow = stored.soft_allow_count || 0;
330
+ const currentSoftAllow = current.soft_allow_count || 0;
331
+ if (currentSoftAllow > storedSoftAllow) {
332
+ reasons.push(
333
+ `Auto Mode soft_allow expanded (${storedSoftAllow} → ${currentSoftAllow}) — additional tools auto-approved`,
334
+ );
335
+ }
336
+
159
337
  return {
160
338
  blocked: reasons.length > 0,
161
339
  reasons,
@@ -272,4 +450,8 @@ module.exports = {
272
450
  saveConfigSnapshot,
273
451
  extractSecurityFields,
274
452
  detectDangerousMutations,
453
+ extractPolicyHashes,
454
+ extractPolicyMetrics,
455
+ countYamlListItems,
456
+ countNetworkEndpoints,
275
457
  };
@@ -17,6 +17,8 @@ const {
17
17
  } = require("./lib/sh-utils");
18
18
  const { detectOpenShell } = require("./lib/openshell-detect");
19
19
  const { checkPolicyCompatibility } = require("./lib/policy-compat");
20
+ const { checkPolicyDrift } = require("./lib/policy-drift");
21
+ const { detectAutoMode } = require("./lib/automode-detect");
20
22
 
21
23
  const HOOK_NAME = "sh-session-start";
22
24
  const CLAUDE_MD = "CLAUDE.md";
@@ -230,6 +232,71 @@ try {
230
232
  }
231
233
  }
232
234
 
235
+ // 2e: Policy drift check (ADR-037 GA Phase)
236
+ const POLICIES_DIR = path.join(".claude", "policies");
237
+ if (fs.existsSync(POLICIES_DIR)) {
238
+ try {
239
+ const driftResult = checkPolicyDrift({
240
+ specPath: PERM_SPEC_FILE,
241
+ policyDir: POLICIES_DIR,
242
+ });
243
+ session.policy_drift = driftResult;
244
+ writeSession(session);
245
+
246
+ if (driftResult.has_drift) {
247
+ contextParts.push(
248
+ `[layer-3b] WARNING: Policy drift detected — ${driftResult.warnings.length} issue(s) found`,
249
+ );
250
+ for (const warning of driftResult.warnings.slice(0, 3)) {
251
+ contextParts.push(`[layer-3b] ${warning}`);
252
+ }
253
+ if (driftResult.warnings.length > 3) {
254
+ contextParts.push(
255
+ `[layer-3b] ... and ${driftResult.warnings.length - 3} more`,
256
+ );
257
+ }
258
+ contextParts.push(
259
+ "[layer-3b] Run: npx shield-harness generate-policy to regenerate",
260
+ );
261
+ }
262
+ } catch {
263
+ // drift check failure is non-blocking
264
+ }
265
+ }
266
+
267
+ // 2f: Auto Mode detection (Phase 7, ADR-038)
268
+ let autoModeResult = null;
269
+ try {
270
+ autoModeResult = detectAutoMode();
271
+ session.auto_mode = autoModeResult;
272
+ writeSession(session);
273
+
274
+ if (autoModeResult.danger_level === "critical") {
275
+ contextParts.push(
276
+ `[auto-mode] CRITICAL: Auto Mode soft_deny detected (${autoModeResult.soft_deny_count} rules) — ALL default protections lost`,
277
+ );
278
+ for (const item of autoModeResult.danger_items.slice(0, 3)) {
279
+ contextParts.push(`[auto-mode] Lost: ${item}`);
280
+ }
281
+ if (autoModeResult.danger_items.length > 3) {
282
+ contextParts.push(
283
+ `[auto-mode] ... and ${autoModeResult.danger_items.length - 3} more protections lost`,
284
+ );
285
+ }
286
+ } else if (autoModeResult.danger_level === "warn") {
287
+ contextParts.push(
288
+ `[auto-mode] WARNING: Auto Mode soft_allow detected — ${autoModeResult.soft_allow_count} rules auto-approved`,
289
+ );
290
+ } else if (autoModeResult.detected) {
291
+ contextParts.push("[auto-mode] Auto Mode configured (safe)");
292
+ } else {
293
+ contextParts.push("[auto-mode] Auto Mode: not configured");
294
+ }
295
+ } catch {
296
+ // Auto Mode detection failure is non-blocking
297
+ contextParts.push("[auto-mode] Auto Mode detection error (non-blocking)");
298
+ }
299
+
233
300
  // --- Module 3: Version Check (§5.1.4) ---
234
301
  // Store baseline hashes for instructions monitoring
235
302
  const hashes = {};
@@ -273,6 +340,22 @@ try {
273
340
  sandbox_version: openshellResult.version || null,
274
341
  sandbox_policy_enforced:
275
342
  openshellResult.available && openshellResult.container_running,
343
+ policy_drift: session.policy_drift
344
+ ? {
345
+ has_drift: session.policy_drift.has_drift,
346
+ warning_count: session.policy_drift.warnings
347
+ ? session.policy_drift.warnings.length
348
+ : 0,
349
+ }
350
+ : null,
351
+ auto_mode: autoModeResult
352
+ ? {
353
+ detected: autoModeResult.detected,
354
+ danger_level: autoModeResult.danger_level,
355
+ soft_deny_count: autoModeResult.soft_deny_count,
356
+ soft_allow_count: autoModeResult.soft_allow_count,
357
+ }
358
+ : null,
276
359
  policy_compat: policyCompat
277
360
  ? {
278
361
  compatible: policyCompat.compatible,
@@ -85,11 +85,26 @@
85
85
  "rationale": "Local settings protection",
86
86
  "threat_id": "T-03"
87
87
  },
88
+ {
89
+ "rule": "Edit(.claude/settings.local.json)",
90
+ "rationale": "Local settings protection — Auto Mode injection prevention (Phase 7, ADR-038)",
91
+ "threat_id": "T-03"
92
+ },
88
93
  {
89
94
  "rule": "Write(.claude/permissions-spec.json)",
90
95
  "rationale": "Permissions SoT self-protection",
91
96
  "threat_id": "T-03"
92
97
  },
98
+ {
99
+ "rule": "Edit(.claude/policies/**)",
100
+ "rationale": "OpenShell policy file protection (ADR-037 GA)",
101
+ "threat_id": "T-03"
102
+ },
103
+ {
104
+ "rule": "Write(.claude/policies/**)",
105
+ "rationale": "OpenShell policy file protection (ADR-037 GA)",
106
+ "threat_id": "T-03"
107
+ },
93
108
  {
94
109
  "rule": "Bash(rm -rf /)",
95
110
  "rationale": "System destruction prevention",
@@ -433,7 +448,7 @@
433
448
  ]
434
449
  },
435
450
  "expected_counts": {
436
- "deny": 41,
451
+ "deny": 44,
437
452
  "ask": 4,
438
453
  "allow": 49
439
454
  }
@@ -0,0 +1,105 @@
1
+ # Auto-generated by Shield Harness tier-policy-gen
2
+ # Source: permissions-spec.json v1.0.0
3
+ # Profile: standard
4
+ # Generated: 2026-03-24T06:03:59.030Z
5
+ #
6
+ # Usage:
7
+ # openshell sandbox create --policy <this-file> -- claude
8
+ #
9
+ # Static policies require sandbox recreation to change.
10
+ # Network policies can be hot-reloaded: openshell policy set <name> --policy <file> --wait
11
+
12
+ version: 1
13
+
14
+ # --- Static (locked at sandbox creation) ---
15
+
16
+ filesystem_policy:
17
+ include_workdir: true
18
+ deny_read:
19
+ - ~/.ssh
20
+ - ~/.aws
21
+ - ~/.gnupg
22
+ - **/.env
23
+ - **/.env.*
24
+ - **/credentials*
25
+ - ./**/*.pem
26
+ - ./**/*.key
27
+ - ./**/*secret*
28
+ - ~/.config/gcloud
29
+ deny_write:
30
+ - .claude/hooks
31
+ - .claude/rules
32
+ - .claude/skills
33
+ - .claude/settings.json
34
+ - .claude/permissions-spec.json
35
+ - .claude/settings.local.json
36
+ - .claude/policies
37
+ - tasks/backlog.yaml
38
+ - .shield-harness
39
+ - .claude/patterns
40
+ read_only:
41
+ - /usr
42
+ - /lib
43
+ - /etc
44
+ read_write:
45
+ - /sandbox
46
+ - /tmp
47
+
48
+ landlock:
49
+ compatibility: best_effort
50
+
51
+ process:
52
+ run_as_user: sandbox
53
+ run_as_group: sandbox
54
+
55
+ # --- Dynamic (hot-reloadable) ---
56
+
57
+ network_policies:
58
+ anthropic_api:
59
+ name: anthropic-api
60
+ endpoints:
61
+ - host: api.anthropic.com
62
+ port: 443
63
+ access: full
64
+ binaries:
65
+ - path: /usr/local/bin/claude
66
+
67
+ github:
68
+ name: github
69
+ endpoints:
70
+ - host: github.com
71
+ port: 443
72
+ access: read-only
73
+ - host: "*.githubusercontent.com"
74
+ port: 443
75
+ access: read-only
76
+ binaries:
77
+ - path: /usr/bin/git
78
+
79
+ npm_registry:
80
+ name: npm-registry
81
+ endpoints:
82
+ - host: registry.npmjs.org
83
+ port: 443
84
+ access: read-only
85
+ binaries:
86
+ - path: /usr/bin/npm
87
+ - path: /usr/bin/node
88
+
89
+ # Blocked network operations (from permissions-spec.json deny rules):
90
+ # - curl *
91
+ # - wget *
92
+ # - Invoke-WebRequest *
93
+ # - nc *
94
+ # - ncat *
95
+ # - nmap *
96
+ # - git push --force *
97
+ # - npm publish *
98
+
99
+ # Blocked process operations (from permissions-spec.json deny rules):
100
+ # - rm -rf /
101
+ # - rm -rf ~
102
+ # - del /s /q C:\\
103
+ # - format *
104
+ # - cat */.ssh/*
105
+ # - type *\\.ssh\\*
package/README.ja.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  **Claude Code の全操作を自動防御するセキュリティハーネス**
6
6
 
7
- > 承認ダイアログなしで安全な自律開発を実現
7
+ > フック駆動の自動判定で安全な自律開発を実現
8
8
 
9
- > **Alpha (v0.1.0)**: セキュリティモデルは開発中です。パーミッションルールと設計ドキュメントの整合作業を進めています。本番利用は推奨しません。
9
+ > **v0.5.0**: 22 フック、4 層防御(L1 権限 + L2 フック + L3 サンドボックス + L3b OpenShell)、426 テスト(OWASP AITG 攻撃シミュレーション 108 テスト + Auto Mode 防御テスト 35 テスト含む)。
10
10
 
11
11
  [![English](https://img.shields.io/badge/lang-English-blue?style=flat-square)](README.md)
12
12
  [![日本語](https://img.shields.io/badge/lang-日本語-red?style=flat-square)](#)
@@ -16,7 +16,7 @@
16
16
  ## Shield Harness とは
17
17
 
18
18
  Claude Code の全操作を自動防御するセキュリティハーネス。
19
- 承認ダイアログなしで安全な自律開発を実現します。`.claude/` ディレクトリに展開される hooks + rules + permissions による多層防御でエージェントを統制します。
19
+ フック駆動の自動判定で安全な自律開発を実現します。`.claude/` ディレクトリに展開される hooks + rules + permissions による多層防御でエージェントを統制します。
20
20
 
21
21
  ## クイックスタート
22
22
 
@@ -27,13 +27,13 @@ npx shield-harness init [--profile minimal|standard|strict]
27
27
  ## なぜ Shield Harness なのか
28
28
 
29
29
  - **フック駆動の防御**: 22 のセキュリティフックが Claude Code の全操作を監視
30
- - **承認レスモード**: hooks に全セキュリティ判定を委譲し、人間の承認ダイアログを排除
30
+ - **自動セキュリティ判定**: hooks が全セキュリティ判断をリアルタイムで処理 — 手動承認のボトルネックなし
31
31
  - **fail-close 原則**: 安全条件を確認できない場合は自動的に停止
32
32
  - **証跡記録**: SHA-256 ハッシュチェーンで全 allow/deny 決定を改ざん不能な形で記録
33
33
 
34
34
  ## アーキテクチャ概要
35
35
 
36
- 3 層防御モデル:
36
+ 4 層防御モデル:
37
37
 
38
38
  | 層 | 防御 | 実装 |
39
39
  | -------- | ---------------------- | ------------------------------------------------------------- |
@@ -52,30 +52,30 @@ npx shield-harness init [--profile minimal|standard|strict]
52
52
 
53
53
  ## フックカタログ
54
54
 
55
- | # | フック | イベント | 責務 |
56
- | --- | ---------------- | --------------------- | --------------------------------------------- |
57
- | 1 | permission | PreToolUse | ツール使用の 4 カテゴリ分類 |
58
- | 2 | gate | PreToolUse | Bash コマンドの 7 攻撃ベクトル検査 |
59
- | 3 | injection-guard | PreToolUse | 9 カテゴリ 50+ パターンのインジェクション検出 |
60
- | 4 | data-boundary | PreToolUse | 本番データ境界 + 管轄追跡 |
61
- | 5 | quiet-inject | PreToolUse | quiet フラグ自動注入 |
62
- | 6 | evidence | PostToolUse | SHA-256 ハッシュチェーン証跡 |
63
- | 7 | output-control | PostToolUse | 出力トランケーション + トークン予算 |
64
- | 8 | dep-audit | PostToolUse | パッケージインストール検出 |
65
- | 9 | lint-on-save | PostToolUse | 自動 lint 実行 |
66
- | 10 | session-start | SessionStart | セッション初期化 + 整合性ベースライン |
67
- | 11 | session-end | SessionEnd | クリーンアップ + 統計 |
68
- | 12 | circuit-breaker | Stop | リトライ上限 (3 回) |
69
- | 13 | config-guard | ConfigChange | 設定変更の監視 |
70
- | 14 | user-prompt | UserPromptSubmit | ユーザー入力のインジェクション検査 |
71
- | 15 | permission-learn | PermissionRequest | 権限学習ガード |
72
- | 16 | elicitation | Elicitation | フィッシング + スコープガード |
73
- | 17 | subagent | SubagentStart | サブエージェント予算制約 (25%) |
74
- | 18 | instructions | InstructionsLoaded | ルールファイル整合性監視 |
75
- | 19 | precompact | PreCompact | コンパクション前バックアップ |
76
- | 20 | postcompact | PostCompact | コンパクション後復元 + 検証 |
77
- | 21 | worktree | WorktreeCreate/Remove | セキュリティ伝播 + 証跡マージ |
78
- | 22 | task-gate | TaskCompleted | テストゲート |
55
+ | # | フック | イベント | 責務 |
56
+ | --- | ---------------- | --------------------- | ---------------------------------------------------------------- |
57
+ | 1 | permission | PreToolUse | ツール使用の 4 カテゴリ分類 |
58
+ | 2 | gate | PreToolUse | Bash コマンドの 7 攻撃ベクトル検査 |
59
+ | 3 | injection-guard | PreToolUse | 9 カテゴリ 50+ パターンのインジェクション検出 |
60
+ | 4 | data-boundary | PreToolUse | 本番データ境界 + 管轄追跡 |
61
+ | 5 | quiet-inject | PreToolUse | quiet フラグ自動注入 |
62
+ | 6 | evidence | PostToolUse | SHA-256 ハッシュチェーン証跡 |
63
+ | 7 | output-control | PostToolUse | 出力トランケーション + トークン予算 |
64
+ | 8 | dep-audit | PostToolUse | パッケージインストール検出 |
65
+ | 9 | lint-on-save | PostToolUse | 自動 lint 実行 |
66
+ | 10 | session-start | SessionStart | セッション初期化 + 整合性ベースライン |
67
+ | 11 | session-end | SessionEnd | クリーンアップ + 統計 |
68
+ | 12 | circuit-breaker | Stop | リトライ上限 (3 回) |
69
+ | 13 | config-guard | ConfigChange | 設定変更の監視 + OpenShell ポリシーファイル保護 + Auto Mode 保護 |
70
+ | 14 | user-prompt | UserPromptSubmit | ユーザー入力のインジェクション検査 |
71
+ | 15 | permission-learn | PermissionRequest | 権限学習ガード |
72
+ | 16 | elicitation | Elicitation | フィッシング + スコープガード |
73
+ | 17 | subagent | SubagentStart | サブエージェント予算制約 (25%) |
74
+ | 18 | instructions | InstructionsLoaded | ルールファイル整合性監視 |
75
+ | 19 | precompact | PreCompact | コンパクション前バックアップ |
76
+ | 20 | postcompact | PostCompact | コンパクション後復元 + 検証 |
77
+ | 21 | worktree | WorktreeCreate/Remove | セキュリティ伝播 + 証跡マージ |
78
+ | 22 | task-gate | TaskCompleted | テストゲート |
79
79
 
80
80
  ## パイプライン
81
81
 
@@ -148,10 +148,75 @@ Windows ユーザーにとっての主なメリット:
148
148
 
149
149
  - ポリシーがエージェントプロセスの**外部**に存在 — エージェント自身がガードレールを無効化できない
150
150
  - Docker Desktop + WSL2 バックエンド(一般的な Windows 開発環境)で動作
151
- - 残余リスクを 5% から 1% 未満に低減
151
+ - Layer 1-2 のパターンマッチング限界による残余リスクを大幅に低減
152
152
  - 自由に取り外し可能 — コンテナを停止すれば Shield Harness は Layer 1-2 にフォールバック
153
153
 
154
- > **注意**: OpenShell は Alpha(v0.0.13)— API は将来変更の可能性があります。
154
+ > **注意**: OpenShell は Alpha(v0.0.13)— API は将来変更の可能性があります。Shield Harness 側の GA Phase 統合は完了済み(ADR-037): config guard によるポリシーファイル保護、ポリシードリフトチェック、全ドキュメント整備が完了しています。
155
+
156
+ #### セットアップ
157
+
158
+ **前提条件**: [Docker Desktop](https://www.docker.com/products/docker-desktop/)(Windows では WSL2 バックエンド)
159
+
160
+ ```bash
161
+ # 1. Docker Desktop をインストールし、起動を確認
162
+ # https://www.docker.com/products/docker-desktop/
163
+ docker --version
164
+
165
+ # 2. OpenShell CLI のインストール
166
+ pip install openshell
167
+
168
+ # 3. permissions-spec.json からポリシーを生成
169
+ # .claude/policies/openshell-generated.yaml が作成されます
170
+ npx shield-harness policy generate
171
+
172
+ # 4. OpenShell コンテナを起動し、その中で Claude Code を実行
173
+ # 初回実行時に Docker がサンドボックスイメージを自動取得します
174
+ # コンテナ内ではカーネルレベル制限(Landlock/Seccomp/Network NS)が自動適用されます
175
+ openshell run --policy .claude/policies/openshell-generated.yaml
176
+ ```
177
+
178
+ OpenShell コンテナ内で動作する Claude Code には、Layer 3b のカーネル強制が自動的に適用されます。Shield Harness はセッション開始時にこれを検出します(`sh-session-start.js`)— 追加設定は不要です。
179
+
180
+ OpenShell なしの場合、Shield Harness は Layer 1-2 防御にフォールバックします(フック保護に劣化なし)。
181
+
182
+ ポリシーファイルは以下で保護されます:
183
+
184
+ - `permissions.deny`: `Edit/Write(.claude/policies/**)` でエージェントによる変更をブロック
185
+ - `sandbox.denyWrite`: `.claude/policies` がファイルシステム deny リストに含まれる
186
+ - `sh-config-guard.js`: ハッシュ追跡でポリシーファイルの改竄・弱体化を検出
187
+ - `sh-session-start.js`: セッション開始時のドリフトチェックで spec-policy 整合性を検証
188
+
189
+ ## テスト
190
+
191
+ ```bash
192
+ # 全テスト実行(426 テスト、攻撃シミュレーション含む)
193
+ npm test
194
+
195
+ # 攻撃シミュレーションテストのみ実行
196
+ node --test tests/attack-sim-*.test.js
197
+ ```
198
+
199
+ | テストスイート | OWASP カテゴリ | テスト数 |
200
+ | ----------------------------- | -------------------------------------- | -------- |
201
+ | attack-sim-prompt-injection | AITG-APP-01: Direct Prompt Injection | 25 |
202
+ | attack-sim-indirect-injection | AITG-APP-02: Indirect Prompt Injection | 18 |
203
+ | attack-sim-data-leak | AITG-APP-03: Sensitive Data Leak | 20 |
204
+ | attack-sim-agentic-limits | AITG-APP-06: Agentic Behavior Limits | 18 |
205
+ | attack-sim-sandbox-escape | NVIDIA 3-axis: Sandbox Escape | 15 |
206
+ | attack-sim-defense-chain | SAIF: Defense-in-depth Chain | 12 |
207
+ | attack-sim-automode-bypass | Auto Mode: soft_deny/soft_allow bypass | 15 |
208
+
209
+ ## Auto Mode 対応 (v0.5.0)
210
+
211
+ Shield Harness はセッション開始時に Claude Code の Auto Mode(Research Preview)設定を検知し、危険な設定を防御します:
212
+
213
+ | 設定 | リスク | Shield Harness の対応 |
214
+ | ---------------------- | ------------------------------------------------------- | ------------------------------------------------------------------- |
215
+ | `autoMode.soft_deny` | **CRITICAL** — 分類器のデフォルト保護が全て無効化される | config-guard が追加をブロック、session-start が CRITICAL 警告を出力 |
216
+ | `autoMode.soft_allow` | WARN — 特定ツールが自動許可される | config-guard が拡大をブロック、session-start が WARNING を出力 |
217
+ | `autoMode.environment` | 安全 — 情報のみ | 検知してセッションに記録 |
218
+
219
+ 既存の全フック(PreToolUse, PostToolUse)は Auto Mode でも通常通り発火します。`permissions.deny` ルールは絶対的に維持されます。
155
220
 
156
221
  ## チャンネル連携
157
222
 
@@ -180,11 +245,11 @@ Shield Harness は [Semantic Versioning](https://semver.org/) に準拠します
180
245
  | `minor` | 新機能(後方互換)、Phase 内 must タスク全完了時 | OCSF 対応、新フック追加、CLI オプション追加 |
181
246
  | `major` | 破壊的変更 | スキーマ非互換変更、settings 構造変更 |
182
247
 
183
- **リリーストリガー**: `git tag v1.x.x && git push origin v1.x.x` で `release.yml` が自動実行(npm publish + GitHub Release)。セキュリティ修正は即座に patch リリース。
248
+ **リリーストリガー**: `git tag vX.Y.Z && git push origin vX.Y.Z` で `release.yml` が自動実行(npm publish + GitHub Release)。セキュリティ修正は即座に patch リリース。
184
249
 
185
250
  ## 参考プロジェクト
186
251
 
187
- Shield Harness は 40 以上の Claude Code セキュリティプロジェクトを調査して設計されました。主な参考:
252
+ 主な参考プロジェクト:
188
253
 
189
254
  | プロジェクト | 影響を受けた点 |
190
255
  | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |