Summary
isExpired in packages/vc/src/verification/is-expired.ts returns false (not expired) when a credential's expirationDate field is present but cannot be parsed as a valid date. This is fail-open behavior for a security-critical check: a credential with a malformed expiration date silently passes all downstream verification.
Affected file
packages/vc/src/verification/is-expired.ts lines 16–18
Steps to reproduce
import { isExpired } from "@agentcommercekit/vc"
const credential = {
"@context": ["https://www.w3.org/2018/credentials/v1"],
type: ["VerifiableCredential"],
issuer: { id: "did:example:issuer" },
issuanceDate: "2020-01-01T00:00:00Z",
expirationDate: "not-a-date", // malformed
credentialSubject: {},
}
console.log(isExpired(credential)) // false credential passes as non-expired
Root cause
// is-expired.ts
if (isNaN(expirationDate.getTime())) {
// Expiration date is invalid, so we consider the credential not expired
return false // ← fail-open
}
An invalid expirationDate should be a hard failure, not a silent pass. This is already handled correctly in is-revoked.ts (which is fail-closed for the same case on status list credentials):
// is-revoked.ts correct behavior
if (Number.isNaN(expiresAt)) {
throw undetermined("...has an unreadable expirationDate")
}
The inconsistency between the two is itself evidence that isExpired's behavior is unintentional.
Impact
- Any credential with a garbage
expirationDate value passes verifyParsedCredential's expiry check without error.
- Callers using
isExpired directly (outside the JWT verification path) get no signal that the credential's validity period cannot be established.
Proposed fix
if (isNaN(expirationDate.getTime())) {
throw new CredentialExpiredError("Credential has an unreadable expirationDate")
}
Or alternatively, return true (treat unparseable as expired) to fail closed without throwing.
Summary
isExpiredinpackages/vc/src/verification/is-expired.tsreturnsfalse(not expired) when a credential'sexpirationDatefield is present but cannot be parsed as a valid date. This is fail-open behavior for a security-critical check: a credential with a malformed expiration date silently passes all downstream verification.Affected file
packages/vc/src/verification/is-expired.tslines 16–18Steps to reproduce
Root cause
An invalid
expirationDateshould be a hard failure, not a silent pass. This is already handled correctly inis-revoked.ts(which is fail-closed for the same case on status list credentials):The inconsistency between the two is itself evidence that
isExpired's behavior is unintentional.Impact
expirationDatevalue passesverifyParsedCredential's expiry check without error.isExpireddirectly (outside the JWT verification path) get no signal that the credential's validity period cannot be established.Proposed fix
Or alternatively, return
true(treat unparseable as expired) to fail closed without throwing.