Skip to content

Web SPA: surface redaction-blocked 403 + confirm-and-retry with force_unsafe (PR #784 follow-up) #785

@memtomem

Description

@memtomem

Background

PR #784 unified the trust-boundary redaction guard across every server-side ingress surface, including the four web mutating routes:

  • POST /api/add
  • POST /api/upload (query param force_unsafe)
  • PATCH /api/chunks/{chunk_id}
  • POST /api/scratch/{key}/promote

On a hit they return HTTP 403 with a structured detail body:

{"detail": "redaction_blocked", "hits": <int>, "surface": "<surface_label>"}

…so the SPA can render a confirm-and-retry dialog and resubmit with force_unsafe=true after the operator acknowledges the matched-pattern count. Codex review of PR #784 flagged that the SPA does not yet exercise this flow.

Concrete gaps (from the Codex review)

  • web/static/app.js:3789/api/add POST body does not include force_unsafe.
  • web/static/app.js:2151, web/static/app.js:3621 — chunk-edit PATCH body does not include force_unsafe.
  • web/static/app.js:3875 — upload form does not append the force_unsafe query param.
  • web/static/settings-harness.js:228 — scratch promote POST body does not include force_unsafe.
  • web/static/app.js:216 — the shared api() helper throws new Error(err.detail) directly. When err.detail is the structured object above it stringifies as [object Object], swallowing hits / surface and breaking any UX that wants to render them.

What to add

  1. api() helper: when a 4xx response has a detail object with detail === "redaction_blocked", throw a typed error (or return a sentinel) carrying {hits, surface} so callers can branch.
  2. For each of the four mutating surfaces, add a confirm-and-retry path:
    • On a redaction-blocked error, surface a modal / toast that names the matched-pattern count (hits) and the surface label.
    • If the user confirms, re-issue the same request with force_unsafe: true (body field) or ?force_unsafe=true (upload query param).
  3. Add at least one Playwright MCP smoke that exercises the full SPA flow: paste a sk-… token → expect the confirm dialog → confirm → expect the row to land and mem_add_redaction_stats to show bypassed incremented under the right by_tool key.

Out of scope

Verification

  • uv run pytest -m "not ollama" stays green.
  • mm web localhost smoke: secret payload → 403 dialog → confirm → 200 + counter increment.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestsecuritySecurity-related issue or fix

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions