feat(security): restrict Engineer Bash via sandbox instead of allowlist (Closes #180) - #188
Merged
Merged
Conversation
Owner
Author
Superintendentによるレビュー(PR #188, Issue #180)
差分の内容確認
懸念点
推奨実装はIssueの受け入れ条件を満たしており、危険な既定値(GITHUB_TOKENのdeny化など)も適切に回避されています。人間による承認後のマージを推奨します。マージ時はREQ-015番号衝突の解消をお願いします。 |
5 tasks
ytnobody
force-pushed
the
hermit/ytnobody/issue-180
branch
2 times, most recently
from
July 28, 2026 02:57
a479b93 to
46611e9
Compare
Owner
Author
|
|
…st (Closes #180) hermit init now generates a `sandbox` block in .claude/settings.json (enabled, allowUnsandboxedCommands=false, network.allowedDomains for GitHub + the Go module proxy, GITHUB_TOKEN masked+injectHosts rather than denied) instead of relying solely on the Bash(*) allowlist, which Issue #138 already showed breaks whenever a new tool/command shape shows up. Re-running `hermit init` on an already-initialized project merges in only missing top-level keys (e.g. sandbox) and never destroys existing permissions customization. hermit doctor gained three warning-level checks for the most common ways this block ends up inert: sandbox.enabled false/missing, allowUnsandboxedCommands true/missing, and a non-empty sandbox.excludedCommands. README documents the settings.json scope/precedence caveat this relies on: boolean keys resolve by scope precedence, but array keys (including sandbox.excludedCommands and permissions.allow) merge additively across scopes, so a project-scope sandbox is a strong default rather than an enforcement boundary against the Engineer itself — real enforcement needs managed settings plus allowManagedReadPathsOnly/allowManagedDomainsOnly, tracked separately in Issue #179.
ytnobody
force-pushed
the
hermit/ytnobody/issue-180
branch
from
July 28, 2026 03:01
46611e9 to
878b87a
Compare
Owner
Author
|
|
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
Implements Issue #180:
hermit init/hermit doctor/ README changes to constrain the Engineer'sBash(*)access via Claude Code's sandbox feature instead of an allowlist (allowlist approach already rejected per #138 — every new tool/command shape breaks it).hermit initnow generates asandboxblock in.claude/settings.json:enabled: true,allowUnsandboxedCommands: false(Claude Code defaults this totrue, which would make the block a no-op if left unset)network.tlsTerminate: {}+allowedDomains: ["*.github.com", "proxy.golang.org", "sum.golang.org", "storage.googleapis.com"](Go toolchain hosts included sogo build/go testkeep working under the sandbox)credentials.files:~/.sshand~/.aws/credentialsdeniedcredentials.envVars:GITHUB_TOKENusesmode: "mask"+injectHosts: ["api.github.com"], never"deny"(deny would breakgh, which the Engineer needs forgh pr createetc.)hermit initon an already-initialized project (internal/permissions.MergeDefaultSettings) preserves every existing top-level key in.claude/settings.json— most importantlypermissions— and only fills in keys that are entirely absent (e.g.sandboxon a pre-Engineer の Bash を allowlist ではなくサンドボックスで制限する(hermit init / doctor / README) #180 project). Verified manually: ranhermit initagainst this repo's own real.claude/settings.jsonand confirmed the existing (much longer, hand-grown)permissions.allowlist survived byte-for-byte whilesandboxwas added.hermit doctor(cmd/hermit/doctor.go) gained three warning-level checks (checkSandboxSettings):sandbox.enabledfalse/missing,allowUnsandboxedCommandstrue/missing,sandbox.excludedCommandsnon-empty. These are warnings, not failures, so doctor still passes cleanly on projects that predate the sandbox recommendation.sandbox.enabledetc.) resolve by scope precedence, but array keys merge additively across every scope (sandbox.excludedCommands,permissions.allow, ...), so nothing in a project-scoped.claude/settings.jsonstops the Engineer from proposing a PR that widens or bypasses it. Real enforcement requires managed settings plusallowManagedReadPathsOnly/allowManagedDomainsOnly, whose automated generation is explicitly out of scope here and tracked in 制御面(risk/permissions/readiness/harness.toml/.claude)への変更を HIGH に昇格し、自動マージから隔離する #179.REQUIREMENTS.mdgained REQ-018 with REQ-ID-named tests.Assumption (ambiguous point, no human reply expected mid-run)
The Issue didn't specify whether
hermit doctor's new sandbox checks should be hard failures (non-zero exit) or warnings. I made them warnings (warn: true, passed: true), matching the existing pattern for the snap-gh check, so thathermit doctordoesn't start failing on every project that predates this feature. The acceptance criteria only say doctor should "detect and warn" (検出して警告する), which is consistent with this choice.Test plan
go build ./...go test ./...(full suite, all packages pass)gofmt -l .cleango run ./cmd/hermit initagainst this repo's real (pre-existing).claude/settings.jsonand confirmed: existingpermissions.allowpreserved verbatim,sandboxblock added with the exact structure from the Issuego run ./cmd/hermit doctoragainst the real (still sandbox-less until this PR merges andinitis rerun).claude/settings.jsonand confirmed the three new checks warn without failing the overall run🤖 Generated with Claude Code