Epic: Security pathway hardening (S0–S7) #8

Open
opened 2026-06-14 07:08:42 +00:00 by padreug · 1 comment
Owner

Migrated from aiolabs/satmachineadmin#13 (2026-06-13). Issue numbers were reassigned in this repo; cross-refs updated.

2026-05-26 — upstream pivot + sequencing decision

aiolabs/lnbits#9 was reframed 2026-05-25 and aiolabs/lnbits#18 was filed the same day. Two load-bearing changes:

  1. NIP-26 is dead. From lnbits#9: "NIP-26… has been officially deprecated… NIP-46 supersedes the use case. We do not use delegation tags anywhere." Our S0 + S2 originally rested on NIP-26 delegation; they now rest on NIP-46 connection tokens issued by a sidecar nsecbunkerd. Per-event handler logic gets simpler, not more complex.
  2. The bunker is promoted from "S7 future / optional" to "standard lnbits infrastructure." lnbits#18 is the concrete phase-2 plan; nsecbunkerd is the chosen sidecar. S7 in this repo therefore shrinks from 4–6 weeks to ~1 week of consumer-side wiring.

Sub-issues updated:

  • #10 (S2) — retitled + rewritten for NIP-46 token enforcement; hard-blocked on lnbits#18.
  • #12 (S7) — retitled + rewritten as "consume lnbits#18"; sequencing unchanged (Sprint 3).
  • #9 (S0) — payload swaps from signed_delegation_token to bunker:// connection URL; hard-blocked on lnbits#18.

Sequencing decision (2026-05-26): wait for the bunker. No transitional S0 shim. lnbits#18 is expected soon; the standing rule about backwards-compat / pre-launch direction changes argues against throwing away seed-URL wiring we'd rewrite in two weeks. Sprint 1 starts with the unblocked work.


Context

v2-bitspire ships a working ATM↔LNbits pathway, but the security model leans on one Nostr primitive (NIP-44 v2 transport encryption) and one stopgap: the operator's own nsec lives on the ATM disk. Money flows on the Lightning rail (cryptographically sound), but attribution flows on Payment.extra — mutable, unauthenticated metadata.

Two real incidents on the branch surfaced this:

  1. A stale sintra machine with placeholder npub npub1111… accepted a real cash-in because routing is purely wallet_id-keyed, not signed.
  2. The provisioning script writes the operator's nsec to /var/lib/bitspire/.env. Physical ATM compromise = full operator-account compromise.

Full state-of-the-union + design is in docs/security-pathway-v1.md (in-repo, generated from ~/.claude/plans/snug-gliding-shamir.md). Note: the design doc still references NIP-26; the §5/§6 references in the sub-issues are accurate for everything except S0/S2/S7 — those three need a follow-up doc edit reflecting the 2026-05-26 pivot.

Trust model we want (one sentence)

A settlement is genuine if (a) the operator authorized the ATM via a scoped, time-bound, revocable bunker token, (b) the ATM published a signed attestation referencing the Lightning preimage, and (c) Payment.extra is treated as a hint, never as truth.

Phases (revised)

# Phase Closes Effort Repo(s) Status
S0 Seed-URL pairing + ATM keypair separation (bunker:// URL) G3, G9 1 wk satmachineadmin + lamassu-next blocked on lnbits#18
S1 ["expiration", now+5m] on all kind-21000 (NIP-40) G4 1–2 d satmachineadmin + lamassu-next + lnbits unblocked
S2 NIP-46 connection-token enforcement in nostr-transport handler G3, G7 1–2 wk lnbits (nostr-transport + bunker client) blocked on lnbits#18
S3 NIP-57-style signed settlement receipts (preimage attestation) G2, G7 1–2 wk lnbits + satmachineadmin unblocked
S4 NIP-78 per-machine config + fleet roster cross-check G1, G9 1 wk satmachineadmin unblocked
S5 sender_pubkey persisted + HMAC over Payment.extra key fields G2, G5, G6 3–5 d lnbits (nostr-transport) unblocked
S6 Roster-gated auto-account + rate limit G8, G9 1 wk lnbits unblocked
S7 Consume LNbits sidecar bunker (operator nsec off LNbits' DB) G6 (rest) 1 wk (was 4–6) lnbits + satmachineadmin blocked on lnbits#18
S8 Wire cash-in path (LNURL-withdraw outbound + naming hygiene) G10 2 wk satmachineadmin unblocked

Gap IDs (G1–G10) map to the audit findings table in docs/security-pathway-v1.md §4.2.

Sequencing

Sprint 1 (everything unblocked, ship while waiting for lnbits#18): S1 + S5 + S3 + S4.
Sprint 2 (once lnbits#18 lands): S0 + S2 + S7 together — that's the full operator-IdP cutover and the cleanest security story.
Sprint 3: S6 + S8.

Sub-issues

LNbits-side trackers:

  • aiolabs/lnbits#14 covers the LNbits primitives needed for S5/S6 (+ S1's expiration filter as defence in depth).
  • aiolabs/lnbits#9 — operator-IdP framing (the why behind S2/S7).
  • aiolabs/lnbits#18 — sidecar bunker integration (the how; S0/S2/S7 all depend on it).

Audit-friendliness checklist

Tracked in §8 of the design doc. Today: 7/14 ✓. After S0–S7: 14/14 ✓.

Cross-references

  • Supersedes aiolabs/satmachineadmin#12 (ATM↔operator pairing sketch) — that issue's content folds into S0/S2/S7.
  • Builds on #5 (v2 multi-tenant overhaul epic — landed).
  • Folds in audit findings from #7.
  • LNbits-side primitives tracked in aiolabs/lnbits#14 and aiolabs/lnbits#9 / aiolabs/lnbits#18.
  • Upstream metadata gap: aiolabs/lamassu-next#44 (Payment.extra split).

Why this isn't just "add TLS pinning"

The Lamassu era answered "is this ATM real?" with a TLS cert fingerprint. We have Nostr — and so far we've used one knob of it. NIP-46 scoped tokens + NIP-57 receipts + NIP-78 fleet rosters give us delegated identity, signed settlement receipts, and operator-published fleet rosters — three independent cryptographic anchors instead of one shared-secret-style pin. Open-source‑auditable end to end.

> _Migrated from aiolabs/satmachineadmin#13 (2026-06-13). Issue numbers were reassigned in this repo; cross-refs updated._ ## 2026-05-26 — upstream pivot + sequencing decision `aiolabs/lnbits#9` was reframed 2026-05-25 and `aiolabs/lnbits#18` was filed the same day. Two load-bearing changes: 1. **NIP-26 is dead.** From lnbits#9: *"NIP-26… has been officially deprecated… NIP-46 supersedes the use case. We do not use delegation tags anywhere."* Our S0 + S2 originally rested on NIP-26 delegation; they now rest on **NIP-46 connection tokens** issued by a sidecar `nsecbunkerd`. Per-event handler logic gets simpler, not more complex. 2. **The bunker is promoted from "S7 future / optional" to "standard lnbits infrastructure."** lnbits#18 is the concrete phase-2 plan; `nsecbunkerd` is the chosen sidecar. **S7 in this repo therefore shrinks from 4–6 weeks to ~1 week of consumer-side wiring.** Sub-issues updated: - #10 (S2) — retitled + rewritten for NIP-46 token enforcement; hard-blocked on lnbits#18. - #12 (S7) — retitled + rewritten as "consume lnbits#18"; sequencing unchanged (Sprint 3). - #9 (S0) — payload swaps from `signed_delegation_token` to `bunker://` connection URL; hard-blocked on lnbits#18. **Sequencing decision (2026-05-26): wait for the bunker.** No transitional S0 shim. lnbits#18 is expected soon; the standing rule about backwards-compat / pre-launch direction changes argues against throwing away seed-URL wiring we'd rewrite in two weeks. Sprint 1 starts with the unblocked work. --- ## Context `v2-bitspire` ships a working ATM↔LNbits pathway, but the security model leans on one Nostr primitive (NIP-44 v2 transport encryption) and one **stopgap**: the operator's own `nsec` lives on the ATM disk. Money flows on the Lightning rail (cryptographically sound), but attribution flows on **`Payment.extra`** — mutable, unauthenticated metadata. Two real incidents on the branch surfaced this: 1. A stale `sintra` machine with placeholder npub `npub1111…` accepted a real cash-in because routing is purely `wallet_id`-keyed, not signed. 2. The provisioning script writes the operator's nsec to `/var/lib/bitspire/.env`. Physical ATM compromise = full operator-account compromise. Full state-of-the-union + design is in `docs/security-pathway-v1.md` (in-repo, generated from `~/.claude/plans/snug-gliding-shamir.md`). **Note:** the design doc still references NIP-26; the §5/§6 references in the sub-issues are accurate for everything except S0/S2/S7 — those three need a follow-up doc edit reflecting the 2026-05-26 pivot. ## Trust model we want (one sentence) > A settlement is genuine if (a) the operator authorized the ATM via a scoped, time-bound, revocable **bunker token**, (b) the ATM published a signed attestation referencing the Lightning preimage, and (c) `Payment.extra` is treated as a hint, never as truth. ## Phases (revised) | # | Phase | Closes | Effort | Repo(s) | Status | |---|---|---|---|---|---| | **S0** | Seed-URL pairing + ATM keypair separation (`bunker://` URL) | G3, G9 | 1 wk | satmachineadmin + lamassu-next | blocked on lnbits#18 | | **S1** | `["expiration", now+5m]` on all kind-21000 (NIP-40) | G4 | 1–2 d | satmachineadmin + lamassu-next + lnbits | unblocked | | **S2** | NIP-46 connection-token enforcement in nostr-transport handler | G3, G7 | 1–2 wk | lnbits (nostr-transport + bunker client) | blocked on lnbits#18 | | **S3** | NIP-57-style signed settlement receipts (preimage attestation) | G2, G7 | 1–2 wk | lnbits + satmachineadmin | unblocked | | **S4** | NIP-78 per-machine config + fleet roster cross-check | G1, G9 | 1 wk | satmachineadmin | unblocked | | **S5** | `sender_pubkey` persisted + HMAC over Payment.extra key fields | G2, G5, G6 | 3–5 d | lnbits (nostr-transport) | unblocked | | **S6** | Roster-gated auto-account + rate limit | G8, G9 | 1 wk | lnbits | unblocked | | **S7** | Consume LNbits sidecar bunker (operator nsec off LNbits' DB) | G6 (rest) | 1 wk (was 4–6) | lnbits + satmachineadmin | blocked on lnbits#18 | | **S8** | Wire cash-in path (LNURL-withdraw outbound + naming hygiene) | G10 | 2 wk | satmachineadmin | unblocked | Gap IDs (G1–G10) map to the audit findings table in `docs/security-pathway-v1.md` §4.2. ## Sequencing **Sprint 1 (everything unblocked, ship while waiting for lnbits#18):** S1 + S5 + S3 + S4. **Sprint 2 (once lnbits#18 lands):** S0 + S2 + S7 together — that's the full operator-IdP cutover and the cleanest security story. **Sprint 3:** S6 + S8. ## Sub-issues - [ ] #9 — S0 — Seed-URL pairing + ATM keypair separation [BLOCKED on lnbits#18] - [ ] aiolabs/satmachineadmin#15 — S1 — NIP-40 expiration on kind-21000 - [ ] #10 — S2 — NIP-46 connection-token enforcement (LNbits) [BLOCKED on lnbits#18] - [ ] #11 — S3 — NIP-57-style settlement receipts - [ ] aiolabs/satmachineadmin#18 — S4 — NIP-78 per-machine config + fleet roster - [ ] aiolabs/satmachineadmin#19 — S5 — sender_pubkey + HMAC on Payment.extra (LNbits) - [ ] aiolabs/satmachineadmin#20 — S6 — Roster-gated auto-account + rate limit (LNbits) - [ ] #12 — S7 — Consume LNbits sidecar bunker [BLOCKED on lnbits#18] - [ ] aiolabs/satmachineadmin#22 — S8 — Wire cash-in path (LNURL-withdraw outbound + naming hygiene) **LNbits-side trackers:** - `aiolabs/lnbits#14` covers the LNbits primitives needed for S5/S6 (+ S1's expiration filter as defence in depth). - `aiolabs/lnbits#9` — operator-IdP framing (the why behind S2/S7). - `aiolabs/lnbits#18` — sidecar bunker integration (the how; S0/S2/S7 all depend on it). ## Audit-friendliness checklist Tracked in §8 of the design doc. Today: 7/14 ✓. After S0–S7: 14/14 ✓. ## Cross-references - Supersedes aiolabs/satmachineadmin#12 (ATM↔operator pairing sketch) — that issue's content folds into S0/S2/S7. - Builds on #5 (v2 multi-tenant overhaul epic — landed). - Folds in audit findings from #7. - LNbits-side primitives tracked in `aiolabs/lnbits#14` and `aiolabs/lnbits#9` / `aiolabs/lnbits#18`. - Upstream metadata gap: `aiolabs/lamassu-next#44` (Payment.extra split). ## Why this isn't just "add TLS pinning" The Lamassu era answered "is this ATM real?" with a TLS cert fingerprint. We have Nostr — and so far we've used one knob of it. **NIP-46 scoped tokens + NIP-57 receipts + NIP-78 fleet rosters** give us delegated identity, signed settlement receipts, and operator-published fleet rosters — three independent cryptographic anchors instead of one shared-secret-style pin. Open-source‑auditable end to end.
Author
Owner

Status 2026-06-16 — Sprint 2 unblocked

The lnbits#18 token-issuance primitive (NsecBunkerAdminClient.create_new_token + policies) shipped on lnbits dev, verified against aiolabs/nsecbunkerd@fb1c239 (see lnbits#18 status 2026-06-16). That was the hard blocker on S0 (#9) and S7 (#12).

Starting S0 seed-URL pairing + the ATM-side bunker consumer (bitspire#52) now. Naming: this repo is aiolabs/spirekeeper (split from satmachineadmin 2026-06-13).

## Status 2026-06-16 — Sprint 2 unblocked The `lnbits#18` token-issuance primitive (`NsecBunkerAdminClient.create_new_token` + policies) shipped on `lnbits` `dev`, verified against `aiolabs/nsecbunkerd@fb1c239` (see lnbits#18 status 2026-06-16). That was the hard blocker on S0 (#9) and S7 (#12). Starting S0 seed-URL pairing + the ATM-side bunker consumer (bitspire#52) now. Naming: this repo is `aiolabs/spirekeeper` (split from satmachineadmin 2026-06-13).
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/spirekeeper#8
No description provided.