|
31 | 31 | # half-confidence finding with nothing anywhere saying it was not understood. |
32 | 32 | FINDING_CONFIDENCES = ("certain", "firm", "tentative") |
33 | 33 |
|
| 34 | +# What happened after an accepted login (RM-118). The report read this from |
| 35 | +# prose — a `; authenticated action:` marker in evidence and title phrases — so a |
| 36 | +# probe rewording flipped a client-facing line with every test green. "" |
| 37 | +# means the finding records no login. |
| 38 | +AUTHENTICATED_ACTION_PERFORMED = "performed" # the RoE-gated action ran and returned output |
| 39 | +AUTHENTICATED_ACTION_ATTEMPTED = "attempted" # permitted; tried or ran, completed nothing |
| 40 | +AUTHENTICATED_ACTION_NOT_PERMITTED = "not_permitted" # the RoE withheld it |
| 41 | +AUTHENTICATED_ACTION_NOT_APPLICABLE = "not_applicable" # no action exists for the service |
| 42 | +AUTHENTICATED_ACTIONS = ( |
| 43 | + AUTHENTICATED_ACTION_PERFORMED, |
| 44 | + AUTHENTICATED_ACTION_ATTEMPTED, |
| 45 | + AUTHENTICATED_ACTION_NOT_PERMITTED, |
| 46 | + AUTHENTICATED_ACTION_NOT_APPLICABLE, |
| 47 | +) |
| 48 | + |
34 | 49 | REQUIRED_FINDING_FIELDS = ( |
35 | 50 | "finding_id", |
36 | 51 | "title", |
|
73 | 88 | # opened, so the caveat the letter promised was recorded and not shown. |
74 | 89 | "severity_source", |
75 | 90 | "backport_status", |
| 91 | + # Read by name for §3.5.2 (RM-118). |
| 92 | + "authenticated_action", |
76 | 93 | "affected_assets", |
77 | 94 | "evidence_items", |
78 | 95 | "evidence_artifacts", |
@@ -149,6 +166,7 @@ class FlatFinding: |
149 | 166 | status: str = "" |
150 | 167 | severity_source: str = "" |
151 | 168 | backport_status: str = "" |
| 169 | + authenticated_action: str = "" |
152 | 170 | # Verbatim carrier for anything the contract does not name. Not a dumping |
153 | 171 | # ground: it is what makes "the contract does not know this field" different |
154 | 172 | # from "this field did not exist", which is the distinction the whitelists |
@@ -214,6 +232,9 @@ def validate_flat_finding(payload: Any) -> list[str]: |
214 | 232 | errors.append(f"severity is invalid: {payload['severity']}") |
215 | 233 | if "confidence" in payload and payload["confidence"] not in FINDING_CONFIDENCES: |
216 | 234 | errors.append(f"confidence is invalid: {payload['confidence']}") |
| 235 | + action = payload.get("authenticated_action", "") |
| 236 | + if action and action not in AUTHENTICATED_ACTIONS: |
| 237 | + errors.append(f"authenticated_action is invalid: {action}") |
217 | 238 |
|
218 | 239 | return errors |
219 | 240 |
|
@@ -251,3 +272,20 @@ def flat_finding_from_dict(payload: dict) -> FlatFinding: |
251 | 272 |
|
252 | 273 | extra = {key: value for key, value in payload.items() if key not in _KNOWN_FIELDS} |
253 | 274 | return FlatFinding(**known, extra=extra, _present=present) |
| 275 | + |
| 276 | + |
| 277 | +def read_archived_findings(findings: Any) -> Any: |
| 278 | + """Read an archived findings list through the contract (RM-062). |
| 279 | +
|
| 280 | + Each finding goes through `flat_finding_from_dict`, so a schema or version |
| 281 | + this build does not understand raises instead of being read under the wrong |
| 282 | + assumptions. The round trip loses nothing, and the source key order is kept so |
| 283 | + the stored content does not change. `None` (no findings key) passes through. |
| 284 | + """ |
| 285 | + if findings is None: |
| 286 | + return None |
| 287 | + out = [] |
| 288 | + for payload in findings: |
| 289 | + restored = flat_finding_from_dict(payload).to_dict() |
| 290 | + out.append({key: restored[key] for key in payload}) |
| 291 | + return out |
0 commit comments