agentleFS
0xmortuex0xmortuex/claude-code-skills/skills/recall-guard/SKILL.md

recall-guard

Skill0 starsChanged 4 months agoMITView on GitHub

Audits whether a message edit-for-everyone, unsend, delete-for-everyone, or recall feature actually reaches every copy of the original content it already made — a push-notification payload already sitting in the OS's own notification store before the recall lands, a webhook/export/search-index/analytics mirror the retraction code never touches, and a transport boundary (relayed email, a federated/bridged platform, a client that already synced) recall can't reach at all — plus whether the recall action itself checks the right actor and actually enforces the time window the UI promises. Use when adding or reviewing an edit/unsend/recall/delete-for-everyone feature, or when asked "does unsend actually remove it everywhere", "can they still see the old message in the notification", "does deleting a message really delete it", or "audit our message recall/retraction".

The file

Files
SKILL.md

recall-guard

The feature reads as binary — recalled or not — but "recall" is a request sent to a system that already made several independent copies of the content the moment it was first delivered: an OS notification store, a push payload, a webhook fired to an integration, a search index, maybe a transcript mirrored to a compliance or analytics pipeline. Flipping the primary message row to "retracted" or blanking its text is the easy part. Everything downstream of that row, made at the instant the original send happened, has no listener for "oh, also undo that" unless someone specifically wrote one.

The case that makes this concrete rather than hypothetical is a 2026 iOS bug, CVE-2026-28950, patched in iOS 26.4.2 / iPadOS 26.4.2 and iOS 18.7.8 / iPadOS 18.7.8. Signal's own disappearing-messages and deletion logic worked exactly as designed at the app layer — but the OS had already cached a full preview of the message content in its own notification database, a store neither Signal nor the user ever told to forget it, and forensic extraction tools read that content back months after the app itself had deleted it (and, in the reported case, after the app had been uninstalled entirely). The bug was never in the app's unsend/deletion code. It was in a layer the app doesn't control, which had already made its own copy before any retraction instruction could possibly reach it. That is the shape to hunt for here: not "does the delete button work," but "what already left before the delete button was pressed, and can anything actually reach it now."

Step 1: enumerate every place the original content landed before recall was requested

Trace the send path for the message type under review, not just its primary table write. Look for: the push-notification payload (does it carry the actual message text, or a generic "new message" ping that reveals nothing if recall never lands?), any webhook or event fired to a third-party integration at send time, a full-text search index, a cache layer, an analytics/audit/compliance export, and any client that already fetched the message via a sync or poll endpoint before the recall request could land. Each of these is a place the "recalled" content can still be read from, independent of what the primary row now says.

Step 2: for each place found in step 1, check whether the retraction path actually touches it

The common failure is an UPDATE messages SET deleted = true WHERE id = ? (or equivalent) that returns success while every other reader — past and future — has to independently know to re-check that flag before trusting what it already cached, indexed, or sent. Separate two different buckets explicitly, because they need different fixes:

  • Should be updated but currently isn't — a search index, cache, or webhook-fed mirror that the recall handler simply never calls out to. This is a real, fixable gap: wire the retraction into the same code path that writes the delete/edit.
  • Can't be recalled, only avoided — a push notification already delivered to the OS notification center. No server-side call un-displays a lock-screen preview the user has already seen. The only real mitigations are architectural: keep full content out of the push payload during any window content is still recallable (a generic alert, with the body fetched only once the client opens the app), or fire a silent/data push instructing the client to remove its local copy — which only helps if the user hasn't already read the preview. Don't accept "we send a delete event to the client" as sufficient without checking it's paired with the payload itself never carrying the sensitive content in the first place.

Step 3: check who can call recall, and against what

Confirm the handler checks that the requesting actor is the original sender or an explicitly authorized moderator/admin — not just that the message ID exists and the caller is authenticated against some valid session. A membership check (same channel, same org) is not an authorship check; test whether a different member of the same conversation can recall someone else's message. Then check the advertised time window ("you can unsend within 10 minutes") is enforced server-side against the message's actual timestamp, not left to the client simply hiding the button after the window — a direct call to the same endpoint an hour later should be rejected if the UI claims the window closed, and allowed only within it if the UI claims permanence afterward.

Step 4: identify the hard boundary — content that already left your infrastructure

Email relayed through SMTP to another organization's mail server, a message mirrored to a federated or bridged platform, or a webhook payload a third party has already consumed and stored in their own system — none of these can be force-recalled from the sending side; recall there can, at best, ask nicely. A "recall" UI or help-center claim that implies universal removal is overselling a guarantee the system cannot keep. Flag this boundary explicitly in the report rather than letting silence imply full reach, and separately note whether the product's own copy (ToS, help docs, in-app text) already discloses this limit honestly or claims more than it can deliver.

Report

Lead with the worst confirmed gap by what it actually exposes. BLOCK for: the author/window check is enforced only client-side and a direct call confirms the server still honors a recall from the wrong actor or after the stated window; or the push payload embeds the full message body with no safeguard and recall cannot reach it. CAUTION for: a secondary copy (webhook, search index, export) exists with no code path updating it, but nothing yet confirms it has actually been read post-recall. SAFE only with evidence: author and window checks enforced server-side against the stored timestamp, the push payload doesn't carry recallable content (or the client reliably removes it before display), and every secondary copy found in step 1 either receives the retraction or is explicitly and accurately disclosed as out of reach.

Boundaries

  • Not stale-guard's territory — stale-guard proves an ordinary cache read path can't serve wrong data for a key that's still live. This skill is about a one-shot retraction action reaching every place one specific piece of content already went, which is a different failure shape (a point-in-time instruction, not an ongoing invalidation contract).
  • Not push-guard's territory — push-guard audits whether the device token itself is still valid and bound to the right account. This skill assumes the token and routing are fine and asks whether the payload already delivered through it can be un-shown, or should never have carried the content to begin with.
  • Not erasure-guard's territory — erasure-guard audits whether deleting an entire account reaches every store that account's data landed in. This skill is about retracting one message a sender chose to take back, which can happen while the account stays fully active and everything else about it stays intact.
  • Can't verify what actually happened on a device or inside a third-party integration's own storage from source code alone — whether a lock-screen preview was really seen, or whether a webhook consumer still has the old payload sitting in their database. Say so plainly rather than asserting it; that needs the provider's own confirmation or a live trace.
  • Doesn't design a recall/unsend feature from scratch or pick an architecture for one — reviews whether an existing edit-for-everyone/unsend/delete-for-everyone/recall implementation actually reaches what it claims to, and says plainly where "recall" can only ever be best-effort.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.