Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions bridgelet-product-audit/glossary/custodial-model-in-practice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Custodial Model in Practice

## The starting definition

`docs/GLOSSARY.md` defines the **Custodial Model** as a system where a service
temporarily controls assets on behalf of users.

That is abstract by design. This entry grounds it in the code paths that actually
implement it — and separates it from a nearby concept it is easy to confuse with.

## Where custody actually happens

The custodial mechanism lives in **bridgelet-sdk**, not in this repo. Per
bridgelet-sdk-audit's `two-phase-account-creation.md`, a backend-held **funding
keypair** creates and funds the ephemeral account, and the backend later signs the
sweep that moves funds to the recipient's destination.

Two words in the glossary definition do real work:

- **"temporarily"** — custody spans the window between the sender's payment and
the recipient's claim. The ephemeral account exists to be emptied.
- **"on behalf of users"** — during that window the recipient cannot move the
funds themselves. They hold a claim token, not a key. Bridgelet's backend holds
the only key that can sweep the account.

This is what makes the product work: the recipient doesn't need a wallet at claim
time precisely *because* someone else is holding the asset for them.
`docs/security-model.mdx` reflects this — sweeps are gated on a valid claim token
plus a **server-side signature with the ephemeral private key**, with no
client-supplied authorization credential.

## What this is *not*: the `generated` wallet type

Do not conflate the custodial funding key with the `generated` `WalletType`.

| | Custodial funding key | `generated` wallet |
|---|---|---|
| Created | Backend, per ephemeral account | Browser, via `Keypair.random()` |
| Held by | Bridgelet's backend | The user |
| Purpose | Fund and sweep the ephemeral account | Give a user their own wallet |
| Repo | bridgelet-sdk | This repo (`frontend/lib/wallet.ts`) |

The `generated` path is the **opposite** of custodial: `generateNewWallet()`
returns the secret key to the user's browser, and Bridgelet never sees it — a
user-held key that happens to be created client-side.

Both can appear in one claim: a recipient may generate a wallet (non-custodial) to
receive funds swept from an ephemeral account (custodial). Conflating them
misstates who is trusted with what, in a system whose value proposition is that
boundary. See [`generated-wallet-key-custody.md`](../integration-notes/generated-wallet-key-custody.md).
50 changes: 50 additions & 0 deletions bridgelet-product-audit/glossary/docs-mdx-vs-pdf-pairs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# `docs/` — `.mdx` Sources and `.pdf` Exports

Some documents in `docs/` exist as an editable `.mdx` source with a matching
`.pdf`; others exist **only** as a PDF. The distinction matters the moment
something needs updating.

## Paired — `.mdx` source with a `.pdf` export

| Source | Export |
|---|---|
| `architecture.mdx` | `architecture.pdf` |
| `security-model.mdx` | `security-model.pdf` |
| `integration-guide.mdx` | `integration-guide.pdf` |

For these, the `.mdx` is authoritative. Edit it, then regenerate the PDF.

## PDF-only — no `.mdx` source

- `getting-started.pdf`
- `mvp-specification.pdf`
- `use-cases.pdf`

These have no source file in the repository.

## Practical implication

**A PDF without a source can only be manually re-created.** There is nothing to
edit and re-render — changing `use-cases.pdf` means reproducing the whole document
in whatever tool made it, matching its styling by eye, and hoping the result
doesn't look like a different document.

Three further consequences worth naming:

- **They are opaque to review.** A PDF in a diff shows as a binary blob. A
reviewer cannot see what changed, so content changes arrive effectively
unreviewed.
- **They are not greppable.** Searching the repo for a term will find it in the
three `.mdx` sources and miss it in the PDF-only trio entirely — so these
documents are easy to forget when updating terminology.
- **Drift is silent even for paired files.** Nothing enforces that a `.pdf` was
regenerated after its `.mdx` changed. A stale export can sit alongside an
updated source indefinitely, and readers who open the PDF get the old answer
with no indication it is out of date.

Note that `security-model.pdf` is among the paired files — the security
documentation specifically is exposed to this drift risk.

**Related:**
[`../runbooks/rebuild-docs-pdf-from-mdx.md`](../runbooks/rebuild-docs-pdf-from-mdx.md)
— the regeneration procedure — and the postmortem entry on regeneration risk.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Claim URL — Security Properties

What makes a claim URL safe (or unsafe) to share, from the frontend's view.

## Stated guarantees

`docs/GLOSSARY.md` defines a **Claim Token** as the token letting a recipient
securely claim funds, and a **Claim URL** as the link carrying it.
`docs/security-model.mdx` is more specific:

- Claim tokens are **JWTs signed by the backend**, using a secret the frontend
never sees — integrity-protected.
- **Single-use** — consumed on redemption, cannot be replayed.
- **Entropy:** derived from a UUID v4 `accountId` (122 bits). Brute-force
enumeration is not feasible, and verify calls are rate-limited (1,000/hour/IP).
- **Path segment, not query string** (`/claim/[token]`) — deliberately, to avoid
leaking the token in `Referer` headers.
- **Recipients are unauthenticated.** The token "is the sole proof of
entitlement. No login, no session."

## The bearer problem

That last point is the defining property: **whoever holds the link can attempt the
claim.** There is no identity check binding the token to an intended recipient, so
a forwarded, screenshotted, or intercepted link is as good as the original.

`security-model.mdx` logs this as **T-01 (claim token theft)** and rates it
mitigated — but read the mitigation precisely: single-use means an attacker cannot
replay a *spent* token, and "first valid redemption wins." That protects against
reuse, **not against a thief redeeming first.** If an attacker gets the link
before the recipient acts, they claim the funds and the legitimate recipient finds
a spent token. The document's own recommendation — share via end-to-end encrypted
channels — is the real control.

**The payload is not encrypted.** Signed JWTs are integrity-protected, not
confidential: `accountId`, `amount` and `assetCode` are readable by anyone holding
the token — including link-preview services and analytics pipelines. JWE is future work.

## Practical consequences

- Treat a claim URL as **cash in an envelope**, not an addressed letter; don't
paste them into shared tickets, logs or chat channels.
- Expiry is not a loss: it lets the sender reclaim via a backend return sweep.

## Double claims

A second claim is caught on-chain by the already-swept guard (bridgelet-audit's
`account-status-state-machine.md`); whether that reason reaches the user is traced
in [`three-repo-error-surface-consistency.md`](./three-repo-error-surface-consistency.md).
**T-13** (sweep authorization) is also an MVP stub there.
Loading