fix(canopy): SEC-F22/F19 #420 follow-up — CI smoke attest + WS per-session log hygiene#426
Merged
Merged
Conversation
…ssion log hygiene Addresses two items from the §9 independent security review of #420. The review's third item (global-cap rejection leaking per-IP/per-session slots) already landed on main via #421 and is NOT re-implemented here (dup-guard). 1. CI Docker smoke — attest past the bind-guard (.github/workflows/ci.yml). #420's startup loopback bind-guard (enforce_loopback_bind_guard, run in main.lifespan) refuses to serve on a non-loopback interface without JUNIPER_CANOPY_FRONTING_AUTH_ATTESTED=true. The Dockerfile bakes JUNIPER_CANOPY_SERVER__HOST=0.0.0.0, so the docker-build "Verify Container Starts" smoke would abort at startup (NonLoopbackBindError) and never reach healthy. Pass -e JUNIPER_CANOPY_FRONTING_AUTH_ATTESTED=true to docker run, scoped to that ephemeral CI container only (the sole container-start site in .github/workflows/). 2. SEC-F19 log hygiene — hash the canopy_session cookie before logging (src/communication/websocket_manager.py). check_per_session_limit logged a raw 8-char prefix of the signed Starlette session cookie (session_key[:8]). New _hash_session_key_for_log emits a keyed HMAC-SHA256 (per-process secret _LOG_HASH_KEY) 12-hex-char tag instead, so the raw cookie never reaches a log line and the digest is not offline-computable. Mirrors juniper-cascor src/api/workers/security.py. Tests: src/tests/unit/test_ws_connection_caps.py::TestPerSessionLogHygiene. Verified: full CI unit+regression scope green (coverage 85.85%); canopy pre-commit green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LX5ToumBaABH3QutQC86sM
Signed-off-by: Overtoad <paul.calnon@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #420 (
fix(canopy): SEC-F22/F19 — startup bind-guard + global/per-session WS caps (D2/D4)), addressing findings from its §9 independent security review. This PR lands the two review items still outstanding after #421:canopy_sessioncookie before logging (Security)1. CI Docker smoke — attest the container past the bind-guard (
.github/workflows/ci.yml)#420's startup loopback bind-guard (
enforce_loopback_bind_guard, run inmain.lifespan) refuses to serve on a non-loopback interface withoutJUNIPER_CANOPY_FRONTING_AUTH_ATTESTED=true. TheDockerfilebakesJUNIPER_CANOPY_SERVER__HOST=0.0.0.0(required for the container to be reachable through the published port), so thedocker-buildVerify Container Starts smoke step would abort at startup (NonLoopbackBindError) and never reachhealthy.Fix: pass
-e JUNIPER_CANOPY_FRONTING_AUTH_ATTESTED=truetodocker run— scoped to that ephemeral CI container only (verified the sole container-start site in.github/workflows/; no fronting proxy is present, the container just needs to boot for the/v1/healthprobe).Mechanism verified directly:
2. SEC-F19 log hygiene — hash the session cookie (
src/communication/websocket_manager.py)check_per_session_limitlogged a raw 8-char prefix of the anonymouscanopy_sessioncookie (session_key[:8]) when the per-session cap tripped. That cookie is a signed Starlette session token, so even a prefix in a log line is an avoidable session-identifier leak. New_hash_session_key_for_loghelper emits a short, non-reversible tag instead — keyed HMAC-SHA256 over the raw cookie with a per-process random secret (_LOG_HASH_KEY), truncated to 12 hex chars — so the logged digest is not an offline-computable function of the cookie and does not correlate across process restarts. Mirrors the cascor sibling, which hashes its identity before logging (juniper-cascor src/api/workers/security.py).Tests
src/tests/unit/test_ws_connection_caps.py::TestPerSessionLogHygiene(new): the over-cap warning omits the raw cookie and its[:8]prefix and carries the keyed hash instead; the hash is deterministic-within-process, hex, compact, and not the raw value.pytest -m "not requires_cascor and not requires_server and not slow" src/tests/unit/ src/tests/regression/ --cov=src --cov-fail-under=80.Merge
Owner-gated — do not auto-merge.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LX5ToumBaABH3QutQC86sM