mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-21 02:26:31 +03:00
Fix: redact URL userinfo from detect findings (#657)
Strip basic-auth credentials from scan-target URLs before goto and finding output, and pass them to page.authenticate instead. Written with AI assistance (Cursor); reviewed by maintainer. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
Abdul Wahab
co-authored by
Cursor
parent
1df992ade0
commit
d5873ff8eb
@@ -1,5 +1,5 @@
|
||||
import { describe, test, expect, afterEach } from 'bun:test';
|
||||
import { launchBrowser } from '../cli/engine/engines/browser/detect-url.mjs';
|
||||
import { launchBrowser, detectUrl, splitScanUrl } from '../cli/engine/engines/browser/detect-url.mjs';
|
||||
|
||||
// launchBrowser prefers the system-installed Chrome on Windows to dodge the
|
||||
// bundled-Chrome GPU crash-loop (issue #372), and keeps the pinned bundled
|
||||
@@ -79,3 +79,111 @@ describe('launchBrowser', () => {
|
||||
expect(p.calls.every(c => c.channel === undefined)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('splitScanUrl', () => {
|
||||
test('strips http(s) userinfo and returns credentials', () => {
|
||||
expect(splitScanUrl('https://user:pass@example.com')).toEqual({
|
||||
href: 'https://example.com/',
|
||||
credentials: { username: 'user', password: 'pass' },
|
||||
});
|
||||
expect(splitScanUrl('https://user:p%40ss@example.com/path?q=1')).toEqual({
|
||||
href: 'https://example.com/path?q=1',
|
||||
credentials: { username: 'user', password: 'p@ss' },
|
||||
});
|
||||
expect(splitScanUrl('https://user@example.com')).toEqual({
|
||||
href: 'https://example.com/',
|
||||
credentials: { username: 'user', password: '' },
|
||||
});
|
||||
expect(splitScanUrl('http://:secret@host.com/')).toEqual({
|
||||
href: 'http://host.com/',
|
||||
credentials: { username: '', password: 'secret' },
|
||||
});
|
||||
});
|
||||
|
||||
test('preserves original string when no userinfo', () => {
|
||||
expect(splitScanUrl('https://example.com')).toEqual({
|
||||
href: 'https://example.com',
|
||||
credentials: null,
|
||||
});
|
||||
expect(splitScanUrl('https://example.com/path?email=a@b.com')).toEqual({
|
||||
href: 'https://example.com/path?email=a@b.com',
|
||||
credentials: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('handles IPv6 and non-http(s) URLs', () => {
|
||||
expect(splitScanUrl('https://user:pass@[::1]:8080/x')).toEqual({
|
||||
href: 'https://[::1]:8080/x',
|
||||
credentials: { username: 'user', password: 'pass' },
|
||||
});
|
||||
expect(splitScanUrl('file:///tmp/a.html')).toEqual({
|
||||
href: 'file:///tmp/a.html',
|
||||
credentials: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('returns original string for invalid URLs', () => {
|
||||
expect(splitScanUrl('not a url')).toEqual({
|
||||
href: 'not a url',
|
||||
credentials: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function makeFakeBrowser() {
|
||||
const calls = { authenticate: [], goto: [] };
|
||||
const page = {
|
||||
on() {},
|
||||
async setViewport() {},
|
||||
async authenticate(creds) { calls.authenticate.push(creds); },
|
||||
async goto(url, opts) { calls.goto.push({ url, opts }); },
|
||||
async evaluate(fn) {
|
||||
if (typeof fn === 'function' && fn.toString().includes('impeccableDetect')) {
|
||||
return [{ findings: [{ type: 'low-contrast', detail: 'x', ignoreValue: '', severity: '' }] }];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
async close() {},
|
||||
};
|
||||
return {
|
||||
calls,
|
||||
browser: {
|
||||
async newPage() { return page; },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('detectUrl credential redaction', () => {
|
||||
test('authenticates with stripped URL and redacts findings', async () => {
|
||||
const { calls, browser } = makeFakeBrowser();
|
||||
const findings = await detectUrl('https://user:p%40ss@example.com/path', {
|
||||
browser,
|
||||
visualContrast: false,
|
||||
contentHidden: false,
|
||||
});
|
||||
|
||||
expect(calls.authenticate).toEqual([{ username: 'user', password: 'p@ss' }]);
|
||||
expect(calls.goto).toHaveLength(1);
|
||||
expect(calls.goto[0].url).toBe('https://example.com/path');
|
||||
expect(findings.length).toBeGreaterThan(0);
|
||||
for (const f of findings) {
|
||||
expect(f.file).toBe('https://example.com/path');
|
||||
}
|
||||
});
|
||||
|
||||
test('does not authenticate when URL has no userinfo', async () => {
|
||||
const { calls, browser } = makeFakeBrowser();
|
||||
const url = 'https://example.com/path';
|
||||
const findings = await detectUrl(url, {
|
||||
browser,
|
||||
visualContrast: false,
|
||||
contentHidden: false,
|
||||
});
|
||||
|
||||
expect(calls.authenticate).toEqual([]);
|
||||
expect(findings.length).toBeGreaterThan(0);
|
||||
for (const f of findings) {
|
||||
expect(f.file).toBe(url);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user