Skip to content

Commit 215c707

Browse files
committed
docs(claude): add programmatic-Claude lockdown rule + skill
Cascaded from socket-repo-template. CLAUDE.md gains one bullet alongside the other security 🚨 rules; the skill at .claude/skills/programmatic-claude-lockdown/SKILL.md holds the four-flag table (`tools`/`allowedTools`/`disallowedTools`/ `permissionMode: 'dontAsk'`), both recipes (read-only and Bash-needing), and the never-do list. Reference impl: socket-lib/tools/prim/src/disambiguate.mts (SDK form); socket-registry weekly-update.yml uses the Bash-needing CLI form.
1 parent 1c77a45 commit 215c707

2 files changed

Lines changed: 85 additions & 0 deletions

File tree

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.mdβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ The umbrella rule: never run a git command that mutates state belonging to a pat
104104
- 🚨 **NEVER write a real customer or company name into any commit, PR, issue, GitHub comment, or release note.** When about to write any name, stop and ask: "is this a real company?" If yes, replace it with `Acme Inc` (or drop the reference entirely). No enumerated denylist exists anywhere β€” a denylist is itself a leak. Recognition is done at write time, every time. The `.claude/hooks/public-surface-reminder` hook re-prints this rule on every public-surface `git`/`gh` command as a priming nudge; the rule still applies when the hook is not installed.
105105
- 🚨 **NEVER mention private repos or internal project names** in commits, PR titles/descriptions/comments, issues, release notes, or any public-surface text. Internal codenames, unreleased product names, internal tooling repo names not on the public org page, customer names, partner names β€” none belong in public surfaces. **Omit the reference entirely.** Don't substitute a placeholder ("an internal tool", "a downstream consumer", etc.) β€” the placeholder itself is a tell that something is being elided. Rewrite the sentence to not need the reference at all.
106106
- 🚨 **NEVER trigger Publish / Release / Provenance / Build-Release workflows** β€” no `gh workflow run`, `gh workflow dispatch`, or `gh api .../dispatches`. Workflow dispatches are irrevocable: Publish workflows push npm versions (unpublishable after 24h), Build/Release workflows pin GitHub releases by SHA, container workflows push immutable tags. Even build workflows with a `dry_run` input still treat the dispatch itself as the prod trigger. The user runs workflow_dispatch jobs manually after CI passes on the release commit + tag β€” Claude **never** dispatches them. If the user asks for a publish, tell them to run the command in their own terminal (or the GitHub Actions UI).
107+
- 🚨 **Programmatic Claude calls** (workflows, skills, scripts that invoke `claude` CLI or `@anthropic-ai/claude-agent-sdk`) MUST set all four lockdown flags: `--tools`/`tools`, `--allowedTools`/`allowedTools`, `--disallowedTools`/`disallowedTools`, and `--permission-mode dontAsk`/`permissionMode: 'dontAsk'`. NEVER `default` mode in headless contexts (falls through to a missing `canUseTool` β†’ undefined behavior). NEVER `bypassPermissions`. See `.claude/skills/programmatic-claude-lockdown/SKILL.md` for the recipe + reference impl (`socket-lib/tools/prim/src/disambiguate.mts`).
107108

108109
## EVOLUTION
109110

0 commit comments

Comments
Β (0)