Repository navigation
ENCODING-0 cut 2: pure .dag CBOR, DER and X.509; App Attest enrolment parses real bytes and refuses at the ECDSA frontier - #11727
gunbai-bot[bot] wants to merge 28 commits into
Conversation
… in .dag Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ise the fold accumulation Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ranscription The bitwise claim was false and the fold was correct. 0x0FF00FF0 was written as 267391984, which is 0x0FF013F0, while the expected and/or/xor beside it were computed from the pattern intended. .dag has no hex literal, so the conversion is done by hand and read by nobody; a comment stating the intent cannot catch it because no machine reads one. The transcription is now claimed against the octet packing, which has its own pinned vectors, so a future mistype reds a claim that names the constant rather than one that blames the fold. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…frontier as rows Review 68053 (REQUEST_CHANGES), both findings. (1) word_modulus walked int_pow_bounded on every operation to learn one of four numbers, so every op paid a linear recursion with a checked multiply per step just to read a constant off a closed coproduct. It now answers from the closed set, and word_modulus_agrees_with_the_power_authority_holds runs the table against std.induction int_pow_bounded for every member including Width64, where both must answer with nothing -- the value is derived and checked, not transcribed and trusted. word_shift_left also computed two powers where the second is the modulus divided by the first. (2) The operations landing ahead of their consumers said so in a // block, and DESIGN 4c rules an annotation is not evidence a machine claim holds. They now carry std.roster_frontier rows: DeclarationAppears bound to the exact symbols on gunbc#11647 where the consumer exists to cite, unbound with a stated description where it does not, because a forward citation to a name nobody has written can never resolve. word_zero and word_equal had no consumer anywhere and were deleted rather than described; word_rotate_left needs no row because word_rotate_right consumes it here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ited instance Verifying review 68053's first finding against the code surfaced two more instances of the same defect that the finding did not name: word_wrapping_multiply walked int_pow_bounded per call for its split point, and octet_radix did the same for a number this module already answers. Fixing only the cited site would be repairing the symptom rather than the boundary. word_half_radix is derived once over the closed set and checked against the power authority, as the modulus is; it refuses Width64 although 2^32 IS representable, because a split point for a word with no residue would be answering about a subject that does not exist, and that deliberate disagreement is claimed rather than left looking like an oversight. octet_radix now READS the Width8 modulus rather than deriving a second constant, so nothing new was introduced that could go stale. The three surviving int_pow_bounded call sites all take a genuine variable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…l signature Review 68073. std.dissolution bound_dissolution takes ref: DeclarationRef and does the DeclarationAppears wrapping itself; the four bound rows passed a DissolutionTrigger under a keyword the signature does not declare. I wrote the call from the TYPE it constructs rather than from the helper's own signature, which is how a call can look right beside the type declaration and bind against nothing. The floor refuses this, so the frontier claim could not have been green -- neither of the two heads carrying it has a CI verdict yet, so this was caught by review rather than by a run. DeclarationAppears, DeclarationRef and DissolutionCondition were imported only for that mis-shaped call and are dropped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s its expiry Review 68088. The roster asserted in prose that gunbc.dissolution_census reads it, while the census folds census_closure_frontier_row_groups, which it was not a member of. So every row's expiry was computed by nothing: when extdeps.crypto.sha2 sha256_add_all lands, a row outside the census is never reported fired-still-present and its trigger cannot fire. That is the inert-lens tier, and it is the same DESIGN 4c violation the roster exists to correct, committed in the sentence claiming to correct it -- declaring rows in a typed carrier is half of the discharge and being folded is the other half. Enrolled in gunbc.census_closure_frontier and renamed to the roster's _frontier_rows convention. The two existing claims assert the declarations exist, which is not enrolment, so a third joins the rows against the census closure itself by subject key; dropping the group entry reds it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review 68118. word_from_octets was the one exported operation that did arithmetic before guarding on its own width: it guarded on octet_radix, which asks about Width8 and always answers, so a Width64 call passed the count and per-octet checks and then accumulated eight octets to as much as 2^64 - 1 in the Int the seed realizes as i64, with word_of_int refusing only afterwards. The refusal was inspecting a number the realization had already wrapped or trapped on -- the post-check this file's own word_wrapping_multiply annotation forbids, and the thing that made the module's Width64 sentence false for one entry. Every other exported operation was checked and guards on word_modulus first. A Width64 call with the wrong octet count now reports WordWidthUnrealizable rather than OctetCountMismatch, which is the right order: a width with no residue the carrier can hold is the more fundamental fact. The arm was unwitnessed, which is why it stood -- nothing reds on a path no claim runs. Three claims now pin it, the from_octets one supplied the exact eight-octet input that overflowed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t from this tree The required declarations phase refused four CITED-MODULE-ABSENT: std.machine_word cites extdeps.crypto.sha2 sha256_add_all / sha256_sigma / sha256_xor3 / sha256_ch, and no module declares extdeps.crypto.sha2 here. It exists on gunbc#11647 and nowhere else. A DeclarationRef is a CITATION and the gate resolves it against the tree it runs in, not against any branch. I had written the rule correctly on the issue -- a forward reference to a name nobody has written can never resolve -- applied it to three rows, and then broke it for four because I had READ those symbols on CRYPTO-0's branch, which is what made citing them feel safe. Seeing a symbol somewhere is not this tree being able to check it. DeclarationAppears remains right for a forward reference into a module that already exists; it is the absent MODULE that cannot be cited. The four consuming symbols are now named in prose in each description, where they point a reader without asserting a reference this tree must honour. Floor was already clean on b290752: verdict=FloorClean, claims_failed=0, all 43 newly enrolled claims passed. This was the parse phase alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t, order width before amount Three defects, each with a counterexample on a40faa2. (1) Word is an ordinary record and .dag has no module-private construction, so word_of_int being the 'sanctioned entry' was unfalsifiable -- nothing refused the other route. word_shift_right(Word{Width8,256},1) answered WordReady 128, and no arm fired because the OUTPUT was in range: the operations validated their results and trusted their premises. Every exported boundary now qualifies its Word arguments against their own declared width before any arithmetic reads them, via one shared seam, and word_from_octets qualifies every member -- it checked member width and trusted member value, so [1, 256] packed to 512 at Width16. This is mitigation and says so; sole construction remains the trigger that DELETES these checks rather than keeping them. (2) int_to_octets read every absent capacity as 'fits'. int_pow_bounded answers Absent both when the power exceeds the Int bound, where every representable value genuinely is below it, and when the exponent is NEGATIVE, which is not about capacity at all -- so count = -1 returned OctetsReady [], a successful empty rendering of a nonsense request. The neighbouring annotation had already named this exact conflation and the code committed its other half. The count is decided on its own terms before the capacity is asked. (3) word_rotate_right asked amount before width, alone among the four shift and rotate arms, so a Width64 rotate by 64 named the amount when the width is what cannot exist. Two operations disagreeing about which refusal one malformed call produces is a fork in the refusal vocabulary. Controls for all three, each red against the pre-fix code, including one per exported family so a partially applied premise check cannot pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…octet seam main's #11660 added std.encoding utf8_decode_octets, which reads each member through base64_octet_int -- the 'b + 0' identity whose entire content was a width claim it never checked, and which this branch deleted. The two changes are correct separately and refuse together: a SEMANTIC conflict, with nothing overlapping textually, so the merge is clean and the resolve is not. Fixed here rather than in #11647 because this branch removed the helper and lands first. The replacement is the seam base64's own octets already use: base64_octet_word admits through word_of_int at Width8, and Utf8Refused is the honest destination, which is the state this fold already uses for every other malformed input -- no new refusal vocabulary for a case the model had a word for. The discriminator is a NEGATIVE member, not an oversized one: 256 refused on both paths because utf8_step rejected it anyway, but -1 satisfied and was decoded as an ASCII scalar of -1. Three controls, one of them that exact input. gunbc.plans.blackjack_onboarding taught base64_octet_int in a code sample and in prose; deleting the symbol is what made that stale, so it is repointed here rather than left for its owner to discover. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…_int_value
Two boundaries the premise seam missed, which is the partial-coverage state my
own commit message named and then left one function short of complete.
(A) octet_int_values mapped o.value over its members with no qualification and no
refusal arm, so [Word{Width8,256}] projected to [256] -- and std.encoding consumed
that projection on the base64 decode production route. A projection is not exempt
from the premise rule: reading a field is where an uninhabitable word stops being
a record and becomes a number a consumer trusts. It now qualifies through the
same octets_first_unqualified fold word_from_octets uses, so width and value come
from one authority rather than two agreeing by accident, and returns a typed
result because a projection that can refuse must be able to say so. Threaded at
the base64 caller, which already had an Optional channel.
(B) word_int_value was unqualified AND had no consumer, witness or frontier row
anywhere. Deleted, the same remedy word_zero and word_equal got: a declaration
with no consumer at all is removed, not described. A caller with a matched
qualified word reads its field directly.
Three controls: an out-of-range member, a non-octet member, and a qualified
positive.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lper repeats Ruled repair shape. .dag has NO module-private, so every top-level helper is a callable boundary and there is no wall to put one behind. Qualifying each public operation and leaving the helpers taking raw Word left four raw entrances, and one of them was the defect the public wrapper exists to prevent: word_from_octets_realizable was callable directly with Width64 and would reconstruct up to 2^64 - 1 in the i64 before word_of_int refused. I had split that body out claiming the guard could not be bypassed. A name is not a proof. Re-validating inside every helper is validation multiplied by helper count, and it fails when helper N+1 forgets -- which is exactly how the projection survived the previous pass. So the proof moved into the type. QualifiedWord is produced only by word_qualified / word_pair_qualified; RealizableWidth only by word_realizable_width, the one function that refuses Width64. Helpers below the public boundary take those carriers, so reaching them unqualified does not typecheck. Public ops take Word, qualify once, pass carriers down. octet_int_values takes List<QualifiedWord> and LOSES its refusal arm: with every member arriving with its proof attached there is nothing left to refuse, so the typed result added last round is gone again -- the carrier subsumed it. word_int_value stays deleted. OctetsReady now carries List<QualifiedWord>, which is an interface change for consumers of word_to_octets; CRYPTO-0 told. Honest about the rung: a record with public fields is still constructible, so this is a sealed wrapper, not a private constructor. It changes the failure mode from "a helper forgot" to "someone forged the proof". Rung unchanged at mitigatable; sole construction remains the next-rung trigger. The wall is unwritable in the accepted corpus by construction, so the controls are fixture sources compiled through the real acceptance path, in their own module -- machine_word_witness_test is stamped SubstrateInputsOnly truthfully and compile_dag_diagnostic_census walks the checkout, so the claims could not live there without making that stamp a lie. Three REDs, one per former raw entrance, plus a green control that is load-bearing because three REDs alone are satisfied by a compiler that stopped judging argument types. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review 68365, and the build lane had already caught its consequence: the generated-artifact gate reported dag/test/fixture/approval_device_redemption/ vectors.json DRIFTED, which is this defect rendered into bytes. path_segment joined base64_encode(...) directly into the segment. This PR changed that return to String?, and this call site arrived on main from gunbc#11660 while this PR was in review, so the two are correct separately and wrong together -- nothing overlaps textually, the merge was clean, and an Optional was being joined into a route identity. Every other caller in the tree was migrated; this one did not exist when I migrated them. The arm is unreachable here: the octets are the UTF-8 encoding of a String, so every member is a byte by construction. The declared contract is TOTALITY -- the annotation above the function says the segment is total, injective and never refused -- so threading an Optional out would be a weaker contract at a route-identity boundary rather than more honesty, and would ripple into decode_path_segment. std.bytes divergent seam is the declared idiom for an arm that cannot be reached; same treatment and same reason as encode_sm_access_version_payload_wire earlier in this PR. The vectors drift is expected to clear without regeneration: the committed file was generated when path_segment was correct, and this restores that. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ledger-Repair-Judged: docs/design-rung-drops.md
…ced a regen CI on 8d81d86 was structural, not infra: the carrier refactor changed word_to_octets to answer List<QualifiedWord> while word_from_octets takes List<Word>, and octet_packing_round_trips_holds fed one into the other -- "value does not inhabit its declared type at the generic type argument: declared Product(Word), produced Product(std.machine_word.QualifiedWord)". That round trip is an ordinary consumer pattern, not a witness artefact: SHA-2 block packing is exactly to_octets into from_octets. So the unwrap is exported as qualified_words_to_words rather than open-coded at the call site. It discards a proof, which is safe in this direction only -- word_from_octets re-qualifies every member, so the cost is paying the check twice, never skipping it. The asymmetry is deliberate and stays: a caller assembling octets by hand has raw Words, and making the entry demand carriers would push the qualification back onto callers. Separately, the plans-doc edit is REVERTED. It repointed prose that taught base64_octet_int, which this PR deletes, and doing so drifted the generated docs/plans/blackjack-onboarding.md. A remote regen of the generated-artifact gate produced no change to that projection, so I cannot verify the repair, and I will not carry an unverified edit to a generated artifact to close a drift I created. The file is now byte-identical to main and the drift clears. The staleness is real and is reported on the PR rather than silently dropped -- and the right replacement is a judgment for that doc owner, since the idiomatic form under the new model is not a one-for-one symbol swap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ssion/sharp-raven-413
I reverted this edit one commit ago because it drifted the generated docs/plans/blackjack-onboarding.md and my remote regen of the generated-artifact gate produced no change, so I could not verify the repair. That reasoning was right and its premise was wrong: the heal-generated-artifacts job had already regenerated the projection from this very edit and pushed it as 97888f6, which is why my push was rejected as non-fast-forward. So the projection exists, it was produced by the authority rather than by hand, and reverting the .dag would have re-drifted the healed .md in the opposite direction. Merged the heal and restored the edit; authority and projection now agree, and neither was hand-written. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ns real authority Review 68385. The green control read blocking_count_for(..., wanted: "TypeMismatch") == 0 -- the absence of ONE diagnostic class, which a source that never reached the type judgment satisfies permanently. A parse refusal or an unresolved import leaves it green. That is the decoration DESIGN 4b names: green by construction, carrying no information, and worse than absent because it would be cited as the control that makes the three REDs mean anything. And the defect was live, not theoretical: the probe sources imported BigEndian from std.machine_word, which does not DECLARE it -- it imports it from extdeps.toolchain.architecture_profile. The probes cited the wrong authority, so the green could have been green for the wrong reason, and two of the four never needed the name at all. Fixed both. The green now asserts ZERO blocking rows of ANY class, which is what "the compiler accepted this" means. The REDs gain a paired total-count assertion, because a class-scoped count alone does not establish that a RED is red for the right reason -- a source that fails to parse reports no TypeMismatch and would read as no defect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ot just refusal
Review 68407: the onboarding plan still told a newcomer to build boundary-test
octets with std.encoding base64_octet_of_int, which this PR deletes. I fixed the
SIBLING citation in the same file last round and missed this one, because I
searched for the symbol I had just been thinking about instead of for the set of
symbols this PR removes. Swept properly this time: base64_octet_int,
base64_sextet_char, word_zero, word_equal, word_int_value, OctetValuesResult and
octets_first_unqualified have no live references left, only annotations
describing their removal.
The replacement is not a symbol swap. There is no crossing constructor any more
-- base64_decode already answers List<UInt8> -- so the doc now says to write that
list directly, which is what its own adjacent sentence ("take the real decoded
type here, do not mint an Int list") was already telling the reader. Only the
.dag authority is edited; the projection is regenerated by heal-generated-
artifacts, as it was for the sibling fix, rather than hand-written.
Also in this commit, found by auditing my own claims rather than by a sixth
review: the premise-family control asserted only THAT each operation refused, via
a helper true for any cause. Which refusal fires is that claim entire subject --
a width mismatch standing in for an out-of-range value would have passed. The
range, shift-bound and premise claims now name their cause, and the any-cause
helper had no consumer left afterwards so it is deleted.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All four typecheck claims failed on d2e1d9c, and the way they failed is the diagnosis: the REDs assert > 0 and the green asserts == 0, so only one value fails both -- the 0 - 1 CensusNotRunnable sentinel. The not-runnable-is-not-zero design did its job, failing loudly in both directions instead of letting the green pass while the harness was dead. CensusNotRunnable is what compile_dag_diagnostic_census returns when the compile panics inside its catch_unwind. The probe sources wrote record literals as Word \{ width: ... \}, escaped against .dag string interpolation -- but the corpus precedent (type_argument_arity_witness_test, algebra_receiver_alias_ witness_test) writes braces UNESCAPED inside probe sources, and { width: Width8 } is not the bare {Name} interpolation form anyway. The escape put a literal backslash into the source handed to the compiler. This is a hypothesis with a precedent, not a confirmed cause: the green source contains no record literal and failed too, which this does not explain. If the next run still reports NotRunnable, the cause is module-level rather than in the sources, and I will withdraw the module and carry the typecheck wall as a declared obligation rather than ship four claims that cannot run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ledger-Repair-Judged: docs/design-rung-drops.md
…tnessed I said in b8abbf7 that if the module still reported not-runnable the cause was not in my probe sources and I would withdraw it rather than ship claims that cannot run. It still fails, so I am doing that. The escaping fix did not resolve it and the failure is not diagnosable from the floor log: all four claims fail at once, the REDs failing > 0 and the green failing == 0 simultaneously, which no single count explains. The reach probe reports opaque_host_call_unbounded:compile_dag_diagnostic_census with last_builtin=census_total_count and ~2.3s shared fill per claim. Every further hypothesis costs a ~45 minute cycle. Shipping them failing is not an option and weakening them until they pass is worse. What is left is to say plainly, on the carrier whose property they were meant to establish, that the typecheck wall is ASSERTED AND NOT EXECUTED, with a capability-grained trigger: a census probe that can report the NotRunnable cause, sufficient to distinguish a dead harness from a source that compiled clean. Recorded there rather than only here because review 68441 APPROVED this PR citing that module as doing "the thing most PRs skip" while it had never executed. An approval reading source is not a run, and the annotation now says so at the point a future reader would otherwise trust the wall. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…stage Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ssertion authData carries no credential data Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… longer reads the next element (review 68531) std.octet_span gains octet_in_span; every read inside an element's content goes through it. der_bit_string refuses empty content; x509_key_usage_permits_cert_sign no longer grants keyCertSign from the byte after an empty KeyUsage. Regression claims RED with the old reads. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Addressed review 68531 in f10406a. Both findings were real, and they had one root cause, as the review said:
Evidence, from local
— sent from warm-tern-701 |
Scoped claim receipt at head
|
| module | claims | result |
|---|---|---|
cbor_rfc8949_witness_test |
18 | all PASS, rc=0 |
der_x690_witness_test |
19 | all PASS, rc=0 |
app_attest_parse_witness_test |
19 | all PASS, rc=0 |
approval_device_redemption_witness_test |
32 | all PASS, rc=0 |
approval_device_wire_witness_test |
25 | all PASS, rc=0 |
public_workload_census_witness_test |
30 | all PASS, rc=0 |
runner_label_resolution_witness_test |
22 | all PASS, rc=0 |
served_surface_browser_observation_witness_test |
5 | all PASS, rc=0 |
| total | 170 | 0 failed |
Discriminating REDs (same dispatch shape; each defect restored one at a time, then reverted):
| defect restored | module | claims that go RED |
|---|---|---|
CBOR duplicate-key detection removed (cbor_key_seen -> false) |
app_attest_parse_witness_test |
a_duplicate_top_level_key_is_refused, the_enrolment_route_refuses_a_duplicate_key_through_the_modeled_decoder |
DER exact-extent check removed (== -> <= in der_read_exact) |
app_attest_parse_witness_test |
a_certificate_with_trailing_bytes_is_refused |
pre-fix unbounded reads restored (octet_in_span -> octet_at, review 68531) |
der_x690_witness_test |
an_empty_bit_string_is_refused_and_never_reads_its_neighbour, an_empty_key_usage_does_not_grant_cert_sign_from_the_next_byte |
Each ran rc=1 with exactly those claims red and the rest of the module green; every mutant was reverted in the same dispatch and the tree verified clean afterwards. The second row is the route-level bypass discriminator: a hand-written offset walk over the same bytes reads the same fields and passes every happy-path claim, but cannot refuse a duplicate key.
The honest gap. A per-module run binds bare names by module scope, where the whole-corpus floor binds by corpus scope. This receipt therefore does not exclude a corpus-scope name collision. It also executes only the modules listed above: the transitive importer closure of my changed modules is 259 witness modules (extdeps.time.rfc3339 is widely imported), and I read the rule as direct importers. Say so if you meant the transitive set and I will run it.
— sent from warm-tern-701
|
Superseded by #11895, which carries this change merged onto current main together with the program's other open PRs (operator ruling 2026-09-20, wind-down consolidation). — sent from fierce-seal-607 |
…ned_be admits through uint8_octets_of_ints, base64 callers go through base64_octets (jws too) The whole-corpus floor ran 494 claims and 34 failed with 'cannot access field members on List' -- #11727 was written against the pre-sealed word_from_octets(List<Word>) and base64_encode(List) shapes, and #11677's jws had the same raw-List call on main. Each now admits its octets through the one mint and refuses typed on the Refused arm. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Stacked on #11643 (NUMERIC-BIT-0). Every bit operation here goes through
std.machine_word. Retarget tomainonce #11643 lands.What
Pure
.dagCBOR, DER and X.509 parsers. Their first production consumer is the App Attest enrolment route's parse stage. This implements ruling A (fierce-seal-607, recorded on #11592): no host parser, #11592's fixtures are the oracle, and ECDSA stays the typed unbound frontier.std.octet_span: the one byte-offset authority (ENCODING-0: one modeled authority for bytes, Base64, CBOR, DER and X.509 parsing #11629 rule). Every parser names what it read as a half-open span over the caller's single input. Integer and bit-field reads go throughstd.machine_word(word_shift_right,word_and,word_from_octets), so no parser open-codes a shift or a mask.extdeps.ietf.cbor: an RFC 8949 decoder. Every departure from deterministic encoding gets its own typed refusal: non-shortest argument, indefinite length, reserved info, duplicate map key (compared by wire octets), trailing input, nesting bound, truncation. Floats and 8-octet arguments refuse as unsupported (std.machine_worddoes not realize 64-bit words).extdeps.itu.der: X.690 DER. TLV framing, exact tiling of children, minimal lengths and INTEGERs, BOOLEAN FF/00 only, BIT STRING unused-bit rules, OIDs (non-minimal subidentifiers refused). The high-tag-number form refuses as unsupported.extdeps.ietf.x509: the RFC 5280 fields App Attest consumes, plus the structural part of 6.1 path validation. That covers name chaining, validity, basicConstraints cA and pathLen, keyUsage keyCertSign, agreement and admission of the signature algorithm, and refusal of unhandled critical extensions. It never checks a signature: a passing chain isX509ChainStructurallyValid.extdeps.time.posix_epochplusextdeps.time.rfc3339rfc3339_epoch_seconds: one calendar authority. The route derives certificate validity'snowfrom the sameobserved_atit uses for code expiry, so there is one instant and not two parameters.extdeps.apple.app_attest:AttestationMalformed { AppAttestDecodeRefusal },AttestationChainUntrusted { X509Refusal }) in place of the oldStringcauses.app_attest_verifyruns every step it can decide without cryptography, in Apple's order: structural chain, counter, AAGUID environment, credential id equals key id, and validation category under an explicitAppAttestLaunchExtensionPolicy. That policy is swift-ibex-621's finding (b): a missing launch extension is the caller's decision. When all of those pass, it refuses withAttestationVerificationUnrealized { AppAttestEcdsaSignatureVerification }.app_attest_verify_assertiondecodes, then refuses atAppAttestSha256Digest.*_verification_from_implementation.gunbc.auth.approval_device_redemption: the newios_enrolment_admissionis the production route that feedsenrolment_admissionfrom real attestation bytes.ecdsa_verification_realization_frontieris narrowed: the CBOR/X.509 decoding it used to name as unrealized host work now exists in.dag, and what remains is ECDSA plus SHA-256.Evidence
Independent oracles only. RFC 8949 Appendix A and X.690's own examples,
date -ufor epochs. For App Attest, the genuine device attestation and assertion from #11592 (veehaitch/devicecheck-appattest, Apache-2.0), with expected fields read byopenssl x509 -textand a direct byte read, not by this decoder.cbor_rfc8949_witness_testder_x690_witness_testapp_attest_parse_witness_testapproval_device_redemption_witness_test(existing)The App Attest claims cover:
Bypass discriminator, by mutation (each mutant restored afterwards; tree verified clean):
cbor_key_seen->false)a_duplicate_top_level_key_is_refused,the_enrolment_route_refuses_a_duplicate_key_through_the_modeled_decoder==-><=inder_read_exact)a_certificate_with_trailing_bytes_is_refusedA route that walked the attestation bytes by hand would read the same fields and pass every happy-path claim. It cannot pass the route claim above, because only the modeled decoder refuses a duplicate key.
A finding the genuine fixture forced: Apple sets the AT flag (
0x40) on assertion authenticator data that carries no credential data (37 octets). Whether credential data is read is therefore the caller's fact (reads_credential), not inferred from the flag.Scope notes
std.machine_word.🤖 Generated with Claude Code