mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 01:07:46 +00:00
Every napplet/web-app grant was keyed by applet coordinate alone
(`<appAuthor>:<identifier>`), which carries no account. The stores and
ledgers are process-wide singletons shared by all accounts, so grants
leaked in two directions:
- **Across apps.** `NappletBroker.sessionAllows` held a bare `op.key`
("sign:1"), and the check ran *before* the per-app ledger lookup. One
app's "Allow for this session" therefore authorized that op for every
other applet and browser origin, silently, for the broker's lifetime.
- **Across accounts.** A grant made under one npub authorized the same
applet under every other npub on the device. For a user keeping a
pseudonymous account separate from a real one, an app authorized by
one could sign as the other with no prompt — defeating the point of
separate accounts.
NIP-46 already solved this shape correctly: `Nip46PermissionAuthorizer`
namespaces by account (`nip46:<signer>:<client>`) and keys session grants
by `(coordinate, op)`. Its comment even claims it "mirrors the napplet
broker's sessionAllows" — the mirror was backwards. This adopts the same
pattern on the napplet side:
- `sessionAllows` and `NappletPermissionLedger.session` are keyed by
account + coordinate + op.
- Napplet storage and the capability store namespace keys by account,
read at call time so a switch moves reads/writes with no rebuild.
- The signer ledger is deliberately NOT account-scoped at the store: it
is shared with NIP-46, whose sessions run for a specific account rather
than the active one, so scoping it there would break a background
bunker. The napplet path namespaces its own coordinate instead.
Also here, found while scoping:
- `identity.watch` consulted only the manifest declaration and never the
ledger, bypassing a standing DENY — it short-circuits before
`NappletBroker.handle`, where the "a standing denial always wins" rule
lives. It now applies that rule itself.
- `DataStoreNappletStorage.keys()` filtered on a space separator while
keys are written with NUL, so it silently matched nothing and always
returned an empty list.
Existing grants live under the old un-namespaced keys and are not
migrated: users are re-prompted once. Migrating would attribute grants
made under the broken model to whichever account is active, preserving
the bug.
Regression tests cover both leak directions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>