2929_SHA1_RE = re .compile (r"^[0-9a-f]{40}$" )
3030_ARTIFACT_DIGEST_RE = re .compile (r"^sha256:[0-9a-f]{64}$" )
3131_ARTIFACT_NAME_RE = re .compile (r"^pert-weekly-period-lock-[1-9][0-9]*$" )
32+ _REPOSITORY_RE = re .compile (r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$" )
33+ MAX_LOCK_BYTES = 64 * 1024
34+ MAX_SNAPSHOT_BYTES = 256 * 1024
35+ MAX_MANIFEST_BYTES = 64 * 1024
36+ MAX_SNAPSHOT_DEPTH = 16
37+ MAX_SNAPSHOT_OBJECT_KEYS = 64
38+ MAX_SNAPSHOT_LIST_LENGTH = 256
39+ MAX_SNAPSHOT_STRING_LENGTH = 4096
40+ MAX_SOURCE_ARTIFACTS = 64
3241_SNAPSHOT_KEYS = frozenset (
3342 {
3443 "snapshot_version" ,
4756 {
4857 "bundle_version" ,
4958 "artifact_name" ,
59+ "repository" ,
5060 "source_run_id" ,
5161 "source_attempt" ,
5262 "workflow_sha" ,
@@ -83,19 +93,29 @@ def _safe_int(value: object, code: str) -> int:
8393 return value
8494
8595
86- def _snapshot_tree (value : object ) -> object :
96+ def _snapshot_tree (value : object , depth : int = 0 ) -> object :
97+ if depth > MAX_SNAPSHOT_DEPTH :
98+ raise _error ("bundle_snapshot_depth_exceeded" )
8799 if isinstance (value , Mapping ):
100+ if len (value ) > MAX_SNAPSHOT_OBJECT_KEYS :
101+ raise _error ("bundle_snapshot_object_oversized" )
88102 result : dict [str , object ] = {}
89103 for key , item in value .items ():
90104 if type (key ) is not str :
91105 raise _error ("bundle_snapshot_invalid" )
92106 if key in result :
93107 raise _error ("bundle_snapshot_duplicate_key" )
94- result [key ] = _snapshot_tree (item )
108+ result [key ] = _snapshot_tree (item , depth + 1 )
95109 return result
96110 if type (value ) is list :
97- return [_snapshot_tree (item ) for item in value ]
98- if value is None or type (value ) is bool or type (value ) is str :
111+ if len (value ) > MAX_SNAPSHOT_LIST_LENGTH :
112+ raise _error ("bundle_snapshot_list_oversized" )
113+ return [_snapshot_tree (item , depth + 1 ) for item in value ]
114+ if value is None or type (value ) is bool :
115+ return value
116+ if type (value ) is str :
117+ if len (value ) > MAX_SNAPSHOT_STRING_LENGTH :
118+ raise _error ("bundle_snapshot_string_oversized" )
99119 return value
100120 if type (value ) is int :
101121 return _safe_int (value , "bundle_snapshot_invalid" )
@@ -127,7 +147,7 @@ def _validate_snapshot(value: object) -> dict[str, object]:
127147 _string (value ["source_snapshot_digest" ], re .compile (r"^[0-9a-f]{64}$" ), "bundle_snapshot_digest_invalid" )
128148 _string (value ["source_provenance" ], re .compile (r"^[a-z][a-z0-9_]*$" ), "bundle_snapshot_provenance_invalid" )
129149 artifacts = value ["source_artifacts" ]
130- if type (artifacts ) is not list or not artifacts :
150+ if type (artifacts ) is not list or not artifacts or len ( artifacts ) > MAX_SOURCE_ARTIFACTS :
131151 raise _error ("bundle_snapshot_artifacts_invalid" )
132152 for item in artifacts :
133153 if not isinstance (item , dict ) or set (item ) != _ARTIFACT_KEYS :
@@ -142,7 +162,10 @@ def _validate_snapshot(value: object) -> dict[str, object]:
142162def _snapshot_bytes (value : Mapping [str , object ]) -> bytes :
143163 try :
144164 tree = _snapshot_tree (value )
145- return _canonical_json (_validate_snapshot (tree ), "bundle_snapshot_invalid" )
165+ result = _canonical_json (_validate_snapshot (tree ), "bundle_snapshot_invalid" )
166+ if len (result ) > MAX_SNAPSHOT_BYTES :
167+ raise _error ("bundle_snapshot_oversized" )
168+ return result
146169 except WeeklyBundleError :
147170 raise
148171 except (TypeError , ValueError , UnicodeError , OverflowError , RecursionError ):
@@ -152,6 +175,8 @@ def _snapshot_bytes(value: Mapping[str, object]) -> bytes:
152175def _parse_snapshot (raw : bytes ) -> dict [str , object ]:
153176 if type (raw ) is not bytes :
154177 raise _error ("bundle_snapshot_invalid" )
178+ if len (raw ) > MAX_SNAPSHOT_BYTES :
179+ raise _error ("bundle_snapshot_oversized" )
155180
156181 def pairs (items : list [tuple [str , object ]]) -> dict [str , object ]:
157182 result : dict [str , object ] = {}
@@ -170,7 +195,7 @@ def reject_constant(_: str) -> None:
170195 raise
171196 except (UnicodeError , json .JSONDecodeError , TypeError , ValueError , RecursionError ):
172197 raise _error ("bundle_snapshot_invalid" ) from None
173- validated = _validate_snapshot (value )
198+ validated = _validate_snapshot (_snapshot_tree ( value ) )
174199 if _canonical_json (validated , "bundle_snapshot_invalid" ) != raw :
175200 raise _error ("bundle_snapshot_noncanonical" )
176201 return validated
@@ -215,13 +240,15 @@ def __post_init__(self) -> None:
215240@dataclass (frozen = True , slots = True )
216241class BundleContext :
217242 run_id : str
243+ repository : str
218244 workflow_sha : str
219245 producer_sha : str
220246 artifact : ArtifactEvidence
221247 period_lock : PoliticalEventWeeklyPeriodLockV1
222248
223249 def __post_init__ (self ) -> None :
224250 _string (self .run_id , _RUN_ID_RE , "bundle_run_invalid" )
251+ _string (self .repository , _REPOSITORY_RE , "bundle_repository_invalid" )
225252 _string (self .workflow_sha , _SHA1_RE , "bundle_workflow_invalid" )
226253 _string (self .producer_sha , _SHA1_RE , "bundle_producer_invalid" )
227254 if type (self .artifact ) is not ArtifactEvidence :
@@ -239,10 +266,23 @@ def __post_init__(self) -> None:
239266@dataclass (frozen = True , slots = True )
240267class RerunContext :
241268 original : BundleContext
269+ current_run_id : str
270+ current_repository : str
271+ current_workflow_sha : str
242272 current_run_attempt : int
243273
244274 def __post_init__ (self ) -> None :
245- if type (self .current_run_attempt ) is not int or self .current_run_attempt != 2 :
275+ if (
276+ type (self .original ) is not BundleContext
277+ or type (self .current_run_attempt ) is not int
278+ or self .current_run_attempt != 2
279+ or type (self .current_run_id ) is not str
280+ or self .current_run_id != self .original .run_id
281+ or type (self .current_repository ) is not str
282+ or self .current_repository != self .original .repository
283+ or type (self .current_workflow_sha ) is not str
284+ or self .current_workflow_sha != self .original .workflow_sha
285+ ):
246286 raise _error ("bundle_rerun_attempt_invalid" )
247287
248288
@@ -275,6 +315,7 @@ def _manifest_bytes(context: BundleContext, lock_bytes: bytes, snapshot_bytes: b
275315 {
276316 "bundle_version" : BUNDLE_VERSION ,
277317 "artifact_name" : context .artifact .name ,
318+ "repository" : context .repository ,
278319 "source_run_id" : context .run_id ,
279320 "source_attempt" : 1 ,
280321 "workflow_sha" : context .workflow_sha ,
@@ -315,6 +356,16 @@ def verify_period_bundle(bundle: BundleWire, expected: BundleContext) -> BundleW
315356 raise _error ("bundle_input_invalid" )
316357 if bundle .artifact != expected .artifact :
317358 raise _error ("bundle_artifact_mismatch" )
359+ if type (bundle .lock_bytes ) is not bytes or len (bundle .lock_bytes ) > MAX_LOCK_BYTES :
360+ raise _error ("bundle_lock_oversized" if type (bundle .lock_bytes ) is bytes else "bundle_lock_invalid" )
361+ if type (bundle .snapshot_bytes ) is not bytes or len (bundle .snapshot_bytes ) > MAX_SNAPSHOT_BYTES :
362+ raise _error (
363+ "bundle_snapshot_oversized" if type (bundle .snapshot_bytes ) is bytes else "bundle_snapshot_invalid"
364+ )
365+ if type (bundle .manifest_bytes ) is not bytes or len (bundle .manifest_bytes ) > MAX_MANIFEST_BYTES :
366+ raise _error (
367+ "bundle_manifest_oversized" if type (bundle .manifest_bytes ) is bytes else "bundle_manifest_invalid"
368+ )
318369 try :
319370 parsed_lock = parse_period_lock_bytes (bundle .lock_bytes )
320371 except PeriodLockError :
@@ -338,6 +389,8 @@ def verify_period_bundle(bundle: BundleWire, expected: BundleContext) -> BundleW
338389def _parse_manifest (raw : bytes ) -> dict [str , object ]:
339390 if type (raw ) is not bytes :
340391 raise _error ("bundle_manifest_invalid" )
392+ if len (raw ) > MAX_MANIFEST_BYTES :
393+ raise _error ("bundle_manifest_oversized" )
341394
342395 def pairs (items : list [tuple [str , object ]]) -> dict [str , object ]:
343396 result : dict [str , object ] = {}
@@ -361,6 +414,13 @@ def pairs(items: list[tuple[str, object]]) -> dict[str, object]:
361414def verify_rerun_context (bundle : BundleWire , rerun : RerunContext ) -> BundleWire :
362415 if type (rerun ) is not RerunContext :
363416 raise _error ("bundle_rerun_context_invalid" )
417+ if (
418+ rerun .current_run_id != rerun .original .run_id
419+ or rerun .current_repository != rerun .original .repository
420+ or rerun .current_workflow_sha != rerun .original .workflow_sha
421+ or rerun .current_run_attempt != 2
422+ ):
423+ raise _error ("bundle_rerun_attempt_invalid" )
364424 return verify_period_bundle (bundle , rerun .original )
365425
366426
0 commit comments