Skip to content

fix: hash full query in gate audit events for cross-path hash parity#79

Merged
CGFixIT merged 1 commit into
mainfrom
claude/cyclaw-gate-audit-query-hash-parity
Jun 20, 2026
Merged

fix: hash full query in gate audit events for cross-path hash parity#79
CGFixIT merged 1 commit into
mainfrom
claude/cyclaw-gate-audit-query-hash-parity

Conversation

@CGFixIT

@CGFixIT CGFixIT commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Problem

In gate.py, the /query handler logged two audit events with a truncated query:

audit_log({"event": "prompt_injection_blocked", "query": req.query[:50]})
...
audit_log({"event": "graph_error", "query": req.query[:50], "error": safe_msg})

But audit_log() (utils/logger.py) does not store the query verbatim — it replaces the query field with its SHA-256 hash:

if "query" in event and audit_fields.get("include_query_hash", True):
    raw_query = event.pop("query")
    event["query_hash"] = hash_query(raw_query)

So req.query[:50] produces a query_hash of only the first 50 characters. That hash does not match the canonical full-query hash written for the same query by:

  • the graph audit node — graph.py passes the full query to audit_log, and
  • the MCP path — mcp_hybrid_server.py passes the full query (this was a deliberate fix; tests/test_mcp_server.py:292 asserts event["query_hash"] == hash_query(long_query)).

Result: for any query longer than 50 chars, an injection-blocked or graph-error event can never be correlated by hash with the same query seen on the MCP/graph paths — defeating the purpose of the shared query_hash.

Change

Pass the full req.query in both audit_log calls (truncation removed). Added a short comment noting the field is hashed downstream.

Benefit

  • Restores cross-path query_hash parity (HTTP gate ↔ graph audit ↔ MCP) for queries of any length — the same guarantee already test-asserted for MCP.
  • No privacy change: the query is hashed before persistence either way; raw text is never written. Truncation gave no size benefit (the stored value is a fixed-length hash) while silently breaking correlation.

🤖 Generated with Claude Code


Generated by Claude Code

The /query handler passed req.query[:50] to audit_log for the
prompt_injection_blocked and graph_error events. audit_log() replaces the
'query' field with hash_query(query) before persisting, so truncating to 50
chars produced a query_hash of only the first 50 characters. That hash does
not match the canonical full-query hash written by the graph audit node
(graph.py) or the MCP path (mcp_hybrid_server.py) for the same query,
breaking cross-path correlation of audit events for queries longer than 50
chars.

Pass the full query in both calls. No raw query text is persisted either way
(the field is hashed), so this is purely a correctness fix that restores the
same hash-parity guarantee already asserted for the MCP path in
tests/test_mcp_server.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ECr44xGUy4SDEJRSmDPNZb
@CGFixIT CGFixIT marked this pull request as ready for review June 20, 2026 08:04
@CGFixIT CGFixIT merged commit 7bfb093 into main Jun 20, 2026
14 checks passed
@CGFixIT CGFixIT deleted the claude/cyclaw-gate-audit-query-hash-parity branch June 20, 2026 09:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants