security: per-campaign permission policy template (#135)#138
Closed
sriumcp wants to merge 1 commit into
Closed
Conversation
…I-native-Systems-Research#135) Replace --dangerously-skip-permissions with a fine-grained, per-campaign permission policy generated at init. The orchestrator's pure renderer (orchestrator/settings_template.py) takes work_dir, repo_path, and an optional experiment_plan, and returns a dict suitable for serialization as .claude/settings.json. The contents: - permissions.allowOnly: campaign work-dir and target repo path. Anything else is denied by default. - permissions.allow: Bash command allowlist — conservative defaults plus any binaries pulled out of experiment_plan.yaml arm conditions, plus caller-provided extras. - permissions.deny: hard blocks for outbound https (curl/wget) and catastrophic shell commands (rm -rf /). - hooks.Stop: registered when bin/nous-execute-stop is present (AI-native-Systems-Research#129 integration). - hooks.PreToolUse: registered when caller provides the path (AI-native-Systems-Research#128 hook). setup_work_dir() now writes the rendered settings file at init time, idempotently (won't clobber a hand-edited file). CLIDispatcher auto-detects work_dir/.claude/settings.json on construction, and when present passes --settings <path> to claude -p instead of --dangerously-skip-permissions. SDKDispatcher already accepted settings_path in AI-native-Systems-Research#121 — wire-up matches. Behavioral tests (tests/test_settings_template.py): 14 cases. Renderer contract: - allowOnly contains work_dir - allowOnly contains repo_path when provided - default bin allowlist contains python, git, grep - plan binaries (./blis, /usr/local/bin/sim) are added by basename - extra_bin_allowlist extends defaults - deny blocks outbound https - hooks section absent unless hook paths provided - Stop hook registered with absolute path - PreToolUse hook registered with Bash matcher Disk write contract: - write_campaign_settings creates parent dir + writes JSON - settings_path_for returns .claude/settings.json under work_dir Init wiring contract: - setup_work_dir writes the file when fresh - setup_work_dir does NOT overwrite a user-customized settings file Replacement invariant (the security property): - rendered settings impose non-empty allowOnly AND non-empty deny (otherwise the file is functionally equivalent to --dangerously and the swap is a regression). Out of scope: the "out-of-worktree write is denied" criterion is an integration test against a live claude session and is verified manually. docs/security.md describes the model end-to-end. Test suite: 338 baseline + 14 new = 352 passing. Closes AI-native-Systems-Research#135. Refs AI-native-Systems-Research#120. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 24, 2026
Collaborator
Author
|
Superseded by #153 — the consolidated tracking-120 PR carrying all 17 commits in merge order. Closing this in favor of that single PR per project owner's request. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
orchestrator/settings_template.py— pure renderer that builds a per-campaign.claude/settings.jsonpolicy from work_dir + repo_path + experiment_plan + hook paths.setup_work_dir()writes the rendered file at init (idempotent — won't clobber hand-edits).CLIDispatcherauto-detectswork_dir/.claude/settings.jsonon construction; when present, passes--settings <path>toclaude -pinstead of--dangerously-skip-permissions.docs/security.mddescribes the model end-to-end.Why
--dangerously-skip-permissionsauto-approves every tool call. Appropriate for a sandbox; unsafe for hours-long campaigns operating against real repos. The settings file imposes deny-by-default with a small, explicit allowlist drawn from what the campaign actually needs.What's in the rendered settings
permissions.allowOnlypermissions.allowgit,python,pytest,grep,rg, etc. + binaries fromexperiment_plan.yaml.permissions.denyhttpsviacurl/wget,rm -rf /*.hooks.Stopbin/nous-execute-stop(#129) when that script is present.hooks.PreToolUseBehavioral tests (14 in
tests/test_settings_template.py)setup_work_dirwrites the file fresh; doesn't overwrite custom edits.allowOnlyAND non-emptydeny. If either were empty, the file would be functionally equivalent to--dangerouslyand the swap a regression.All assertions describe externally-visible properties (file contents, paths, JSON shape). None inspect which functions ran or how the renderer organized its work.
Out of scope
claudesession — verified manually.--dangerously-skip-permissionsremains available as a fallback when no settings file exists, so emergency / unsandboxed runs still work.Test plan
pytest tests/test_settings_template.py— 14/14 passpytest(full suite) — 352/352 pass (was 338; 14 new)claudeis invoked with--settingsnot--dangerously, and confirm an out-of-worktree write fails.Closes #135.
Refs #120.
🤖 Generated with Claude Code