Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ site/
.docs-requirements.txt

.docs-published-wheel.txt
.claude/settings.local.json
**/.claude/settings.local.json
6 changes: 6 additions & 0 deletions scripts/check-public-boundary.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ cd "$root"

status=0

# Claude Code's personal local settings must never be tracked (.github#355).
if git ls-files -z | tr '\0' '\n' | grep -E '(^|/)\.claude/settings\.local\.json$'; then
echo 'refused: tracked .claude/settings.local.json' >&2
exit 1
fi

pattern_from_hex() {
local hex="$1"
local output=""
Expand Down
44 changes: 44 additions & 0 deletions tests/test_public_boundary_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,47 @@ def test_local_identity_commit_is_rejected(self, scratch_repo: Path) -> None:
assert result.returncode == 1, (result.stdout, result.stderr)
assert "forbidden commit metadata" in result.stderr
assert sha[:12] in result.stderr

@pytest.mark.parametrize(
"path",
[".claude/settings.local.json", "component/.claude/settings.local.json"],
)
def test_tracked_claude_local_settings_are_refused(self, scratch_repo: Path, path: str) -> None:
# durable-workflow/.github#355: refuse the path, never print the content.
target = scratch_repo / path
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text("canary-value-must-not-be-printed\n", encoding="utf-8")
_git(scratch_repo, "add", "-f", path)
_git(scratch_repo, "commit", "-q", "-m", "Track local settings")
_commit(scratch_repo, "hello.txt", "unrelated\n")

result = _run_scanner(scratch_repo, "-1 HEAD")

assert result.returncode == 1, (result.stdout, result.stderr)
assert path in result.stdout
assert "refused: tracked .claude/settings.local.json" in result.stderr
assert "canary-value-must-not-be-printed" not in result.stdout + result.stderr

def test_claude_local_settings_near_names_pass(self, scratch_repo: Path) -> None:
for path in (
".claude/settings.json",
".claude/settings.local.json.example",
"component/settings.local.json",
):
target = scratch_repo / path
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text("{}\n", encoding="utf-8")
_git(scratch_repo, "add", path)
_git(scratch_repo, "commit", "-q", "-m", "Add near names")

result = _run_scanner(scratch_repo, "-1 HEAD")

assert result.returncode == 0, (result.stdout, result.stderr)

def test_claude_local_settings_are_ignored(self, scratch_repo: Path) -> None:
shutil.copy2(REPO_ROOT / ".gitignore", scratch_repo / ".gitignore")
for path in (".claude/settings.local.json", "component/.claude/settings.local.json"):
target = scratch_repo / path
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text("{}\n", encoding="utf-8")
_git(scratch_repo, "check-ignore", "-q", path)
Loading