Skip to content

feat(redaction): add event-level redactEvent hook#49

Merged
naji247 merged 3 commits into
mainfrom
feat/redact-event-hook
Jul 16, 2026
Merged

feat(redaction): add event-level redactEvent hook#49
naji247 merged 3 commits into
mainfrom
feat/redact-event-hook

Conversation

@naji247

@naji247 naji247 commented Jul 15, 2026

Copy link
Copy Markdown
Member

Summary

Redaction decisions often need more context than a single string. The existing redactSensitiveInformation hook is applied blindly to every string field in an event, so consumers can't tell which tool produced a value, what type of event is being published, or which field a string belongs to.

This PR adds a new event-level redaction hook, redactEvent, alongside the existing string-level hook. It receives the full Event object — resourceName, eventType, parameters, response, userIntent, and the rest — and returns a modified event, or null to drop the event entirely.

agentcat.track(mcpServer, "proj_0000000", {
  redactEvent: (event) => {
    // Drop events from tools that handle secrets entirely
    if (event.resourceName === "get_credentials") {
      return null;
    }
    // Strip response payloads from a specific tool
    if (event.resourceName === "export_report") {
      return { ...event, response: undefined };
    }
    return event;
  },
});

Behavior

  • Signature: (event: Event) => Event | null | Promise<Event | null> — sync or async, exported as RedactEventFunction.
  • Ordering: redactEvent runs first and sees raw, unredacted values with full event context. redactSensitiveInformation (if configured), sanitization, and truncation all run on its output, so the string-level scrubber remains the last line of defense.
  • Drop semantics: returning null/undefined drops the event. If the hook throws, the event is dropped and the error is logged to ~/agentcat.log — the same fail-closed semantics as the existing string-level hook.
  • System-managed fields: id, sessionId, projectId, eventType, and timestamp are restored if the hook changes or removes them, protecting session and project attribution. Everything else is fully consumer-controlled.
  • Field deletions are honored: fields removed by the hook stay removed rather than being merged back in.

Behavior change worth noting

The hook is attached centrally in the publish pipeline, which means custom events published via publishCustomEvent on a tracked server now also pass through redactEvent. Custom events previously bypassed redaction entirely; the string-level hook's behavior for them is unchanged. Custom events published with a bare session ID string (no tracked server) still bypass redaction, since no tracked configuration exists there.

Test plan

  • Unit tests for the new applyEventRedaction helper: sync/async hooks, null/undefined drops, deletion honoring, system-managed field restoration, internal function fields never exposed to or persisted from the hook, error propagation (src/tests/redaction.test.ts)
  • Queue-level tests: hook attachment from track() options, drop-on-null, drop-on-throw, ordering relative to string redaction (src/tests/eventQueue.test.ts)
  • End-to-end integration tests over a real MCP client/server pair: metadata visibility, field replacement, selective drop by event type, hook composition with string redaction, publishCustomEvent coverage (src/tests/redaction.test.ts)
  • Full suite green: 499 passed, plus lint and typecheck via pnpm run prepublishOnly
  • Manual end-to-end verification against the built package (dist): drove a real MCP client/server with both hooks configured and a local HTTP capture server in place of the AgentCat API; confirmed rewritten parameters, dropped events, string redaction composing after the event hook, and that a throwing hook drops only the affected event without disrupting the server

naji247 added 2 commits July 15, 2026 19:03
Add a redactEvent option to AgentCatOptions that receives the full Event
object before publishing, complementing the existing string-level
redactSensitiveInformation hook. Consumers can inspect event metadata
(resourceName, eventType, parameters, response, etc.) to make
context-aware redaction decisions, return a modified event, or return
null to drop the event entirely.

- New RedactEventFunction type, exported from the package root
- Hook runs before string-level redaction, so it sees raw values;
  string redaction, sanitization, and truncation still run on its output
- System-managed fields (id, sessionId, projectId, eventType, timestamp)
  are restored if the hook alters them
- Hook errors drop the event and log, matching existing redaction
  semantics
- Attached centrally in publishEvent, which also brings custom events
  published via publishCustomEvent on tracked servers under the hook
…tion

The queue's process() previously handled the mechanics of applying the
redactEvent hook result: clearing the event object, reassigning fields,
and re-attaching the string-level redaction function. Consolidate all of
that into applyEventRedaction, which already owns the rest of the hook
contract (carrier-field stripping, system-managed field restoration).

The helper now rewrites the event in place and returns whether the event
was kept, so the queue reduces to a single call plus its existing
drop/log handling. No behavior change.
@naji247
naji247 merged commit a7498d2 into main Jul 16, 2026
8 checks passed
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