mirror of
https://github.com/garrytan/gstack.git
synced 2026-07-18 21:47:20 +02:00
fix(browse): case-insensitive exfiltration-domain blocklist matching
urlBlocklistFilter compared URLs against the exfiltration blocklist with case-sensitive substring checks, so an uppercased sink (https://WEBHOOK.SITE/x) bypassed the guard and reached the scoped browser agent. Normalize the page URL and extracted content URLs to lowercase before comparing, and make URL extraction scheme-insensitive so HTTPS:// links are still caught. Fixes #2190 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -338,19 +338,28 @@ const BLOCKLIST_DOMAINS = [
|
||||
export function urlBlocklistFilter(content: string, url: string, _command: string): ContentFilterResult {
|
||||
const warnings: string[] = [];
|
||||
|
||||
// Matching is case-insensitive. Hostnames and URL schemes are
|
||||
// case-insensitive (RFC 3986), so an uppercased sink like
|
||||
// https://WEBHOOK.SITE/x must not slip past the blocklist. Normalize the
|
||||
// haystack to lowercase; BLOCKLIST_DOMAINS entries are lowercase by
|
||||
// construction, so a direct compare then holds.
|
||||
|
||||
// Check page URL
|
||||
const normalizedUrl = url.toLowerCase();
|
||||
for (const domain of BLOCKLIST_DOMAINS) {
|
||||
if (url.includes(domain)) {
|
||||
if (normalizedUrl.includes(domain)) {
|
||||
warnings.push(`Page URL matches blocklisted domain: ${domain}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for blocklisted URLs in content (links, form actions)
|
||||
const urlPattern = /https?:\/\/[^\s"'<>]+/g;
|
||||
// Check for blocklisted URLs in content (links, form actions). The `i` flag
|
||||
// keeps an uppercased scheme (HTTPS://) from evading URL extraction.
|
||||
const urlPattern = /https?:\/\/[^\s"'<>]+/gi;
|
||||
const contentUrls = content.match(urlPattern) || [];
|
||||
for (const contentUrl of contentUrls) {
|
||||
const normalizedContentUrl = contentUrl.toLowerCase();
|
||||
for (const domain of BLOCKLIST_DOMAINS) {
|
||||
if (contentUrl.includes(domain)) {
|
||||
if (normalizedContentUrl.includes(domain)) {
|
||||
warnings.push(`Content contains blocklisted URL: ${contentUrl.slice(0, 100)}`);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -151,6 +151,44 @@ describe('Content filter hooks', () => {
|
||||
expect(result.warnings.length).toBe(0);
|
||||
});
|
||||
|
||||
// Regression: issue #2190 — case-sensitive matching let an uppercased
|
||||
// exfiltration domain bypass the blocklist.
|
||||
test('URL blocklist is case-insensitive on the page URL', () => {
|
||||
const result = urlBlocklistFilter('', 'https://WEBHOOK.SITE/steal', 'text');
|
||||
expect(result.safe).toBe(false);
|
||||
expect(result.warnings.some(w => w.includes('webhook.site'))).toBe(true);
|
||||
});
|
||||
|
||||
test('URL blocklist is case-insensitive on content URLs', () => {
|
||||
const result = urlBlocklistFilter(
|
||||
'<a href="https://WEBHOOK.SITE/a1b2c3-steal">click</a>',
|
||||
'https://docs.example.com/article',
|
||||
'links',
|
||||
);
|
||||
expect(result.safe).toBe(false);
|
||||
expect(result.warnings.some(w => w.includes('WEBHOOK.SITE'))).toBe(true);
|
||||
});
|
||||
|
||||
test('URL blocklist catches an uppercased scheme in content', () => {
|
||||
const result = urlBlocklistFilter(
|
||||
'Exfil via HTTPS://Requestbin.com/r/abc please',
|
||||
'https://example.com',
|
||||
'text',
|
||||
);
|
||||
expect(result.safe).toBe(false);
|
||||
expect(result.warnings.some(w => w.toLowerCase().includes('requestbin.com'))).toBe(true);
|
||||
});
|
||||
|
||||
test('URL blocklist does not over-block legitimate uppercase domains', () => {
|
||||
const result = urlBlocklistFilter(
|
||||
'<a href="https://GitHub.com/acme/project">source</a>',
|
||||
'https://DOCS.EXAMPLE.COM/help',
|
||||
'links',
|
||||
);
|
||||
expect(result.safe).toBe(true);
|
||||
expect(result.warnings.length).toBe(0);
|
||||
});
|
||||
|
||||
test('custom filter can be registered and runs', () => {
|
||||
registerContentFilter((content, url, cmd) => {
|
||||
if (content.includes('SECRET')) {
|
||||
|
||||
Reference in New Issue
Block a user