7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
122 lines
4.0 KiB
JavaScript
122 lines
4.0 KiB
JavaScript
// url-validator.js attaches to window.URLValidator
|
|
import '@js/security/url-validator.js';
|
|
|
|
const { URLValidator } = window;
|
|
|
|
describe('URLValidator', () => {
|
|
describe('isUnsafeScheme', () => {
|
|
it('detects javascript: scheme', () => {
|
|
expect(URLValidator.isUnsafeScheme('javascript:alert(1)')).toBe(true);
|
|
});
|
|
|
|
it('detects data: scheme', () => {
|
|
expect(URLValidator.isUnsafeScheme('data:text/html,<h1>hi</h1>')).toBe(true);
|
|
});
|
|
|
|
it('is case-insensitive', () => {
|
|
expect(URLValidator.isUnsafeScheme('JAVASCRIPT:void(0)')).toBe(true);
|
|
});
|
|
|
|
it('returns false for safe schemes', () => {
|
|
expect(URLValidator.isUnsafeScheme('https://example.com')).toBe(false);
|
|
});
|
|
|
|
it('returns false for empty/null input', () => {
|
|
expect(URLValidator.isUnsafeScheme('')).toBe(false);
|
|
expect(URLValidator.isUnsafeScheme(null)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isSafeUrl', () => {
|
|
it('accepts https URLs', () => {
|
|
expect(URLValidator.isSafeUrl('https://example.com')).toBe(true);
|
|
});
|
|
|
|
it('accepts http URLs', () => {
|
|
expect(URLValidator.isSafeUrl('http://example.com')).toBe(true);
|
|
});
|
|
|
|
it('rejects javascript: URLs', () => {
|
|
expect(URLValidator.isSafeUrl('javascript:alert(1)')).toBe(false);
|
|
});
|
|
|
|
it('rejects non-string input', () => {
|
|
expect(URLValidator.isSafeUrl(null)).toBe(false);
|
|
expect(URLValidator.isSafeUrl(123)).toBe(false);
|
|
expect(URLValidator.isSafeUrl(undefined)).toBe(false);
|
|
});
|
|
|
|
it('handles fragment-only URLs based on option', () => {
|
|
expect(URLValidator.isSafeUrl('#section', { allowFragments: true })).toBe(true);
|
|
expect(URLValidator.isSafeUrl('#section', { allowFragments: false })).toBe(false);
|
|
});
|
|
|
|
it('rejects mailto by default', () => {
|
|
expect(URLValidator.isSafeUrl('mailto:user@example.com')).toBe(false);
|
|
});
|
|
|
|
it('allows mailto when option is set', () => {
|
|
expect(URLValidator.isSafeUrl('mailto:user@example.com', { allowMailto: true })).toBe(true);
|
|
});
|
|
|
|
it('validates trusted domains', () => {
|
|
const opts = { trustedDomains: ['example.com'] };
|
|
expect(URLValidator.isSafeUrl('https://example.com/page', opts)).toBe(true);
|
|
expect(URLValidator.isSafeUrl('https://sub.example.com/page', opts)).toBe(true);
|
|
expect(URLValidator.isSafeUrl('https://evil.com/page', opts)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('sanitizeUrl', () => {
|
|
it('returns null for unsafe schemes', () => {
|
|
expect(URLValidator.sanitizeUrl('javascript:alert(1)')).toBeNull();
|
|
});
|
|
|
|
it('adds default https scheme when missing', () => {
|
|
expect(URLValidator.sanitizeUrl('example.com')).toBe('https://example.com');
|
|
});
|
|
|
|
it('preserves existing scheme', () => {
|
|
expect(URLValidator.sanitizeUrl('http://example.com')).toBe('http://example.com');
|
|
});
|
|
|
|
it('returns null for empty input', () => {
|
|
expect(URLValidator.sanitizeUrl('')).toBeNull();
|
|
expect(URLValidator.sanitizeUrl(null)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('safeAssign', () => {
|
|
it('allows internal paths starting with /', () => {
|
|
const el = {};
|
|
expect(URLValidator.safeAssign(el, 'href', '/settings')).toBe(true);
|
|
expect(el.href).toBe('/settings');
|
|
});
|
|
|
|
it('allows fragment URLs', () => {
|
|
const el = {};
|
|
expect(URLValidator.safeAssign(el, 'href', '#top')).toBe(true);
|
|
expect(el.href).toBe('#top');
|
|
});
|
|
|
|
it('allows blob URLs for downloads', () => {
|
|
const el = {};
|
|
const blobUrl = 'blob:http://localhost/abc-123';
|
|
expect(URLValidator.safeAssign(el, 'href', blobUrl)).toBe(true);
|
|
expect(el.href).toBe(blobUrl);
|
|
});
|
|
|
|
it('blocks unsafe external URLs', () => {
|
|
const el = {};
|
|
expect(URLValidator.safeAssign(el, 'href', 'javascript:alert(1)')).toBe(false);
|
|
expect(el.href).toBeUndefined();
|
|
});
|
|
|
|
it('allows safe external URLs', () => {
|
|
const el = {};
|
|
expect(URLValidator.safeAssign(el, 'href', 'https://example.com')).toBe(true);
|
|
expect(el.href).toBe('https://example.com');
|
|
});
|
|
});
|
|
});
|