Skip to content

Commit d14e956

Browse files
ndycodeneilclaude
authored
test: fix flaky #163 email-masking property test (real assertion bug) (#179)
The "masked email never contains the full local part" property test was seed-flaky in CI. Root cause is a fragile assertion, not the masker: `expect(masked.includes(local)).toBe(false)` false-positives in two classes fast-check eventually generates — 1. local part repeats in the preserved domain: "abc@abc.com" -> "ab***@abc.com" (the kept domain legitimately contains "abc"); 2. local part contains the mask character itself: "a.*@a.aa" -> "a.***@a.aa" (kept "a." + a "*" from the "***" mask reconstructs the local "a.*"). Neither is a real leak — the masker correctly reveals at most the first 2 local chars. Replaced the substring heuristic with an assertion of the exact masking contract: masked local === first min(2,len) chars + "***", checked on the local segment only (domain is preserved verbatim by design). Added deterministic regression cases covering both tricky classes. Verified seed-independent: a 200k-run local probe of the exact-shape invariant passes; full suite green (2538 passed, 1 skipped) with no flake. No production code change. Co-authored-by: neil <neil@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7e39abd commit d14e956

2 files changed

Lines changed: 32 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- `codex-doctor --fix` no longer fails silently when a stored credential is genuinely dead: a failed token refresh now reports `N account(s) need re-login` and points the user at `opencode auth login`, instead of leaving an all-dark pool unrepaired with no surfaced cause. (#171)
1414
- `codex-health` now surfaces the same recovery diagnostics as `codex-doctor` (read-only): it flags accounts blocked only by a stale cooldown/rate-limit (pointing at `codex-doctor --fix`) and disabled token-source duplicates (pointing at `codex-remove`), and includes `staleRecoverableSlots` / `disabledDuplicateSlots` in JSON output. This addresses the part of the issue that named `codex-health` explicitly. (#171)
1515
- Caller-cancellation during a retry/backoff wait now surfaces as a proper `AbortError` carrying the caller's `signal.reason`, instead of an opaque `new Error("Aborted")` that dropped the cause. This aligns the retry-wait path with the fetch path and the `isAbortError` convention in `lib/codex-usage.ts`, improving diagnosability of the `Error: Aborted` symptom. (#176)
16+
- Fixed a flaky email-masking property test (#163) that intermittently failed CI: the assertion used a fragile `masked.includes(local)` substring check that false-positived when the local part repeats in the preserved domain (`abc@abc.com`) or contains the mask character itself (`a.*@a.aa``a.***`). It now asserts the exact masking contract (masked local = first ≤2 chars + `***`), with deterministic regression cases. No production code change.
1617

1718
### Security
1819
- Bumped `hono` to 4.12.26, resolving a high-severity Windows `serve-static` path traversal via encoded backslash (`%5C`) and four moderate advisories (GHSA-88fw-hqm2-52qc, GHSA-j6c9-x7qj-28xf, GHSA-rv63-4mwf-qqc2, GHSA-wgpf-jwqj-8h8p, GHSA-wwfh-h76j-fc44). This also clears the transitive `@openauthjs/openauth` advisory inherited via hono.

test/property/redaction.property.test.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,51 @@ const arbOpaqueSecret = fc
2424
.filter((s) => s.trim().length >= 8);
2525

2626
describe("DEEP STRESS: email masking invariant (#163)", () => {
27-
it("masked email never contains the full local part", () => {
27+
it("masked email reveals at most the first 2 local chars", () => {
2828
fc.assert(
2929
fc.property(arbEmail, (email) => {
3030
const masked = maskEmailForDisplay(email);
3131
if (!masked) return true;
3232
const atIndex = email.indexOf("@");
3333
const local = email.slice(0, atIndex);
34-
// The full local part must not appear verbatim unless it is <= 2 chars
35-
// (the masker intentionally keeps up to the first 2 chars).
36-
if (local.length > 2) {
37-
expect(masked.includes(local)).toBe(false);
38-
}
39-
// Domain is preserved for distinguishability.
4034
const domain = email.slice(atIndex);
35+
// The masker contract is: masked local = first min(2, len) chars + "***".
36+
// We assert that EXACT shape rather than a substring check, because a
37+
// substring check is fragile when the local part itself contains the
38+
// mask characters (e.g. "a.*@a.aa" -> "a.***@a.aa", where the local
39+
// "a.*" reappears as the kept "a." plus a "*" from the mask). The real
40+
// security property is that no more than the first 2 local chars survive.
41+
const maskedAtIndex = masked.indexOf("@");
42+
const maskedLocal =
43+
maskedAtIndex >= 0 ? masked.slice(0, maskedAtIndex) : masked;
44+
const expectedRevealed = local.slice(0, Math.min(2, local.length));
45+
expect(maskedLocal).toBe(`${expectedRevealed}***`);
46+
// Domain is preserved verbatim for distinguishability.
4147
expect(masked.endsWith(domain)).toBe(true);
4248
return true;
4349
}),
4450
{ numRuns: 500 },
4551
);
4652
});
4753

54+
// Deterministic regression for the two classes that previously made the
55+
// property test seed-flaky: (1) the local part also occurs in the domain
56+
// (abc@abc.com), and (2) the local part contains the mask character itself
57+
// (a.*@a.aa -> a.***). Both broke a naive substring check; the real contract
58+
// is that the masked local reveals at most the first 2 chars. (#163)
59+
it("reveals at most the first 2 local chars in tricky cases", () => {
60+
const cases: Array<[string, string]> = [
61+
["abc@abc.com", "ab***@abc.com"],
62+
["tom@tom.io", "to***@tom.io"],
63+
["xyz@sub.xyz.org", "xy***@sub.xyz.org"],
64+
["a.*@a.aa", "a.***@a.aa"],
65+
["a@a.com", "a***@a.com"],
66+
];
67+
for (const [email, expected] of cases) {
68+
expect(maskEmailForDisplay(email)).toBe(expected);
69+
}
70+
});
71+
4872
it("resolveDisplayEmail with masking enabled never returns the raw email (len>3 local)", () => {
4973
fc.assert(
5074
fc.property(arbEmail, (email) => {

0 commit comments

Comments
 (0)