audit-1 (critical 1): mandatory operator signatures bound to content+deployment

- signing module: canonical content hashes + approval_message
  sha256(cayce-ops:<type>:<deployment_id>:<id>:<content_hash>)
- approve/approve-draft/publisher-approve REQUIRE signature; without
  CAYCE_OPS_OPERATOR_PUBKEY+DEPLOYMENT_ID the daemons refuse to
  execute/send/publish (no degraded mode)
- run-time re-verification at execute/send/publish: unsigned rows (raw SQL
  approval) or edited content revert to pre-approval state, audited as error
- FULL signatures stored on rows (operator_signature, migration 0003) and
  in audit detail — never truncated
- signature failures audited from CLI wrappers too (1f)
- AUTONOMOUS_MODE approval path removed (D13): no non-human approvals

tests: SQL-approved row refused at execute/send/publish; to_addr change
blocks send; foreign deployment/content signatures refused; full-signature
persistence. 65 tests green.
This commit is contained in:
2026-10-20 02:20:09 +00:00
parent 61d5cfd67d
commit 40453aadb4
13 changed files with 1209 additions and 273 deletions
+10 -2
View File
@@ -84,11 +84,15 @@ class Config:
enable_http_actions: bool = field(
default_factory=lambda: _bool("CAYCE_OPS_ENABLE_HTTP_ACTIONS", False)
)
# when set (64-char hex x-only pubkey), human approvals MUST carry a valid
# BIP-340 signature over "cayce-ops:approve:<id>" made by the operator key.
# MANDATORY for approval-gated work: the operator x-only pubkey and the
# deployment identifier are part of every signed approval message.
# Without BOTH, daemons refuse to execute/send/publish — no degraded mode.
operator_pubkey: str = field(
default_factory=lambda: os.environ.get("CAYCE_OPS_OPERATOR_PUBKEY", "")
)
deployment_id: str = field(
default_factory=lambda: os.environ.get("CAYCE_OPS_DEPLOYMENT_ID", "")
)
# --- mail ---
imap_host: str = field(
@@ -167,6 +171,10 @@ class Config:
)
class ConfigError(RuntimeError):
"""Raised when mandatory configuration is missing."""
def load() -> Config:
"""Build the process Config snapshot."""
return Config()