Security model
The threat model, the XSS boundary, and what Ghost does not prove.
Ghost is smaller than an auth platform. It is not less security-sensitive. This page is core product documentation, not a footer.
The fundamental statement
Ghost authenticates a key, not a person.
A successful verification proves exactly one sentence: the requester possesses an active credential for this ghost ID. It does not prove a human, a device, a unique individual, or the same person as last time — only the same Ghost credential state. Everything stronger requires systems Ghost deliberately does not include.
What Ghost does not prove
- Not a person. A browser-held key is not a human. Do not call the key holder a verified user.
- Not uniqueness. Anyone can mint unlimited fresh identities by clearing storage or opening profiles. Ghost has no sybil resistance.
- Not anonymity. Ghost is pseudonymous. Your application can still link a ghost to IPs, behaviour, payments, or anything typed into a form.
- Not guaranteed recoverability. Recovery exists only when the user opted in and still has the recovery secret.
If the application needs legal identity, verified email, organizations, SSO, payment identity, or account-recovery guarantees — it needs an account system, not Ghost.
Threat model
Assets: the private key, the identity, signed proofs, and whatever your application lets a ghost own.
Threats Ghost defends against:
- Key exfiltration via script. The key is a non-extractable
CryptoKey. There is no code path — in Ghost or in the platform — that returns the raw private bytes to JavaScript. - Replay. Nonces verify exactly once; challenges expire in minutes.
- Cross-site proof reuse.
audienceis inside the signed bytes; a proof for one audience is typed garbage at another. - Consent scope creep.
actionis inside the signed bytes; a login proof cannot authorize a deletion. - Identity spoofing. The verifier recomputes the ghost ID from the public key for legacy identities, or checks the active credential registry for stable recoverable identities. The ID field is never trusted by itself.
- Cross-origin correlation. Storage is origin-scoped, so identities are pairwise by default.
Threats Ghost cannot solve alone: malicious JavaScript running in your origin (see XSS below), a compromised browser or OS, physical access to an unlocked device, and authorization bugs in your own server. No client library fixes those.
The XSS boundary — read this one
Non-extractable keys change what XSS steals, not whether XSS is bad.
If an attacker runs script in your origin, they cannot exfiltrate the key: the identity cannot be cloned onto their machine, and cleaning up the injection genuinely ends the compromise. That is a real, meaningful improvement over a stolen password or long-lived token.
But while their script runs, they can use the key: call sign() on
challenges of their choosing and act as the ghost. Non-extractable does not
make XSS harmless — it converts permanent identity theft into abuse bounded
by the duration of the injection. Short challenge TTLs and per-action
proofs shrink that window; your CSP and output escaping remain the actual
defense.
Storage loss and recovery
Cleared site data, evicted storage, a reinstalled browser: the key is gone,
and Ghost will mint a fresh one on the next createGhost() unless recovery
was enabled. If recovery was enabled, the user-held recovery secret plus
your app-stored recovery record can attach a fresh non-extractable
credential to the same Ghost ID.
Recovery is not an account reset. Ghost does not email a link, phone home to a central service, import the old private key, or silently cloud-sync the secret. If the user loses the recovery secret, Ghost cannot help.
Practices the packages enforce or expect
- Challenges are random (32-byte nonces), short-lived, and single-use — the
provided
createChallengeand stores make the safe path the easy path. - Signatures cover deterministic canonical bytes, never ad-hoc JSON.
- Stable Ghost IDs require an application credential registry; do not treat
a random claimed
ghostIdplus a valid signature as sufficient. - Verification failures are generic to clients, specific in your logs.
- Signatures may be engine-randomized (WebKit hedges Ed25519): treat them as opaque valid/invalid values, never as identifiers or cache keys.
- No telemetry, no central service, no third-party requests — the dependency surface of both entry points is zero packages.