@@ -53,6 +53,7 @@ class DaemonConfig:
5353 host : str = "127.0.0.1"
5454 port : int = 8787
5555 policy_poll_interval_s : float = 2.0
56+ max_request_body_bytes : int = 1_048_576
5657
5758
5859@dataclass (frozen = True )
@@ -167,6 +168,7 @@ def do_POST(self) -> None: # noqa: N802
167168 "/policy/reload" : self ._handle_policy_reload ,
168169 "/revoke/principal" : self ._handle_revoke_principal ,
169170 "/revoke/intent" : self ._handle_revoke_intent ,
171+ "/revoke/mandate" : self ._handle_revoke_mandate ,
170172 "/identity/task" : self ._handle_identity_task ,
171173 "/identity/revoke" : self ._handle_identity_revoke ,
172174 "/ledger/flush-ack" : self ._handle_ledger_flush_ack ,
@@ -201,6 +203,15 @@ def _handle_revoke_intent(self) -> None:
201203 self .server .daemon_ref .revoke_intent (intent_hash .strip ()) # type: ignore[attr-defined]
202204 self ._send_json (200 , {"ok" : True , "intent_hash" : intent_hash .strip ()})
203205
206+ def _handle_revoke_mandate (self ) -> None :
207+ payload = self ._read_json_body ()
208+ mandate_id = payload .get ("mandate_id" )
209+ if not isinstance (mandate_id , str ) or mandate_id .strip () == "" :
210+ self ._send_json (400 , {"error" : "mandate_id is required" })
211+ return
212+ self .server .daemon_ref .revoke_mandate (mandate_id .strip ()) # type: ignore[attr-defined]
213+ self ._send_json (200 , {"ok" : True , "mandate_id" : mandate_id .strip ()})
214+
204215 def _handle_identity_task (self ) -> None :
205216 payload = self ._read_json_body ()
206217 principal_id = payload .get ("principal_id" )
@@ -273,6 +284,9 @@ def _read_json_body(self) -> dict[str, Any]:
273284 return {}
274285 if content_length <= 0 :
275286 return {}
287+ max_body = self .server .daemon_ref .max_request_body_bytes () # type: ignore[attr-defined]
288+ if content_length > max_body :
289+ return {}
276290 payload = self .rfile .read (content_length ).decode ("utf-8" )
277291 try :
278292 loaded = json .loads (payload )
@@ -388,6 +402,12 @@ def revoke_principal(self, principal_id: str) -> None:
388402 def revoke_intent (self , intent_hash : str ) -> None :
389403 self ._sidecar .revoke_intent_hash (intent_hash )
390404
405+ def revoke_mandate (self , mandate_id : str ) -> None :
406+ self ._sidecar .revoke_mandate_id (mandate_id )
407+
408+ def max_request_body_bytes (self ) -> int :
409+ return max (0 , int (self ._config .max_request_body_bytes ))
410+
391411 def issue_task_identity (
392412 self ,
393413 principal_id : str ,
@@ -592,6 +612,7 @@ def _build_default_sidecar(
592612 control_plane_config : ControlPlaneBootstrapConfig | None = None ,
593613 local_identity_config : LocalIdentityBootstrapConfig | None = None ,
594614 identity_bridge : ExchangeTokenBridge | None = None ,
615+ mandate_signing_key : str | None = None ,
595616) -> PredicateAuthoritySidecar :
596617 policy_rules : tuple [PolicyRule , ...] = ()
597618 global_max_delegation_depth : int | None = None
@@ -647,7 +668,7 @@ def _build_default_sidecar(
647668
648669 guard = ActionGuard (
649670 policy_engine = policy_engine ,
650- mandate_signer = LocalMandateSigner (secret_key = secrets .token_hex (32 )),
671+ mandate_signer = LocalMandateSigner (secret_key = mandate_signing_key or secrets .token_hex (32 )),
651672 proof_ledger = proof_ledger ,
652673 )
653674 return PredicateAuthoritySidecar (
@@ -709,6 +730,22 @@ def _build_identity_bridge_from_args(args: argparse.Namespace) -> ExchangeTokenB
709730 raise SystemExit (f"Unsupported identity mode: { mode } " )
710731
711732
733+ def _resolve_mandate_signing_key (
734+ signing_key_file : str | None ,
735+ signing_key_env : str ,
736+ ) -> str :
737+ if signing_key_file is not None and str (signing_key_file ).strip () != "" :
738+ key_path = Path (signing_key_file )
739+ if key_path .exists ():
740+ loaded = key_path .read_text (encoding = "utf-8" ).strip ()
741+ if loaded != "" :
742+ return loaded
743+ env_value = os .getenv (signing_key_env )
744+ if env_value is not None and env_value .strip () != "" :
745+ return env_value .strip ()
746+ return secrets .token_hex (32 )
747+
748+
712749def main () -> None :
713750 parser = argparse .ArgumentParser (description = "predicate-authorityd sidecar daemon" )
714751 parser .add_argument ("--host" , default = "127.0.0.1" )
@@ -816,6 +853,16 @@ def main() -> None:
816853 )
817854 parser .set_defaults (control_plane_fail_open = True )
818855 parser .add_argument ("--control-plane-usage-credits-per-decision" , type = int , default = 1 )
856+ parser .add_argument (
857+ "--mandate-signing-key-env" ,
858+ default = "PREDICATE_AUTHORITY_SIGNING_KEY" ,
859+ help = "Env var name for mandate signing key." ,
860+ )
861+ parser .add_argument (
862+ "--mandate-signing-key-file" ,
863+ default = None ,
864+ help = "Optional file path containing mandate signing key." ,
865+ )
819866 args = parser .parse_args ()
820867
821868 mode = AuthorityMode (args .mode )
@@ -851,20 +898,26 @@ def main() -> None:
851898 default_ttl_seconds = max (1 , int (args .local_identity_default_ttl_s )),
852899 )
853900 identity_bridge = _build_identity_bridge_from_args (args )
901+ mandate_signing_key = _resolve_mandate_signing_key (
902+ signing_key_file = args .mandate_signing_key_file ,
903+ signing_key_env = args .mandate_signing_key_env ,
904+ )
854905 sidecar = _build_default_sidecar (
855906 mode = mode ,
856907 policy_file = args .policy_file ,
857908 credential_store_file = args .credential_store_file ,
858909 control_plane_config = control_plane_bootstrap ,
859910 local_identity_config = local_identity_bootstrap ,
860911 identity_bridge = identity_bridge ,
912+ mandate_signing_key = mandate_signing_key ,
861913 )
862914 daemon = PredicateAuthorityDaemon (
863915 sidecar = sidecar ,
864916 config = DaemonConfig (
865917 host = args .host ,
866918 port = args .port ,
867919 policy_poll_interval_s = args .policy_poll_interval_s ,
920+ max_request_body_bytes = 1_048_576 ,
868921 ),
869922 flush_worker = FlushWorkerConfig (
870923 enabled = bool (args .flush_worker_enabled ),
0 commit comments