Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .claude/skills/programmatic-claude-lockdown/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
name: programmatic-claude-lockdown
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.
user-invocable: false
allowed-tools: Read, Grep, Glob
---

# Programmatic Claude lockdown

**Rule:** every programmatic Claude callsite sets four flags. Skip any one and a future edit silently widens the surface.

## The four flags

| Layer | SDK option | CLI flag | What it does |
|---|---|---|---|
| Definition | `tools` | `--tools` | Base set the model is told about. Tools not listed are invisible β€” no `tool_use` block possible. |
| Auto-approve | `allowedTools` | `--allowedTools` | Step 4. Listed tools run without invoking `canUseTool`. |
| Deny | `disallowedTools` | `--disallowedTools` | Step 2. Wins even against `bypassPermissions`. Defense-in-depth. |
| Mode | `permissionMode: 'dontAsk'` | `--permission-mode dontAsk` | Step 3. Unmatched tools denied without falling through to a missing `canUseTool`. |

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`.

## Recipe β€” read-only agent (audit, classify, summarize)

```ts
import { query } from '@anthropic-ai/claude-agent-sdk'

query({
prompt: '...',
options: {
tools: ['Read', 'Grep', 'Glob'],
allowedTools: ['Read', 'Grep', 'Glob'],
disallowedTools: ['Agent', 'Bash', 'Edit', 'NotebookEdit', 'Task', 'WebFetch', 'WebSearch', 'Write'],
permissionMode: 'dontAsk',
},
})
```

CLI form for workflow YAML / shell scripts:

```yaml
claude --print \
--tools "Read" "Grep" "Glob" \
--allowedTools "Read" "Grep" "Glob" \
--disallowedTools "Agent" "Bash" "Edit" "NotebookEdit" "Task" "WebFetch" "WebSearch" "Write" \
--permission-mode dontAsk \
--model "$MODEL" \
--max-turns 25 \
"<prompt>"
```

## Recipe β€” agent that needs Bash (e.g. `/updating`: pnpm + git + jq)

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):

```yaml
DISALLOW_BASE='Agent Task NotebookEdit WebFetch WebSearch Bash(curl:*) Bash(wget:*) Bash(rm -rf*) Bash(sudo:*)'
DISALLOW_PKG_EXEC='Bash(npx:*) Bash(pnpm dlx:*) Bash(yarn dlx:*)' # zizmor: documentation-prohibition
claude --print \
--tools "Bash" "Read" "Write" "Edit" "Glob" "Grep" \
--allowedTools "Bash(pnpm:*)" "Bash(git:*)" "Bash(jq:*)" "Read" "Write" "Edit" "Glob" "Grep" \
--disallowedTools $DISALLOW_BASE $DISALLOW_PKG_EXEC \
--permission-mode dontAsk \
--model "$MODEL" --max-turns 25 \
"<prompt>"
```

## Never

- ❌ `permissionMode: 'default'` in headless contexts β€” falls through to a missing `canUseTool`. Behavior undefined.
- ❌ `permissionMode: 'bypassPermissions'` / `allowDangerouslySkipPermissions: true`.
- ❌ Omitting `tools` β€” SDK default is the full claude_code preset.
- ❌ `Agent` / `Task` permitted β€” sub-agents inherit modes and can escape per-subagent restrictions when the parent is `bypassPermissions`/`acceptEdits`/`auto`.

## Reference implementation

`socket-lib/tools/prim/src/disambiguate.mts` β€” canonical SDK-form callsite. The file header documents each flag against the eval-flow step it enforces.

`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.

## Existing fleet callsites

- `socket-registry/.github/workflows/weekly-update.yml` β€” two `claude --print` invocations (run `/updating` skill, fix test failures). Bash recipe above.
- `socket-lib/tools/prim/src/disambiguate.mts` β€” read-only recipe above (`query()` SDK form).
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ concurrency:
jobs:
ci:
name: Run CI Pipeline
uses: SocketDev/socket-registry/.github/workflows/ci.yml@85a2fc0d33af6304246620365de3e7f053035a8d # main
uses: SocketDev/socket-registry/.github/workflows/ci.yml@0fc1abfd5e9ae4396f9fb507371aeea75e2f412c # main
with:
test-script: 'pnpm run test --all --skip-build'
6 changes: 3 additions & 3 deletions .github/workflows/generate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ jobs:
echo "Sleeping for $delay seconds..."
sleep $delay

- uses: SocketDev/socket-registry/.github/actions/setup-and-install@85a2fc0d33af6304246620365de3e7f053035a8d # main
- uses: SocketDev/socket-registry/.github/actions/setup-and-install@0fc1abfd5e9ae4396f9fb507371aeea75e2f412c # main

- name: Configure push credentials
env:
GH_TOKEN: ${{ github.token }}
run: git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git"

- uses: SocketDev/socket-registry/.github/actions/setup-git-signing@85a2fc0d33af6304246620365de3e7f053035a8d # main
- uses: SocketDev/socket-registry/.github/actions/setup-git-signing@0fc1abfd5e9ae4396f9fb507371aeea75e2f412c # main
with:
gpg-private-key: ${{ secrets.BOT_GPG_PRIVATE_KEY }}

Expand Down Expand Up @@ -145,5 +145,5 @@ jobs:
> \`\`\`
EOF

- uses: SocketDev/socket-registry/.github/actions/cleanup-git-signing@85a2fc0d33af6304246620365de3e7f053035a8d # main
- uses: SocketDev/socket-registry/.github/actions/cleanup-git-signing@0fc1abfd5e9ae4396f9fb507371aeea75e2f412c # main
if: always()
2 changes: 1 addition & 1 deletion .github/workflows/provenance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
permissions:
contents: write # To create GitHub releases
id-token: write # For npm trusted publishing via OIDC
uses: SocketDev/socket-registry/.github/workflows/provenance.yml@85a2fc0d33af6304246620365de3e7f053035a8d # main
uses: SocketDev/socket-registry/.github/workflows/provenance.yml@0fc1abfd5e9ae4396f9fb507371aeea75e2f412c # main
with:
debug: ${{ inputs.debug }}
dist-tag: ${{ inputs.dist-tag }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/weekly-update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ permissions:

jobs:
weekly-update:
uses: SocketDev/socket-registry/.github/workflows/weekly-update.yml@85a2fc0d33af6304246620365de3e7f053035a8d # main
uses: SocketDev/socket-registry/.github/workflows/weekly-update.yml@0fc1abfd5e9ae4396f9fb507371aeea75e2f412c # main
with:
test-setup-script: 'pnpm run build'
test-script: 'pnpm test'
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ The umbrella rule: never run a git command that mutates state belonging to a pat
- **minimumReleaseAge**: NEVER add packages to `minimumReleaseAgeExclude` in CI. Locally, ASK before adding β€” the age threshold is a security control.
- 🚨 **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.
- 🚨 **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).
- 🚨 **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`).
- File existence: ALWAYS `existsSync` from `node:fs`. NEVER `fs.access`, `fs.stat`-for-existence, or an async `fileExists` wrapper. Import form: `import { existsSync, promises as fs } from 'node:fs'`.
- Null-prototype objects: ALWAYS use `{ __proto__: null, ...rest }` for config, return, and internal-state objects. Prevents prototype pollution and accidental inheritance. See `src/socket-sdk-class.ts` and `src/file-upload.ts` for examples.
- Linear references: NEVER reference Linear issues (e.g. `SOC-123`, `ENG-456`, Linear URLs) in code, code comments, or PR titles/descriptions/review comments. Keep the codebase and PR history tool-agnostic β€” tracking lives in Linear.
Expand Down
14 changes: 7 additions & 7 deletions external-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
},
"pnpm": {
"description": "pnpm β€” the fleet's package manager.",
"version": "11.0.0-rc.5",
"version": "11.0.0",
"packageManager": "pnpm",
"repository": "github:pnpm/pnpm",
"release": "asset",
Expand All @@ -34,27 +34,27 @@
"checksums": {
"darwin-arm64": {
"asset": "pnpm-darwin-arm64.tar.gz",
"sha256": "32a50710ccacfdcf14e6d5995d5368298eec913b0ce3903b9e09b6555f06f4e5"
"sha256": "3620a0fcaf81ecd3aaeccd5965919d90dbc913f4d07a96e11e7cafc2c785054b"
},
"darwin-x64": {
"asset": "pnpm-darwin-x64.tar.gz",
"sha256": "71dca33f4275da6b43bf1eb40bdc4d876f59a116716eacbf01079c3d985ff85d"
"sha256": "1701748b75187f1333a9c616827943ff84ff46cc42becc156ff6864b9bd0f948"
},
"linux-arm64": {
"asset": "pnpm-linux-arm64.tar.gz",
"sha256": "2dd04127ff10b1f9dd20bae248b779c77a8ec67e3afa35e7256e5f94abddd493"
"sha256": "1e6d87ebfd7ff169966ff5b3ad71b780b883c68d3e59987df1096dfd8853df75"
},
"linux-x64": {
"asset": "pnpm-linux-x64.tar.gz",
"sha256": "7ebef4b616ba41fb0d54a207b36508fae3346723283a088b43fc1e038ee6fed0"
"sha256": "9b44acc77ada40fc41b665fde1d57367a5ebec31bd4b1b00598daed195da3e17"
},
"win-arm64": {
"asset": "pnpm-win32-arm64.zip",
"sha256": "e4a39ad4c251db5e34b18b98561ef25bab5506ad65cad2fa3602af58d1972667"
"sha256": "0746be8e98ca183078d0747559f0cbbd30a13a53eb177f67474eb3c52dc21bc8"
},
"win-x64": {
"asset": "pnpm-win32-x64.zip",
"sha256": "147485ae2f38c3d1ccf2f5db00d0244416bcd22b9114c02388e6a78f41538fc4"
"sha256": "581e222e622cd0cc4f0ac5f85dd0db76b65117e3b17507979d89e63fdc68edca"
}
}
},
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
},
"engines": {
"node": ">=18.20.8",
"pnpm": ">=11.0.0-rc.0"
"pnpm": ">=11.0.0"
},
"packageManager": "pnpm@11.0.0-rc.5"
"packageManager": "pnpm@11.0.0"
}