{"record":{"id":"b3ac0b05c06c0a49","repo":"headroomlabs-ai/headroom","slug":"comparator-ccr-not-implemented-phase-0","errorCode":null,"errorMessage":"comparator ccr not implemented (Phase 0)","messagePattern":"comparator ccr not implemented \\(Phase 0\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/headroom-parity/src/lib.rs","lineNumber":173,"sourceCode":"// * `cache_aligner` — needs the volatile-content detector, which lives in\n//   `headroom-proxy` while this crate depends only on `headroom-core`. Either\n//   add the dependency or move the detector down into core.\n// * `ccr` — the fixtures compare `ccr_retrieve` tool-definition injection\n//   (`headroom/ccr/tool_injection.py`), which has no Rust port at all.\n\nmacro_rules! stub_comparator {\n    ($ty:ident, $name:literal) => {\n        pub struct $ty;\n        impl TransformComparator for $ty {\n            fn name(&self) -> &str {\n                $name\n            }\n            fn run(\n                &self,\n                _input: &serde_json::Value,\n                _config: &serde_json::Value,\n            ) -> Result<serde_json::Value> {\n                anyhow::bail!(concat!(\"comparator \", $name, \" not implemented (Phase 0)\"))\n            }\n        }\n    };\n}\n\nstub_comparator!(CacheAlignerComparator, \"cache_aligner\");\nstub_comparator!(CcrComparator, \"ccr\");\n\n/// Real comparator for the `log_compressor` transform.\n///\n/// Two wrinkles beyond the usual adapter shape:\n///\n/// * **bias.** Python's signature is `compress(content, context=\"\", bias=1.0)`\n///   and the recorder captured only `content`, so every fixture was produced at\n///   the default `bias = 1.0`. Rust takes `bias` positionally — pass 1.0.\n/// * **CCR store.** The Python compressor owns its store internally, while Rust\n///   mints a `cache_key` only when one is handed to `compress_with_store`.\n///   Without a store the CCR branch bails out with `\"no store provided\"` and","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/crates/headroom-parity/src/lib.rs#L155-L191","documentation":"HTTP 403 from require_same_origin (loopback_guard.py:248): the Origin header names a host that is not loopback. This is the CSRF gate for mutating routes — a remote web page's JavaScript POSTing directly to http://127.0.0.1:port is rejected even though the Host header is loopback, because the Origin betrays the cross-site caller.","triggerScenarios":"JavaScript on https://evil.example running fetch('http://127.0.0.1:8080/settings', {method:'POST', body:...}) with a simple Content-Type; any browser page on a non-loopback origin targeting the proxy's mutating endpoints.","commonSituations":"Drive-by localhost CSRF attempts (the reason the guard exists); a legitimately hosted dashboard on another domain trying to drive the local proxy; browser extensions injecting requests from their own origin.","solutions":["Serve your dashboard/control page from the same loopback origin as the proxy","Add the dashboard's domain to the trusted-dashboard-client allowlist the server composes around these dependencies (server.py wraps them with an escape hatch)","Use a non-browser client for automation — it sends no Origin and is not affected"],"exampleFix":"// before: page served from https://dashboard.example\nfetch('http://127.0.0.1:8080/settings', {method: 'POST', ...})\n\n// after: serve the control page from the proxy origin and use a relative URL\nfetch('/settings', {method: 'POST', ...})","handlingStrategy":"validation","validationCode":"// Only call mutating endpoints from the proxy's own origin\nif (location.hostname !== \"127.0.0.1\" && location.hostname !== \"localhost\") {\n  throw new Error(\"cross-origin calls are rejected by the same-origin guard\");\n}","typeGuard":null,"tryCatchPattern":"if (resp.status === 403) { /* re-serve the control page from the proxy origin */ }","preventionTips":["Host dashboards on the proxy origin or use the trusted-dashboard-client allowlist","Expect 403 for browser cross-origin POSTs even with correct Host headers"],"tags":["security","csrf","origin","http-403"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}