Skip to content

Security: enforce signed credential not-before times #151

Description

@Tristan-Stoltz-ERC

Finding

A cross-component credential-validity audit found that several signed/MACed credentials carried issued_at but did not enforce it during verification:

  • daemon operator tokens verified expiry but did not reject now < issued_at;
  • operator-agent sessions verified expiry but did not reject now < issued_at;
  • sovereign-admin operator-session validity ignored issued_at;
  • the browser-side agent-session freshness precheck ignored issued_at.

Because issued_at is already covered by the credential signature/MAC, ignoring it wastes a meaningful authenticated time bound. Under wall-clock rollback, an already-issued credential could be treated as valid before its own signed issuance time.

Security contract

Credential validity is uniformly:

issued_at <= now < expires_at

This complements #147's exclusive expiry boundary. The left edge is inclusive; the right edge is exclusive.

Authored fix

Commit 6 in the ordered pre-Hyperstream security stack:

  • commit: 56f4e66c8eaef461898377b936937e6dae9325c0
  • subject: fix(auth): enforce signed credential not-before times
  • V22.1 base tree: 46464f1ad514bc55e9d1c4b7b0cdc95048f6d1a3
  • cumulative six-commit result tree: 8beafa0941dcaeb3d965f08a14f30c08660a9305

Changes:

  1. daemon operator-token verification rejects now < issued_at and still rejects now >= expires_at;
  2. operator-agent session verification adds a not-yet-valid error path and enforces the same interval;
  3. sovereign-admin OperatorSession::is_valid requires issued_at <= now && now < expires_at;
  4. browser agent-session freshness refuses/clears sessions used before signed issuance time;
  5. Rust boundary tests cover the exact not-before instant;
  6. scripts/check-auth-not-before-semantics.py enforces the cross-component invariant;
  7. six source-policy mutation tests prove any component dropping the left-bound check is rejected;
  8. the existing expiry checker was updated so the two guards coexist and independently enforce both halves of the interval.

Canonical cumulative qualification

Pre-Hyperstream Security Hardening v2:

  • archive: xenia-pre-hyperstream-security-hardening-v2-20260826.tar.gz
  • SHA-256: ad1211763841631414c743e4c60711c13703bc31e276e44644386fada2dcc5e9
  • ordered commits: 6
  • cumulative result tree: 8beafa0941dcaeb3d965f08a14f30c08660a9305

Fresh replay of that exact final archive from untouched V22.1:

  • deterministic archive self-rebuild: PASS
  • manifest / qualification ledger: PASS
  • patches: 6/6 PASS
  • exact result tree: PASS
  • signed not-before checker: PASS
  • exclusive-expiry checker: PASS
  • complete Python validator suite: 50/50 PASS
  • Xenia policy / CODEOWNERS / secure-default / Cargo-boundary / runtime-risk / reviewed-unsafe gates: PASS
  • Python + shell syntax: PASS
  • git diff --check: PASS
  • replay worktree: CLEAN

Native Rust/Nix qualification remains mandatory and is currently blocked by #143. Do not merge on static evidence alone.

Integration ordering

This belongs in the same pre-Hyperstream security baseline as #145, #146, #147, and the durability-first operator-admin fix. If that baseline lands first, Hyperstream V24.4's live-base freshness gate should intentionally invalidate the old V22.1 qualification and require a fresh rebase/requalification.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions