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.
Files changed (43) hide show
  1. package/.claude/hooks/lib/ocsf-mapper.js +279 -0
  2. package/.claude/hooks/lib/openshell-detect.js +235 -0
  3. package/.claude/hooks/lib/policy-compat.js +176 -0
  4. package/.claude/hooks/lib/session-modules/.gitkeep +0 -0
  5. package/.claude/hooks/lib/sh-utils.js +340 -0
  6. package/.claude/hooks/lint-on-save.js +240 -0
  7. package/.claude/hooks/sh-circuit-breaker.js +113 -0
  8. package/.claude/hooks/sh-config-guard.js +275 -0
  9. package/.claude/hooks/sh-data-boundary.js +390 -0
  10. package/.claude/hooks/sh-dep-audit.js +101 -0
  11. package/.claude/hooks/sh-elicitation.js +244 -0
  12. package/.claude/hooks/sh-evidence.js +193 -0
  13. package/.claude/hooks/sh-gate.js +365 -0
  14. package/.claude/hooks/sh-injection-guard.js +196 -0
  15. package/.claude/hooks/sh-instructions.js +212 -0
  16. package/.claude/hooks/sh-output-control.js +217 -0
  17. package/.claude/hooks/sh-permission-learn.js +227 -0
  18. package/.claude/hooks/sh-permission.js +157 -0
  19. package/.claude/hooks/sh-pipeline.js +623 -0
  20. package/.claude/hooks/sh-postcompact.js +173 -0
  21. package/.claude/hooks/sh-precompact.js +114 -0
  22. package/.claude/hooks/sh-quiet-inject.js +148 -0
  23. package/.claude/hooks/sh-session-end.js +143 -0
  24. package/.claude/hooks/sh-session-start.js +277 -0
  25. package/.claude/hooks/sh-subagent.js +86 -0
  26. package/.claude/hooks/sh-task-gate.js +141 -0
  27. package/.claude/hooks/sh-user-prompt.js +185 -0
  28. package/.claude/hooks/sh-worktree.js +230 -0
  29. package/.claude/patterns/injection-patterns.json +137 -0
  30. package/.claude/policies/openshell-default.yaml +65 -0
  31. package/.claude/rules/binding-governance.md +62 -0
  32. package/.claude/rules/channel-security.md +90 -0
  33. package/.claude/rules/coding-principles.md +79 -0
  34. package/.claude/rules/dev-environment.md +40 -0
  35. package/.claude/rules/implementation-context.md +132 -0
  36. package/.claude/rules/language.md +26 -0
  37. package/.claude/rules/security.md +109 -0
  38. package/.claude/rules/testing.md +43 -0
  39. package/LICENSE +21 -0
  40. package/README.ja.md +176 -0
  41. package/README.md +174 -0
  42. package/bin/shield-harness.js +241 -0
  43. package/package.json +42 -0
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env node
2
+ // sh-worktree.js — Worktree security propagation & evidence merge
3
+ // Spec: DETAILED_DESIGN.md §5.8
4
+ // Events: WorktreeCreate, WorktreeRemove
5
+ // Target response time: < 200ms
6
+ "use strict";
7
+
8
+ const fs = require("fs");
9
+ const path = require("path");
10
+ const {
11
+ readHookInput,
12
+ allow,
13
+ deny,
14
+ appendEvidence,
15
+ EVIDENCE_FILE,
16
+ } = require("./lib/sh-utils");
17
+
18
+ const HOOK_NAME = "sh-worktree";
19
+
20
+ // Files/directories to copy to worktree
21
+ const COPY_TARGETS = [
22
+ { src: path.join(".claude", "settings.json"), type: "file" },
23
+ {
24
+ src: path.join(".claude", "patterns", "injection-patterns.json"),
25
+ type: "file",
26
+ },
27
+ ];
28
+
29
+ // Directories to recursively copy
30
+ const COPY_DIRS = [
31
+ {
32
+ src: path.join(".claude", "hooks"),
33
+ filter: (f) => f.startsWith("sh-") && f.endsWith(".js"),
34
+ },
35
+ {
36
+ src: path.join(".claude", "hooks", "lib"),
37
+ filter: (f) => f === "sh-utils.js",
38
+ },
39
+ { src: path.join(".claude", "rules"), filter: (f) => f.endsWith(".md") },
40
+ ];
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Copy Helpers
44
+ // ---------------------------------------------------------------------------
45
+
46
+ /**
47
+ * Copy a single file to the worktree, preserving directory structure.
48
+ * @param {string} srcFile - Relative path from project root
49
+ * @param {string} worktreePath - Absolute path to worktree root
50
+ */
51
+ function copyFileToWorktree(srcFile, worktreePath) {
52
+ if (!fs.existsSync(srcFile)) return;
53
+
54
+ const destFile = path.join(worktreePath, srcFile);
55
+ const destDir = path.dirname(destFile);
56
+
57
+ if (!fs.existsSync(destDir)) {
58
+ fs.mkdirSync(destDir, { recursive: true });
59
+ }
60
+
61
+ fs.copyFileSync(srcFile, destFile);
62
+ }
63
+
64
+ /**
65
+ * Copy filtered files from a directory to worktree.
66
+ * @param {string} srcDir - Relative path from project root
67
+ * @param {Function} filter - File name filter function
68
+ * @param {string} worktreePath - Absolute path to worktree root
69
+ */
70
+ function copyDirToWorktree(srcDir, filter, worktreePath) {
71
+ if (!fs.existsSync(srcDir)) return;
72
+
73
+ const files = fs.readdirSync(srcDir).filter(filter);
74
+ for (const f of files) {
75
+ copyFileToWorktree(path.join(srcDir, f), worktreePath);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Propagate Shield Harness security config to a worktree.
81
+ * @param {string} worktreePath
82
+ * @returns {number} Number of files copied
83
+ */
84
+ function propagateConfig(worktreePath) {
85
+ let count = 0;
86
+
87
+ // Copy individual files
88
+ for (const { src } of COPY_TARGETS) {
89
+ if (fs.existsSync(src)) {
90
+ copyFileToWorktree(src, worktreePath);
91
+ count++;
92
+ }
93
+ }
94
+
95
+ // Copy filtered directories
96
+ for (const { src, filter } of COPY_DIRS) {
97
+ if (fs.existsSync(src)) {
98
+ const files = fs.readdirSync(src).filter(filter);
99
+ for (const f of files) {
100
+ copyFileToWorktree(path.join(src, f), worktreePath);
101
+ count++;
102
+ }
103
+ }
104
+ }
105
+
106
+ return count;
107
+ }
108
+
109
+ /**
110
+ * Merge worktree evidence ledger into main ledger.
111
+ * @param {string} worktreePath
112
+ * @returns {number} Number of entries merged
113
+ */
114
+ function mergeEvidence(worktreePath) {
115
+ const worktreeLedger = path.join(
116
+ worktreePath,
117
+ ".shield-harness",
118
+ "logs",
119
+ "evidence-ledger.jsonl",
120
+ );
121
+
122
+ if (!fs.existsSync(worktreeLedger)) return 0;
123
+
124
+ const content = fs.readFileSync(worktreeLedger, "utf8").trim();
125
+ if (!content) return 0;
126
+
127
+ const lines = content.split("\n");
128
+ let merged = 0;
129
+
130
+ for (const line of lines) {
131
+ if (!line.trim()) continue;
132
+ try {
133
+ const entry = JSON.parse(line);
134
+ // Mark as merged from worktree
135
+ entry.source_worktree = worktreePath;
136
+ appendEvidence(entry);
137
+ merged++;
138
+ } catch {
139
+ // Skip malformed entries
140
+ }
141
+ }
142
+
143
+ return merged;
144
+ }
145
+
146
+ // ---------------------------------------------------------------------------
147
+ // Main
148
+ // ---------------------------------------------------------------------------
149
+
150
+ try {
151
+ const input = readHookInput();
152
+ const { hookType } = input;
153
+ const worktreePath =
154
+ input.toolInput.worktree_path || input.toolInput.path || "";
155
+
156
+ if (!worktreePath) {
157
+ allow();
158
+ return;
159
+ }
160
+
161
+ if (hookType === "WorktreeCreate") {
162
+ // Propagate security config to new worktree
163
+ const filesCopied = propagateConfig(worktreePath);
164
+
165
+ try {
166
+ appendEvidence({
167
+ hook: HOOK_NAME,
168
+ event: "WorktreeCreate",
169
+ decision: "allow",
170
+ worktree_path: worktreePath,
171
+ files_copied: filesCopied,
172
+ session_id: input.sessionId,
173
+ });
174
+ } catch {
175
+ // Non-blocking
176
+ }
177
+
178
+ allow(
179
+ `[${HOOK_NAME}] Shield Harness 設定を worktree にコピーしました (${filesCopied} files)`,
180
+ );
181
+ return;
182
+ }
183
+
184
+ if (hookType === "WorktreeRemove") {
185
+ // Merge evidence from worktree
186
+ const entriesMerged = mergeEvidence(worktreePath);
187
+
188
+ try {
189
+ appendEvidence({
190
+ hook: HOOK_NAME,
191
+ event: "WorktreeRemove",
192
+ decision: "allow",
193
+ worktree_path: worktreePath,
194
+ entries_merged: entriesMerged,
195
+ session_id: input.sessionId,
196
+ });
197
+ } catch {
198
+ // Non-blocking
199
+ }
200
+
201
+ allow(
202
+ `[${HOOK_NAME}] Worktree 証跡をマージしました (${entriesMerged} entries)`,
203
+ );
204
+ return;
205
+ }
206
+
207
+ // Unknown event type — allow passthrough
208
+ allow();
209
+ } catch (err) {
210
+ // SECURITY hook — fail-close
211
+ process.stdout.write(
212
+ JSON.stringify({
213
+ reason: `[${HOOK_NAME}] Hook error (fail-close): ${err.message}`,
214
+ }),
215
+ );
216
+ process.exit(2);
217
+ }
218
+
219
+ // ---------------------------------------------------------------------------
220
+ // Exports (for testing)
221
+ // ---------------------------------------------------------------------------
222
+
223
+ module.exports = {
224
+ COPY_TARGETS,
225
+ COPY_DIRS,
226
+ propagateConfig,
227
+ mergeEvidence,
228
+ copyFileToWorktree,
229
+ copyDirToWorktree,
230
+ };
@@ -0,0 +1,137 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "categories": {
4
+ "direct_injection": {
5
+ "severity": "critical",
6
+ "description": "Direct prompt injection — bypass instructions embedded in user input (TH-02-1)",
7
+ "patterns": [
8
+ "ignore\\s+(all\\s+)?(previous|prior|above|earlier)\\s+(instructions|rules|guidelines|directives)",
9
+ "disregard\\s+(all\\s+)?(previous|prior|above)\\s+(instructions|rules)",
10
+ "forget\\s+(all\\s+)?(previous|prior|above)\\s+(instructions|rules|context)",
11
+ "override\\s+(all\\s+)?(safety|security|restrictions|rules|guidelines)",
12
+ "bypass\\s+(all\\s+)?(safety|security|restrictions|filters|rules)",
13
+ "you\\s+must\\s+(now\\s+)?ignore\\s+(all|any|every)",
14
+ "new\\s+instructions?:\\s",
15
+ "system\\s*:\\s*(you\\s+are|from\\s+now)"
16
+ ],
17
+ "action": "deny"
18
+ },
19
+ "indirect_injection": {
20
+ "severity": "critical",
21
+ "description": "Indirect prompt injection — instructions embedded in file content or MCP output (TH-02-2)",
22
+ "patterns": [
23
+ "\\[SYSTEM\\]",
24
+ "\\[INST\\]",
25
+ "<<SYS>>",
26
+ "<\\|im_start\\|>\\s*system",
27
+ "BEGIN\\s+HIDDEN\\s+(INSTRUCTIONS?|PROMPT)",
28
+ "IMPORTANT:\\s*ignore\\s+previous",
29
+ "AI\\s+INSTRUCTIONS?:\\s",
30
+ "ASSISTANT\\s+OVERRIDE:\\s"
31
+ ],
32
+ "action": "deny"
33
+ },
34
+ "role_hijack": {
35
+ "severity": "critical",
36
+ "description": "Role hijacking — persona/role override attempts (TH-02-3)",
37
+ "patterns": [
38
+ "you\\s+are\\s+(now|no\\s+longer)\\s+(a|an|the)?\\s*\\w+",
39
+ "act\\s+as\\s+(if\\s+you\\s+are\\s+)?(a|an)?\\s*(unrestricted|unfiltered|jailbroken|DAN)",
40
+ "pretend\\s+(you\\s+are|to\\s+be)\\s+(a|an)?\\s*(unrestricted|evil|malicious)",
41
+ "DAN\\s*(mode|prompt|jailbreak)?",
42
+ "Developer\\s+Mode\\s+(enabled|activated|on)",
43
+ "jailbreak(ed)?\\s*(mode|prompt)?",
44
+ "do\\s+anything\\s+now"
45
+ ],
46
+ "action": "deny"
47
+ },
48
+ "encoding_attack": {
49
+ "severity": "high",
50
+ "description": "Encoding/obfuscation attacks — base64, Unicode escape, homoglyph (TH-02-4)",
51
+ "patterns": [
52
+ "eval\\s*\\(\\s*atob\\s*\\(",
53
+ "Buffer\\.from\\s*\\([^)]+,\\s*['\"]base64['\"]\\)",
54
+ "\\\\u0{0,2}(7[0-9a-fA-F]|[89a-fA-F][0-9a-fA-F])",
55
+ "\\\\x[0-9a-fA-F]{2}\\\\x[0-9a-fA-F]{2}",
56
+ "String\\.fromCharCode\\s*\\(",
57
+ "\\u200[b-f]",
58
+ "\\u00ad",
59
+ "\\ufeff"
60
+ ],
61
+ "action": "deny"
62
+ },
63
+ "context_manipulation": {
64
+ "severity": "high",
65
+ "description": "Context manipulation — fake system messages, delimiter injection (TH-02-5)",
66
+ "patterns": [
67
+ "<system[_-]?(message|prompt|reminder)>",
68
+ "</?(system|assistant|user|human|ai)\\s*>",
69
+ "---\\s*(system|assistant)\\s*(message|response|output)\\s*---",
70
+ "\\*\\*\\*\\s*(SYSTEM|ADMIN|ROOT)\\s*(MESSAGE|OVERRIDE|COMMAND)\\s*\\*\\*\\*",
71
+ "={3,}\\s*(BEGIN|START)\\s*(SYSTEM|ADMIN|OVERRIDE)",
72
+ "<\\|endoftext\\|>",
73
+ "<\\|pad\\|>"
74
+ ],
75
+ "action": "deny"
76
+ },
77
+ "instruction_smuggling": {
78
+ "severity": "high",
79
+ "description": "Instruction smuggling — hidden instructions in HTML comments, markdown, etc (TH-02-6)",
80
+ "patterns": [
81
+ "<!--\\s*(ignore|override|bypass|system|instruction)",
82
+ "```\\s*(system|instruction|override|hidden)",
83
+ "\\[//\\]:\\s*#\\s*\\(",
84
+ "\\u200b.*\\u200b",
85
+ "%00",
86
+ "\\\\0{1,3}[0-7]"
87
+ ],
88
+ "action": "deny"
89
+ },
90
+ "zero_width": {
91
+ "severity": "high",
92
+ "description": "Zero-width character injection — invisible characters used to bypass pattern matching (TH-02-4)",
93
+ "patterns": [
94
+ "[\\u200b\\u200c\\u200d\\u200e\\u200f]",
95
+ "[\\u2028\\u2029]",
96
+ "[\\u2060\\u2061\\u2062\\u2063\\u2064]",
97
+ "[\\ufeff]",
98
+ "[\\u00ad]",
99
+ "[\\u034f]",
100
+ "[\\u115f\\u1160]"
101
+ ],
102
+ "action": "deny"
103
+ },
104
+ "data_exfiltration": {
105
+ "severity": "high",
106
+ "description": "Data exfiltration — sensitive data embedded in URLs or external requests (TH-02-7)",
107
+ "patterns": [
108
+ "curl\\s+.*(-d|--data|--data-raw|--data-binary)\\s+.*(@/etc/|@~/|@\\.env|@.*\\.pem|@.*\\.key)",
109
+ "wget\\s+.*--post-data=.*(\\.env|password|secret|token|api.?key)",
110
+ "curl\\s+.*\\?.*=(\\$[A-Z_]+|\\$\\{[A-Z_]+\\})",
111
+ "fetch\\s*\\(['\"]https?://[^'\"]+\\?[^'\"]*(?:key|token|secret|password|auth)=",
112
+ "\\|\\s*curl\\s+",
113
+ "\\|\\s*nc\\s+",
114
+ "\\|\\s*wget\\s+"
115
+ ],
116
+ "action": "deny"
117
+ },
118
+ "privilege_escalation": {
119
+ "severity": "medium",
120
+ "description": "Privilege escalation hints — attempts to gain elevated access or modify security controls",
121
+ "patterns": [
122
+ "sudo\\s+",
123
+ "chmod\\s+[0-7]*7[0-7]*\\s+",
124
+ "chown\\s+root",
125
+ "setuid",
126
+ "capabilities?\\s*=",
127
+ "security\\.yaml|security\\.json|security\\.toml"
128
+ ],
129
+ "action": "warn"
130
+ }
131
+ },
132
+ "metadata": {
133
+ "generated_at": "2026-03-22T12:00:00.000Z",
134
+ "pattern_count": 62,
135
+ "sha256": "86059a6935daf72ea23751045f04b4f3740328568715f987570d75bac307ce18"
136
+ }
137
+ }
@@ -0,0 +1,65 @@
1
+ # Shield Harness — OpenShell Default Policy Template
2
+ # Schema: OpenShell Policy v1
3
+ # Reference: ADR-037, https://docs.nvidia.com/openshell/latest/reference/policy-schema.html
4
+ #
5
+ # Usage:
6
+ # openshell sandbox create --policy .claude/policies/openshell-default.yaml -- claude
7
+ #
8
+ # Customize this template for your project's needs.
9
+ # Static policies (filesystem, landlock, process) 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
+ read_only:
19
+ - /usr
20
+ - /lib
21
+ - /etc
22
+ read_write:
23
+ - /sandbox
24
+ - /tmp
25
+
26
+ landlock:
27
+ compatibility: best_effort
28
+
29
+ process:
30
+ run_as_user: sandbox
31
+ run_as_group: sandbox
32
+
33
+ # --- Dynamic (hot-reloadable) ---
34
+
35
+ network_policies:
36
+ anthropic_api:
37
+ name: anthropic-api
38
+ endpoints:
39
+ - host: api.anthropic.com
40
+ port: 443
41
+ access: full
42
+ binaries:
43
+ - path: /usr/local/bin/claude
44
+
45
+ github:
46
+ name: github
47
+ endpoints:
48
+ - host: github.com
49
+ port: 443
50
+ access: read-only
51
+ - host: "*.githubusercontent.com"
52
+ port: 443
53
+ access: read-only
54
+ binaries:
55
+ - path: /usr/bin/git
56
+
57
+ npm_registry:
58
+ name: npm-registry
59
+ endpoints:
60
+ - host: registry.npmjs.org
61
+ port: 443
62
+ access: read-only
63
+ binaries:
64
+ - path: /usr/bin/npm
65
+ - path: /usr/bin/node
@@ -0,0 +1,62 @@
1
+ # Binding Governance Rules
2
+
3
+ Binding is the governance control layer for shield-harness. It is NOT an LLM — it is a set of rules, gates, and contracts enforced by Claude Code (Orchestra).
4
+
5
+ ## Operating Profiles
6
+
7
+ | Profile | Gate Density | Use When |
8
+ | ------------ | -------------------------------- | ------------------------------------------- |
9
+ | **Lite** | STG0 + STG6 only | Low-risk tasks (docs, minor edits) |
10
+ | **Standard** | All STG gates | Normal development tasks |
11
+ | **Strict** | All STG gates + mandatory review | Security-sensitive or public-facing changes |
12
+
13
+ Profile is selected at `/startproject` time based on task risk level.
14
+
15
+ ## STG Gates (Stage Gates)
16
+
17
+ | Gate | Name | Purpose |
18
+ | ---- | -------------- | ---------------------------------- |
19
+ | STG0 | Requirements | Task acceptance criteria confirmed |
20
+ | STG1 | Design | Architecture/approach reviewed |
21
+ | STG2 | Implementation | Code written and self-reviewed |
22
+ | STG3 | Verification | Tests pass, lint clean |
23
+ | STG4 | Automation | CI/CD checks pass |
24
+ | STG5 | Commit/Push | Changes committed and pushed |
25
+ | STG6 | PR/Merge | Pull request created and merged |
26
+
27
+ ## fail-close Principle
28
+
29
+ - When safety conditions are NOT met, Binding STOPS execution
30
+ - On stop: output reason and recovery steps in Japanese
31
+ - Never skip a gate — always fail-close
32
+
33
+ ## Layer Contract
34
+
35
+ ### Orchestra -> Binding (Input)
36
+
37
+ | Field | Type | Description |
38
+ | ---------- | ------ | ------------------------------ |
39
+ | profile | string | "lite" / "standard" / "strict" |
40
+ | task_id | string | e.g., "TASK-001" |
41
+ | intent | string | What the task aims to achieve |
42
+ | risk_level | string | "low" / "medium" / "high" |
43
+ | request_id | string | Unique request identifier |
44
+
45
+ ### Binding -> Orchestra (Output)
46
+
47
+ | Field | Type | Description |
48
+ | ------------------- | ------ | ---------------------------------------------------------------- |
49
+ | decision | string | "allow" / "stop" |
50
+ | failed_steps | array | List of failed gate checks |
51
+ | required_actions | array | Steps needed to proceed |
52
+ | evidence_paths | array | Paths to evidence files |
53
+ | next_state | string | backlog/STG update result |
54
+ | quality_gate_source | string | Source of blocking/high judgments (CI results, review summaries) |
55
+ | quality_gate_counts | object | { blocking: number, high: number } |
56
+
57
+ ## Single Source of Truth
58
+
59
+ - **Task state**: `tasks/backlog.yaml` is the ONLY authoritative source
60
+ - **Stage status**: Tracked in `stage_status` field within backlog.yaml
61
+ - **Evidence**: Recorded in `evidence` array within each task
62
+ - **Decision log**: Decision log is maintained per-project.
@@ -0,0 +1,90 @@
1
+ # Channel Security Rules
2
+
3
+ ## Approval-Free Mode (ADR-032)
4
+
5
+ When `approval_free: true` in pipeline-config.json:
6
+
7
+ - All security decisions are delegated to hooks (no human approval dialogs)
8
+ - `permissions.deny` rules remain absolute — hooks cannot override them
9
+ - `permissions.ask` rules are promoted to `allow` at SessionStart
10
+ - All hook-based defenses (gate, injection-guard, permission) run at 100%
11
+
12
+ ## Security Invariants
13
+
14
+ These guarantees hold regardless of approval_free setting:
15
+
16
+ 1. **deny is absolute**: No hook, agent, or automation can override a deny rule
17
+ 2. **fail-close**: If a hook cannot determine safety, it denies (exit 2)
18
+ 3. **evidence required**: All allow/deny decisions are recorded in evidence-ledger
19
+ 4. **no silent bypass**: Hook errors result in deny, not silent allow
20
+
21
+ ## Claude Code Channels Security Harness (ADR-036 §D4)
22
+
23
+ Claude Code Channels (2026-03-20 公式リリース) は MCP ベースの Telegram/Discord 連携。
24
+ Shield Harness は独自のチャンネル実装を持たず、公式 Channels のセキュリティハーネスとして機能する。
25
+
26
+ ### 対応方針
27
+
28
+ - 公式 Channels のみサポート(Telegram, Discord)
29
+ - 独自チャンネル実装は行わない(公式 Channels の `--channels` が唯一のプッシュ手段)
30
+ - VS Code パネルモードでの `--channels` 対応は公式の対応を待つ
31
+ - Shield Harness hooks はタグ検知方式で自動追従(環境固有コード不要)
32
+
33
+ ### チャンネルイベント検知
34
+
35
+ Channel messages arrive as `<channel source="telegram|discord">` events.
36
+ Shield Harness hooks detect this tag automatically on any hook event:
37
+
38
+ - `UserPromptSubmit`: channel source tag detection → NFKC normalization → injection scan
39
+ - `PreToolUse`: normal gate/permission checks apply to channel-originated commands
40
+ - `PostToolUse`: evidence recording with `source: channel` metadata
41
+
42
+ ### 自動適応保証
43
+
44
+ 以下の理由により、Shield Harness は公式 Channels の環境拡大に自動追従する:
45
+
46
+ 1. hooks は `<channel source="...">` タグの有無で判定(環境非依存)
47
+ 2. settings.json の hook 登録は CLI / VS Code 共通
48
+ 3. `--channels` が VS Code で有効化された瞬間から Shield Harness のセキュリティゲートが発火
49
+ 4. チャンネル無効環境ではイベント自体が到着しないため、誤検知なし(fail-safe)
50
+
51
+ ### チャンネル固有の脅威緩和
52
+
53
+ - **Injection via channel message**: NFKC normalization + injection-patterns.json scan
54
+ - **Unauthorized sender**: 公式 allowlist(第一防衛線)+ Shield Harness 二重検証
55
+ - **Privilege escalation**: channel messages cannot modify deny rules or hook configuration
56
+ - **Task creation via channel**: requires STG0 gate validation before acceptance
57
+
58
+ ### Implementation Mapping
59
+
60
+ Hook implementations that handle channel-related security:
61
+
62
+ | Threat | Hook | Function | Spec |
63
+ | --------------------- | ------------------- | ----------------------------------------------------------------------- | ---------- |
64
+ | Injection via channel | sh-user-prompt.js | `scanPatterns()` with `isChannel` flag | §5.4, §8.6 |
65
+ | Severity boost | sh-user-prompt.js | `boostSeverity()` — elevates severity by one level for channel messages | §8.6.2 |
66
+ | Channel detection | sh-user-prompt.js | `session.source === "channel"` check via `readSession()` | §8.6.1 |
67
+ | Evidence metadata | sh-evidence.js | `is_channel` field in evidence-ledger.jsonl entries | §8.6.3 |
68
+ | Data boundary | sh-data-boundary.js | Jurisdiction tracking applies to channel-originated commands | §3.4 |
69
+ | Config protection | sh-config-guard.js | deny rules and hook config immutable regardless of source | §5.3 |
70
+ | Task gate | sh-pipeline.js | STG0 validation required for channel-originated task creation | §8.1 |
71
+
72
+ ### Severity Boost Table
73
+
74
+ Channel messages automatically boost pattern severity by one level:
75
+
76
+ | Original | Boosted | Action |
77
+ | -------- | -------- | ---------- |
78
+ | low | medium | warn |
79
+ | medium | high | deny |
80
+ | high | critical | deny |
81
+ | critical | critical | deny (cap) |
82
+
83
+ ### Phase 2 Migration
84
+
85
+ When `--channels` becomes available in VS Code panel mode:
86
+
87
+ 1. `sh-session-start.js` version-check detects the new capability
88
+ 2. additionalContext suggests migration via `npx shield-harness channels migrate`
89
+ 3. No hook code changes required — tag-based detection is environment-agnostic
90
+ 4. New channel providers (beyond Telegram/Discord) are automatically covered
@@ -0,0 +1,79 @@
1
+ # Coding Principles
2
+
3
+ Core coding rules to always follow.
4
+
5
+ ## Simplicity First
6
+
7
+ - Choose readable code over complex code
8
+ - Avoid over-abstraction
9
+ - Prioritize "understandable" over "working"
10
+
11
+ ## Single Responsibility
12
+
13
+ - One function does one thing only
14
+ - One class has one responsibility only
15
+ - Target 200-400 lines per file (max 800)
16
+
17
+ ## Early Return
18
+
19
+ ```python
20
+ # Bad: Deep nesting
21
+ def process(value):
22
+ if value is not None:
23
+ if value > 0:
24
+ return do_something(value)
25
+ return None
26
+
27
+ # Good: Early return
28
+ def process(value):
29
+ if value is None:
30
+ return None
31
+ if value <= 0:
32
+ return None
33
+ return do_something(value)
34
+ ```
35
+
36
+ ## Type Hints Required
37
+
38
+ All functions must have type annotations:
39
+
40
+ ```python
41
+ def call_llm(
42
+ prompt: str,
43
+ model: str = "gpt-4",
44
+ max_tokens: int = 1000
45
+ ) -> str:
46
+ ...
47
+ ```
48
+
49
+ ## Immutability
50
+
51
+ Create new objects instead of mutating existing ones:
52
+
53
+ ```python
54
+ # Bad: Mutating existing object
55
+ data["new_key"] = value
56
+
57
+ # Good: Creating new object
58
+ new_data = {**data, "new_key": value}
59
+ ```
60
+
61
+ ## Naming Conventions
62
+
63
+ - **Variables/Functions**: snake_case (English)
64
+ - **Classes**: PascalCase (English)
65
+ - **Constants**: UPPER_SNAKE_CASE (English)
66
+ - **Meaningful names**: `user_count` over `x`
67
+
68
+ ## No Magic Numbers
69
+
70
+ ```python
71
+ # Bad
72
+ if retry_count > 3:
73
+ ...
74
+
75
+ # Good
76
+ MAX_RETRIES = 3
77
+ if retry_count > MAX_RETRIES:
78
+ ...
79
+ ```