@@ -24,6 +24,7 @@ class ValidationResult:
2424VALID_APPROVAL = {"none" , "notify" , "required" , "human_confirm" , "step_up" , "manager" , "block" }
2525VALID_AUTH_MODE = {"delegated" , "service" , "just_in_time" }
2626VALID_OIDC_TOKEN_MODES = {"jwks" , "demo_hs256" }
27+ VALID_ATTESTATION_RESULTS = {"pass" , "fail" , "partial" , "unknown" }
2728
2829
2930def load_manifest (path : str | Path ) -> dict [str , Any ]:
@@ -56,6 +57,9 @@ def validate_manifest(manifest: dict[str, Any]) -> ValidationResult:
5657 if not agent .get (field ):
5758 errors .append (f"Missing required field: agent.{ field } " )
5859
60+ _validate_agent_identity (manifest , errors , warnings )
61+ _validate_trusted_issuers (manifest , errors , warnings )
62+ _validate_attestations (manifest , errors , warnings )
5963 _validate_jit_authorization (manifest , errors , warnings )
6064 _validate_oidc (manifest , errors , warnings )
6165 _validate_tools (manifest , errors , warnings )
@@ -76,6 +80,87 @@ def validate_manifest(manifest: dict[str, Any]) -> ValidationResult:
7680 return ValidationResult (ok = not errors , errors = errors , warnings = warnings )
7781
7882
83+ def _validate_agent_identity (manifest : dict [str , Any ], errors : list [str ], warnings : list [str ]) -> None :
84+ agent = manifest .get ("agent" , {})
85+ if not isinstance (agent , dict ):
86+ return
87+
88+ did = agent .get ("did" )
89+ if did is None :
90+ return
91+ if not isinstance (did , str ) or not did :
92+ errors .append ("agent.did must be a non-empty string if provided." )
93+ elif not did .startswith ("did:" ):
94+ warnings .append ("agent.did does not look like a decentralized identifier." )
95+
96+
97+ def _validate_trusted_issuers (manifest : dict [str , Any ], errors : list [str ], warnings : list [str ]) -> None :
98+ issuers = manifest .get ("trusted_issuers" )
99+ attestations = manifest .get ("attestations" , [])
100+
101+ if issuers is None :
102+ if attestations :
103+ warnings .append ("trusted_issuers is not set. Attestation signatures may lack an explicit trust policy." )
104+ return
105+
106+ if not isinstance (issuers , list ):
107+ errors .append ("trusted_issuers must be a list." )
108+ return
109+
110+ for idx , issuer in enumerate (issuers ):
111+ if not isinstance (issuer , str ) or not issuer :
112+ errors .append (f"trusted_issuers[{ idx } ] must be a non-empty string." )
113+
114+
115+ def _validate_attestations (manifest : dict [str , Any ], errors : list [str ], warnings : list [str ]) -> None :
116+ attestations = manifest .get ("attestations" )
117+ if attestations is None :
118+ runtime = manifest .get ("runtime" , {})
119+ if isinstance (runtime , dict ) and runtime .get ("require_valid_attestations" ):
120+ errors .append ("attestations is required when runtime.require_valid_attestations is true." )
121+ return
122+
123+ if not isinstance (attestations , list ):
124+ errors .append ("attestations must be a list." )
125+ return
126+
127+ agent_did = manifest .get ("agent" , {}).get ("did" ) if isinstance (manifest .get ("agent" ), dict ) else None
128+ trusted_issuers = set (manifest .get ("trusted_issuers" , []) or [])
129+
130+ for idx , attestation in enumerate (attestations ):
131+ prefix = f"attestations[{ idx } ]"
132+ if not isinstance (attestation , dict ):
133+ errors .append (f"{ prefix } must be an object." )
134+ continue
135+
136+ for field in ["type" , "issuer" , "result" ]:
137+ if not attestation .get (field ):
138+ errors .append (f"{ prefix } .{ field } is required." )
139+
140+ issuer = attestation .get ("issuer" )
141+ if isinstance (issuer , str ) and trusted_issuers and issuer not in trusted_issuers :
142+ warnings .append (f"{ prefix } .issuer is not listed in trusted_issuers: { issuer } ." )
143+
144+ subject = attestation .get ("subject" )
145+ if subject is not None and (not isinstance (subject , str ) or not subject ):
146+ errors .append (f"{ prefix } .subject must be a non-empty string if provided." )
147+ elif agent_did and subject and subject != agent_did :
148+ warnings .append (f"{ prefix } .subject does not match agent.did." )
149+
150+ result = attestation .get ("result" )
151+ if result and result not in VALID_ATTESTATION_RESULTS :
152+ errors .append (f"{ prefix } .result must be one of: { ', ' .join (sorted (VALID_ATTESTATION_RESULTS ))} ." )
153+
154+ expires_at = attestation .get ("expires_at" )
155+ if expires_at :
156+ _validate_date_field (f"{ prefix } .expires_at" , expires_at , errors , warnings )
157+ else :
158+ warnings .append (f"{ prefix } .expires_at is not set." )
159+
160+ if not attestation .get ("credential_status" ):
161+ warnings .append (f"{ prefix } .credential_status is not set. Revocation checks may not be possible." )
162+
163+
79164def _validate_oidc (manifest : dict [str , Any ], errors : list [str ], warnings : list [str ]) -> None :
80165 oidc = manifest .get ("oidc" )
81166 if oidc is None :
@@ -331,6 +416,18 @@ def _validate_runtime(manifest: dict[str, Any], errors: list[str], warnings: lis
331416 if not runtime .get (field ):
332417 warnings .append (f"runtime.{ field } is not true." )
333418
419+ if runtime .get ("require_valid_attestations" ):
420+ if not manifest .get ("attestations" ):
421+ errors .append ("runtime.require_valid_attestations is true but attestations is empty." )
422+ if not manifest .get ("trusted_issuers" ):
423+ errors .append ("runtime.require_valid_attestations is true but trusted_issuers is empty." )
424+
425+ if runtime .get ("require_valid_attestations" ) and not runtime .get ("deny_if_attestation_expired" ):
426+ warnings .append ("runtime.deny_if_attestation_expired is not true while valid attestations are required." )
427+
428+ if runtime .get ("require_valid_attestations" ) and not runtime .get ("deny_if_credential_revoked" ):
429+ warnings .append ("runtime.deny_if_credential_revoked is not true while valid attestations are required." )
430+
334431
335432def _validate_audit (manifest : dict [str , Any ], errors : list [str ], warnings : list [str ]) -> None :
336433 audit = manifest .get ("audit" , {})
@@ -365,3 +462,17 @@ def _validate_expiry(value: Any, warnings: list[str], errors: list[str]) -> None
365462
366463 if expiry < date .today ():
367464 warnings .append ("agent.expires_at is in the past." )
465+
466+
467+ def _validate_date_field (field : str , value : Any , errors : list [str ], warnings : list [str ]) -> None :
468+ try :
469+ if isinstance (value , date ):
470+ parsed = value
471+ else :
472+ parsed = datetime .strptime (str (value ), "%Y-%m-%d" ).date ()
473+ except ValueError :
474+ errors .append (f"{ field } must be YYYY-MM-DD." )
475+ return
476+
477+ if parsed < date .today ():
478+ warnings .append (f"{ field } is in the past." )
0 commit comments