Skip to content
Alpha — Odal Node is in active development. APIs, schemas, and docs may and will change before 1.0.

Operating a node securely

The core defines how a passport is signed and trusted. The engine’s job is to run that in production without becoming the weak link. This page covers what protects a live node: where the signing key lives, how access is controlled, and how the node behaves when something is missing or fails.

The operator’s Ed25519 signing key is generated and held on their own infrastructure, encrypted at rest, and used in-process — the node signs passports itself, with no separate signing service and no key to hand off. Odal never holds it; in a self-hosted deployment Odal has no access to the node at all. The cryptography of how the key is generated and protected is the core’s; see Security & cryptography and What Odal can and cannot see.

A node accepts two kinds of credential, and the public read path needs neither.

  • API keys — for machines and integrations, presented as Bearer tokens. The full key is shown once, at creation, and is never stored: the node keeps only a one-way hash of it plus a short prefix used to find the right record quickly. A revoked or expired key is refused, and the comparison is constant-time so a near-miss leaks nothing. Each key carries a scope — read, write, or admin — so a credential grants no more authority than its job needs.
  • Admin login — a local username and password supplied through the node’s environment, used for first setup and for recovery. It is not stored in the database.
  • Self-lockout guard — a key cannot revoke itself, so an automated rotation can’t accidentally strand you mid-operation; the admin login is the deliberate recovery path.

The public passport endpoint requires no credential at all — it only ever serves passports that are already published, and is never a way back into an operator’s data.

The node connects to PostgreSQL with an application role that cannot change the schema, and that holds no DELETE grant anywhere except on the import-job table, where a cleanup sweep needs one. Schema migrations run under a separate, privileged credential that is used only at startup and never kept in the live connection pool. On top of that, the database enforces the permanence guarantees itself — a trigger rejects edits to a locked passport, and the audit trail is append-only at the database level — so those rules hold even if application code is wrong.

A node refuses to start without its secrets — database credentials, the key-store passphrase, the signing domain. The production stack goes further and refuses to start on left-over development defaults. There is no insecure default that quietly “just works”; a misconfigured node does not run rather than running unsafely.

Each sector’s compliance logic runs in a Wasm sandbox with no filesystem and no network, capped at 64 MiB of memory and a fixed CPU budget (fuel metering). It is not a vacuum: the plugin can read a clock and draw randomness. The clock is pinned to a single instant for the whole invocation, so a determination cannot vary with the time it ran; randomness is real OS entropy, deliberately not pinned, because a fixed seed would make a plugin’s hash iteration order predictable without making any determination more reproducible. A buggy or hostile plugin exhausts its budget and is stopped — it can never reach the signing key, the database, or the rest of the node. This is the host enforcing, with concrete limits, the sandbox model described in Security & cryptography.

Persistence comes first; everything else is a notification. Lifecycle events are published only after the database write commits, and a failure to publish is logged, never propagated — a passport is published whether or not the event bus is healthy. Optional infrastructure degrades to a no-op instead of blocking the node, so a single-node deployment needs no message broker to function. Anything consuming events treats them as hints and reads the database for canonical state.

A node serves exactly one operator. Isolation is a property of the deployment — its own node, its own database — not an in-process setting that could be misconfigured. There is no shared cluster, no cross-operator code path, and no per-row tenant scoping to get wrong; the failure modes of multi-tenancy are absent because multi-tenancy is absent. Serving many operators means running many nodes, an infrastructure concern handled above the node, never inside it. See How the node works.

The signing path is the core’s: the engine holds no signature or key-derivation code of its own. It does hash on its own account — SHA-256 for API-key and admin-password digests, compared in constant time — because those are node-operational concerns rather than passport ones. Dependencies are scanned for known advisories on every change. Suspected vulnerabilities go to security@odal-node.io under coordinated disclosure, never to a public issue first.