Skip to content
EXTENDED DOCUMENTATION

Security Audit Notes

This document tracks the current security hardening baseline for NeoOS Services and the checks that must pass before release.

September 2026 Trust-Boundary Re-audit

The current tree was re-audited rather than treating earlier issue notes as current fact. The findings below were live at the start of the remediation wave and are now closed in the paired implementation/tests:

  • measured production code no longer compiles an ordinary Secrets Manager
    text
    GetSecretValue
    client for confidential masters; Recipient-attested KMS release is the only unwrap path;
  • text
    /provision
    is one-shot, allowlisted, and refuses plaintext key/token overlays; temporary AWS credentials rotate only through
    text
    /provision/credentials
    ;
  • X25519 and NeoDID KMS ciphertexts materialize in-memory as purpose/network envelopes during provision;
  • signed compute/oracle/NeoDID lanes return 503 unless custody identity matches; HTTP 200 never carries a null signature or public key;
  • raw signing uses dedicated raw-only keys and a distinct
    text
    raw_sign
    capability;
  • production
    text
    privacy.add_noise
    draws from
    text
    OsRng
    (deterministic xorshift remains a test seam only);
  • attested boundary JSON includes numeric
    text
    network_magic
    ;
  • production Cloudflare confidential execution requires exactly one origin;
  • runtime credential domain separation is enforced by deploy checkers, the shared
    text
    credential-separation
    helper, production
    text
    apps/web/lib/config
    startup, and the Cloudflare control-plane auth path (
    text
    auth_credential_collision
    );
  • local/CI/EIF toolchain is Rust 1.98.0 and publish triggers watch
    text
    packages/shared
    ;
  • dBFT reservations checkpoint PrepareRequest/PrepareResponse together, ignore view for Commit, key immutable public key + network magic, and reserve before signing.

The following earlier concerns were stale in the current tree and must not be reimplemented:

  • fee ceilings already cover both system and network fees;
  • rolling value and signature decisions are serialized across service clones, with audit/value/idempotency writes committed in one SQLite transaction;
  • request-id completion is durable and exact-retry safe;
  • transaction parsing already rejects negative/overflowing fees, trailing bytes, unknown attributes/opcodes, dynamic witness rules, and unaccounted script behavior.

Required resolved posture

  1. Measured production code accepts confidential material only as a KMS CiphertextBlob and unwraps it with a fresh Nitro Recipient attestation inside the enclave. The parent role has no
    text
    secretsmanager:GetSecretValue
    ,
    text
    kms:Encrypt
    ,
    text
    kms:GenerateDataKey*
    , or plaintext-secret permission.
  2. text
    /provision
    is one-shot, uses an explicit allowlist, and rejects unknown names.
    text
    NODE_ENV
    , network, endpoints, custody routing, raw key material, feature/policy toggles, and trust anchors are image-owned. A separately authenticated
    text
    /provision/credentials
    route may rotate only temporary AWS access credentials.
  3. Production X25519 and NeoDID KMS plaintexts are purpose/network/version envelopes. The enclave validates and materializes them only in memory and marks their source internally as Nitro Recipient KMS. Remote confidential references remain explicit 503 until their authenticated claim transport is implemented.
  4. All signed response lanes fail 503 unless custody key identity, enabled state, network, and response identity match. HTTP 200 never contains a null signature or public key.
  5. Raw-only keys and
    text
    raw_sign
    callers are distinct from semantic transaction and consensus authorities.
  6. Security-sensitive random draws use
    text
    OsRng
    ; deterministic closures remain test seams only.
  7. text
    network_magic
    is present in key wire types and the attested boundary digest.
  8. Production images/units set
    text
    NODE_ENV=production
    ; confidential Cloudflare execution has one origin and no cross-origin retry; runtime/workflow/operator/ relayer/signing/relay/custody credentials are pairwise distinct via
    text
    @r3e-network/neo-os-services-shared/credential-separation
    , deploy checkers, production web startup, and control-plane auth.
  9. Rust 1.98.0 is the single local, CI, and EIF toolchain, and publish triggers cover every Docker build input, including
    text
    packages/shared
    .
  10. dBFT v3.10.1 bodies are fully consumed and checkpointed by immutable public key, network magic, height, and safe phase semantics before signing. The claimed validator index must match an exact retry but cannot create a parallel slot.

Current Automated Coverage

  • Root dependency audit is guarded by
    text
    scripts/check-root-audit-allowlist.mjs
    so known CityOfZion baseline findings stay explicit instead of silently expanding. The checker runs the active npm CLI through the current Node executable when
    text
    npm_execpath
    is available (including Windows), uses a shell-backed npm fallback only for direct Windows invocation, and reports process-launch failures instead of misclassifying them as an empty audit.
  • Repository verification runs script tests, control-plane tests, worker checks/tests, relayer checks/tests, web consistency checks, web unit tests, and web production build.
  • Web security headers are defined in
    text
    apps/web/next.config.mjs
    and covered by
    text
    apps/web/__tests__/security-headers.test.ts
    .
  • Documentation navigation is covered by
    text
    apps/web/__tests__/docs-navigation.test.ts
    , including static docs routes and extended Markdown-backed
    text
    /docs/r/*
    pages.
  • Contract artifact generation is validated by
    text
    scripts/contract-build-regressions.test.mjs
    when
    text
    dotnet
    and the pinned
    text
    nccs
    compiler are available.

Rust Advisory Disposition

text
cargo audit
currently reports RUSTSEC-2023-0071 for RustCrypto
text
rsa
; no patched release exists. The measured runtime uses RSA private operations only for a fresh, per-request RSA-2048 recipient key that unwraps one KMS response inside the enclave and is then dropped. The key is never persisted or reused, and the authenticated release flow generates a new key before each ciphertext, so a remote caller cannot collect the repeated timings for one long-lived key that the Marvin attack requires. Other
text
rsa
call sites perform public-key verification only and are outside the advisory's private-key recovery path.

Until RustCrypto publishes a constant-time release, the release check is:

bash
cargo audit --ignore RUSTSEC-2023-0071

The exception must be removed when a patched version exists, or earlier if an RSA private key is ever reused, persisted, or exposed through a remotely repeatable decryption/signing oracle.

text
paste
is an unmaintained build-time macro inherited from Boa and retires with that compatibility engine;
text
serde_cbor
is the unmaintained target-specific dependency of AWS's Nitro NSM API. Neither warning currently identifies a vulnerability, but both remain migration debt rather than permanent exemptions.

Manual Release Checklist

Before a production release, verify:

  1. text
    npm run verify:repo
    passes in a clean checkout.
  2. The contract CI job installs the pinned Neo C# compiler and runs
    text
    contracts/build.sh
    successfully.
  3. No secrets, private keys, service-role keys, tokens, or connection strings are committed or printed in logs.
  4. Production environment variables are scoped by network and deployment target.
  5. Callback, relayer, paymaster, and txproxy allowlists are reviewed against the intended deployment contracts.
  6. Any live smoke tests use dedicated testnet funds/accounts and do not reuse production signer material.
  7. text
    cargo audit --ignore RUSTSEC-2023-0071
    reports no other vulnerability.
  8. text
    npm run format:rust:check
    ,
    text
    cargo clippy --workspace --all-targets --locked -- -D warnings
    , and
    text
    cargo test --workspace --all-targets --locked
    pass under Rust 1.98.0.
  9. text
    npm run check:rust-migration
    , documentation coverage, worker/relayer/ops/script suites, custody-plane E2E, the NeoNexus real-process contract, and gitleaks pass.
  10. External-only blockers in
    text
    docs/EXTERNAL_RELEASE_BLOCKERS.md
    are cleared or explicitly accepted by operators (key rotation, on-chain upgrades, host installs).
  11. Production preflight confirms pairwise-distinct runtime/workflow/operator/ relayer/signing/relay credentials and includes both signing and relay admin keys.
  12. The release's PCR measurements are rebuilt, committed, and pinned by the KMS key policy before ciphertext is provisioned.

Public API Route Policy

The following

text
apps/web
routes are intentionally public and are hardened against enumeration and DoS abuse. They are rate-limited by
text
apps/web/lib/rate-limit.ts
(Upstash-backed when configured, in-memory otherwise) and enforce Zod input validation via
text
apps/web/lib/validators.ts
.

RouteAuthenticationRate limitValidation
text
/api/workflows
Runtime/admin token required60 req/minNo unexpected query params
text
/api/policies
Runtime/admin token required60 req/minNo unexpected query params
text
/api/networks
Public (no token)60 req/minNo unexpected query params
text
/api/neodid/resolve
Runtime/admin token required30 req/min
text
did
and optional
text
format

Authentication uses the shared runtime-token check in

text
apps/web/lib/control-plane-auth.ts
(
text
MORPHEUS_RUNTIME_TOKEN
,
text
NITRO_API_TOKEN
, or
text
NITRO_SHARED_SECRET
presented as a Bearer token or
text
x-morpheus-runtime-token
header). Unauthenticated requests to protected routes receive
text
401 { error: 'unauthorized' }
; malformed inputs receive
text
400 { error: 'invalid query parameters' }
; rate-limited requests receive
text
429 { error: 'Too many requests', retryAfter }
. Responses include
text
X-RateLimit-Limit
,
text
X-RateLimit-Remaining
, and
text
X-RateLimit-Reset
headers.

Known Environment Caveat

Local developer machines that do not have

text
dotnet
and
text
~/.dotnet/tools/nccs
installed will skip the contract compilation regression test. CI must still enforce contract compilation using the pinned compiler before release.

Compatibility Child Process Hardening

The measured Rust EIF has no Node child or fallback engine. The separately published Node compatibility worker still spawns untrusted scripts and WASM modules in child processes. In that compatibility deployment the following settings must be enforced:

  • The Node.js permission model is ON by default for script/WASM child processes. It can only be disabled by the explicit dev override
    text
    MORPHEUS_ALLOW_UNSANDBOXED_CHILDREN=1
    , and that override is IGNORED when
    text
    NODE_ENV=production
    (fail closed). The retired
    text
    SCRIPT_CHILD_ENABLE_PERMISSION_MODEL
    toggle no longer has any effect.
  • text
    SCRIPT_CHILD_ALLOW_NET
    must contain specific hostnames or IP addresses. Wildcards (
    text
    *
    ),
    text
    0.0.0.0/0
    ,
    text
    ::/0
    , and CIDR blocks are rejected at startup.
  • text
    SCRIPT_CHILD_ALLOW_FS_READ
    and
    text
    SCRIPT_CHILD_ALLOW_FS_WRITE
    should be limited to the paths required by the workload.
  • The Node.js permission model is applied automatically via
    text
    buildPermissionExecArgv
    in
    text
    workers/nitro-worker/src/platform/script-runner.js
    and
    text
    wasm-runner.js
    .

Cron Diagnostic Policy

The

text
/api/cron/feed
endpoint returns a generic
text
401 { error: 'Unauthorized' }
response to unauthenticated callers. Diagnostic details (header presence, request metadata, and configuration checks) are logged server-side only and never included in the response body.

CURRENT DESIGNUPDATED FOR DUAL-CVM ARCHITECTURE
NeoOS Services