shield-harness 0.1.0 → 0.3.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.
- package/.claude/hooks/lib/ocsf-mapper.js +17 -0
- package/.claude/hooks/lib/permissions-validator.js +211 -0
- package/.claude/hooks/lib/sh-utils.js +22 -0
- package/.claude/hooks/lib/tier-policy-gen.js +348 -0
- package/.claude/hooks/sh-evidence.js +14 -1
- package/.claude/hooks/sh-gate.js +9 -1
- package/.claude/hooks/sh-injection-guard.js +28 -9
- package/.claude/hooks/sh-pipeline.js +36 -0
- package/.claude/hooks/sh-session-start.js +28 -0
- package/.claude/permissions-spec.json +440 -0
- package/README.ja.md +25 -0
- package/README.md +25 -0
- package/bin/shield-harness.js +83 -0
- package/package.json +8 -2
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
nfkcNormalize,
|
|
14
14
|
loadPatterns,
|
|
15
15
|
appendEvidence,
|
|
16
|
+
trackDeny,
|
|
16
17
|
} = require("./lib/sh-utils");
|
|
17
18
|
|
|
18
19
|
// Zero-width character regex (checked BEFORE pattern matching to prevent bypass)
|
|
@@ -92,11 +93,20 @@ if (require.main === module) {
|
|
|
92
93
|
detail: "Zero-width character detected in raw input",
|
|
93
94
|
session_id: sessionId,
|
|
94
95
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
"
|
|
99
|
-
|
|
96
|
+
const zwTracker = trackDeny("injection:zero_width");
|
|
97
|
+
if (zwTracker.exceeded) {
|
|
98
|
+
deny(
|
|
99
|
+
"[sh-injection-guard] PROBING DETECTED: zero_width denied " +
|
|
100
|
+
zwTracker.count +
|
|
101
|
+
" times. User confirmation required.",
|
|
102
|
+
);
|
|
103
|
+
} else {
|
|
104
|
+
deny(
|
|
105
|
+
"[sh-injection-guard] Zero-width character detected. " +
|
|
106
|
+
"Invisible characters can be used to bypass security patterns. " +
|
|
107
|
+
"Category: zero_width (severity: high)",
|
|
108
|
+
);
|
|
109
|
+
}
|
|
100
110
|
return;
|
|
101
111
|
}
|
|
102
112
|
|
|
@@ -140,11 +150,20 @@ if (require.main === module) {
|
|
|
140
150
|
pattern: patternStr,
|
|
141
151
|
session_id: sessionId,
|
|
142
152
|
});
|
|
143
|
-
|
|
144
|
-
`
|
|
145
|
-
`Category: ${categoryName} (severity: ${severity}). ` +
|
|
146
|
-
`Description: ${category.description || "N/A"}`,
|
|
153
|
+
const patternTracker = trackDeny(
|
|
154
|
+
`injection:${categoryName}:${patternStr}`,
|
|
147
155
|
);
|
|
156
|
+
if (patternTracker.exceeded) {
|
|
157
|
+
deny(
|
|
158
|
+
`[sh-injection-guard] PROBING DETECTED: ${categoryName} denied ${patternTracker.count} times. User confirmation required.`,
|
|
159
|
+
);
|
|
160
|
+
} else {
|
|
161
|
+
deny(
|
|
162
|
+
`[sh-injection-guard] Injection pattern detected. ` +
|
|
163
|
+
`Category: ${categoryName} (severity: ${severity}). ` +
|
|
164
|
+
`Description: ${category.description || "N/A"}`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
148
167
|
return;
|
|
149
168
|
}
|
|
150
169
|
|
|
@@ -318,6 +318,42 @@ try {
|
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
+
// Permissions alignment gate (permanent countermeasure — Requirement 1: Hard Gate)
|
|
322
|
+
const PERM_SPEC_FILE = path.join(".claude", "permissions-spec.json");
|
|
323
|
+
if (fs.existsSync(PERM_SPEC_FILE)) {
|
|
324
|
+
try {
|
|
325
|
+
const {
|
|
326
|
+
validateAlignment,
|
|
327
|
+
} = require("./lib/permissions-validator");
|
|
328
|
+
const alignResult = validateAlignment(
|
|
329
|
+
PERM_SPEC_FILE,
|
|
330
|
+
path.join(".claude", "settings.json"),
|
|
331
|
+
);
|
|
332
|
+
if (!alignResult.aligned) {
|
|
333
|
+
updateBacklog(taskId, {
|
|
334
|
+
stage_status: "stg2_blocked",
|
|
335
|
+
stg_history_push: {
|
|
336
|
+
gate: "stg2_blocked",
|
|
337
|
+
passed_at: timestamp,
|
|
338
|
+
reason: "permissions_divergence",
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
appendEvidence({
|
|
342
|
+
hook: HOOK_NAME,
|
|
343
|
+
action: "stg2_blocked",
|
|
344
|
+
reason: "permissions_divergence",
|
|
345
|
+
diff_summary: alignResult.summary,
|
|
346
|
+
});
|
|
347
|
+
summary = `STG2 BLOCKED: permissions divergence — ${alignResult.summary}`;
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
} catch (err) {
|
|
351
|
+
// fail-close: validation error also blocks STG2
|
|
352
|
+
summary = `STG2 BLOCKED: permissions check error — ${err.message}`;
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
321
357
|
// Update backlog
|
|
322
358
|
updateBacklog(taskId, {
|
|
323
359
|
stage_status: "stg2_passed",
|
|
@@ -123,6 +123,29 @@ try {
|
|
|
123
123
|
);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// 1e: Permissions alignment check (permanent countermeasure)
|
|
127
|
+
const PERM_SPEC_FILE = path.join(".claude", "permissions-spec.json");
|
|
128
|
+
if (fs.existsSync(PERM_SPEC_FILE)) {
|
|
129
|
+
try {
|
|
130
|
+
const { validateAlignment } = require("./lib/permissions-validator");
|
|
131
|
+
const result = validateAlignment(PERM_SPEC_FILE, SETTINGS_FILE);
|
|
132
|
+
if (result.aligned) {
|
|
133
|
+
contextParts.push(
|
|
134
|
+
`[gate-check] Permissions alignment: OK (${result.counts})`,
|
|
135
|
+
);
|
|
136
|
+
} else {
|
|
137
|
+
contextParts.push(
|
|
138
|
+
"[gate-check] WARNING: Permissions divergence detected",
|
|
139
|
+
);
|
|
140
|
+
contextParts.push(`[gate-check] ${result.summary}`);
|
|
141
|
+
}
|
|
142
|
+
} catch (err) {
|
|
143
|
+
contextParts.push(
|
|
144
|
+
`[gate-check] WARNING: Permissions check failed: ${err.message}`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
126
149
|
// --- Module 2: Env Check (§5.1.2) ---
|
|
127
150
|
|
|
128
151
|
// 2a: OS detection
|
|
@@ -137,6 +160,7 @@ try {
|
|
|
137
160
|
session.session_start = new Date().toISOString();
|
|
138
161
|
session.retry_count = 0;
|
|
139
162
|
session.stop_hook_active = false;
|
|
163
|
+
session.deny_tracker = {};
|
|
140
164
|
contextParts.push("[env-check] Session initialized, token budget set");
|
|
141
165
|
|
|
142
166
|
// 2c: OpenShell detection (Layer 3b, ADR-037)
|
|
@@ -245,6 +269,10 @@ try {
|
|
|
245
269
|
}
|
|
246
270
|
: { available: false, reason: openshellResult.reason },
|
|
247
271
|
session_id: input.sessionId,
|
|
272
|
+
sandbox_state: openshellResult.available ? "active" : "inactive",
|
|
273
|
+
sandbox_version: openshellResult.version || null,
|
|
274
|
+
sandbox_policy_enforced:
|
|
275
|
+
openshellResult.available && openshellResult.container_running,
|
|
248
276
|
policy_compat: policyCompat
|
|
249
277
|
? {
|
|
250
278
|
compatible: policyCompat.compatible,
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"profile": "standard",
|
|
4
|
+
"generated_from": "DETAILED_DESIGN.md §6 + §8.4",
|
|
5
|
+
"description": "Permissions SoT for shield-harness. Changes here must be reflected in settings.json. Protected by deny rules to prevent unauthorized modification.",
|
|
6
|
+
"permissions": {
|
|
7
|
+
"deny": [
|
|
8
|
+
{
|
|
9
|
+
"rule": "Read(~/.ssh/**)",
|
|
10
|
+
"rationale": "SSH key exfiltration prevention",
|
|
11
|
+
"threat_id": "T-01"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"rule": "Read(~/.aws/**)",
|
|
15
|
+
"rationale": "AWS credentials protection",
|
|
16
|
+
"threat_id": "T-01"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"rule": "Read(~/.gnupg/**)",
|
|
20
|
+
"rationale": "GPG key protection",
|
|
21
|
+
"threat_id": "T-01"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"rule": "Read(**/.env)",
|
|
25
|
+
"rationale": "Environment variable file protection",
|
|
26
|
+
"threat_id": "T-02"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"rule": "Read(**/.env.*)",
|
|
30
|
+
"rationale": "Environment variable file protection",
|
|
31
|
+
"threat_id": "T-02"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"rule": "Read(**/credentials*)",
|
|
35
|
+
"rationale": "Credential file protection",
|
|
36
|
+
"threat_id": "T-02"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"rule": "Edit(.claude/hooks/**)",
|
|
40
|
+
"rationale": "Hook tampering prevention",
|
|
41
|
+
"threat_id": "T-03"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"rule": "Edit(.claude/rules/**)",
|
|
45
|
+
"rationale": "Rule tampering prevention",
|
|
46
|
+
"threat_id": "T-03"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"rule": "Edit(.claude/skills/**)",
|
|
50
|
+
"rationale": "Skill tampering prevention",
|
|
51
|
+
"threat_id": "T-03"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"rule": "Edit(.claude/settings.json)",
|
|
55
|
+
"rationale": "Settings self-protection",
|
|
56
|
+
"threat_id": "T-03"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"rule": "Edit(.claude/permissions-spec.json)",
|
|
60
|
+
"rationale": "Permissions SoT self-protection",
|
|
61
|
+
"threat_id": "T-03"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"rule": "Write(.claude/hooks/**)",
|
|
65
|
+
"rationale": "Hook tampering prevention",
|
|
66
|
+
"threat_id": "T-03"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"rule": "Write(.claude/rules/**)",
|
|
70
|
+
"rationale": "Rule tampering prevention",
|
|
71
|
+
"threat_id": "T-03"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"rule": "Write(.claude/skills/**)",
|
|
75
|
+
"rationale": "Skill tampering prevention",
|
|
76
|
+
"threat_id": "T-03"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"rule": "Write(.claude/settings.json)",
|
|
80
|
+
"rationale": "Settings self-protection",
|
|
81
|
+
"threat_id": "T-03"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"rule": "Write(.claude/settings.local.json)",
|
|
85
|
+
"rationale": "Local settings protection",
|
|
86
|
+
"threat_id": "T-03"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"rule": "Write(.claude/permissions-spec.json)",
|
|
90
|
+
"rationale": "Permissions SoT self-protection",
|
|
91
|
+
"threat_id": "T-03"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"rule": "Bash(rm -rf /)",
|
|
95
|
+
"rationale": "System destruction prevention",
|
|
96
|
+
"threat_id": "T-04"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"rule": "Bash(rm -rf ~)",
|
|
100
|
+
"rationale": "Home directory destruction prevention",
|
|
101
|
+
"threat_id": "T-04"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"rule": "Bash(del /s /q C:\\\\)",
|
|
105
|
+
"rationale": "Windows system destruction prevention",
|
|
106
|
+
"threat_id": "T-04"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"rule": "Bash(format *)",
|
|
110
|
+
"rationale": "Disk format prevention",
|
|
111
|
+
"threat_id": "T-04"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"rule": "Bash(cat */.ssh/*)",
|
|
115
|
+
"rationale": "SSH key display prevention",
|
|
116
|
+
"threat_id": "T-01"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"rule": "Bash(type *\\\\.ssh\\\\*)",
|
|
120
|
+
"rationale": "Windows SSH key display prevention",
|
|
121
|
+
"threat_id": "T-01"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"rule": "Bash(curl *)",
|
|
125
|
+
"rationale": "Network isolation - data exfiltration prevention",
|
|
126
|
+
"threat_id": "T-05"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"rule": "Bash(wget *)",
|
|
130
|
+
"rationale": "Network isolation - data exfiltration prevention",
|
|
131
|
+
"threat_id": "T-05"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"rule": "Bash(Invoke-WebRequest *)",
|
|
135
|
+
"rationale": "Windows network isolation",
|
|
136
|
+
"threat_id": "T-05"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"rule": "Bash(nc *)",
|
|
140
|
+
"rationale": "Network reconnaissance prevention",
|
|
141
|
+
"threat_id": "T-05"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"rule": "Bash(ncat *)",
|
|
145
|
+
"rationale": "Network reconnaissance prevention",
|
|
146
|
+
"threat_id": "T-05"
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"rule": "Bash(nmap *)",
|
|
150
|
+
"rationale": "Network scanning prevention",
|
|
151
|
+
"threat_id": "T-05"
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"rule": "Bash(git push --force *)",
|
|
155
|
+
"rationale": "Force push prevention",
|
|
156
|
+
"threat_id": "T-06"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"rule": "Bash(npm publish *)",
|
|
160
|
+
"rationale": "Unauthorized package publication prevention",
|
|
161
|
+
"threat_id": "T-06"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"rule": "Edit(tasks/backlog.yaml)",
|
|
165
|
+
"rationale": "Backlog SoT protection (§8.4)",
|
|
166
|
+
"threat_id": "T-07"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"rule": "Write(tasks/backlog.yaml)",
|
|
170
|
+
"rationale": "Backlog SoT protection (§8.4)",
|
|
171
|
+
"threat_id": "T-07"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"rule": "Read(./**/*.pem)",
|
|
175
|
+
"rationale": "Certificate private key protection",
|
|
176
|
+
"threat_id": "T-02"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"rule": "Read(./**/*.key)",
|
|
180
|
+
"rationale": "Key file protection",
|
|
181
|
+
"threat_id": "T-02"
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"rule": "Read(./**/*secret*)",
|
|
185
|
+
"rationale": "Secret file protection",
|
|
186
|
+
"threat_id": "T-02"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"rule": "Read(~/.config/gcloud/**)",
|
|
190
|
+
"rationale": "GCP credential protection",
|
|
191
|
+
"threat_id": "T-01"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"rule": "Edit(.shield-harness/**)",
|
|
195
|
+
"rationale": "Session data protection",
|
|
196
|
+
"threat_id": "T-03"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"rule": "Write(.shield-harness/**)",
|
|
200
|
+
"rationale": "Session data protection",
|
|
201
|
+
"threat_id": "T-03"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"rule": "Edit(.claude/patterns/**)",
|
|
205
|
+
"rationale": "Injection pattern protection",
|
|
206
|
+
"threat_id": "T-03"
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"rule": "Write(.claude/patterns/**)",
|
|
210
|
+
"rationale": "Injection pattern protection",
|
|
211
|
+
"threat_id": "T-03"
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
"ask": [
|
|
215
|
+
{
|
|
216
|
+
"rule": "Bash(git push *)",
|
|
217
|
+
"rationale": "Remote push requires confirmation",
|
|
218
|
+
"threat_id": "T-06"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"rule": "Edit(.claude/**)",
|
|
222
|
+
"rationale": "Config changes require confirmation",
|
|
223
|
+
"threat_id": "T-03"
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"rule": "Bash(npm install *)",
|
|
227
|
+
"rationale": "Dependency changes require confirmation",
|
|
228
|
+
"threat_id": "T-08"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"rule": "Bash(npx *)",
|
|
232
|
+
"rationale": "Arbitrary package execution requires confirmation",
|
|
233
|
+
"threat_id": "T-08"
|
|
234
|
+
}
|
|
235
|
+
],
|
|
236
|
+
"allow": [
|
|
237
|
+
{
|
|
238
|
+
"rule": "Bash(git status)",
|
|
239
|
+
"rationale": "Read-only git operation"
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"rule": "Bash(git diff *)",
|
|
243
|
+
"rationale": "Read-only git operation"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"rule": "Bash(git log *)",
|
|
247
|
+
"rationale": "Read-only git operation"
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"rule": "Bash(git branch *)",
|
|
251
|
+
"rationale": "Read-only git operation"
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"rule": "Bash(git show *)",
|
|
255
|
+
"rationale": "Read-only git operation"
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
"rule": "Bash(git blame *)",
|
|
259
|
+
"rationale": "Read-only git operation"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"rule": "Bash(git stash list)",
|
|
263
|
+
"rationale": "Read-only git operation"
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"rule": "Bash(git stash show *)",
|
|
267
|
+
"rationale": "Read-only git operation"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"rule": "Bash(npm test)",
|
|
271
|
+
"rationale": "Safe build/test operation"
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
"rule": "Bash(npm run *)",
|
|
275
|
+
"rationale": "Safe build/test operation"
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"rule": "Bash(npm list *)",
|
|
279
|
+
"rationale": "Read-only npm operation"
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
"rule": "Bash(npm outdated)",
|
|
283
|
+
"rationale": "Read-only npm operation"
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
"rule": "Bash(npm audit *)",
|
|
287
|
+
"rationale": "Security audit operation"
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"rule": "Bash(node --version)",
|
|
291
|
+
"rationale": "Version check"
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
"rule": "Bash(bun --version)",
|
|
295
|
+
"rationale": "Version check"
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"rule": "Bash(python3 --version)",
|
|
299
|
+
"rationale": "Version check"
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"rule": "Bash(ls *)",
|
|
303
|
+
"rationale": "Read-only filesystem operation"
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
"rule": "Bash(dir *)",
|
|
307
|
+
"rationale": "Windows read-only filesystem operation"
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
"rule": "Bash(cat *)",
|
|
311
|
+
"rationale": "Read-only file operation"
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"rule": "Bash(head *)",
|
|
315
|
+
"rationale": "Read-only file operation"
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
"rule": "Bash(tail *)",
|
|
319
|
+
"rationale": "Read-only file operation"
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
"rule": "Bash(wc *)",
|
|
323
|
+
"rationale": "Read-only file operation"
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
"rule": "Bash(find *)",
|
|
327
|
+
"rationale": "Read-only filesystem operation"
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"rule": "Bash(grep *)",
|
|
331
|
+
"rationale": "Read-only search operation"
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"rule": "Bash(rg *)",
|
|
335
|
+
"rationale": "Read-only search operation"
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"rule": "Bash(ag *)",
|
|
339
|
+
"rationale": "Read-only search operation"
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"rule": "Bash(which *)",
|
|
343
|
+
"rationale": "Read-only command lookup"
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
"rule": "Bash(type *)",
|
|
347
|
+
"rationale": "Read-only command lookup"
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
"rule": "Bash(file *)",
|
|
351
|
+
"rationale": "Read-only file type detection"
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
"rule": "Bash(pwd)",
|
|
355
|
+
"rationale": "Read-only directory info"
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"rule": "Bash(whoami)",
|
|
359
|
+
"rationale": "Read-only user info"
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
"rule": "Bash(date)",
|
|
363
|
+
"rationale": "Read-only system info"
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
"rule": "Bash(uname *)",
|
|
367
|
+
"rationale": "Read-only system info"
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"rule": "Bash(echo *)",
|
|
371
|
+
"rationale": "Safe output operation"
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
"rule": "Bash(jq *)",
|
|
375
|
+
"rationale": "Read-only JSON processing"
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
"rule": "Bash(sed *)",
|
|
379
|
+
"rationale": "Text processing"
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"rule": "Bash(awk *)",
|
|
383
|
+
"rationale": "Text processing"
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"rule": "Bash(sort *)",
|
|
387
|
+
"rationale": "Read-only text operation"
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
"rule": "Bash(uniq *)",
|
|
391
|
+
"rationale": "Read-only text operation"
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"rule": "Read(**)",
|
|
395
|
+
"rationale": "Full read access (deny rules exclude sensitive paths)"
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"rule": "Glob(*)",
|
|
399
|
+
"rationale": "Read-only filesystem search"
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
"rule": "Grep(*)",
|
|
403
|
+
"rationale": "Read-only content search"
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"rule": "WebSearch(*)",
|
|
407
|
+
"rationale": "Read-only web search"
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
"rule": "WebFetch(*)",
|
|
411
|
+
"rationale": "Read-only web fetch"
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"rule": "Task(*)",
|
|
415
|
+
"rationale": "Internal task management"
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
"rule": "Skill(*)",
|
|
419
|
+
"rationale": "Internal skill execution"
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
"rule": "TodoWrite(*)",
|
|
423
|
+
"rationale": "Internal todo management"
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
"rule": "Bash(diff *)",
|
|
427
|
+
"rationale": "Read-only file comparison"
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
"rule": "Bash(tree *)",
|
|
431
|
+
"rationale": "Read-only directory view"
|
|
432
|
+
}
|
|
433
|
+
]
|
|
434
|
+
},
|
|
435
|
+
"expected_counts": {
|
|
436
|
+
"deny": 41,
|
|
437
|
+
"ask": 4,
|
|
438
|
+
"allow": 49
|
|
439
|
+
}
|
|
440
|
+
}
|
package/README.ja.md
CHANGED
|
@@ -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
|
| メカニズム | 対象 | 保護内容 |
|
package/README.md
CHANGED
|
@@ -109,6 +109,31 @@ For enterprise environments, supplementing with Windows Firewall outbound rules
|
|
|
109
109
|
|
|
110
110
|
### Layer 3b: NVIDIA OpenShell (Optional)
|
|
111
111
|
|
|
112
|
+
#### Why Layer 3b?
|
|
113
|
+
|
|
114
|
+
Layer 1 (permissions) and Layer 2 (hooks) inspect tool call inputs — the command text before execution. Once a command passes these checks, the **spawned child process runs freely at the OS level**.
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
Layer 1-2 (in-process):
|
|
118
|
+
Claude Code → [Hook inspects input] → Command execution → [Child process is free]
|
|
119
|
+
↑ Only controls this point
|
|
120
|
+
|
|
121
|
+
Layer 3b (out-of-process = kernel-level):
|
|
122
|
+
Claude Code → Command execution → [Landlock: Filesystem access control]
|
|
123
|
+
[Seccomp: Syscall control]
|
|
124
|
+
[Network NS: Network isolation]
|
|
125
|
+
↑ Kernel controls ALL processes including children
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
| Attack Vector | Layer 1-2 Defense | Why It Bypasses | Layer 3b Defense |
|
|
129
|
+
| ------------------------ | --------------------------- | -------------------------------------- | ------------------------------------- |
|
|
130
|
+
| Pipe chain file access | Pattern matching | Indirect access via `awk`, `python -c` | Landlock LSM denies at kernel level |
|
|
131
|
+
| Raw socket communication | `curl`/`wget` deny rules | Direct socket via `python`/`node` | Seccomp BPF blocks socket syscalls |
|
|
132
|
+
| DNS tunneling | sandbox.network (WSL2 only) | Data embedded in DNS queries | Network Namespace isolates all DNS |
|
|
133
|
+
| PowerShell sockets | Pattern matching | Encoding/obfuscation | Seccomp BPF + Network NS dual defense |
|
|
134
|
+
|
|
135
|
+
**Structural guarantee**: The agent **cannot** disable its own guardrails — policies exist outside the container and are locked at sandbox creation.
|
|
136
|
+
|
|
112
137
|
[NVIDIA OpenShell](https://github.com/NVIDIA/OpenShell) (Apache 2.0) provides **kernel-level isolation** for AI agents via Docker:
|
|
113
138
|
|
|
114
139
|
| Mechanism | Target | Protection |
|