Skip to content
Open
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
13 changes: 13 additions & 0 deletions packages/ack-pay/src/payment-request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,17 @@ describe("isPaymentRequest", () => {
it("returns false if given a non-object", () => {
expect(isPaymentRequest(1)).toBe(false)
})

it("returns false when decimals is negative", () => {
// decimals uses minValue(0) — a validation that rejects negative values.
// The previous toMinValue(0) silently clamped -6 to 0 instead of rejecting it.
expect(
isPaymentRequest({
...validPaymentRequest,
paymentOptions: [
{ ...validPaymentRequest.paymentOptions[0], decimals: -6 },
],
}),
).toBe(false)
})
})
2 changes: 1 addition & 1 deletion packages/ack-pay/src/schemas/valibot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const urlOrDidUri = v.union([v.pipe(v.string(), v.url()), didUriSchema])
export const paymentOptionSchema = v.object({
id: v.string(),
amount: v.union([v.pipe(v.number(), v.integer(), v.gtValue(0)), v.string()]),
decimals: v.pipe(v.number(), v.integer(), v.toMinValue(0)),
decimals: v.pipe(v.number(), v.integer(), v.minValue(0)),
currency: v.string(),
recipient: v.string(),
network: v.optional(v.string()),
Expand Down
11 changes: 9 additions & 2 deletions packages/vc/src/verification/is-expired.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,16 @@ describe("isExpired", () => {
expect(isExpired(credential)).toBe(false)
})

it("handles invalid date strings gracefully", () => {
it("returns true for an unparseable expiration date (fail-closed)", () => {
const credential = buildCredential("invalid-date")
// An unparseable date must not silently pass as not expired; failing
// closed is safer than failing open for a security-critical check.
expect(isExpired(credential)).toBe(true)
})

expect(isExpired(credential)).toBe(false)
it("returns true for an empty-string expiration date (fail-closed)", () => {
const credential = buildCredential("")
// Empty string is present but unparseable — fail closed.
expect(isExpired(credential)).toBe(true)
})
})
9 changes: 6 additions & 3 deletions packages/vc/src/verification/is-expired.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@ import type { W3CCredential } from "../types"
* @returns `true` if the credential is expired, `false` otherwise
*/
export function isExpired(credential: W3CCredential): boolean {
if (!credential.expirationDate) {
if (credential.expirationDate === undefined) {
return false
}

const expirationDate = new Date(credential.expirationDate)

if (isNaN(expirationDate.getTime())) {
// Expiration date is invalid, so we consider the credential not expired
return false
// Expiration date is present but unparseable — fail closed and treat as
// expired. Returning false (not expired) would allow a credential with a
// garbage date to pass all downstream checks, which is the wrong default
// for a security-critical guard.
return true
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

return expirationDate < new Date()
Expand Down