Skip to content

fix(redaction): close forgery gap in event-level hook (v2.0.2) - #51

Merged
naji247 merged 2 commits into
mainfrom
fix/event-level-redaction-followups
Aug 8, 2026
Merged

fix(redaction): close forgery gap in event-level hook (v2.0.2)#51
naji247 merged 2 commits into
mainfrom
fix/event-level-redaction-followups

Conversation

@naji247

@naji247 naji247 commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary

Follow-up to #50 (redact_event event-level redaction hook), based on an adversarial code review of that change:

  • Widened RESTORED_FIELDS — the event-level redact_event hook could previously forge or erase actor-identity fields (actor_id, identify_actor_given_id, identify_actor_name, identify_data), tags/properties, and client/server identity fields (client_name, client_version, server_name, server_version), even though those same fields are protected from the string-level redact_sensitive_information hook via PROTECTED_FIELDS. Now restored after the hook runs, same as id/session_id/project_id/event_type/timestamp.
  • Stopped dict aliasing — the restored tags/properties/identify_data were the same dict objects as the original event, not copies; mutating one event's dict in place could silently corrupt another event sharing the reference. Now deep-copied.
  • Tightened typing_sync_event_redactor and apply_event_redaction now take RedactEventFunction and return Event | None instead of Callable[..., Any], so a hook that doesn't conform to the Event | None contract is a type-checker error, not a silent runtime surprise.
  • Documented the contracts customers actually hit: the hook must mutate-and-return the Event it's handed (the return value replaces the event's fields wholesale, not as a diff — a partial/fresh Event silently nulls the rest), and the pydantic-Event return type is enforced by static typing only, not at runtime. Added to the docstrings on apply_event_redaction, the RedactEventFunction type alias, AgentCatOptions.redact_event, and the README example.
  • Dropped an inaccurate doc claim — the docstring said the hook is dropped "or times out"; no timeout is implemented (or planned) on this hook's path, so removed the claim.
  • Deduped id generation — the same if not event.id: event.id = generate_prefixed_ksuid(...) snippet was copy-pasted three times in event_queue.py's _process_event (one variant hardcoding "evt" instead of the EVENT_ID_PREFIX constant); extracted into one _ensure_event_id helper.
  • Backfilled test coverage — fixed two tests that passed regardless of the behavior they claimed to check (an event_redaction_fn-clearing test whose mock fixture pre-baked the expected result, and a function-fields-exclusion test that couldn't detect a missing exclusion because the hook's input type never declares those fields anyway), and added coverage for SystemExit handling in the event-level hook, an async hook resolving to None, the original-event-unmutated invariant, a hook returning a non-Event value, client/server field protection, and the partial-Event field-nulling behavior. Every new/fixed test was mutation-tested against the behavior it covers.
  • Bumped 2.0.12.0.2 (patch).

Test plan

  • pytest tests/test_redaction.py tests/test_event_queue.py — all passing, including new coverage for actor/tags/properties/client/server forgery resistance
  • pytest (full suite) — all passing (828 collected)
  • mypy src/agentcat — no new errors vs. baseline (pre-existing, unrelated agentcat_api stub-package errors only)

naji247 added 2 commits August 8, 2026 15:04
…ration

Adversarial review of the redact_event hook found the RESTORED_FIELDS set was
too narrow — a hook could forge or erase actor identity, tags, and properties
despite those being protected from the string-level hook. Widen it to match,
tighten the hook's typing to RedactEventFunction/Event|None so a
non-conforming return is a type error rather than a silent contract, document
the mutate-and-return / full-dump-not-diff contract everywhere a hook author
would read it, drop the undocumented "or times out" claim, and dedupe the
id-generation snippet in event_queue.py into one helper.

Bump to 2.0.2 (patch).
RESTORED_FIELDS omitted client_name/client_version/server_name/server_version,
so a redact_event hook could forge or erase MCP client/server identity even
though those fields are protected from the string-level hook. The restored
tags/properties/identify_data were also aliased rather than copied, letting
in-place mutation of one event's dict corrupt another that shared the
reference.

Also backfills test coverage: two tests that passed regardless of the
behavior they claimed to check, plus new coverage for SystemExit handling in
the event-level hook, an async hook resolving to None, the original-event-
unmutated invariant, a hook returning a non-Event value, server/client field
protection, and the partial-Event field-nulling behavior.
@naji247
naji247 merged commit 8846cf5 into main Aug 8, 2026
41 checks passed
@naji247
naji247 deleted the fix/event-level-redaction-followups branch August 8, 2026 16:48
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