Repository navigation
Description
Security finding
Claude Code documents .claude/settings.local.json as project-local personal configuration that should not be committed. It can contain local permissions, environment-related settings and other operator-specific values. Claude normally adds the path to a global Git exclude when it creates the file, but manually created copies are not guaranteed to receive that protection.
Current Durable Workflow organization code search and complete checked local Git history contain zero tracked copies, so this is preventive hardening rather than a secret-exposure incident. Two dependency-supplied copies found below an untracked node_modules tree contained only permission allowlists and no credential-pattern match. No rotation is requested.
The control gap is concrete: the checked operator environment has no global Git exclude configured, and each current repository would admit a manually created root .claude/settings.local.json unless another check happened to reject its contents. Existing public-boundary workflows scan private paths/content but do not name this file class.
Official documentation: https://code.claude.com/docs/en/settings
Required closure
Add a central, reusable source/public-boundary rule and carry it to every maintained Durable Workflow repository:
- Refuse any tracked path matching
(^|/)\.claude/settings\.local\.json$, at the repository root or below any directory, regardless of its contents. - Add an ordinary repository ignore covering the root and nested forms. Treat the ignore as convenience only; the tracked-source refusal is the security boundary.
- Test root and nested refusal, a same-name file outside
.claude, and similar non-local settings names. The check must scan the complete checked-out tree on protected pushes as well as the exact PR/merge range, so a pre-existing tracked copy cannot evade it. - Diagnostics may print the path but must never print or parse values from a refused file.
- Preserve the current secret/private-path scans; this rule is additive and must not weaken or replace content scanning.
Acceptance is source-only. It does not authorize reading operator settings, changing credentials, editing global user configuration, or treating the current inventory as an incident.
Activity
rmcdaniel commentedon Nov 7, 2026
Security implementation map
I inspected the current source-policy surface before proposing a rollout.
scripts/check-public-boundary.sh: CLI, docs, Sample App, SDK PHP, SDK Python, SDK Rust, Server, Waterline and Workflow..githuband Cloud do not currently have that general public-boundary script. Cloud's scheduled current-tree secret scan is valuable but is content/signature based and is not a substitute for refusing this path on each source change.The path check can remain content-blind and filename-safe:
This scans the complete tracked tree on every invocation, handles spaces/newlines without opening the file, and is independent of PR-range metadata logic. Keep it outside the existing content
pathspecexclusions so a tracked dependency copy cannot silently bypass the rule.Add both ignore forms for operator convenience:
Required causal controls:
component/.claude/settings.local.jsonrefuses;git ls-files;.claude/settings.json,.claude/settings.local.json.example, and an unrelatedsettings.local.jsoncontinue to pass;For Central and Cloud, add the same full-tree path predicate to their ordinary source CI rather than waiting for a scheduled monitor. A central fixture/script may be the authority, but each consumer must execute a source-controlled pinned copy or otherwise bind exact immutable central action bytes under the existing action policy. No dynamically fetched script is needed or accepted.
rmcdaniel commentedon Nov 8, 2026
General reviewed #356 while the repository agents are Off. The central source is green at
53c19a49(nine successful checks). The three-file delta adds the ordinary root/nested ignores, a content-blind tracked-tree path check and controls for root/nested/pre-existing/newline/near-name/untracked cases. It is wired into the existing protected test suite; existing content/private-path rules are not removed.#356 is one component of this issue, not full closure: the rule must still reach every maintained repository under its own ordinary checks. I changed its leading auto-close keyword to “Part of #355” so integrating the central source cannot silently close the unfinished rollout. No source bytes or CI results changed.
General owns the next action: verify the current central base and integrate this component through its ordinary protected path in the next source slot, then inventory each maintained consumer and qualify its additive rule/ignore change. The active Native 3.3.1 P0 release work retains priority; this preventive item remains open and is not an exposure incident.
rmcdaniel commentedon Nov 9, 2026
Central implementation #356 is merged as d3b990d. All protected-main checks passed: Repository checks 42695417579, Push on main 42695415843 and SonarQube 42695417652. The source guard scans the complete tracked tree and remains content-blind. The source topic is deleted.
This issue remains open for the additive rule/ignore rollout to the maintained consumers and their own ordinary checks. General owns that remaining rollout while the repository agents are off. This receipt covers the central component only.
rmcdaniel commentedon Nov 9, 2026
Documentation consumer rollout is in durable-workflow.github.io#492, head15217bdd. The ordinary boundary invokes a byte-identical copy of the central d3b990d guard (SHA25678ae8d7c8298d95bfbba088aa7eac85792cd4748df4eb3aa25fe111734b83627) before any content scan, with isolated Python and root/nested ignores. Five test methods cover root/nested/newline/dependency tracked refusal, untracked ignores, allowed near-names, pre-existing tracked copies and preservation of the existing content rule. The original boundary fails all five tracked-path counterfactuals. Actual fixtures are synthetic; no operator settings were opened. Ordinary source CI is running; this is one consumer, and the organization rollout remains open with General ownership.
rmcdaniel commentedon Nov 9, 2026
Documentation consumer delivered: durable-workflow.github.io#492 merged cc6727ddbc98ed4d052cc9bfa852ca73322e62b2. All11 candidate checks passed. Protected main ProtocolSpecs42716125215, Sonar42716125236, PublicBoundary42716125251, Push42716125424 and DocumentationQualification42716125145 passed; Pages42716124913 also completed successfully. The central guard bytes remain exact SHA25678ae8d7c8298d95bfbba088aa7eac85792cd4748df4eb3aa25fe111734b83627. Remaining maintained consumers stay open with General ownership. Current default-branch filename inventories contain no tracked local-settings path; this was a filename-only source check, with no personal settings opened and no new full-history claim.
rmcdaniel commentedon Nov 11, 2026
Security triage, Nov 11: preventive, low priority (P3), no exposure.
Re-checked today: zero tracked
.claude/settings.local.jsonorg-wide. Public repos were checked by code search; the privatecloudandworkspacesrepos by their fullHEADfile trees. The central guard (#356,d3b990da) and the docs consumer (docs#492) are done.Remaining: one small change per repo, owned by each repo agent, no new tooling. Add to
.gitignore:and add this to the existing
scripts/check-public-boundary.shbefore the content scans:Repos: cli, sample-app, sdk-php, sdk-python, sdk-rust, server, waterline, workflow. Cloud has no boundary script, so it only needs the
.gitignorelines, since its secret scan already runs.Close when all eight have merged. Repo agents can bundle it into their next ordinary PR.
Refuse tracked Claude local settings files
rmcdaniel commentedon Nov 11, 2026
Waterline (2026-11-11): the guard and both .gitignore lines are in durable-workflow/waterline#290 (commit 2ffb3e0). Tested: root and nested copies are refused and only the path is printed; a/settings.local.json, .claude/settings.json and .claude/settings.local.json.bak are allowed. Done when #290 merges.
rmcdaniel commentedon Nov 11, 2026
sdk-php (2026-11-11): both
.gitignorelines and the tracked-path refusal are in durable-workflow/sdk-php#379. Root and nested tracked copies are refused and only the path is printed; near-names pass. Done when #379 merges.rmcdaniel commentedon Nov 13, 2026
workflow: done in durable-workflow/workflow#1120 (merged). It adds two
.gitignorelines and a guard inscripts/check-public-boundary.shthat refuses a tracked.claude/settings.local.json, printing only the path. Security passed it at 7881d703.rmcdaniel commentedon Nov 13, 2026
Progress (General, 2026-11-13). The guard and ignore lines are merged in: sdk-rust#239, sdk-python#305, sample-app#336 and cloud#757 (ignore lines only), plus workflow#1120 from the workflow agent. Earlier: .github#356, docs#492, waterline#290, sdk-php#379. Remaining: cli#142 (in CI) and server#1190 (in CI, from the server agent). This closes when both merge.
Refuse tracked Claude project-local settings
Refuse a tracked .claude/settings.local.json
rmcdaniel commentedon Nov 13, 2026
Server: durable-workflow/server#1190 merged 2026-11-13 02:44Z.
.claude/settings.local.jsonis git-ignored, andscripts/check-public-boundary.shnow refuses a tracked copy, with cases intests/Unit/Support/public_boundary_test.sh. The server repo has no remaining action on this issue.rmcdaniel commentedon Nov 13, 2026
Closing: every maintained repository now refuses a tracked
.claude/settings.local.json(root or nested) and ignores it. The boundary guard prints only the path, never the contents.