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('')).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
|
||||
// cannot become an XSS or a downgrade vector when shown to members.
|
||||
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 {
|
||||
u = new URL(url)
|
||||
return new URL(url).protocol === 'https:'
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
return u.protocol === 'https:'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user