Skip to content

fix: bunker replay protection + sync quota fallback (deferred audit items) - #2

Closed
vveerrgg wants to merge 1 commit into
fix/nsec-import-and-backupfrom
harden/sync-bunker-followups
Closed

fix: bunker replay protection + sync quota fallback (deferred audit items)#2
vveerrgg wants to merge 1 commit into
fix/nsec-import-and-backupfrom
harden/sync-bunker-followups

Conversation

@vveerrgg

Copy link
Copy Markdown
Collaborator

Follow-up to #1 — the two contained, well-tested deferred items from the audit pass. Stacked on fix/nsec-import-and-backup; will retarget to main once #1 merges.

Bunker replay protection (bunker-server.js)

The NIP-46 server signs requests from authenticated clients. Without replay protection, a captured authenticated sign_event event could be re-published to the relay and re-signed.

  • _isFreshEvent() rejects request ids already processed (bounded seen-set, 500) and events whose created_at is outside a ±5min window.
  • State cleared on stop().

Sync quota fallback (sync-manager.js)

storage.sync.set is all-or-nothing: an over-quota batch was rejected whole and silently synced nothing — not even P1 profiles.

  • setSyncRespectingQuota() drops the lowest-priority entries and retries, guaranteeing P1 (profiles) syncs. Logs what it dropped (no silent truncation). Throws only if P1 + meta alone exceeds quota.

Tests

  • test/bunker-replay.test.js (7), test/sync-quota.test.js (4). Full suite: 181 passed, 2 skipped.

Deliberately NOT included — needs a design decision

Sync deletion tombstones. While scoping it I found vault docs live on Nostr relays (NIP-78) and vault.delete publishes a NIP-09 deletion to relays — storage.sync is only a mirror of a local cache. So deletion propagation is entangled with the relay layer, not a pure sync-schema addition. It needs a dedicated design pass to reconcile the relay deletion path with the sync mirror, rather than a half-correct tombstone bolted onto storage.

🤖 Generated with Claude Code

Two deferred items from the audit pass:

- Bunker replay protection: the NIP-46 server now rejects request events whose
  id was already processed and events whose created_at is outside a ±5min
  window. A captured authenticated sign_event could otherwise be re-published
  to the relay and re-signed. Seen-id set is bounded (500) and cleared on stop().

- Sync quota fallback: storage.sync.set is all-or-nothing, so an over-quota
  batch was rejected whole and silently synced nothing — not even P1 profiles.
  setSyncRespectingQuota now drops the lowest-priority entries and retries,
  guaranteeing high-priority data syncs, and logs what was dropped (no silent
  truncation). Throws only if even P1 + meta exceeds quota.

Tests: bunker-replay (7) and sync-quota (4) suites (+11).
@vveerrgg
vveerrgg force-pushed the fix/nsec-import-and-backup branch from 704c359 to 5f2c654 Compare July 20, 2026 22:35
@vveerrgg
vveerrgg force-pushed the harden/sync-bunker-followups branch 2 times, most recently from 0729dde to 936e7c4 Compare July 21, 2026 15:02
@vveerrgg
vveerrgg force-pushed the fix/nsec-import-and-backup branch from 5f2c654 to 49c1aee Compare July 21, 2026 15:02
@vveerrgg

Copy link
Copy Markdown
Collaborator Author

Superseded by the v1.7.0 security hardening merged to main (fix/security-audit-2026-07) — bunker per-connection/default-deny signing, encrypt-at-rest + never-sync-plaintext, NIP-06 fix, key-theft trust boundary. v1.7.0 shipped (App Store accepted 2026-07-20). This branch is an earlier parallel implementation of the same fixes and conflicts with the shipped version, so closing rather than re-applying older/duplicate hardening. Branch retained if any stray change needs cherry-picking.

@vveerrgg vveerrgg closed this Jul 21, 2026
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.

1 participant