Security validation, attack simulations, and enforcement status for Spanda’s hierarchical distributed decision architecture.
Status: Stable — live attack simulations exercise enforcement code paths with constructed adversarial inputs.
| Capability | Status | Evidence |
|---|---|---|
| Reflex safety without central dependency | Tested | rule_enforcement::reflex_safety_actions_run_without_central_approval |
| Signed offline policy verification | Tested | Ed25519 via spanda-audit; sign-policy, cache sync |
| Offline duration and forbidden action gates | Tested | validate_offline_action, runtime bridge |
| Central approval for high-risk actions | Tested | requires_central_approval + escalation trace |
| Nonce replay rejection | Tested | NonceRegistry, register_decision_nonce |
| Policy tamper detection | Tested | Signature mismatch on tampered payload |
| Split-brain precedence resolution | Tested | resolve_conflict + CONFLICT_PRECEDENCE |
| v3 decision trace proof fields | Tested | validate_decision_trace_payload |
| Live attack simulations | Tested | run_attack_simulation, CI smoke |
Attack simulations exercise enforcement code paths with constructed adversarial inputs. They do not spin up fleet mesh, inject network packets, or compromise real devices.
| Scenario | CLI | What runs |
|---|---|---|
| Policy tampering | spanda decision simulate-attack policy-tamper |
Sign policy, tamper allowed_actions, verify signature fails |
| Replayed decision | spanda decision simulate-attack replayed-decision |
Register nonce twice, second rejected |
| Fake coordinator | spanda decision simulate-attack fake-coordinator |
Untrusted entity blocked from takeover |
| Offline abuse | spanda decision simulate-attack offline-abuse |
Duration exceeded + forbidden action blocked |
| Compromised robot | spanda decision simulate-attack compromised-robot |
disable_safety forbidden while offline |
| Poisoned telemetry | spanda decision simulate-attack poisoned-telemetry |
Stale timestamp rejected |
| Split-brain coordinator | spanda decision simulate-attack split-brain-coordinator |
Safety precedence wins |
| Split-brain mesh | spanda decision simulate-attack split-brain-mesh |
Mesh conflict + shared nonce replay (HTTP when SPANDA_FLEET_MESH_URL set) |
All simulations exit non-zero when the unsafe decision is not blocked.
Offline policies use canonical JSON signing via spanda-audit:
export SPANDA_DECISION_POLICY_SIGNING_KEY=<material>
spanda decision sign-policy mission.sd --policy RoverOffline --write-cache
export SPANDA_DECISION_REQUIRE_SIGNED_OFFLINE_POLICY=1
export SPANDA_DECISION_POLICY_TRUST_KEY=<material>
Enforcement checks:
policy_hash)expires_at_ms on offline policyverify_decision_tree_hash (not cryptographic)validate_decision_timestampvalidate_authority_scopeNote: Decision tree hash is a non-crypto fingerprint for tamper detection in tests and traces. Offline policy signatures use real Ed25519 — do not mock in production paths.
Run the full enforcement suite:
cargo test -p spanda-decision --test rule_enforcement
cargo test -p spanda-decision --test attack_simulations
./scripts/distributed_decisions_smoke.sh
Tests prove:
spanda decision threat-model
spanda decision security-audit [--json]
Static catalog documents mitigations. Live simulations (simulate-attack) produce JSON evidence.
| Control | Status |
|---|---|
| Runtime conflict resolution | Stable — wired in interpreter |
| Persistent escalation store | Stable — disk + API + Control Center |
| Decision tree Ed25519 signing | Stable — sign-tree + cache |
| Persisted nonce registry | Stable — disk-backed replay protection |
| v3 envelope signatures | Stable — Ed25519 when signing key configured |
| Fleet mesh conflict aggregation | Stable — POST /v1/fleet/decisions/vote/ingest, GET /v1/fleet/decisions/conflicts |
| Shared fleet nonce registry | Stable — POST /v1/fleet/decisions/nonce/register (+ local mirror) |
| Pluggable signing backend | Stable — SPANDA_CRYPTO_BACKEND=software|mock_hsm|script|tpm2, SPANDA_HSM_SIGN_SCRIPT |
export SPANDA_CRYPTO_BACKEND=script # or tpm2, mock_hsm
export SPANDA_DECISION_SIGNING_KEY_ID=fleet-signing-key-1
export SPANDA_HSM_SIGN_SCRIPT=/opt/spanda/bin/hsm-sign.sh # reads stdin, prints hex Ed25519 sig
export SPANDA_DECISION_POLICY_TRUST_KEY=<matching public key or material>
The script receives the canonical signing payload on stdin and must print a hex-encoded Ed25519
signature on stdout. Set SPANDA_HSM_SIGN_REQUIRED=1 to fail closed when the script errors.
| Enhancement | Resolution |
|---|---|
| Fleet mesh coordinator aggregation | Mesh ingest + conflict resolution; runtime uses coordinator winner when SPANDA_FLEET_MESH_URL set |
| Distributed nonce store | Shared mesh registry with local file fallback |
| Hardware-backed signing keys | sign_with_backend + SPANDA_HSM_SIGN_SCRIPT / SPANDA_TPM2_SIGN_SCRIPT for production HSM |
| Gap | Resolution |
|---|---|
resolve_conflict not wired at runtime |
Wired in evaluate_live_decision_trees |
| Escalation approval in-memory only | PersistedEscalationStore + API + Control Center |
| Decision tree signing | Ed25519 sign-tree + cache merge |
| Global nonce registry | .spanda/decision-nonce-registry.json |
| Crypto signature on v3 envelope | Ed25519 envelope signing + verification |
The distributed-decisions CI Integration job runs:
cargo test -p spanda-decisionSee .github/workflows/ci-integration.yml job distributed-decisions.