Skip to content

bug: isExpired returns false for unparseable expirationDate (fail open) #148

Description

@Sertug17

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.

Metadata

Metadata

Assignees

No one assigned

    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