ci / lint-test-leaks (pull_request) Canceled after 0s
Background: CAYCE_OPS_OPERATOR_PUBKEY was set to Cayce nostr pubkey — Cayce was approving itself. Signature was technically valid but semantically null: human approval signed by the agent is not human approval. - signing.py: validate_no_self_approval() rejects the operator pubkey when it matches (a) the pubkey derived from any private key in the config, or (b) any pubkey in CAYCE_OPS_FORBIDDEN_OPERATOR_PUBKEYS deny-list. - executor/maild/publisher _signing() now calls the validation, so every daemon and every CLI approve-* path refuses to operate (D16). - tests: 6 new (agent-key refusal, human key passes, empty passes, explicit denylist, end-to-end daemon refusal, derivation roundtrip). - docs/DECISIONS.md: D16. The operator signature is now produced OUTSIDE the VPS. CAYCE_OPS_OPERATOR_PUBKEY must stay empty until the external signer exists — daemons refuse, which is correct. The Cayce nsec itself is NOT touched (still the agent identity for publishing and anchoring).
83 lines
2.9 KiB
Python
83 lines
2.9 KiB
Python
"""Tests for D16: the operator key may never be an agent identity.
|
|
|
|
Background: the operator key was, at some point, Cayce's nostr key. The
|
|
daemon must refuse to operate in that configuration, because "human
|
|
approval" signed by the agent is not human approval.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from cayceops.signing import (
|
|
SelfApprovalError,
|
|
pubkey_from_private_hex,
|
|
validate_no_self_approval,
|
|
)
|
|
|
|
# keys invented for tests only (allowlisted in .gitleaks.toml)
|
|
CAYCE_SECRET = "11" * 32 # simulates Cayce nostr identity
|
|
CAYCE_PUBKEY = pubkey_from_private_hex(CAYCE_SECRET)
|
|
HUMAN_SECRET = "33" * 32 # a proper human operator key
|
|
HUMAN_PUBKEY = pubkey_from_private_hex(HUMAN_SECRET)
|
|
|
|
|
|
def _cfg(monkeypatch, operator_pubkey: str, nostr_private_key: str = CAYCE_SECRET):
|
|
from cayceops.config import Config
|
|
|
|
monkeypatch.setenv("CAYCE_OPS_DEPLOYMENT_ID", "itest")
|
|
monkeypatch.setenv("CAYCE_OPS_OPERATOR_PUBKEY", operator_pubkey)
|
|
monkeypatch.setenv("CAYCE_OPS_NOSTR_PRIVATE_KEY", nostr_private_key)
|
|
return Config()
|
|
|
|
|
|
def test_agent_pubkey_as_operator_is_refused(monkeypatch):
|
|
"""The exact bug: operator pubkey = Cayce pubkey must fail."""
|
|
cfg = _cfg(monkeypatch, operator_pubkey=CAYCE_PUBKEY)
|
|
with pytest.raises(SelfApprovalError):
|
|
validate_no_self_approval(cfg)
|
|
|
|
|
|
def test_human_pubkey_passes(monkeypatch):
|
|
cfg = _cfg(monkeypatch, operator_pubkey=HUMAN_PUBKEY)
|
|
validate_no_self_approval(cfg) # does not raise
|
|
|
|
|
|
def test_empty_operator_pubkey_passes_validation(monkeypatch):
|
|
"""Empty is the 'not configured' state — rejected elsewhere, not here."""
|
|
cfg = _cfg(monkeypatch, operator_pubkey="")
|
|
validate_no_self_approval(cfg)
|
|
|
|
|
|
def test_explicit_denylist_is_honoured(monkeypatch):
|
|
"""A key held outside this process can be listed explicitly."""
|
|
outside_pubkey = pubkey_from_private_hex("44" * 32)
|
|
monkeypatch.setenv(
|
|
"CAYCE_OPS_FORBIDDEN_OPERATOR_PUBKEYS", f"{outside_pubkey}, {HUMAN_PUBKEY}"
|
|
)
|
|
cfg = _cfg(monkeypatch, operator_pubkey=HUMAN_PUBKEY, nostr_private_key="")
|
|
with pytest.raises(SelfApprovalError):
|
|
validate_no_self_approval(cfg)
|
|
|
|
|
|
def test_daemon_refuses_to_start_with_agent_key(monkeypatch, sess, db_engine):
|
|
"""End-to-end: executor.run must refuse (exit 2) when the operator
|
|
pubkey is Cayce's identity."""
|
|
from cayceops.components.executor import executor
|
|
from cayceops.models import DaemonRun
|
|
|
|
cfg = _cfg(monkeypatch, operator_pubkey=CAYCE_PUBKEY)
|
|
code = executor.run(cfg, sess)
|
|
assert code == 2
|
|
run_row = sess.query(DaemonRun).filter_by(daemon="executor").one()
|
|
assert run_row.status == "error"
|
|
assert "agent identity" in run_row.detail["refused"]
|
|
|
|
|
|
def test_pubkey_derivation_roundtrip():
|
|
assert pubkey_from_private_hex(HUMAN_SECRET) == HUMAN_PUBKEY
|
|
with pytest.raises(ValueError):
|
|
pubkey_from_private_hex("")
|
|
with pytest.raises(ValueError):
|
|
pubkey_from_private_hex("nothex")
|