Add an optional Receipt Required guard to destructive row deletion tools - #11
Open
FutureEnterprises wants to merge 3 commits into
Open
Add an optional Receipt Required guard to destructive row deletion tools#11FutureEnterprises wants to merge 3 commits into
FutureEnterprises wants to merge 3 commits into
Conversation
Gate baserow_delete_row and baserow_batch_delete_rows behind a verifiable authorization receipt (EP-RECEIPT-v1): missing -> 428 Receipt Required, valid -> deletes, replay -> refused, tampered -> refused. Optional schema arg, other tools untouched, no backend, one Apache-2.0 dependency. RR-1 conformance proven against the real handler.
…tized rejections
Three defects fixed across both guarded delete tools:
A. Consume the receipt only AFTER the irreversible delete succeeds. guardReceipt
now returns a commit() callback instead of consuming at verify time; row.ts
calls it only after deleteRow/batchDeleteRows resolves. A failed delete leaves
the approval retryable. Replay protection still enforced at verify time.
B. Bind the receipt to the specific target, not just the action type:
baserow_delete_row -> baserow.row.delete:<table_id>:<row_id>
baserow_batch_delete_rows -> baserow.rows.batch_delete:<table_id>:<sorted row_ids>
A receipt minted for one row/table/set cannot delete another.
C. Sanitize rejected-receipt output to { rejected: { reason } } — no longer echoes
the full verified object (signer/subject/detail).
Verified: npm run build (tsc) green; RR-1 (missing/valid/replay/forged) plus
cross-row, cross-table, and cross-set binding checks all pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the hand-rolled verify/replay/consume/sanitize logic in the delete
guard with the now-published canonical makeReceiptGate. The hardening
(per-target binding, verify, replay refusal, consume-after-success, sanitized
{reason} rejections) now comes from the reviewed helper instead of bespoke code,
shrinking the guard. Behavior is identical: receipts stay bound per row /
per sorted row-set, are consumed only after a successful delete (failures stay
retryable via gate.run's release), replays and cross-target receipts are
refused, and the optional authorization_receipt tool arg is unchanged.
- Bump @emilia-protocol/require-receipt to ^0.4.0
- receipt-guard.ts thin-wraps makeReceiptGate (loaded via the same dynamic
import() indirection, since this package is CJS and require-receipt is ESM)
- delete_row / batch_delete_rows call through gate.run
- ambient shim declares makeReceiptGate + its gate/run return shapes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
This PR adds an optional Receipt Required guard around destructive Baserow row deletion tools. Missing receipt returns 428, a valid receipt allows deletion, replay is refused, and tampering is refused. It is intentionally scoped to
delete_rowandbatch_delete_rows, with tests proving no delete happens without a valid receipt.What it does
Before
baserow_delete_roworbaserow_batch_delete_rowsexecutes, the call must carry anauthorization_receipt— a verifiable EP-RECEIPT-v1 proving a named human accountably approved this exact deletion. No receipt → the tool returns a structured Receipt-Required challenge (HTTP 428 shape) telling the agent exactly what to bring, instead of silently deleting. A well-behaved agent obtains one and retries.What this is — and isn't
Not auth ("who are you") and not permissions ("are you allowed here"). It is portable accountability evidence the operator keeps for their own liability — necessary, not sufficient. It sits alongside your Baserow tokens/scopes for the destructive path; it does not replace them.
Footprint
@emilia-protocol/require-receipt(Apache-2.0).Conformance (RR-1), proven against the real handler
Implementation note
@emilia-protocol/require-receiptis ESM-only, while this project emits CommonJS, so the guard preserves dynamic import through TypeScript output. Happy to adjust if you prefer an.mjsboundary or tsconfig change.Production note
The guard currently passes
allowInlineKey: true, which proves a receipt's integrity but not issuer trust. For production, pintrustedKeysto the issuers you trust and dropallowInlineKey. The consumed-receipt set is in-memory (per process); back it with shared storage if you run multiple instances.Format and semantics track
draft-schrock-ep-authorization-receipts, an individual IETF Internet-Draft (not an RFC, no IETF consensus implied). Happy to adjust scope, naming, or the import boundary to fit the project's conventions.