If you believe you have found a security vulnerability in @specterpq/sdk, please do not open a public issue.
Email hello@specterpq.com with:
- A description of the vulnerability and its impact
- Steps to reproduce (PoC code, browser, OS, package version)
- The commit SHA or release tag affected
- Your contact information (optional but appreciated)
You will receive a response within 72 hours. We coordinate disclosure on a 90-day window unless an active in-the-wild exploit requires faster action.
Until 1.0.0 ships, only the latest minor version of the 0.x series receives security fixes. Once 1.x ships, the latest minor and the previous minor will both receive backports for security issues.
| Version | Status |
|---|---|
| 0.x | Active |
@specterpq/sdk is designed to be offline-by-construction. The package surface contains:
- No network calls (
fetch,XMLHttpRequest,WebSocket, etc. are not used). - No telemetry.
- No remote code loading. The WASM bytes ship inside the npm tarball and are loaded from a local URL or the package's own bytes.
- No persistent storage. Generated keys live in memory until the consumer explicitly serializes them. The SDK does not call
localStorage,IndexedDB,sessionStorage, orcookies.
What this SDK assumes you will do:
- Generate ML-KEM-768 secret keys only in trusted contexts (the user's own browser tab, an Electron renderer you control, a Node service you operate).
- Persist secrets only with consumer-controlled at-rest encryption (e.g.
aes-gcm, OS keychain, hardware-backed key store). The SDK does not ship persistence. - Treat the returned
secretKey,ethPrivateKey, andsharedSecretstrings as toxic. Although the SDK marks them non-enumerable and redacts them intoString/JSON.stringify/ Node'sutil.inspect, you are still responsible for not copying them into application logs, error reporters, analytics events, or remote crash dumps.
- ML-KEM-768 key generation, encapsulation, and decapsulation come from the upstream RustCrypto
ml-kemcrate, FIPS 203 compliant. - SHAKE256 with domain separation drives view-tag and stealth derivation. Domain separators are pinned in
vendor/specter-core/src/constants.rsand asserted by the upstream test suite. - Constant-time comparisons are used for view-tag verification and key-pair round-trip verification (
subtlecrate). - Zeroization on drop is enforced for
KyberSecretKeyandStealthPrivateKeyin upstream Rust code. Thewasm-bindgenboundary copies bytes into JS-ownedUint8Arraybuffers; once those are converted to redacted hex strings inside the SDK, the original Rust buffers are dropped and zeroized. #![forbid(unsafe_code)]is set in upstreamspecter-coreandspecter-crypto, and the bridge craterust/specter-wasmsets it as well.- Browser RNG: in WASM, randomness comes from
crypto.getRandomValuesviagetrandom = { version = "0.2", features = ["js"] }. - Panics abort: the WASM build profile sets
panic = "abort"andstrip = trueso panic strings are not leaked to JS error messages.
- Backend Rust crates are vendored, not pulled at build time. The pinned upstream commit is recorded in
vendor/VENDORED_AT.json. - CI (
vendor-verifyjob) re-fetches the pinned SHA on every PR and fails ifvendor/differs from upstream without an updated pin. - Releases are published with npm provenance (
--provenance) from.github/workflows/release.ymlusing GitHub OIDC. - The repository uses Changesets so every published version has an explicit changelog entry.
- It does not generate, hold, or transmit Ethereum or Sui transaction signatures. Pass the returned
ethPrivateKeytoviem,ethers, or@mysten/suito sign. - It does not resolve ENS or SuiNS names. That responsibility lives in the consumer (or the SPECTER backend API).
- It does not publish or fetch announcements. The consumer must wire the returned
ephemeralCiphertextandviewTaginto their announcement transport (the SPECTER registry API or any other backend). - It does not ship persistent storage for generated keys.