Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions scripts/check-stageable-secrets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,12 @@ function describeDetail(detail) {
if (!detail || detail.kind !== 'jwt') return '';
const claims = renderClaims(detail.claims);
let expiry;
if (detail.noExpiry) expiry = 'NO EXPIRY CLAIM';
else if (detail.expired) expiry = `EXPIRED ${detail.expiresAt}`;
else expiry = `live until ${detail.expiresAt} (${detail.daysRemaining} days)`;
// Everything here is what the token SAYS about itself. Decoding `exp` proves
// neither that the token still works nor that it was revoked, so the wording
// stays scoped to the claim (codexmb, post-merge review of #123).
if (detail.noExpiry) expiry = 'no expiry claim; validity and revocation unverified';
else if (detail.expired) expiry = `expiry claim: ${detail.expiresAt} (past)`;
else expiry = `expiry claim: ${detail.expiresAt} (in ${detail.daysRemaining} days)`;
return `claims: ${claims} | ${expiry}`;
}

Expand Down
10 changes: 7 additions & 3 deletions scripts/scan-history-for-secrets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,13 @@ function printHuman(report) {
// vocabulary we defined; everything else is presence and length. A
// JWT's claims are free text chosen by whoever made the token.
out(` claims: ${renderClaims(f.detail.claims)}`);
if (f.detail.noExpiry) out(' expiry: NO EXPIRY CLAIM - this token does not stop working');
else if (f.detail.expired) out(` expiry: EXPIRED ${f.detail.expiresAt}`);
else out(` expiry: LIVE until ${f.detail.expiresAt} (${f.detail.daysRemaining} days remaining)`);
// The exp claim is the token's own statement, not evidence about it.
// "LIVE until" read as proof of validity and "does not stop working" as
// proof of permanence; neither survives the fact that we never asked an
// issuer anything (codexmb, post-merge review of #123).
if (f.detail.noExpiry) out(' expiry: no expiry claim; validity and revocation unverified');
else if (f.detail.expired) out(` expiry: expiry claim ${f.detail.expiresAt} (past); revocation unverified`);
else out(` expiry: expiry claim ${f.detail.expiresAt} (in ${f.detail.daysRemaining} days); validity and revocation unverified`);
}
out(` blob: ${f.blob}`);
out(` commit: ${f.commit ?? '(not attributable to a single commit)'}`);
Expand Down
2 changes: 1 addition & 1 deletion src/secret-patterns.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export function renderClaims(claims) {
*
* Claims yes, token never - and only the constrained parts of the claims, see
* the note above. The point is a report that is safe to share: it says what the
* token IS (a service_role key for project x, live until 2036) without
* token CLAIMS to be (a service_role key for project x, exp 2036) without
* republishing anything the token's author chose to write.
*
* Returns null for an eyJ-prefixed string that is not actually a JWT, which is
Expand Down
14 changes: 11 additions & 3 deletions test/scan-history-for-secrets.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -630,9 +630,17 @@ test('an expired JWT is reported and distinguished from a live one', () => {
// conversation, not a non-event.
assert.equal(report.findings.length, 3);
const human = runScanner([dir]);
assert.match(human.stderr, /EXPIRED/);
assert.match(human.stderr, /LIVE until/);
assert.match(human.stderr, /NO EXPIRY CLAIM/);
// The report may describe what the token CLAIMS, never what it IS. We never
// asked an issuer anything, so validity and revocation are both unknown.
assert.match(human.stderr, /expiry claim [^\n]*\(past\)/);
assert.match(human.stderr, /expiry claim [^\n]*\(in \d+ days\)/);
assert.match(human.stderr, /no expiry claim; validity and revocation unverified/);

// The wording this replaced asserted facts the scanner cannot establish:
// "LIVE until" read as proof the token still works, and "does not stop
// working" as proof it never will. Assert they cannot come back.
assert.doesNotMatch(human.stderr, /live until/i);
assert.doesNotMatch(human.stderr, /does not stop working/i);
});

test('a malformed eyJ-prefixed string does not crash the scanner', () => {
Expand Down
Loading