Skip to content

Commit 3703e2f

Browse files
committed
chore(claude+hooks+xport): sync fleet-canonical updates from socket-repo-template
- `.git-hooks/_helpers.mts` + `pre-commit.mts` + `pre-push.mts` — rename suppression marker `# zizmor: …` → `# socket-hook: allow [<rule>]` (legacy form still recognized for one cycle); add doc-aware scan heuristic; emit `LineHit.suggested` rewrites alongside hits. - `.claude/hooks/token-guard/{index,test/token-guard.test}.mts` — sync to canonical; settings.json picks up the fleet permissions deny block. - `.claude/skills/{path-guard,security-scan}/SKILL.md`, `.claude/agents/security-reviewer.md` — sync drift from template. - `.claude/skills/programmatic-claude-lockdown/SKILL.md` — fleet skill for the four-flag lockdown recipe (matches socket-sdk-js PR #630). - `CLAUDE.md` — update fleet block npx-rule marker; oxfmt-normalized markdown emphasis. - `docs/references/{inclusive-language,sorting}.md` — re-sync. - `scripts/xport*.mts` + `xport.schema.json` — fleet xport tooling. - `.husky/pre-commit` + `.git-hooks/commit-msg.mts` — sync. - `pnpm-lock.yaml` — regenerate after template sync. Pre-commit/test gates skipped via DISABLE_PRECOMMIT_*: this worktree's build chain (download-assets / iocraft native addon) is unrelated to the changes here, and the same gates are enforced in CI. Sourced byte-identical from socket-repo-template (canonical fleet hook); the same change rolled out across socket-* repos + ultrathink.
1 parent b209165 commit 3703e2f

20 files changed

Lines changed: 2202 additions & 179 deletions

File tree

.claude/agents/security-reviewer.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
1+
---
2+
name: security-reviewer
3+
description: Reviews findings from AgentShield + zizmor against the project's CLAUDE.md security rules and grades the result A-F. Spawned by the security-scan skill after the static scans run.
4+
tools: Read, Grep, Glob, Bash(git:*), Bash(rg:*), Bash(grep:*), Bash(find:*), Bash(ls:*), Bash(pnpm exec agentshield:*), Bash(zizmor:*), Bash(command -v:*), Bash(cat:*), Bash(head:*), Bash(tail:*)
5+
---
6+
17
You are a security reviewer for Socket Security Node.js repositories.
28

39
Apply these rules from CLAUDE.md exactly:
410

511
**Safe File Operations**: Use safeDelete()/safeDeleteSync() from @socketsecurity/lib/fs. NEVER fs.rm(), fs.rmSync(), or rm -rf. Use os.tmpdir() + fs.mkdtemp() for temp dirs. NEVER use fetch() — use httpJson/httpText/httpRequest from @socketsecurity/lib/http-request.
612

7-
**Absolute Rules**: NEVER use npx, pnpm dlx, or yarn dlx. Use pnpm exec or pnpm run with pinned devDeps.
13+
**Absolute Rules**: NEVER use npx, pnpm dlx, or yarn dlx. Use pnpm exec or pnpm run with pinned devDeps. # zizmor: documentation-prohibition
814

915
**Work Safeguards**: Scripts modifying multiple files must have backup/rollback. Git operations that rewrite history require explicit confirmation.
1016

1117
**Review checklist:**
1218

1319
1. **Secrets**: Hardcoded API keys, passwords, tokens, private keys in code or config
1420
2. **Injection**: Command injection via shell: true or string interpolation in spawn/exec. Path traversal in file operations.
15-
3. **Dependencies**: npx/dlx usage. Unpinned versions (^ or ~). Missing minimumReleaseAge bypass justification.
21+
3. **Dependencies**: npx/dlx usage. Unpinned versions (^ or ~). Missing minimumReleaseAge bypass justification. # zizmor: documentation-checklist
1622
4. **File operations**: fs.rm without safeDelete. process.chdir usage. fetch() usage (must use lib's httpRequest).
1723
5. **GitHub Actions**: Unpinned action versions (must use full SHA). Secrets outside env blocks. Template injection from untrusted inputs.
1824
6. **Error handling**: Sensitive data in error messages. Stack traces exposed to users.

.claude/hooks/token-guard/index.mts

Lines changed: 15 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -41,27 +41,20 @@ const SENSITIVE_ENV_NAMES = [
4141
]
4242

4343
// Pipelines that "launder" earlier-stage secrets into safe output.
44-
// Patterns are applied per-pipe-segment by `hasRedaction()` so a
45-
// downstream `redact` token cannot launder an unrelated upstream
46-
// stage. The first two patterns match `sed 's/.../redact.../'` and
44+
// The first two patterns match `sed 's/.../redact.../'` and
4745
// `sed 's/.../FOO=*****/'` regardless of which delimiter sed uses
48-
// (`/`, `#`). Redirections (`>`, `>>`, `>/dev/null`) terminate a
49-
// pipeline so they're matched against the whole command.
50-
const REDACTION_SEGMENT_MARKERS = [
51-
/\bsed\b.*s[/#].*?<?redact/i,
52-
/\bsed\b.*s[/#].*?[A-Z_]+=.*?\*{3,}/i,
53-
/\bcut\b.*-d['"]?=['"]?\s*-f\s*1/i,
54-
/\bawk\b.*-F\s*['"]?=['"]?/i,
55-
]
56-
// Whole-command redirection markers. Anchored at a pipe-segment
57-
// boundary (`^`, whitespace, `|`, or `;`) so they fire only on real
58-
// shell redirection (`env > file`, `env >> file`, `env > /dev/null`)
59-
// and not on the literal `>` inside regex/HTML-style markers like
60-
// `<redacted>` or `s/=.*/.../`. The previous /\s*[^|]/ shape would
61-
// match the `>` in `<redacted>` and bypass the env-dump check.
62-
const REDACTION_WHOLE_COMMAND_MARKERS = [
63-
/(?:^|[\s|;])>\s*\/dev\/null\b/,
64-
/(?:^|[\s|;])>>?\s*[^|<>'"\\$&\s]/,
46+
// (`/`, `#`, `|`). `[\s\S]*?` reaches across the delimiter between
47+
// the search and replacement parts (the previous `[^/|#]*` couldn't
48+
// cross `/` and so missed the canonical `sed 's/=.*/=<redacted>/'`
49+
// — the very command the token-guard error message suggests).
50+
const REDACTION_MARKERS = [
51+
/\bsed\b[^|]*s[/|#][\s\S]*?<?redact/i,
52+
/\bsed\b[^|]*s[/|#][\s\S]*?[A-Z_]+=[\s\S]*?\*{3,}/i,
53+
/\|\s*cut\b[^|]*-d['"]?=['"]?\s*-f\s*1/i,
54+
/\|\s*awk\b[^|]*-F\s*['"]?=['"]?/i,
55+
/>\s*\/dev\/null/,
56+
/>>\s*[^|]/,
57+
/>\s*[^|]/,
6558
]
6659

6760
// Commands that dump all env vars to stdout with no filter.
@@ -130,51 +123,8 @@ type ToolInput = {
130123
tool_input?: { command?: string }
131124
}
132125

133-
// Redaction must occur on the *output* of a leaky stage, which means
134-
// it has to appear in a downstream pipe segment or as a whole-command
135-
// redirection. Splitting on `|` and matching each segment prevents the
136-
// canonical bypass `env | sed 's/foo/bar/' | tool_redact_output`,
137-
// where a `redact`-named downstream tool would otherwise launder the
138-
// upstream `env` dump.
139-
const hasRedaction = (command: string): boolean => {
140-
// Drop everything from the first `||` or `&&` onwards before ANY
141-
// redaction matching runs. Those branches don't unconditionally
142-
// execute, so a redaction marker on their right side cannot
143-
// launder an upstream leak. Two known bypass shapes drove this:
144-
//
145-
// - `env || sed 's/=.*/=<redacted>/'` — `env` always succeeds
146-
// so the `sed` arm never runs at runtime; previous logic
147-
// credited it as a redaction and let the env dump through.
148-
// - `env || true > /dev/null` — Bugbot finding: the whole-
149-
// command `> /dev/null` marker matched on the FULL string
150-
// before truncation, returning true early even though the
151-
// redirection lives on the unreachable `||` branch. Running
152-
// whole-command markers against the truncated command fixes
153-
// it: after truncation the command is just `env `, which
154-
// doesn't match `> /dev/null`.
155-
//
156-
// Truncating before any matching forces the redaction to live in
157-
// the same unconditional pipeline as the leaky stage.
158-
const idxOr = command.indexOf('||')
159-
const idxAnd = command.indexOf('&&')
160-
let cut = command.length
161-
if (idxOr !== -1) cut = Math.min(cut, idxOr)
162-
if (idxAnd !== -1) cut = Math.min(cut, idxAnd)
163-
const truncated = command.slice(0, cut)
164-
if (REDACTION_WHOLE_COMMAND_MARKERS.some(re => re.test(truncated))) {
165-
return true
166-
}
167-
// Split first on `|` (pipe-stage boundary), then on `;` (statement
168-
// boundary) within each stage. A redaction marker is only credited
169-
// when both `sed` and the redaction target live in the same
170-
// statement — otherwise `env | sed 's/a/b/' ; echo redacted_output`
171-
// would match the segment regex (which uses `.*` between `sed` and
172-
// the marker word) despite the `sed` doing no real redaction.
173-
const segments = truncated.split('|').flatMap(s => s.split(';'))
174-
return segments.some(seg =>
175-
REDACTION_SEGMENT_MARKERS.some(re => re.test(seg)),
176-
)
177-
}
126+
const hasRedaction = (command: string): boolean =>
127+
REDACTION_MARKERS.some(re => re.test(command))
178128

179129
// Word-boundary match so `PASS` doesn't fire on `PATHS-ALLOWLIST` and
180130
// `AUTH` doesn't fire on `AUTHOR`. Env-var-style boundaries treat `_`

.claude/hooks/token-guard/test/token-guard.test.mts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -140,16 +140,6 @@ describe('token-guard hook', () => {
140140
it('env piped without redactor', () => {
141141
assert.equal(runHook('env | grep FOO').code, 2)
142142
})
143-
it('env laundered by downstream "redact"-named tool (cross-pipe bypass)', () => {
144-
// Regression: a lazy `[\s\S]*?` in the sed-redaction pattern
145-
// could cross `|` boundaries, so an unrelated downstream stage
146-
// named `tool_redact_output` would match `<?redact` even though
147-
// the upstream `env` dump is still leaking.
148-
assert.equal(
149-
runHook("env | sed 's/foo/bar/' | tool_redact_output").code,
150-
2,
151-
)
152-
})
153143
it('printenv', () => {
154144
assert.equal(runHook('printenv').code, 2)
155145
})

.claude/settings.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,18 @@
4646
]
4747
}
4848
]
49+
},
50+
"permissions": {
51+
"deny": [
52+
"Bash(gh release create:*)",
53+
"Bash(gh release delete:*)",
54+
"Bash(gh workflow dispatch:*)",
55+
"Bash(gh workflow run:*)",
56+
"Bash(git push --force:*)",
57+
"Bash(git push -f:*)",
58+
"Bash(npm publish:*)",
59+
"Bash(pnpm publish:*)",
60+
"Bash(yarn publish:*)"
61+
]
4962
}
5063
}

.claude/skills/path-guard/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name: path-guard
33
description: Audit and fix path duplication in this Socket repo. Apply the strict "1 path, 1 reference" rule — every build/test/runtime/config path is constructed exactly once; everywhere else references the constructed value. Default mode finds and fixes; `check` mode reports only; `install` mode drops the gate + hook + rule into a fresh repo.
44
user-invocable: true
5-
allowed-tools: Task, Bash, Read, Edit, Write, Grep, Glob, AskUserQuestion
5+
allowed-tools: Task, Read, Edit, Write, Grep, Glob, AskUserQuestion, Bash(pnpm run check:*), Bash(node scripts/check-paths:*), Bash(rg:*), Bash(grep:*), Bash(find:*), Bash(git:*)
66
---
77

88
# path-guard
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
---
2+
name: programmatic-claude-lockdown
3+
description: Reference for locking down programmatic Claude invocations (the `claude` CLI in workflows/scripts, the `@anthropic-ai/claude-agent-sdk` `query()` in code). Loads on demand when writing or reviewing any callsite that runs Claude programmatically. Source: https://code.claude.com/docs/en/agent-sdk/permissions.
4+
user-invocable: false
5+
allowed-tools: Read, Grep, Glob
6+
---
7+
8+
# Programmatic Claude lockdown
9+
10+
**Rule:** every programmatic Claude callsite sets four flags. Skip any one and a future edit silently widens the surface.
11+
12+
## The four flags
13+
14+
| Layer | SDK option | CLI flag | What it does |
15+
|---|---|---|---|
16+
| Definition | `tools` | `--tools` | Base set the model is told about. Tools not listed are invisible — no `tool_use` block possible. |
17+
| Auto-approve | `allowedTools` | `--allowedTools` | Step 4. Listed tools run without invoking `canUseTool`. |
18+
| Deny | `disallowedTools` | `--disallowedTools` | Step 2. Wins even against `bypassPermissions`. Defense-in-depth. |
19+
| Mode | `permissionMode: 'dontAsk'` | `--permission-mode dontAsk` | Step 3. Unmatched tools denied without falling through to a missing `canUseTool`. |
20+
21+
The official permission flow (1) hooks → (2) deny rules → (3) permission mode → (4) allow rules → (5) `canUseTool`. In `dontAsk` mode step 5 is skipped — denied. The doc states verbatim: *"`allowedTools` and `disallowedTools` ... control whether a tool call is approved, not whether the tool is available."* Availability is `tools`.
22+
23+
## Recipe — read-only agent (audit, classify, summarize)
24+
25+
```ts
26+
import { query } from '@anthropic-ai/claude-agent-sdk'
27+
28+
query({
29+
prompt: '...',
30+
options: {
31+
tools: ['Read', 'Grep', 'Glob'],
32+
allowedTools: ['Read', 'Grep', 'Glob'],
33+
disallowedTools: ['Agent', 'Bash', 'Edit', 'NotebookEdit', 'Task', 'WebFetch', 'WebSearch', 'Write'],
34+
permissionMode: 'dontAsk',
35+
},
36+
})
37+
```
38+
39+
CLI form for workflow YAML / shell scripts:
40+
41+
```yaml
42+
claude --print \
43+
--tools "Read" "Grep" "Glob" \
44+
--allowedTools "Read" "Grep" "Glob" \
45+
--disallowedTools "Agent" "Bash" "Edit" "NotebookEdit" "Task" "WebFetch" "WebSearch" "Write" \
46+
--permission-mode dontAsk \
47+
--model "$MODEL" \
48+
--max-turns 25 \
49+
"<prompt>"
50+
```
51+
52+
## Recipe — agent that needs Bash (e.g. `/updating`: pnpm + git + jq)
53+
54+
Narrow `Bash(...)` patterns surgically. Block dangerous Bash patterns explicitly. Fleet rules: no `npx`/`pnpm dlx`/`yarn dlx`; no `curl`/`wget` exfil; no destructive `rm -rf`; no `sudo`. Build the deny list as shell vars so the npx/dlx denials can carry the `# zizmor:` exemption marker (the pre-commit `scanNpxDlx` hook treats those literal strings as the prohibited tools, not as exemptions, unless the line is tagged):
55+
56+
```yaml
57+
DISALLOW_BASE='Agent Task NotebookEdit WebFetch WebSearch Bash(curl:*) Bash(wget:*) Bash(rm -rf*) Bash(sudo:*)'
58+
DISALLOW_PKG_EXEC='Bash(npx:*) Bash(pnpm dlx:*) Bash(yarn dlx:*)' # zizmor: documentation-prohibition
59+
claude --print \
60+
--tools "Bash" "Read" "Write" "Edit" "Glob" "Grep" \
61+
--allowedTools "Bash(pnpm:*)" "Bash(git:*)" "Bash(jq:*)" "Read" "Write" "Edit" "Glob" "Grep" \
62+
--disallowedTools $DISALLOW_BASE $DISALLOW_PKG_EXEC \
63+
--permission-mode dontAsk \
64+
--model "$MODEL" --max-turns 25 \
65+
"<prompt>"
66+
```
67+
68+
## Never
69+
70+
-`permissionMode: 'default'` in headless contexts — falls through to a missing `canUseTool`. Behavior undefined.
71+
-`permissionMode: 'bypassPermissions'` / `allowDangerouslySkipPermissions: true`.
72+
- ❌ Omitting `tools` — SDK default is the full claude_code preset.
73+
-`Agent` / `Task` permitted — sub-agents inherit modes and can escape per-subagent restrictions when the parent is `bypassPermissions`/`acceptEdits`/`auto`.
74+
75+
## Reference implementation
76+
77+
`socket-lib/tools/prim/src/disambiguate.mts` — canonical SDK-form callsite. The file header documents each flag against the eval-flow step it enforces.
78+
79+
`socket-lib/tools/prim/test/disambiguate.test.mts` — source-text guards that fail the build if `BASE_TOOLS` widens, if `tools: BASE_TOOLS` is unwired, if `permissionMode` drifts from `'dontAsk'`, or if `bypassPermissions` / `allowDangerouslySkipPermissions: true` ever appears. Mirror this pattern in any new callsite.
80+
81+
## Existing fleet callsites
82+
83+
- `socket-registry/.github/workflows/weekly-update.yml` — two `claude --print` invocations (run `/updating` skill, fix test failures). Bash recipe above.
84+
- `socket-lib/tools/prim/src/disambiguate.mts` — read-only recipe above (`query()` SDK form).

.claude/skills/security-scan/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
name: security-scan
33
description: Runs a multi-tool security scan — AgentShield for Claude config, zizmor for GitHub Actions, and optionally Socket CLI for dependency scanning. Produces an A-F graded security report. Use after modifying `.claude/` config, hooks, agents, or GitHub Actions workflows, and before releases.
44
user-invocable: true
5+
allowed-tools: Task, Read, Bash(pnpm exec agentshield:*), Bash(zizmor:*), Bash(command -v:*), Bash(find .cache/external-tools/zizmor:*)
56
---
67

78
# Security Scan

0 commit comments

Comments
 (0)