fix(issue-12): scrub-secrets selects only records with a stored password

Review of 4176d75 (planner-reviewer-claude-01, N1): _cmd_scrub_secrets chose
records by the presence of broker.password, not by its value. The old
to_registry_block wrote "password": null even for an unauthenticated broker,
so on this repo's registry (901 of 903 records carry the null key, none a
real password) --dry-run reported "would scrub 901 record(s)" and a real run
would rewrite every one of them while holding the registry lock.

- registry.py: select on broker.get("password"); an empty key is no secret,
  and _atomic_write_record drops it on the record's next write anyway
- tests: the migration test gains a legacy "password": null record that must
  be neither listed nor rewritten (fails on 4176d75 with "would scrub 2")
- VERSIONS.md v4.4.4 item 2 and registry.md now describe the stored-value
  rule

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
This commit is contained in:
GodopuandClaude Opus 5 committed 2026-09-13 12:07:42 +09:00
1 parent 4176d7570f
commit 0fedae2f46
4 files changed
+23 -5

No files matched your search

+11
View File
@@ -79,12 +79,22 @@ def test_scrub_secrets_cli_migrates_a_registry(mam_sandbox, monkeypatch):
rec["broker"]["password"] = "leaked-earlier"
dirty_path.write_text(json.dumps(rec))
clean_before = (Path(rd) / f"{clean}.json").read_text()
# What the old to_registry_block wrote for every unauthenticated job: the key
# with a null value. No secret, so the migration must neither report nor
# rewrite it (a real PoC registry held ~900 of these and no password at all).
empty = registry.register_job("empty-key", "claude-code", "herdr:claude", registry_dir=rd)
empty_path = Path(rd) / f"{empty}.json"
rec_empty = json.loads(empty_path.read_text())
rec_empty["broker"]["password"] = None
empty_path.write_text(json.dumps(rec_empty))
empty_before = empty_path.read_text()
env = dict(os.environ, PYTHONPATH=f"{SKILLS}:{os.environ.get('PYTHONPATH', '')}")
dry = subprocess.run([sys.executable, str(REGISTRY_PY), "--registry-dir", rd, "scrub-secrets", "--dry-run"],
capture_output=True, text=True, env=env, cwd=str(mam_sandbox))
assert dry.returncode == 0, dry.stderr
assert f"would scrub 1 record(s)" in dry.stdout and dirty in dry.stdout
assert empty not in dry.stdout, "an empty password key is not a stored secret"
assert "leaked-earlier" in dirty_path.read_text(), "--dry-run must not write"
res = subprocess.run([sys.executable, str(REGISTRY_PY), "--registry-dir", rd, "scrub-secrets"],
@@ -95,3 +105,4 @@ def test_scrub_secrets_cli_migrates_a_registry(mam_sandbox, monkeypatch):
assert "password" not in scrubbed["broker"]
assert scrubbed["prompt"] == "dirty" and scrubbed["auth_token"] == rec["auth_token"]
assert (Path(rd) / f"{clean}.json").read_text() == clean_before, "untouched records are not rewritten"
assert empty_path.read_text() == empty_before, "a null password key is left for the record's next write"