Skip to content

test(core): raise identity.ts mutation score - #100

Merged
Mearman merged 5 commits into
mainfrom
fix/mutation-identity-coverage
Sep 14, 2026
Merged

Mearman merged 5 commits into
mainfrom
fix/mutation-identity-coverage

Conversation

@Mearman

@Mearman Mearman commented Sep 14, 2026

Copy link
Copy Markdown
Member

Closes progress on agent-comms#64 (the identity.ts slice).

Adds regression tests for gaps the Stryker baseline run found in src/core/identity.ts, raising its mutation score from 77.55% (70 killed, 6 timeout, 22 survived, out of 98 covered mutants) to 92.86% (85 killed, 6 timeout, 7 survived).

New tests:

  • CERTIFICATE_VALIDITY_MS pinned to its exact millisecond value (365 * 24 * 60 * 60 * 1000).
  • Certificate encodes as X.509 v3 (context tag [0] EXPLICIT INTEGER 2).
  • Serial number's leading byte varies across identities instead of collapsing to a fixed value.
  • notBefore/notAfter checked against an exact, fully zero-padded date under a faked system clock (year/month/day/minute all single-digit), closing gaps that only show up when a date component needs padding.
  • rawPublicKeyFromPrivateKey throws for a non-EC private key (RSA) whose JWK export has no x/y coordinates.
  • Basic Constraints extension is marked critical (DER BOOLEAN TRUE byte sequence).
  • Certificate's base64 body is wrapped at 64 characters per line.

The remaining 7 survivors are mutants I traced and confirmed are mathematically equivalent given this file's actual call graph, not fixable without a contrived test:

  • derLength's long-form (0x81) branch: every real DER length in this file's certificate structure is either under 128 bytes or over 256 bytes (TBSCertificate content is a fixed 287 bytes; the outer certificate content is bounded to ~376-378 bytes by ECDSA-P256 signature encoding), so the [128, 255] branch is never reached with a true condition by any real invocation.
  • derIntegerBytes's leading-zero-padding branch: both real callers (the version field's Buffer.from([2]) and the serial number, whose first byte is explicitly masked to [1, 127] before encoding) never produce a byte with the high bit set, so this branch's condition is always false in practice.
  • fingerprintDer's null-match fallback: hex.match(/.{2}/g) on a SHA-256 hex digest (always exactly 64 characters) can never return null — this is a mathematical certainty regardless of input, since 64 is even and the pattern always matches fully.
  • derToCertificatePem's b64 ?? [] fallback: the certificate DER is never empty, so der.toString("base64").match(/.{1,64}/g) never returns null.

None of these are reachable through this file's actual, current call graph, so a test built to reach them would be testing hypothetical input the code never receives.

Test plan:

  • pnpm typecheck
  • pnpm lint
  • pnpm test (full suite, 345 passing)
  • Full local Stryker run on src/core/identity.ts: 92.86% (85 killed, 6 timeout, 7 survived, out of 98 covered)

Nothing previously asserted the constant's actual numeric value, so any
operator swap in its 365 * 24 * 60 * 60 * 1000 derivation (e.g. one *
becoming /) produced a wrong validity period with every existing test
still passing.
Nothing verified the version field encoded as v3 (context tag [0]
EXPLICIT INTEGER 2), so a mutation truncating it to an empty INTEGER
(effectively v1) went undetected despite the Subject Alternative Name
and Basic Constraints extensions being illegal on a v1 certificate.

Separately, the DER-INTEGER minimal-padding fix-up for the serial
number's leading byte had an unguarded assignment path that forces
that byte to a fixed value regardless of its randomly generated
content; nothing previously checked that serial numbers actually vary
across identities.
Fakes the system clock to a date where every UTCTime component (year
mod 100, month, day, minute) is below 10, then checks the certificate's
parsed validity window against the exact expected instants. Every
existing test ran against the real "now", whose year-mod-100 and
month/day/hour/minute happen to already be two digits most of the
time, so a missing zero-pad or an inverted getUTCMonth() + 1 went
unnoticed as long as no field happened to need padding.
rawPublicKeyFromPrivateKey is exported public API, so a caller can
genuinely pass a non-EC private key (RSA, Ed25519) whose JWK export
carries no x/y coordinates. Nothing exercised that guard before, so
its error message could be mutated to an empty string without any
test noticing.
Neither the extension's critical flag nor the certificate body's
64-char line wrapping had any assertion: a flipped critical bit or a
one-char-per-line PEM body both still parse and load fine via
tls.createServer, so the existing loadability-only checks never
noticed either regression.
@Mearman
Mearman marked this pull request as ready for review September 14, 2026 07:08
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review Completed 2026-09-14T07:11:40.186211Z c63ad84 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@Mearman
Mearman merged commit 5027e62 into main Sep 14, 2026
6 checks passed
@Mearman
Mearman deleted the fix/mutation-identity-coverage branch September 14, 2026 07:38
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 2.21.4 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant