shield-harness 0.1.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 +279 -0
- package/.claude/hooks/lib/openshell-detect.js +235 -0
- package/.claude/hooks/lib/policy-compat.js +176 -0
- package/.claude/hooks/lib/session-modules/.gitkeep +0 -0
- package/.claude/hooks/lib/sh-utils.js +340 -0
- package/.claude/hooks/lint-on-save.js +240 -0
- package/.claude/hooks/sh-circuit-breaker.js +113 -0
- package/.claude/hooks/sh-config-guard.js +275 -0
- package/.claude/hooks/sh-data-boundary.js +390 -0
- package/.claude/hooks/sh-dep-audit.js +101 -0
- package/.claude/hooks/sh-elicitation.js +244 -0
- package/.claude/hooks/sh-evidence.js +193 -0
- package/.claude/hooks/sh-gate.js +365 -0
- package/.claude/hooks/sh-injection-guard.js +196 -0
- package/.claude/hooks/sh-instructions.js +212 -0
- package/.claude/hooks/sh-output-control.js +217 -0
- package/.claude/hooks/sh-permission-learn.js +227 -0
- package/.claude/hooks/sh-permission.js +157 -0
- package/.claude/hooks/sh-pipeline.js +623 -0
- package/.claude/hooks/sh-postcompact.js +173 -0
- package/.claude/hooks/sh-precompact.js +114 -0
- package/.claude/hooks/sh-quiet-inject.js +148 -0
- package/.claude/hooks/sh-session-end.js +143 -0
- package/.claude/hooks/sh-session-start.js +277 -0
- package/.claude/hooks/sh-subagent.js +86 -0
- package/.claude/hooks/sh-task-gate.js +141 -0
- package/.claude/hooks/sh-user-prompt.js +185 -0
- package/.claude/hooks/sh-worktree.js +230 -0
- package/.claude/patterns/injection-patterns.json +137 -0
- package/.claude/policies/openshell-default.yaml +65 -0
- package/.claude/rules/binding-governance.md +62 -0
- package/.claude/rules/channel-security.md +90 -0
- package/.claude/rules/coding-principles.md +79 -0
- package/.claude/rules/dev-environment.md +40 -0
- package/.claude/rules/implementation-context.md +132 -0
- package/.claude/rules/language.md +26 -0
- package/.claude/rules/security.md +109 -0
- package/.claude/rules/testing.md +43 -0
- package/LICENSE +21 -0
- package/README.ja.md +176 -0
- package/README.md +174 -0
- package/bin/shield-harness.js +241 -0
- package/package.json +42 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// sh-permission.js — 4-category tool governance (Tier 2)
|
|
3
|
+
// Spec: DETAILED_DESIGN.md §3.1
|
|
4
|
+
// Hook event: PreToolUse
|
|
5
|
+
// Matcher: Bash|Edit|Write|Read|WebFetch|MCP
|
|
6
|
+
// Target response time: < 50ms
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const { readHookInput, allow, deny } = require("./lib/sh-utils");
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Category Constants
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
const CATEGORY = {
|
|
16
|
+
READONLY: 1,
|
|
17
|
+
AGENT_SPAWN: 2,
|
|
18
|
+
EXECUTION: 3,
|
|
19
|
+
WRITE: 4,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// READONLY_PATTERNS (Category 1 — auto-approve for Bash commands)
|
|
24
|
+
// Spec: §3.1 READONLY_PATTERNS
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
const READONLY_PATTERNS = [
|
|
28
|
+
/^git\s+(status|diff|log|branch|show|blame|stash\s+list)\b/,
|
|
29
|
+
/^(ls|dir|pwd|whoami|date|uname|cat|head|tail|wc|find|which|type|file)\b/,
|
|
30
|
+
/^npm\s+(test|run|list|outdated|audit)\b/,
|
|
31
|
+
/^(node|bun|python|python3)\s+--version\b/,
|
|
32
|
+
/^(grep|rg|ag|awk)\s/,
|
|
33
|
+
/^sed\s+[^-]/, // sed without flags (read-only pipe usage only)
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// WRITE_PATTERNS (Category 4 — write operation detection for Bash commands)
|
|
38
|
+
// Spec: §3.1 WRITE_PATTERNS
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
const WRITE_PATTERNS = [
|
|
42
|
+
/^(rm|del|rmdir|mkdir|mv|cp|chmod|chown)\b/,
|
|
43
|
+
/^git\s+(push|commit|merge|rebase|reset|checkout|clean)\b/,
|
|
44
|
+
/^npm\s+(install|publish|uninstall|update|link)\b/,
|
|
45
|
+
/^pip3?\s+install\b/,
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Classification Logic
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Classify a tool invocation into one of 4 categories.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} toolName - Claude Code tool name
|
|
56
|
+
* @param {Object} toolInput - Tool input parameters
|
|
57
|
+
* @returns {{ category: number, label: string }}
|
|
58
|
+
*/
|
|
59
|
+
function classify(toolName, toolInput) {
|
|
60
|
+
// --- Category 1: Read-only tools (always auto-approve) ---
|
|
61
|
+
if (
|
|
62
|
+
toolName === "Read" ||
|
|
63
|
+
toolName === "Grep" ||
|
|
64
|
+
toolName === "Glob" ||
|
|
65
|
+
toolName === "WebSearch"
|
|
66
|
+
) {
|
|
67
|
+
return { category: CATEGORY.READONLY, label: "read-only tool" };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// --- Category 2: Agent spawn (delegate to SubagentStart hook) ---
|
|
71
|
+
if (toolName === "Task" || toolName === "Agent") {
|
|
72
|
+
return { category: CATEGORY.AGENT_SPAWN, label: "agent spawn" };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// --- Bash command classification (Categories 1, 3, or 4) ---
|
|
76
|
+
if (toolName === "Bash") {
|
|
77
|
+
const command = (toolInput.command || "").trim();
|
|
78
|
+
|
|
79
|
+
// Check read-only patterns first (Category 1)
|
|
80
|
+
for (const pattern of READONLY_PATTERNS) {
|
|
81
|
+
if (pattern.test(command)) {
|
|
82
|
+
return { category: CATEGORY.READONLY, label: "read-only command" };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check write patterns (Category 4)
|
|
87
|
+
for (const pattern of WRITE_PATTERNS) {
|
|
88
|
+
if (pattern.test(command)) {
|
|
89
|
+
return { category: CATEGORY.WRITE, label: "write command" };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Neither read-only nor write: Execution (Category 3)
|
|
94
|
+
return { category: CATEGORY.EXECUTION, label: "execution command" };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// --- Category 4: Write tools ---
|
|
98
|
+
if (toolName === "Edit" || toolName === "Write") {
|
|
99
|
+
return { category: CATEGORY.WRITE, label: "file write" };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// --- Category 3: WebFetch ---
|
|
103
|
+
if (toolName === "WebFetch") {
|
|
104
|
+
return { category: CATEGORY.EXECUTION, label: "web fetch" };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// --- Category 3: MCP tools ---
|
|
108
|
+
// Any tool not matched above is treated as MCP / unknown execution
|
|
109
|
+
return { category: CATEGORY.EXECUTION, label: "MCP tool" };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Main
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const input = readHookInput();
|
|
118
|
+
const toolName = input.toolName;
|
|
119
|
+
const toolInput = input.toolInput;
|
|
120
|
+
|
|
121
|
+
const { category, label } = classify(toolName, toolInput);
|
|
122
|
+
|
|
123
|
+
switch (category) {
|
|
124
|
+
case CATEGORY.READONLY:
|
|
125
|
+
// Category 1: auto-approve, no context needed
|
|
126
|
+
allow();
|
|
127
|
+
break;
|
|
128
|
+
|
|
129
|
+
case CATEGORY.AGENT_SPAWN:
|
|
130
|
+
// Category 2: allow here, SubagentStart hook handles governance
|
|
131
|
+
allow();
|
|
132
|
+
break;
|
|
133
|
+
|
|
134
|
+
case CATEGORY.EXECUTION:
|
|
135
|
+
// Category 3: allow with context for awareness
|
|
136
|
+
allow(`[sh-permission] Category 3 (execution): ${label}`);
|
|
137
|
+
break;
|
|
138
|
+
|
|
139
|
+
case CATEGORY.WRITE:
|
|
140
|
+
// Category 4: allow (protected path checks are gate.sh's responsibility)
|
|
141
|
+
allow();
|
|
142
|
+
break;
|
|
143
|
+
|
|
144
|
+
default:
|
|
145
|
+
// Unknown category — fail-close
|
|
146
|
+
deny("Unknown category in sh-permission");
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
} catch (err) {
|
|
150
|
+
// fail-close: any uncaught error = deny
|
|
151
|
+
process.stdout.write(
|
|
152
|
+
JSON.stringify({
|
|
153
|
+
reason: `Hook error (sh-permission): ${err.message}`,
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
process.exit(2);
|
|
157
|
+
}
|