finding (ops-gh): 2026-05-08 - Bare gh issue ran wrapper-free on May 8 because parent-dir .claude/settings.local.json allow-listed it #35
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally filed by @coilysiren on 2026-05-18T03:42:50Z - https://github.com/coilysiren/coily/issues/222
Migrated from
coily-ops-gh-meta/findings/2026-05-08-parent-dir-allowlist-overrides-per-repo-gh-lockdown.mdon 2026-05-17 as part of coilysiren/coily#215. Original file preserved in git history; see deletion commit on coilysiren/coily#215.2026-05-08 - Bare
gh issueran wrapper-free on May 8 because parent-dir.claude/settings.local.jsonallow-listed itWhat was observed
Working session began at cwd
/Users/kai/projects/coilysiren/(the parent directory containing everycoilysiren/*repo clone; itself not a git repo). During the session the agent ran three bareghinvocations from inside subordinate repos:gh issue list --search "gif OR fixture..." --state all --limit 20(run with cwd atcoilysiren/otel-a2a-relay)gh issue view 92 --json title,body,state(same cwd)gh issue create --title "Pin GIF fixture..." --body ...(same cwd)gh issue list --search "openclaw OR JOURNAL OR ..."(run with cwd atcoilysiren/agentic-os-kai)gh issue view 98 --json ...(same cwd)All five succeeded without prompting and produced zero audit rows. Confirmed:
/Users/kai/.coily/audit/coilysiren-otel-a2a-relay.jsonlwas 0 bytes, last touched May 7 01:51, despitegh issue createagainst that repo on May 8./Users/kai/.coily/audit/coilysiren-agentic-os-kai.jsonllast touched May 7 23:08, no May 8 rows for the bareghcalls.Each subordinate repo's
.claude/settings.jsoncorrectly contains"Bash(gh:*)"in its deny list - so the per-repo lockdown is intact. The bypass came from/Users/kai/projects/coilysiren/.claude/settings.local.json:That allow lives at the session-start cwd, which is the parent of every locked-down repo. Claude Code's permission engine appears to evaluate against the session-start project root, not the per-Bash-command cwd, so the parent-dir allow took precedence over the per-repo deny for the entire session.
Why it slipped
Distinct from the 2026-05-05
claude-bypasses-coily-gh-wrapperfinding, which observed denials. Here the bypass succeeded silently because the allow-list rule was already in place at a scope that dominates per-repo lockdown.Three compounding gaps:
Bash(gh issue *)allow rule exists. Whatever its original purpose (likely convenience for org-wide issue triage), it now silently shadows every child repo'sBash(gh:*)deny for any session that starts at the parent.~/.claude/projects/**/*.jsonlfor "denied" entries will not surface this class.The May 5 finding's forward shape ("make lockdown the default-on state for any session that has coily installed") would not have caught this case either, because lockdown was default-on for the child repos. The shadowing happens above lockdown.
Rule it produced
Anti-signal: "per-repo lockdown is sufficient when sessions start above the repo." False. A parent-dir
.claude/settings.local.jsonallow overrides every child-dir deny for the session, and the multi-repo parent is a common session-start cwd for cross-repo work. Lockdown discipline has to extend to the session-root cwd actually used, not just the per-repo roots.Forward shape candidates (file under coily#61):
coily lockdownwarn or refuse when applied to a child repo whose ancestor.claude/settings*.jsoncarries broader permissions.coily lockdown --recursivemode that walks up from any locked-down repo and either (a) refuses to leave broad allows above, or (b) installs a sibling.claude/settings.jsonat the session-root cwd that re-asserts the deny.gh issue createevents on github.com againstcoilysiren-<repo>.jsonlrows for the same repo, flagging the gap.