Security
Noa cannot stop an action that never reaches it. The guarantee requires all four rules below.
NOA Mandate has one way of use: all four rules. There is no partial mode.
With these rules enforced, no protected action runs without a valid approval. The signed receipt records the decision; it does not prove that money moved or a physical task happened. Checking an old receipt also requires the public key and its validity history.
Status, 3 October 2026: the door guard — the JavaScript/TypeScript software kit and the MCP proxy, a bridge for AI tools — is live on npm, the package store. The Gate is an open-source reference prototype using a test ledger — a demo account book, not real money. The vault guard is being built: its verification specification and test vectors are public in noa-mandate-core; the guard itself is not yet available. No external payment, database or production system is yet evidenced as meeting all four rules. Pilots start after the vault lock’s core parts are built and checked by someone other than its builders. The door guard alone does not deliver this guarantee.
The hosted console is in beta. Phone approval through the relay mailbox was last proven for our own workspace on 29 September 2026. Signup is open and the console is free during beta, with no uptime promise. Do not route protected production actions through beta.
Below are the console controls present in source code, with their limits. The console source is private; qualified reviewers can request access under a confidentiality agreement.
Not a certification
This page describes controls that exist in checked-in, tested code or in a documented operational procedure — it is not a SOC 2 report, a penetration-test result, or a certification of any kind, and none of those is claimed anywhere on this page. A completed SOC 2 report can only be issued by an independent CPA firm after their own examination. See /regulations for the limits of the regulatory mapping. SOC 2 is an independent audit report.
Who can do what, and whether one tenant can ever see another tenant’s data.
4 roles (internal_admin, org_admin, approver, auditor) and 45 permissions, enumerated as data rather than scattered across `if` statements, enforced at every console route before the route body runs.
lib/console-permissions.tslib/console-guard.tsPostgres Row-Level Security enforced at the database ROLE level, not only in application `WHERE` filters — production requires a dedicated NOINHERIT, non-owner login that SET LOCAL ROLE’s into a non-superuser, non-BYPASSRLS application role per transaction. Pre-deploy rejects privileged runtime credentials, and readiness fails closed (503) if the effective role can bypass RLS.
infra/postgres/005_noa_console_rls.sqlinfra/postgres/014_noa_console_app_role.sql/health/ready (live check)How an operator proves who they are, and how an enterprise IdP plugs in.
Step-up re-authentication is required before sensitive mutations (audit export, key rotation, trust-root management, device pairing) and must be fresh within 10 minutes. By default, re-entering your password is enough. An organization can raise its minimum to a passkey (WebAuthn) or to a fresh sign-in through its single sign-on provider.
lib/console-guard.tslib/console-webauthn.tslib/console-step-up-policy.tsOIDC (PKCE + nonce + state) and SAML (signature-verified, replay-cached).
app/admin/auth/oidc/[connectionId]/{start,callback}/route.tsapp/admin/auth/saml/[connectionId]/{start,callback,metadata}/route.tsSCIM v2, bearer-token authenticated and tenant-scoped, with deprovision-cascade. The bearer token is itself revocable.
lib/console-scim.tsTamper-EVIDENT, not tamper-PROOF: a rewrite is detectable, not impossible for someone who holds both the database and the signing key.
Append-only audit events, each linking to the previous event’s hash (sha256 of a canonical JSON form). A step-up-gated export includes the console’s own recomputed chain-verification result alongside the data, so a buyer’s auditor can re-derive the same check independently instead of trusting our attestation. The export is bounded honestly: a hard cap of 10,000 events per export — above it the route refuses and names the resumable SIEM stream, rather than silently truncating.
lib/console-audit.tslib/console-audit-chain.tsapp/admin/audit/export/route.tsEach chain’s head can be signed with an Ed25519 key held outside the database (optionally timestamped by an external RFC 3161 authority) and streamed to a customer’s own SIEM as NDJSON. This makes a later rewrite of an earlier event detectable to anyone holding a copy of the anchor — it does not by itself prevent a rewrite by an actor who holds both the database and the signing key. Inert until an operator configures a signing key; never fabricates an anchor and never exits silently as if it had.
scripts/audit-anchor.mjsscripts/siem-export.mjsOne verified evidence bundle at a time, streamed as its canonical bytes so `sha256(exported file)` matches a response header — no re-derivation trust required on the reader’s side.
app/admin/evidence/export/route.tsTwo checks: browser/Node parity in this site's private CI, and a public cross-language verifier matrix.
The canonical Node (node:crypto) Ed25519 verifier vs. the browser-swap module (@noble/curves) this site’s own /playground uses, checked against 5 adversarial vector classes (altered look-alike signatures, weak keys, out-of-range numbers, invalid key formats and malformed base64 text). The sixth proof card links to a separate public verifier matrix. Runs in this site’s CI on every push to main; the test code is private.
What is stored and in what form. The console and database are hosted in Railway’s EU West region; backup destinations and other processors are listed at /subprocessors.
Scoped claim, stated precisely: for the mobile app’s magic-link sign-in artifacts, the email is stored only as a keyed hash, the login link token and session bearer are stored as sha256 hashes compared in constant time, and the session subject is an opaque internal user id. The console account profile itself does store your sign-in email so we can operate your account — see /privacy.
lib/console-mobile-auth-core.tslib/console-mobile-auth.tsAccount email — /privacyProcessors — /subprocessorsThe live relay mailbox stores the Gate-signed hold, its DEFERRED receipt (the record that the action is waiting), the approval display encrypted to the device key, the device’s signed decision, and agent, credential and Gate identifiers. The console cannot read the display and never authorizes from these rows. Expired rows become deletable only after the longer of 30 days and the organization’s retention window has passed since expiry.
infra/postgres/067_noa_console_gate_relay_mailbox.sqlapp/v1/mobile/approvals/route.tslib/console-gate-relay.tsThe cleanup tool uses each organization’s configured retention window and purges expired sign-in artifacts, aged security events and expired relay mailbox rows past their retention floor on an explicit --apply run (dry-run is the default). Deliberately does not touch the append-only audit chain itself.
scripts/retention-sweep.mjsHow to report a problem, and how change gets shipped.
RFC 9116 security.txt, machine-readable, at the standard well-known path.
Idempotent, checksum-tracked Postgres migrations, run against a real PostgreSQL 18 instance in CI before merge. The migration runner refuses every other server major before execution.
scripts/migrate.mjsscripts/migrate-smoke.shA fail-closed logical restore procedure with checked-in drill tooling: restore-drill.sh plus dedicated verifiers for the restored migration ledger, the restored audit chain, and the core-8 invariants. The historical drill recorded on 2026-08-29 restored a hash-bound production logical backup into isolated PostgreSQL 18 at exact head 060: 60 migrations and 92 public tables, with every MVCC table fingerprint and the audit and RLS/ACL probes passing. Its bounded verdict is SEMANTIC_PASS_WITH_NON_MVCC_SEQUENCE_BOUNDARY because PostgreSQL sequences were not quiesced. It does not prove provider-native backup restorability, provider PITR configuration or live health, or production RPO/RTO.
scripts/restore-drill.shscripts/verify-restored-migration-ledger.mjsrelease-evidence/production-logical-restore-snapshot-20260829T125200Z.jsondocs/PRODUCTION-RUNBOOK.mdAn automated readiness pager runs from checked-in code: a scheduled workflow probes production, opens one deduplicated P1 incident issue on failure, and closes it after verified recovery. Its cadence is stated honestly — GitHub’s scheduler is best-effort, so every run measures and reports its own gap since the previous probe (observed gaps up to 2h35m are disclosed in the workflow itself) instead of claiming a five-minute interval it cannot keep; a checked-in external-probe script runs the same readiness check and pages on failure each time an external scheduler calls it. Its interval is set by that scheduler, and none is connected in production yet. Still open, named plainly: a guaranteed-interval scheduler in production and a public status page. The SIEM export above remains the path into a customer’s own monitoring stack.
.github/workflows/production-readiness-pager.ymlmonitoring/external-probe/probe-and-page.mjsscripts/production-monitor.mjsResidual risks — named, not hidden
Found a vulnerability, or need repository access under NDA?
Report a security issue via /.well-known/security.txt, or email us directly.