refactor(memory-defense): per-bank regex defense, webhooks, drop dead surface - #2077
Merged
Conversation
Allow users to prevent token and secret leakage in agent memory. feat(memory-defense): reject quarantine action in policy parser refactor(retain): drop quarantine branch from orchestrator test(retain): remove quarantine-path tests refactor(memory-defense): remove DefenseAction.QUARANTINE enum value refactor(api): remove include_quarantined query parameter refactor(recall): drop include_quarantined parameter from memory engine test(memory-defense): replace stale parser-reject test with full-union accept test The previous parametrized test asserted parse_policy() should 422 on any detector name other than sensitive_data. That contract was deliberately widened on 2026-06-07 so cloud-style policies pass through api-slim's parser unchanged. The test was stale; the runtime is correct. Replaced with test_parse_policy_accepts_full_detector_union, which proves the actual contract: all 7 detector names are valid in the parser, with dispatch and entitlement enforcement deferred to the loaded extension. Memory defense UI
The clients were previously hand-generated in a way that diverged from the project's tooling — including a non-standard hindsight-clients/typescript/client/ directory the generator never produces (the standard output is typescript/generated/), plus ~150 spurious files. Revert the entire hindsight-clients/ tree to main and regenerate from the OpenAPI spec using ./scripts/generate-clients.sh (Rust via progenitor build.rs, Python/Go via openapi-generator, TypeScript via @hey-api/openapi-ts). The spec itself is unchanged (a code-regenerated spec is byte-identical to what was already committed). Net result is the real API delta only: the new nullable MemoryItem.receipt_uri field propagated to the Python, TypeScript and Go models.
… surface
Review cleanup of the memory-defense feature:
- Rename the OSS extension Lite -> Regex (MemoryDefenseRegexExtension,
memory_defense_regex.py). It is pure regex redaction now.
- Drop the agent_memory_guard (OWASP) dependency entirely — the
SensitiveDataDetector fallback and to_owasp_policy are gone; nothing
cloud-tier remains in api-slim.
- Trim the policy to what OSS enforces: { enabled, rules:[{on:sensitive_data,
action}] }. Removed default_action, protected/immutable namespaces,
detector_overrides, min_severity, and the unused
memory_defense_enabled_default server default. Per-bank override stays
(memory_defense is a configurable field) and the UI writes the trimmed shape.
- Fire a memory_defense.triggered webhook on every non-allow decision (redact
and block) when one is configured, via the retain orchestrator. Adds
WebhookEventType.MEMORY_DEFENSE_TRIGGERED + MemoryDefenseEventData. Replaces
the no-op record_violation hook.
- Block is now actually enforced (drop item / 422 when all blocked) instead of
being silently downgraded to redact.
- Remove the unused 'status' lifecycle: the add_status migration + its two
merge migrations, the recall quarantine filter, the status column reads in
search, and MemoryFact.status. Branch now adds zero migrations (single head).
- Remove receipt_uri from the API (MemoryItem) and clients — it was always
None and carried no value.
Tests updated/renamed accordingly; OWASP smoke + enabled-default tests removed.
- Delete test_migration_status.py (asserted the removed status column/constraint). - Remove receipt_uri from the Rust CLI (memory.rs + integration_test.rs) — the generated client struct no longer has the field, so it wouldn't compile. - Type the blocked-violations as a BlockedViolation dataclass instead of raw dicts (serialized via asdict() in the 422 body); type the webhook helper's decision param as DefenseDecision. - Add an end-to-end test asserting a redact decision queues a memory_defense.triggered webhook delivery. - Drop the unrelated docs/ entry from .gitignore (local scratch, not this PR).
Merge the 10 scattered memory-defense test modules (policy parser, regex engine/screen, redaction benchmark, extension loader, extension-context wiring, bank-config validation, and the three retain e2e files) into one test_memory_defense.py, deduping the overlapping unit screen tests and the duplicated retain redact e2e. 36 tests, same coverage.
…ck action - Add memory_defense.triggered to the control-plane webhook event-type selector (it was firing but wasn't selectable in the UI). - Document the memory_defense.triggered event (payload + data fields) on the webhooks API page, and link it from the Memory Defense page. - Document the block action (the page only described redact) and add a Notifications section. Regenerate the docs skill copies.
The feature was snapshotted into the 0.7 versioned docs by mistake — 0.7 never shipped Memory Defense. Remove the page and its (sole) Security sidebar category.
- test_retain_fires_webhook_on_redact now parses the queued delivery and asserts the MemoryDefenseEventData payload (action/detector/matched_types/ message + event status), not just that the event type was queued. - Add test_retain_fires_webhook_on_block: a block decision fires the webhook (before the 422 is raised) with action=block. Confirms the delivery persists despite the blocked retain returning 422. - Factor out _memory_defense_webhook_events() helper.
A blocked retain returns 422 with detail {violations: [{message, ...}]}; the
proxy forwards it as `details` and the client passed that object straight into
the sonner toast, crashing with "Objects are not valid as a React child".
Add describeErrorDetails() to reduce details to a string — joining violation
messages when present (so a Memory Defense block shows e.g. "Sensitive data
pattern matched: aws_access_key"), else JSON-stringifying.
Emit a fire-and-forget 'memory_defense' audit entry for each non-allow decision (alongside the webhook), with the action/detector/document_id/matched_types in metadata. Threads the engine's AuditLogger into retain_batch like the webhook manager; gated by the existing audit_log_enabled switch (off by default). - Add the memory_defense option to the audit-logs UI action filter + the actionMemoryDefense i18n key across all locales. - Document it on the Memory Defense page and the audit-logging config section. - Test: a redact retain writes a memory_defense audit row with the expected metadata (audit enabled on the test engine).
…-regex # Conflicts: # hindsight-docs/docs/developer/configuration.md # skills/hindsight-docs/references/developer/configuration.md
7 tasks
nicoloboschi
added a commit
that referenced
this pull request
Jun 12, 2026
This was referenced Jun 12, 2026
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.
Review cleanup of the memory-defense feature (forked from #2068
feat/memory-defense-extension, which stays as-is).Changes
{ enabled, rules: [{ on: "sensitive_data", action }] }. Dropped the cloud-onlydefault_action, protected/immutable namespaces,detector_overrides,min_severity, and the unused server-levelmemory_defense_enabled_default. Per-bank override still resolves via the configurablememory_defensefield; the UI writes the trimmed shape.memory_defense.triggeredwebhook for redact and block decisions when one is configured (from the retain orchestrator). AddsWebhookEventType.MEMORY_DEFENSE_TRIGGERED+MemoryDefenseEventData. Replaces the no-oprecord_violationhook.MemoryDefenseRegexExtension/memory_defense_regex.py; pure regex redaction.agent_memory_guardentirely (theSensitiveDataDetectorfallback +to_owasp_policy). Block is now actually enforced (drop item / 422 when all blocked) instead of being downgraded to redact.status. Deleted theadd_statusmigration + its two merge migrations, the recall quarantine filter, thestatusreads across the search queries, andMemoryFact.status. Branch now adds zero migrations (single alembic head).receipt_urifrom the APIMemoryItem. It was the only API delta this branch carried, so the OpenAPI spec + generated clients are now identical tomain.Tests
lint.sh(ruff +ty+ prettier) clean.Note for reviewers
The webhook fires from the orchestrator (which has
operation_id/schema/conn) rather than inside the extension, andrecord_violationwas dropped from the OSS contract —screen()is the only abstract method now. Confirm that seam if Cloud is meant to subclass this for its own event persistence.