relay: log-key allowlist test (#36)#54
Conversation
Code Review: #36Decision: PASS Findings
Verification
Security reviewTicket carries SummaryFaithful execution of the spec with one well-justified deviation (first-arg-string-literal narrowing to exclude |
Adds a fourth defence layer for docs/threat-model.md § Log hygiene
beyond spec, code review, and structural absence: a Go test that
fails if any logger.{Info,Warn,Error,Debug} call in internal/relay
carries a key outside the closed set in log_allowlist.go.
The walker also flags non-literal key arguments (dynamic keys
defeat the allowlist) and Logger.With/LogAttrs/Log selectors
(method shapes the walker does not parse — a future contributor
must either convert the call or extend the walker). Narrowed to
calls whose first arg is a string literal so unrelated same-
named calls like http.Error(w, "", code) are excluded.
Threat-model § Log hygiene gains a one-line enforcement
cross-reference pointing at the allowlist as the canonical
machine-checkable rule.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…try (#36) Per-ticket #36 codebase note describing the AST-walk test, the seven-key allowlist, the three violation classes (key not in allowlist, non-literal key, unsupported method), and the threats it gates vs. those it deliberately does not (value-side leaks, tampering, off-package logs). Lessons-learned section captures the "stochastic spec → deterministic test" pattern and the selector-name-AST-match-is-enough heuristic. INDEX.md gains a one-line Cross-cutting entry pointing at the allowlist file and test as the canonical machine-checkable rule. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
What
Adds a deterministic fourth defence layer for
docs/threat-model.md§ Log hygiene:internal/relay/log_allowlist.go—allowedLogKeysmap enumerating the seven structured-log keys the relay currently emits (server_id,conn_id,binary_conn_id,device_name,remote,binary_version,err). Canonical machine-checkable form of the threat-model rule; adding a key here is the commit-time gate paired with the doc.internal/relay/log_keys_test.go—TestLogKeysAreAllowlisted. AST-walks every non-test.gofile in the package usinggo/parser+go/ast(nogo/types), matches*ast.CallExprwhose selector isInfo/Warn/Error/Debug, narrows to calls whose first arg is a string literal (filtershttp.Error(w, "", code)), and flags:allowedLogKeyslogger.With/LogAttrs/Logselectors (shapes the walker does not parse)docs/threat-model.md— one-paragraph cross-reference under § Log hygiene pointing at the allowlist source as the canonical enforcement point.Issue
Closes #36.
Testing
go test -race ./...— all tests pass, including the new one against currentmaincontent.go vet ./...— clean.go build ./cmd/pyrycode-relay— builds."forbidden_key", "x"to alogger.Infocall; test failed withserver_endpoint.go:93:58: log key "forbidden_key" is not in allowedLogKeys— file path, line, and offending key all named without-v. Reverted.Architecture compliance
Follows the spec's three-artefact design exactly: allowlist as a normal
.gosource (not_test.go) so the doc can point at a stable path; AST walker local to the test file with no shared visitor types; violations accumulated and reported at end of walk with a single trailingt.Fatalfcount. Walker matches bySel.Nameonly (no type resolution), as the spec specifies.Deviation from spec, documented in the test docstring: the walker additionally requires the matched call's first argument to be a string literal. The spec said false positives from same-named non-logger methods were acceptable, but
http.Error(w, "", http.StatusBadRequest)is present in bothclient_endpoint.goandserver_endpoint.goand would have failed the test onmain(it hasSel.Name == "Error"). The first-arg-must-be-string-literal narrowing matches the slog convention at every existing call site, excludeshttp.Errorcleanly, and stays inside the spec's "deterministic structural check" principle.🤖 Generated with Claude Code