shield-harness 0.2.0 → 0.4.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.
@@ -18,6 +18,7 @@ const {
18
18
  const HOOK_NAME = "sh-config-guard";
19
19
  const SETTINGS_FILE = path.join(".claude", "settings.json");
20
20
  const CONFIG_HASH_FILE = path.join(".claude", "logs", "config-hash.json");
21
+ const POLICIES_DIR = path.join(".claude", "policies");
21
22
 
22
23
  // ---------------------------------------------------------------------------
23
24
  // Config Analysis
@@ -63,10 +64,91 @@ function saveConfigSnapshot(snapshot) {
63
64
  fs.writeFileSync(CONFIG_HASH_FILE, JSON.stringify(snapshot, null, 2));
64
65
  }
65
66
 
67
+ /**
68
+ * Count items in a YAML list section.
69
+ * Matches a section header like "deny_read:" followed by indented list items.
70
+ * @param {string} content - YAML file content
71
+ * @param {string} sectionName - Name of the YAML section to count
72
+ * @returns {number} Number of list items in the section
73
+ */
74
+ function countYamlListItems(content, sectionName) {
75
+ const regex = new RegExp(sectionName + ":\\n((?:\\s+-\\s+.+\\n)*)", "m");
76
+ const match = content.match(regex);
77
+ if (!match) return 0;
78
+ return match[1].split("\n").filter((l) => l.trim().startsWith("- ")).length;
79
+ }
80
+
81
+ /**
82
+ * Count host: entries in network_policies section.
83
+ * @param {string} content - YAML file content
84
+ * @returns {number} Number of network endpoint entries
85
+ */
86
+ function countNetworkEndpoints(content) {
87
+ const matches = content.match(/^\s+[-\s]*host:\s/gm);
88
+ return matches ? matches.length : 0;
89
+ }
90
+
91
+ /**
92
+ * Scan .claude/policies/ for YAML files and compute SHA-256 hash of each.
93
+ * @returns {Object.<string, string>} Map of file path to SHA-256 hash
94
+ */
95
+ function extractPolicyHashes() {
96
+ const hashes = {};
97
+ if (!fs.existsSync(POLICIES_DIR)) return hashes;
98
+ try {
99
+ const files = fs
100
+ .readdirSync(POLICIES_DIR)
101
+ .filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
102
+ for (const file of files) {
103
+ const filePath = path.join(POLICIES_DIR, file);
104
+ try {
105
+ const content = fs.readFileSync(filePath, "utf8");
106
+ hashes[filePath] = sha256(content);
107
+ } catch {
108
+ /* skip unreadable */
109
+ }
110
+ }
111
+ } catch {
112
+ /* dir read error */
113
+ }
114
+ return hashes;
115
+ }
116
+
117
+ /**
118
+ * Extract security-critical counts from each YAML policy file.
119
+ * @returns {Object.<string, { deny_read_count: number, deny_write_count: number, read_write_count: number, network_endpoint_count: number }>}
120
+ */
121
+ function extractPolicyMetrics() {
122
+ const metrics = {};
123
+ if (!fs.existsSync(POLICIES_DIR)) return metrics;
124
+ try {
125
+ const files = fs
126
+ .readdirSync(POLICIES_DIR)
127
+ .filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
128
+ for (const file of files) {
129
+ const filePath = path.join(POLICIES_DIR, file);
130
+ try {
131
+ const content = fs.readFileSync(filePath, "utf8");
132
+ metrics[filePath] = {
133
+ deny_read_count: countYamlListItems(content, "deny_read"),
134
+ deny_write_count: countYamlListItems(content, "deny_write"),
135
+ read_write_count: countYamlListItems(content, "read_write"),
136
+ network_endpoint_count: countNetworkEndpoints(content),
137
+ };
138
+ } catch {
139
+ /* skip */
140
+ }
141
+ }
142
+ } catch {
143
+ /* dir read error */
144
+ }
145
+ return metrics;
146
+ }
147
+
66
148
  /**
67
149
  * Extract security-critical fields from settings.
68
150
  * @param {Object} settings
69
- * @returns {{ deny_rules: string[], hook_count: number, hook_events: string[], hook_commands: string[], sandbox: boolean, unsandboxed: boolean, disableAllHooks: boolean }}
151
+ * @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
152
  */
71
153
  function extractSecurityFields(settings) {
72
154
  const denyRules = (settings.permissions && settings.permissions.deny) || [];
@@ -100,6 +182,8 @@ function extractSecurityFields(settings) {
100
182
  : true,
101
183
  unsandboxed: Boolean(settings.allowUnsandboxedCommands),
102
184
  disableAllHooks: Boolean(settings.disableAllHooks),
185
+ policy_hashes: extractPolicyHashes(),
186
+ policy_metrics: extractPolicyMetrics(),
103
187
  };
104
188
  }
105
189
 
@@ -156,6 +240,52 @@ function detectDangerousMutations(stored, current) {
156
240
  reasons.push("disableAllHooks set to true");
157
241
  }
158
242
 
243
+ // Check 6: OpenShell policy file tampering (ADR-037 GA Phase)
244
+ const storedHashes = stored.policy_hashes || {};
245
+ const currentHashes = current.policy_hashes || {};
246
+ const storedMetrics = stored.policy_metrics || {};
247
+ const currentMetrics = current.policy_metrics || {};
248
+
249
+ for (const [filePath, storedHash] of Object.entries(storedHashes)) {
250
+ if (!(filePath in currentHashes)) {
251
+ // Policy file was deleted
252
+ reasons.push(`OpenShell policy file removed: "${filePath}"`);
253
+ continue;
254
+ }
255
+ if (currentHashes[filePath] !== storedHash) {
256
+ // Policy file changed — check for weakening
257
+ const sm = storedMetrics[filePath] || {};
258
+ const cm = currentMetrics[filePath] || {};
259
+
260
+ if (
261
+ sm.deny_read_count > 0 &&
262
+ (cm.deny_read_count || 0) < sm.deny_read_count
263
+ ) {
264
+ reasons.push(
265
+ `OpenShell policy weakened: deny_read reduced (${sm.deny_read_count} → ${cm.deny_read_count || 0}) in "${filePath}"`,
266
+ );
267
+ }
268
+ if (
269
+ sm.deny_write_count > 0 &&
270
+ (cm.deny_write_count || 0) < sm.deny_write_count
271
+ ) {
272
+ reasons.push(
273
+ `OpenShell policy weakened: deny_write reduced (${sm.deny_write_count} → ${cm.deny_write_count || 0}) in "${filePath}"`,
274
+ );
275
+ }
276
+ if ((cm.network_endpoint_count || 0) > (sm.network_endpoint_count || 0)) {
277
+ reasons.push(
278
+ `OpenShell policy weakened: network endpoints expanded (${sm.network_endpoint_count || 0} → ${cm.network_endpoint_count}) in "${filePath}"`,
279
+ );
280
+ }
281
+ if ((cm.read_write_count || 0) > (sm.read_write_count || 0)) {
282
+ reasons.push(
283
+ `OpenShell policy weakened: read_write paths expanded (${sm.read_write_count || 0} → ${cm.read_write_count}) in "${filePath}"`,
284
+ );
285
+ }
286
+ }
287
+ }
288
+
159
289
  return {
160
290
  blocked: reasons.length > 0,
161
291
  reasons,
@@ -272,4 +402,8 @@ module.exports = {
272
402
  saveConfigSnapshot,
273
403
  extractSecurityFields,
274
404
  detectDangerousMutations,
405
+ extractPolicyHashes,
406
+ extractPolicyMetrics,
407
+ countYamlListItems,
408
+ countNetworkEndpoints,
275
409
  };
@@ -111,8 +111,9 @@ try {
111
111
 
112
112
  // Check channel source for evidence metadata (§8.6.3)
113
113
  let isChannel = false;
114
+ let session = {};
114
115
  try {
115
- const session = readSession();
116
+ session = readSession();
116
117
  isChannel = session.source === "channel";
117
118
  } catch {
118
119
  // Session read failure is non-blocking for evidence
@@ -135,6 +136,18 @@ try {
135
136
  category: null,
136
137
  is_channel: isChannel,
137
138
  session_id: sessionId,
139
+ // OpenShell metadata (Beta Phase)
140
+ sandbox_state:
141
+ session.sandbox_openshell && session.sandbox_openshell.available
142
+ ? "active"
143
+ : "inactive",
144
+ sandbox_version:
145
+ (session.sandbox_openshell && session.sandbox_openshell.version) || null,
146
+ sandbox_policy_enforced: !!(
147
+ session.sandbox_openshell &&
148
+ session.sandbox_openshell.available &&
149
+ session.sandbox_openshell.container_running
150
+ ),
138
151
  };
139
152
 
140
153
  // Collect context messages
@@ -17,6 +17,7 @@ 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");
20
21
 
21
22
  const HOOK_NAME = "sh-session-start";
22
23
  const CLAUDE_MD = "CLAUDE.md";
@@ -230,6 +231,38 @@ try {
230
231
  }
231
232
  }
232
233
 
234
+ // 2e: Policy drift check (ADR-037 GA Phase)
235
+ const POLICIES_DIR = path.join(".claude", "policies");
236
+ if (fs.existsSync(POLICIES_DIR)) {
237
+ try {
238
+ const driftResult = checkPolicyDrift({
239
+ specPath: PERM_SPEC_FILE,
240
+ policyDir: POLICIES_DIR,
241
+ });
242
+ session.policy_drift = driftResult;
243
+ writeSession(session);
244
+
245
+ if (driftResult.has_drift) {
246
+ contextParts.push(
247
+ `[layer-3b] WARNING: Policy drift detected — ${driftResult.warnings.length} issue(s) found`,
248
+ );
249
+ for (const warning of driftResult.warnings.slice(0, 3)) {
250
+ contextParts.push(`[layer-3b] ${warning}`);
251
+ }
252
+ if (driftResult.warnings.length > 3) {
253
+ contextParts.push(
254
+ `[layer-3b] ... and ${driftResult.warnings.length - 3} more`,
255
+ );
256
+ }
257
+ contextParts.push(
258
+ "[layer-3b] Run: npx shield-harness generate-policy to regenerate",
259
+ );
260
+ }
261
+ } catch {
262
+ // drift check failure is non-blocking
263
+ }
264
+ }
265
+
233
266
  // --- Module 3: Version Check (§5.1.4) ---
234
267
  // Store baseline hashes for instructions monitoring
235
268
  const hashes = {};
@@ -269,6 +302,18 @@ try {
269
302
  }
270
303
  : { available: false, reason: openshellResult.reason },
271
304
  session_id: input.sessionId,
305
+ sandbox_state: openshellResult.available ? "active" : "inactive",
306
+ sandbox_version: openshellResult.version || null,
307
+ sandbox_policy_enforced:
308
+ openshellResult.available && openshellResult.container_running,
309
+ policy_drift: session.policy_drift
310
+ ? {
311
+ has_drift: session.policy_drift.has_drift,
312
+ warning_count: session.policy_drift.warnings
313
+ ? session.policy_drift.warnings.length
314
+ : 0,
315
+ }
316
+ : null,
272
317
  policy_compat: policyCompat
273
318
  ? {
274
319
  compatible: policyCompat.compatible,
@@ -90,6 +90,16 @@
90
90
  "rationale": "Permissions SoT self-protection",
91
91
  "threat_id": "T-03"
92
92
  },
93
+ {
94
+ "rule": "Edit(.claude/policies/**)",
95
+ "rationale": "OpenShell policy file protection (ADR-037 GA)",
96
+ "threat_id": "T-03"
97
+ },
98
+ {
99
+ "rule": "Write(.claude/policies/**)",
100
+ "rationale": "OpenShell policy file protection (ADR-037 GA)",
101
+ "threat_id": "T-03"
102
+ },
93
103
  {
94
104
  "rule": "Bash(rm -rf /)",
95
105
  "rationale": "System destruction prevention",
@@ -433,7 +443,7 @@
433
443
  ]
434
444
  },
435
445
  "expected_counts": {
436
- "deny": 41,
446
+ "deny": 43,
437
447
  "ask": 4,
438
448
  "allow": 49
439
449
  }
@@ -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.4.0**: 22 フック、4 層防御(L1 権限 + L2 フック + L3 サンドボックス + L3b OpenShell)、391 テスト(OWASP AITG 攻撃シミュレーション 108 テスト含む)。
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 ポリシーファイル保護 |
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
 
@@ -111,6 +111,31 @@ Windows ネイティブでは Claude Code のサンドボックス機能(`sand
111
111
 
112
112
  ### Layer 3b: NVIDIA OpenShell(オプション)
113
113
 
114
+ #### なぜ Layer 3b が必要か?
115
+
116
+ Layer 1(permissions)と Layer 2(hooks)はツール呼び出しの入力テキスト(実行前のコマンド文字列)を検査します。しかし検査を通過したコマンドが実行されると、**OS 上の子プロセスは自由に動作します**。
117
+
118
+ ```
119
+ Layer 1-2(プロセス内):
120
+ Claude Code → [Hook が入力を検査] → コマンド実行 → [子プロセスは自由]
121
+ ↑ ここしか制御できない
122
+
123
+ Layer 3b(プロセス外 = カーネルレベル):
124
+ Claude Code → コマンド実行 → [Landlock: ファイルアクセス制御]
125
+ [Seccomp: syscall 制御]
126
+ [Network NS: ネットワーク隔離]
127
+ ↑ 子プロセスも含めて全てカーネルが制御
128
+ ```
129
+
130
+ | 攻撃ベクトル | Layer 1-2 の対処 | すり抜ける理由 | Layer 3b の防御 |
131
+ | ------------------------------------ | ---------------------------- | ------------------------------------- | ------------------------------------- |
132
+ | パイプチェーンによるファイル読み取り | パターンマッチング | `awk`、`python -c` による間接アクセス | Landlock LSM がカーネルレベルで拒否 |
133
+ | Raw ソケット通信 | `curl`/`wget` の deny ルール | `python`/`node` でソケットを直接操作 | Seccomp BPF がソケット syscall を拒否 |
134
+ | DNS トンネリング | sandbox.network(WSL2 のみ) | DNS クエリにデータを埋め込み | Network Namespace が全 DNS を隔離 |
135
+ | PowerShell ソケット | パターンマッチング | エンコード・難読化 | Seccomp BPF + Network NS の二重防御 |
136
+
137
+ **構造的保証**: エージェント自身がガードレールを無効化することは**不可能** — ポリシーはコンテナ外に存在し、サンドボックス作成時にロックされます。
138
+
114
139
  [NVIDIA OpenShell](https://github.com/NVIDIA/OpenShell)(Apache 2.0)は Docker 上で AI エージェントに**カーネルレベルの隔離**を提供します:
115
140
 
116
141
  | メカニズム | 対象 | 保護内容 |
@@ -123,10 +148,62 @@ Windows ユーザーにとっての主なメリット:
123
148
 
124
149
  - ポリシーがエージェントプロセスの**外部**に存在 — エージェント自身がガードレールを無効化できない
125
150
  - Docker Desktop + WSL2 バックエンド(一般的な Windows 開発環境)で動作
126
- - 残余リスクを 5% から 1% 未満に低減
151
+ - Layer 1-2 のパターンマッチング限界による残余リスクを大幅に低減
127
152
  - 自由に取り外し可能 — コンテナを停止すれば Shield Harness は Layer 1-2 にフォールバック
128
153
 
129
- > **注意**: 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
+ # 全テスト実行(391 テスト、OWASP AITG 攻撃シミュレーション 108 テスト含む)
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 |
130
207
 
131
208
  ## チャンネル連携
132
209
 
@@ -155,11 +232,11 @@ Shield Harness は [Semantic Versioning](https://semver.org/) に準拠します
155
232
  | `minor` | 新機能(後方互換)、Phase 内 must タスク全完了時 | OCSF 対応、新フック追加、CLI オプション追加 |
156
233
  | `major` | 破壊的変更 | スキーマ非互換変更、settings 構造変更 |
157
234
 
158
- **リリーストリガー**: `git tag v1.x.x && git push origin v1.x.x` で `release.yml` が自動実行(npm publish + GitHub Release)。セキュリティ修正は即座に patch リリース。
235
+ **リリーストリガー**: `git tag vX.Y.Z && git push origin vX.Y.Z` で `release.yml` が自動実行(npm publish + GitHub Release)。セキュリティ修正は即座に patch リリース。
159
236
 
160
237
  ## 参考プロジェクト
161
238
 
162
- Shield Harness は 40 以上の Claude Code セキュリティプロジェクトを調査して設計されました。主な参考:
239
+ 主な参考プロジェクト:
163
240
 
164
241
  | プロジェクト | 影響を受けた点 |
165
242
  | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |