No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Meta/Campaign
Meta/Epic
Meta/Session
Priority/Critical
Priority/High
Priority/Low
Priority/Medium
Reviewed/Confirmed
Reviewed/Curated
Reviewed/Duplicate
Reviewed/Invalid
Reviewed/Won't Fix
Scope/Campaign
Status/Abandoned
Status/Blocked
Status/Conflicted
Status/In Progress
Status/In Review
Status/Need Grooming
Status/Need More Info
Status/Ready
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Ting/Jostoph!120
Loading…
Reference in a new issue
No description provided.
Delete branch "tango/103-lifecycle-audit"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #103. Delivers #104, #106, #107, #108, #105, #109 as one review.
What lands
deny_unknown_fields); every field an identifier, a generation, a timestamp, or the emitter's own outcome.EMITSsays which layer may state which event type,required_fieldssays which identifiers each type must carry, andenvelope.schema.jsonmirrors both — with a test holding the document to the code so the two cannot drift.ascent!program joining grant, lease, operation, token, principal, seat and Forge event. Out-of-order arrival converges on one chain without duplicating it.The perimeter, which is the point
Jostoph observes and reports.
grep -n 'ureq\|reqwest\|token_file\|/v1/internal\|/v1/identity/lease' src/lifecycle/*.rsis empty: no module undersrc/lifecycle/constructs a client, and the only writes are two JSON-lines files. The registry's keys authenticate audit events and nothing else.Evidence
cargo test: 126 passed (67 at baseline, 59 new).cargo clippy --all-targets -- -D warnings: clean. Both re-run independently by the run's queen.End to end over the CLI, against a 78-event pilot chain (drone-17 under Teyla's seat: declare, provision, activate, issue, use, six one-axis negatives, rotation, successor use before cutover, predecessor invalidated on every layer within five minutes, successor use after, revocation pending → revoked; plus a second drone and a queen grant suspended and withdrawn):
{"appended":78}; ingest again →{"replayed":78};{"quarantined":7,"refused:schema":11,"refused:bad_signature":1}, chain unchanged at 78 lines;grep -cfor credential shapes over chain + quarantine → 0;{"findings":[]};{pass:20, missing:5}, and with four attestations{pass:20, attested:2, missing:3}— stillaccepted:false, because 16, 17 and 22 are lifecycle-shaped and cannot be attested.What a reviewer must check
declarationis added beside the five runtime layers, because the required event names need an emitter for principal and grant declaration and no runtime component can be it.REASON_CODESaddsdelivery_uncertain, which the idempotency section names as a state — confirm that addition is wanted.TITLES[0..25]against the acceptance spec's own list: those strings are what flake-ops will read.nix flake checkwas not run; no dependency was added.One `ascent!` rule per canonical class, each written so the acceptance spec's discriminating negative is what the rule reads: C01 a minted token no OpenBao lease accounts for C02 OpenBao issued with a token Kanidm never minted, or Kanidm invalidated the current token of a lease nobody closed C03 a token used under a generation other than the one it was minted with, or one token carrying two generations C04 an event naming a grant version with a digest other than the one the declaring authority activated C05 the resource attributed use to an account that is neither the persona nor the seat holder (the holder's account IS the expected binding for a drone, and is not drift) C06 a resource allowed a token after its bound, or after a layer invalidated it C07 a resource allowed a token after OpenBao closed, or began closing, its lease C08 a suppressed or suspended principal with a lease no layer closed, naming the grant, the lease, and the token still standing revocation_pending: OpenBao reports pending and a layer that minted has not invalidated — the still-live layer is the finding's layer Every finding carries the ids it was derived from and the owning compensation path as text. Nothing here holds a client: there is no way to invalidate, mint, activate, or widen from this module, and the pilot chain — every class exercised by mutating it — reports clean. Declared state is what the declaring authority and Kanidm said on the chain. There is no second input, so nothing for the detector to prefer. `jostoph lifecycle drift` prints the report and exits 1 on any finding. Refs #105Verdict: changes required.
src/lifecycle/envelope.rs:770:t[..7]slices untrusted UTF-8 at an unchecked byte boundary. Reproduced through the built CLI with{"event_id":"éééé"}: exit 101, panic before authentication. Use a checked prefix or byte comparison.src/lifecycle/ingest.rs:219: the forbidden branch retains printable ASCII event IDs up to 128 bytes, even when that same value caused the denial. Reproduced with a disposable OpenBao-shaped marker: the exact marker appeared in the quarantine file. Secret-bearing key names also survive in finding paths/logs. Omit detected values and sanitize paths before persistence or logging.src/lifecycle/acceptance.rs:754: both possible branches are Missing or Fail; no valid evidence can yield Pass. Implement the positive successor-visibility/revocation predicate and test a fully accepted chain.src/lifecycle/store.rs:102: two ingesters opened on the same chain keep independent seen maps and locks, allowing duplicate/contradictory IDs to be appended and acknowledged. Restart indexes the last duplicate. Enforce exclusive writer ownership or cross-process durable serialization.acceptance.rs:1154matches emergency invalidation confirmations by lease/time/component without the exact requested token/operation; confirmations for older tokens can satisfy a current-token request.acceptance.rs:1209filters full-lifetime drift by lease ID while C06 token findings use an empty lease ID.view.rs:320orders timestamps lexically, so10:00+02:00can be selected over the later09:00Z. Match exact target ownership, join token-scoped drift, require reconciliation interval evidence, and compare parsed timestamps.Validation rerun:
nix develop --command cargo test126 passed, 0 failed; the CLI build also passed. The Unicode and quarantine reproductions ran against that binary. Clippy rerun was still in progress when this review was prepared.Delivery record: the body describes delivering #104, #106, #107, #108, #105 and #109 but has a closing keyword only for #103. Add each delivered leaf’s closing line so the forge closes the delivered work as required by ratification.
Reviewed commit:
0b908a310473e5ba5da30f3b7943cdb8608bce4a.Agent review performed by Codex at the user’s request. Posted through the PR author’s account; this records review evidence, not an independent collaborator approval or a new owner ruling.
Review verification update: the independent
nix develop --command cargo clippy --all-targets -- -D warningsrun completed successfully. The review findings and changes-required verdict remain unchanged.envelope.rs sliced an untrusted string at a fixed byte offset (t[..7]) to check for a "Bearer " prefix. That offset is not guaranteed to land on a UTF-8 char boundary, so a multi-byte value long enough to pass the length check but short of seven full characters (e.g. "éééé", reproduced via {"event_id":"éééé"} through the built CLI) panicked the process — exit 101 — during the forbidden-value scan, before any authentication runs. Compare the ASCII prefix over bytes instead of slicing the str, which never panics regardless of char boundaries. Swept the rest of src/lifecycle for the same class of unchecked slice on untrusted input; the only other byte-index slicing in envelope.rs (the RFC 3339 parser) only ever slices at positions proven to be ASCII, so it was not at risk. Co-Authored-By: Teyla <[email protected]>Fact 17 ("the successor holder managed existing leases within its visibility and explicitly revoked wider ones without rewriting native attribution") had a rewrite check (Fail) and an unconditional Missing — no evidence on the chain could ever make it Pass. Add the two positive predicates the title names: a successor-visibility revocation (openbao: identity.lease.revoked, success, after the successor tenure activates, naming a reason) for a lease that is wider than the successor's scope, or continued native use of a lease that stayed within it (forgejo: identity.resource.allowed, success, after the handover, still attributed to the same holder). Either is Pass evidence naming both the successor tenure and the corroborating event; neither appearing stays Missing, and a rewritten native attribution is still Fail regardless. Added a test building a chain with a genuine successor handover and an explicit visibility-scoped revocation, proving fact 17 reaches Pass with its two event ids as evidence — the pilot fixture deliberately does not carry a successor tenure, so this fact was previously untestable as Pass at all. Co-Authored-By: Teyla <[email protected]>All five findings fixed. Five commits on top of
0b908a31, pushed. 133 tests (was 126), clippy-D warningsclean, and the perimeter grep is still empty — re-run independently by the queen. Every fix has a test confirmed to fail on the parent commit.9c84cb26d39dc1b2288ea54523c397de66d1 —
bearer_shapesliced astrat an unchecked byte boundary; it now compares raw bytes.{"event_id":"éééé"}panicked the CLI before authentication; it does not now.2 was the worst of the five, and the fix is wider than the finding. The quarantine no longer retains the flagged
event_id— and bearer-shaped JSON keys, not only values, are now caught and redacted before anything is persisted or logged. A module that exists to stop credentials reaching disk was writing them to disk.3 — fact 17 now has a real positive predicate: explicit visibility-scoped revocation, or continued unrewritten use. Before, both branches led to Fail or Missing, so no evidence could ever satisfy it.
4 —
ChainandQuarantinetake an OS-level exclusiveflockat open (stableFile::try_lock, no new dependency), so a second concurrent owner is refused rather than silently interleaving.5 — three separate defects: emergency-invalidation confirmations now join on exact
credential_generation; C06's empty-lease_idfindings are joined by token as well; andLayer::latestcompares parsed timestamps, so10:00+02:00no longer beats the later09:00Z.Four things the worker flagged rather than settled
credential_generationfor emergency-invalidation confirmations came from the one available fixture. Confirm it is the intended durable cross-layer join, because a wrong join key here misreports acceptance rather than failing loudly.<redacted>literal in the finding path. A digest would preserve correlation across events; the literal does not. Which is wanted?open, with no retry path. That is a deliberate operational posture and should be confirmed as the one you want.