Skip to content

Security Model

Executor Key Compromise

Impact

An attacker with the backend executor key can submit sponsored operations available to that key, including vault deployment, deposit setup, claim executor grant management, claim transactions, and exit requests. The backend uses SEAT_MANAGER_OWNER_PRIVATE_KEY for owner-send execution and sponsored claims in seat-manager/README.md:121, and the operation executor encodes deposit, grant, claim, and exit calls in seat-manager/src/contracts/operationExecutor.ts:839, seat-manager/src/contracts/operationExecutor.ts:917, and seat-manager/src/contracts/operationExecutor.ts:1395.

Detection signal

Primary detection is audit and operation review. Look for unexpected seat.claim_requested, seat.voluntary_exit_requested, seat.migrate_trigger_exit_requested, and admin retry/cancel events emitted in seat-manager/src/api/server.ts:2167, seat-manager/src/api/server.ts:2023, seat-manager/src/api/server.ts:2261, and seat-manager/src/api/server.ts:3316. Operation intent review shows unexpected target/calldata/nonce rows from OperationTxIntent in seat-manager/prisma/schema.prisma:175.

Containment

Stop the watcher first so no queued operation is executed. Disable claim traffic by pausing or removing executor grants at the controller; claim executor pause and grant revoke functions are exposed in contracts/src/CenturionEconomicController.sol:339 and contracts/src/CenturionEconomicController.sol:343. Cancel unsafe queued operations through the admin operation cancel path when their status allows it in seat-manager/src/api/server.ts:3333.

Rotation or recovery

Replace SEAT_MANAGER_OWNER_PRIVATE_KEY, restart API and watcher processes, then run node dist/src/index.js backfill-claim-executor to grant the new signer on live seats. The backfill command requires owner-send mode in seat-manager/src/api/runtime.ts:351, and grant TTL resolution lives in seat-manager/src/contracts/operationExecutor.ts:630. The old signer must lose controller grants through revokeClaimExecutorGrant in contracts/src/CenturionClaimGatekeeper.sol:59.

Post-incident verification

Run staker-console-v2 status --json for representative live seats and confirm claim signer readiness and no unexpected open exceptions. In the database, review audit events for the incident window and operation intents for the old sender address. The admin audit endpoint filters action, result, and dates in seat-manager/src/api/server.ts:3406, and operation intent rows expose sender, nonce, tx hash, receipt status, block, confirmations, and error in seat-manager/src/api/server.ts:3239.

JWT Issuer Key Compromise

Impact

An attacker with the enrollment issuer key can mint JWTs for seats until the verifier key is rotated and outstanding unconsumed nonces are treated as hostile. Accepted tokens can call wc-template and register, consume a nonce, populate validator/vault fields, issue an operator API key, and enqueue deposit in seat-manager/src/api/server.ts:1446 and seat-manager/src/api/server.ts:1594.

Detection signal

Detection is audit-driven. Every token verification emits auth.token_verified with seat id, operator id, and nonce prefix in seat-manager/src/api/server.ts:1451 and seat-manager/src/api/server.ts:1606. Suspicious registers emit seat.registered with pubkey, vault address, canonical tuple hash, and nonce prefix in seat-manager/src/api/server.ts:1766.

Containment

Stop enrollment by removing token distribution, disabling the API ingress for /v1/seats/:seatId/wc-template and /v1/seats/:seatId/register, or stopping the API process. Do not revoke already registered seats blindly; inspect NonceLedger, Seat, WcTemplate, and AuditEvent first because register is atomic in seat-manager/src/onboarding/prismaStore.ts:866.

Rotation or recovery

Rotate the issuer key, update ENROLLMENT_JWT_PUBLIC_KEY_PEM, and restart the API. The verifier caches the resolved key by fingerprint in seat-manager/src/onboarding/tokenVerifier.ts:38, so a running process keeps using its cached verifier until restart or reload. Token TTL is capped by MAX_TOKEN_TTL_SECONDS = 172800 in seat-manager/src/onboarding/tokenVerifier.ts:8; outstanding tokens with the compromised key remain untrusted even before expiry.

Post-incident verification

Filter audit events for auth.token_verified, seat.wc_template_issued, and seat.registered during the compromise window. Confirm no unknown pubkeys or vault addresses were registered. Verify a newly minted token with staker-console-v2 verify-token --issuer-public-key <new.pem>; strict verification is enforced in staker-console/src/commands/verifyToken.ts:43. Confirm old-key tokens fail with TOKEN_INVALID_SIGNATURE, which the backend emits in seat-manager/src/onboarding/tokenVerifier.ts:70.

Malicious sm_url

Impact

A malicious sm_url in an enrollment token can point the operator console at an attacker-controlled backend. The attacker can collect enrollment request metadata and attempt to trick the operator into using a fake response. The console reduces this by verifying the token locally before resolving sm_url; missing verifier config fails in staker-console/src/commands/onboard.ts:517, and signature verification runs before claim decoding in staker-console/src/commands/onboard.ts:531.

Detection signal

The signal is local and procedural: verify-token output and onboarding command errors. The token parser exposes sm_url as a claim in staker-console/src/lib/token.ts:67, and strict local verification fails before network access when issuer material is absent or invalid in staker-console/src/commands/verifyToken.ts:43. Backend audit events do not fire when the console refuses the token locally.

Containment

Tell the operator to stop onboarding and preserve the token, command output, and local workspace. Block the suspicious URL at DNS/proxy level. Do not run onboard with an HS256 override unless this is a controlled diagnostic; HS256 strict mode requires explicit opt-in in staker-console/src/commands/verifyToken.ts:53.

Rotation or recovery

Issue a new JWT with the correct sm_url, issuer, audience, seat id, operator id, and fresh nonce. If the operator already contacted the malicious URL but never completed backend register, the real backend has no NonceLedger row because nonce consumption happens only during register in seat-manager/src/onboarding/prismaStore.ts:967. If the operator sent key material to the wrong process outside this console, treat the BLS key as exposed and revoke/reissue the seat.

Post-incident verification

Run staker-console-v2 verify-token --json and confirm the verified sm_url matches the Foundation endpoint before running onboard. Then run onboard with --issuer-public-key so local verification is mandatory in staker-console/src/index.ts:67. On the backend, confirm no seat.wc_template_issued or seat.registered audit event exists for the suspicious nonce prefix in seat-manager/src/api/server.ts:1579 and seat-manager/src/api/server.ts:1766.

Operator Running Only A VC

Impact

An approved operator can complete enrollment and then run only a validator client without the expected full machine setup, monitoring, or execution-layer pairing. The backend cannot prove machine topology after onboarding. Current controls are manual approval, one non-revoked seat per operator, and lifecycle/economics observation through status; the active-seat rule is encoded in seat-manager/src/onboarding/store.ts:19 and enforced in seat-manager/prisma/migrations/20260514165745_one_active_seat_per_operator/migration.sql:37.

Detection signal

Detection is currently manual and telemetry-assisted. Admins inspect status, economics, lifecycle state, and operation history through /admin/v1/seats/:seatId in seat-manager/src/api/server.ts:2846. Operator status includes risk state, claim gate state, validator active/exited flags, and open exceptions in staker-console/src/index.ts:146. There is no automated "VC-only" detector in the current implementation; launch-grade observability gaps remain in seat-manager/README.md:73.

Containment

Stop granting new seats to the operator. Disable new JWT issuance for that operator and review any pending operation rows. If the active validator must be retired, use governed revocation and then migration or exit procedures rather than deleting state. Revocation requires ADMIN, CSRF, fresh step-up, governance evidence, and idempotency in seat-manager/src/api/server.ts:2983.

Rotation or recovery

For benign misconfiguration, require the operator to complete the expected machine setup and prove status through staker-console output. For dishonest or unrecoverable cases, revoke the seat with governance evidence, then provision a new seat only after approval. The revocation transition writes governance evidence in seat-manager/src/api/server.ts:3060, and the operator becomes eligible for a new seat only after status is REVOKED under the partial index in seat-manager/prisma/migrations/20260514165745_one_active_seat_per_operator/migration.sql:37.

Post-incident verification

Verify the old seat is REVOKED, the audit event admin.seat.revoke exists, and no live operations remain for the old seat. The audit success event is emitted in seat-manager/src/api/server.ts:3134, and the operations endpoint can filter by seat id in seat-manager/src/api/server.ts:3151. For a remediated live operator, capture fresh status --json output and admin seat detail evidence.

EL Deposit Watcher Reorg

Impact

An EL reorg can invalidate deposit evidence that moved a seat to DEPOSITED, SEEN_BY_CL, or ACTIVE. If ignored, the backend state can overstate deposit finality or CL progression. The watcher stores el:last_scanned_block and per-slot block-ring keys to detect this in seat-manager/src/watchers/lifecycleWatchLoop.ts:356 and seat-manager/src/watchers/lifecycleWatchLoop.ts:399.

Detection signal

The watcher logs lifecycle watcher detected head rollback when latest block falls below cursor in seat-manager/src/watchers/lifecycleWatchLoop.ts:376. It logs lifecycle watcher detected persisted reorg mismatch when a persisted block number has a different block hash in seat-manager/src/watchers/lifecycleWatchLoop.ts:436. Affected seats move to EXCEPTION with reorg_quarantine evidence from seat-manager/src/watchers/lifecycleWatchLoop.ts:293.

Containment

Keep the watcher running only against a stable RPC source. Stop claim, exit, and migrate decisions for quarantined seats. Use admin status and support bundle reads to list seats with EXCEPTION and reorg_quarantine evidence; support bundles include lifecycle summary and operation summary from seat-manager/src/api/server.ts:3467.

Rotation or recovery

Fix the RPC source, restart the watcher, and let it rescan from the rewound cursor. The watcher rewinds to zero on head rollback and rewinds to twice the confirmation window on persisted mismatch in seat-manager/src/watchers/lifecycleWatchLoop.ts:380 and seat-manager/src/watchers/lifecycleWatchLoop.ts:441. Returning a quarantined seat to a live state is not documented in current implementation; this is part of the durable reorg rollback/replay gap in seat-manager/README.md:73.

Post-incident verification

Check /admin/v1/health for a non-null el_last_scanned_block and lifecycle_watcher.ok = true in seat-manager/src/api/server.ts:2655. Search lifecycle evidence for reorg_quarantine, verify affected seats are still under manual review, and confirm new deposit events are accepted only after the confirmation gate in seat-manager/src/watchers/lifecycleWatchLoop.ts:466.