harden: isSafeDuesUrl requires literal https:// so app and DB CHECK agree
Edge URLs like https:example.com parse to protocol https: but lack //, which the migration-050 CHECK rejects -- the admin would see a confusing save failure instead of a clear validation error. Require the prefix so the Zod boundary matches the data layer. Fail-closed either way; no security impact.
This commit is contained in:
@@ -48,4 +48,12 @@ describe('isSafeDuesUrl', () => {
|
|||||||
expect(isSafeDuesUrl('paypal.me/x')).toBe(false)
|
expect(isSafeDuesUrl('paypal.me/x')).toBe(false)
|
||||||
expect(isSafeDuesUrl('')).toBe(false)
|
expect(isSafeDuesUrl('')).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('rejects https without // (stays in lockstep with the DB CHECK)', () => {
|
||||||
|
// these parse to protocol https: but lack the // the DB CHECK requires
|
||||||
|
expect(isSafeDuesUrl('https:example.com')).toBe(false)
|
||||||
|
expect(isSafeDuesUrl('https:/x')).toBe(false)
|
||||||
|
// bare scheme with no host
|
||||||
|
expect(isSafeDuesUrl('https://')).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -26,11 +26,15 @@ export function isDuesLinkPlatform(p: string): p is DuesLinkPlatform {
|
|||||||
// (javascript:, data:, mailto:, etc.) are rejected so an admin-entered value
|
// (javascript:, data:, mailto:, etc.) are rejected so an admin-entered value
|
||||||
// cannot become an XSS or a downgrade vector when shown to members.
|
// cannot become an XSS or a downgrade vector when shown to members.
|
||||||
export function isSafeDuesUrl(url: string): boolean {
|
export function isSafeDuesUrl(url: string): boolean {
|
||||||
let u: URL
|
// Require the literal https:// prefix AND a parseable URL. The prefix check
|
||||||
|
// keeps this in lockstep with the DB CHECK (`url ~* '^https://'`, migration
|
||||||
|
// 050) so the app and data layers never disagree (e.g. `https:example.com`
|
||||||
|
// parses to https: but lacks `//`, which the DB rejects); the parse check
|
||||||
|
// rejects a bare `https://` with no host.
|
||||||
|
if (!/^https:\/\//i.test(url)) return false
|
||||||
try {
|
try {
|
||||||
u = new URL(url)
|
return new URL(url).protocol === 'https:'
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return u.protocol === 'https:'
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user