# Orb testnet security release gate

> Local review updated October 6, 2026. See [the readiness evidence and remaining gates](orb-production-readiness-2026-10-01.md) and [the release index](README.md). This review does not certify the currently deployed build.

Orb is a Monad **testnet** prototype. Passing the checks below does not make wallet code, smart contracts, or external venues immune to loss. Do not enable mainnet funds until an independent review and a real two-account rehearsal have passed.

## Implemented controls

- Privy login and the linked Orb Wallet have distinct roles; trade and game actions select the Orb Wallet and require the user's reviewed action in Orb; supported embedded-wallet calls suppress only the duplicate Privy transaction popup. Server-side AI and alert workers do not receive a signing key.
- Perpl browser trading keys are trade-only and issued with a 12-hour expiry. Orb rejects missing, overly long, or expired key claims locally. Perpl order submission stays in the browser.
- Chat trade ideas omit private margin and transcript by default. Onchain demo cards are pointers, not authority: before message acceptance or display of verified terms, the server checks the configured market contract, exact runtime bytecode, card existence, and room scope on Monad testnet.
- The demo market executes with a fresh quote and each participant's own wallet. Token allowance is the exact input rather than unlimited approval. A confirmed card can be retried for chat delivery without publishing a duplicate transaction.
- CSP nonces protect the main, independent, token, council and missions Next pages. Production-bundle HTTP tests verify unique per-response nonces and reject unsafe-eval. Secrets remain server-only environment values.
- Bot webhook requests pin the validated DNS address while preserving the original TLS hostname. Private/reserved IPv4/IPv6, cloud metadata, mixed public/private answers, redirects and excessive response bodies are rejected. A file database alone never permits localhost in production; local fixtures require an explicit flag and loopback public origin.
- Next was upgraded to 16.3.8. This includes the next/og SVG-image security fix tracked as GHSA-vcvr-r3jv-pc5j (patched in 16.3.6). The shadcn generation CLI is now a development dependency and is removed by the release's production prune.
- Kuru Spot is pinned to Monad testnet (chain 10143), its documented MON/Kuru-USDC market and token contracts. The assistant can only prepare a review ticket; the user must explicitly review the action in Orb. Supported calls suppress the second Privy transaction modal; auth/MFA/recovery are not bypassed. Approval is for the exact input, not an unlimited allowance. A prior smaller allowance must be revoked before a new approval, and the Spot screen exposes a reviewed, user-signed revoke-to-zero action for any remaining allowance.
- The Spot order encodes minimum output, no margin and fill-or-kill. Before signing, the client checks the calldata, target, chain, input, minimum output and a capped gas price/fee. A fee increase requires another review. A returned transaction hash is only a submission; the status endpoint verifies the onchain transaction belongs to the bound Orb Wallet and matches the reviewed action before reporting confirmation. The assistant only labels a trade confirmed after that check.
- Oracle releases prune development-only dependencies after build and migrations; shell deploy scripts use LF line endings so the Linux release procedure remains reproducible.

- Council (v2): refundable escrow, exact allowances, finalized votes, square-root voice per wallet, quorum, anti-sniping (at most three ten-minute extensions, only on a flip), author withdrawal before the first vote, a team spam flag that moves no funds, and team-only public decisions. It cannot execute treasury transfers or code. This does not make the electorate Sybil-proof.
- ORB: fixed supply, separately identified vaults, enforced team vesting, bounded faucet and owner-bound Holder Pro. Single-operator testnet administration remains a concentration of authority.

October 5 addition to this October 1 report — controls that shipped after the list above was written:

- Membership is checked before an installed community app's command runs (`lib/commands.ts`).
- Anonymous rate limits are keyed per client address; in production, a request whose address cannot be determined is refused with 503 naming `ORBITA_TRUST_LOCAL_PROXY=1` rather than sharing one global bucket (`lib/rate-limit.ts`, `lib/client-address.ts`).
- Apps: a bearer token is stored only as a digest and shown once; every call needs the scope the developer asked for *and* the role a community admin accepted; an app never gets a wallet, a signing key, a role grant or a money action. Webhook delivery is signed with a key derived from the token and refuses private, reserved and metadata addresses.
- Orb Bot Registry (`contracts/OrbBotRegistry.sol`): an app id is bound to the wallet that registered it; a community's install keeps the scopes it accepted; a one-time price is split 95/5 in code with no admin, pause, upgrade or take-back. The server never signs for it: every transaction is the person's own, simulated and fee-capped first. The `chain_*` columns are a remembered reading and never an authority: an install of a priced app re-reads the chain fresh, a retired app cannot be installed, and a chain that cannot be read never waives a price (it does not block a free app). The registry code is checked against the published build with its immutable values masked, then the three immutables are read back, before anything is signed.
- English-canonical messages: the server throws English only. A reader of Portuguese gets the original wording from a table (`lib/server-messages-pt.ts`) applied by a `fetch` wrapper to the `error` of same-origin JSON error answers; it rewrites text fields only, never HTML, and does nothing for any other language or answer.
- Member-scoped read throttles on GIF search (40/min, `app/api/gifs/route.ts`) and feed profile (60/min, `app/api/orb/feed/profile/route.ts`).
- Assistant inference is metered to five free conversations per wallet per day (each up to 12 messages, bound to a server-side thread) and then requires an active Holder Pro licence, else 402 (`lib/ai-access.ts`).
- Encrypted-block writes are refused when a folder volume has less than `ORB_CHAT_MIN_FREE_BYTES` free (2 GiB default, `lib/chat-block-store.ts`).

October 6 local security verification:

- The lockfile pins `source-map-js` to 1.2.2, fixing the high-severity indexed source-map denial-of-service advisory GHSA-68fv-2mgg-jv7q without downgrading Privy.
- The npm audit still reports findings inside Ganache's development-only dependency tree. Filtering those nodes leaves 23 moderate advisories and no high or critical advisories in the remaining dependency paths; these are concentrated in the Privy/wagmi/MetaMask/WalletConnect chain. No forced SDK downgrade was applied.
- `npm run check:production` passed 50/50 isolated suites. A fresh production build passed the focused HTTP, CSP, session, wallet-surface, Arena API, read-only testnet RPC, and bounded-load checks (7/7). TypeScript and `npm run build` passed. No wallet signed a trade or transfer; no deployment was changed.

October 8 hardening (second review, verified on a clean clone and a disposable local instance):

- Uploads are bounded per profile and per caller. A profile without a Privy-verified wallet has small allowances (10 MB of feed media and 20 MB of chat attachments by default) and shares a 50 MB daily budget per caller address; verified wallets keep the 100 MB allowances. A file refused after the budget was charged hands its share back, so one profile reaching its own cap cannot exhaust the budget of everybody behind the same address. The limits are environment values (`ORB_GUEST_MEDIA_BYTES`, `ORB_GUEST_ATTACHMENT_BYTES`, `ORB_GUEST_UPLOAD_BYTES_PER_ADDRESS_DAY`); see `lib/upload-policy.ts` and migration 0052. Before this change a profile created with no login could store 100 MB of feed media, and creating another profile cost nothing.
- A disk floor protects the host: uploads answer 507 when the database volume has less than `ORB_MIN_FREE_BYTES` free (1 GiB default) and text messages keep working (`lib/storage-guard.ts`).
- Uploaded images are recognised by their first bytes before any decoder sees them, videos must open with an MP4/MOV atom or a WebM header, and libvips' SVG loader is blocked (`lib/uploads.ts`).
- sharp 0.35.5 replaces 0.35.4, which carried GHSA-wq5f-xc86-pv6w (a librsvg memory-safety advisory) in the code that decodes untrusted uploads.
- `npm run uploads:test`, also part of `npm run check:production`, exercises every rule above against a disposable database.

Not changed, and tracked as known limits: sealed-channel ciphertext is not yet bound to its room and sender as authenticated data, so a malicious server could move a sender's ciphertext between rooms that share the same recipients; there is still no forward secrecy; and the public RPC rate-limits bursts, which the independent client does not yet retry.

## Required before claiming production readiness

October 1 local additions: NFT avatars require a verified Privy session matching
the Orb profile before the ERC-721 ownership check. Assistant attempts are written
as unknown before submission, so navigation cannot restore them as fresh drafts.
Account changes destroy private conversation memory. Passport drops obsolete
community replies. The `/proofs` page publishes only sanitized testnet manifest
fields and explains the scope of each verification. See the [updated report](orb-production-readiness-2026-10-01.md).

1. **Rotate any credential that has ever left its server** - pasted into a chat tool, ticket, log or screenshot (the Privy app secret, inference keys, operator wallet keys) - then invalidate the old values at their providers. Inspect deployment environment variables and logs after rotation. The rotation record is kept outside this repository.
2. **Revoke legacy Perpl keys** from the [Perpl API Keys page](https://testnet.perpl.xyz/apikeys). Keys enrolled before the expiry fix could have had no venue expiry. Browser deletion alone does not revoke them; Perpl's published integrations document no revocation API.
3. **Review dependency advisories.** The October 6 lockfile audit still reports 23 moderate advisories outside Ganache's development-only tree, mainly transitively through wallet integrations; no high or critical advisory remains in those paths after the source-map and sharp fixes. Rerun for each release. Clearing the wallet-chain findings currently requires upstream SDK changes; a forced Privy downgrade was not applied. Evaluate upstream fixes and regression test any SDK upgrade. Development/local-EVM packages have additional advisories and must not be installed on the pruned server. An audit count is not a security guarantee.
4. **Independent contract and wallet-flow audit.** Review deployment bytecode against source, ownership/treasury permissions, wallet binding and recovery, E2EE recovery, approval paths, replay protection, and all contract integrations. Resolve findings before handling real value.
5. **Rehearse on the exact deployed build** with two distinct Privy accounts and Orb Wallets. Confirm account linking, room authorization, AI draft and Perpl status reporting, onchain card creation, receipt ID, room-verified share, second-wallet quote/approval/execution, independent observer, expiry, and disconnect/retry behavior. Capture transaction hashes and redacted evidence. Simulated local-wallet tests do not substitute for this.
6. **Operational controls:** establish backups and recovery drills for Orb's separate database, secret rotation procedures, request/RPC rate limits, alerting, rollback, and incident response. Keep production Órbita data and credentials separate.

The AI-to-Perpl perpetual path and the public Monad demo swap market are separate journeys. A Perpl order acknowledgement is not a fill or a Monad transaction. The demo assets have no redemption value. Do not present either path as a real-money AI autopilot.

Kuru Spot safety validation: `node scripts/test-kuru-spot-security.mjs`, `node scripts/test-orb-app-wallet.mjs`, `node scripts/test-assistant-trading.mjs`, `npx tsc --noEmit`, and `npm run build`. The transaction tests use simulated transaction objects; no funded Orb Wallet signed an end-to-end Kuru order in this review. Kuru contract behavior and token liquidity are external dependencies, so passing Orb's checks cannot guarantee execution or safety of a third-party contract.
