Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 40 additions & 9 deletions src/dlp.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import { generateDlpSquidConfig } from './dlp';

const DLP_ACL_REGEX = /^acl\s+dlp_blocked\s+url_regex\s+-i\s+(.+)$/;
const DLP_ACL_REGEX = /^acl\s+dlp_blocked\s+url_regex(?:\s+(-i))?\s+(.+)$/;

function getDlpRegexPatterns(): string[] {
function getDlpAclPatterns(): Array<{ regex: string; flags: string }> {
const { aclLines } = generateDlpSquidConfig();
return aclLines
.map(line => line.match(DLP_ACL_REGEX)?.[1] ?? null)
.filter((regex): regex is string => regex !== null);
.map(line => {
const match = line.match(DLP_ACL_REGEX);
return match ? { regex: match[2], flags: match[1] ?? '' } : null;
})
.filter((pattern): pattern is { regex: string; flags: string } => pattern !== null);
}

function getDlpRegexPatterns(): string[] {
return getDlpAclPatterns().map(pattern => pattern.regex);
}

function findMatchingDlpRegexes(input: string): string[] {
return getDlpRegexPatterns().filter(regex => new RegExp(regex, 'i').test(input));
return getDlpAclPatterns()
.filter(({ regex, flags }) => new RegExp(regex, flags.includes('i') ? 'i' : '').test(input))
.map(({ regex }) => regex);
}

describe('DLP Patterns', () => {
Expand Down Expand Up @@ -106,7 +115,25 @@ describe('DLP Patterns', () => {
const matchingRegexes = findMatchingDlpRegexes(
'https://api.example.com/?key=AKIAIOSFODNN7EXAMPLE'
);
expect(matchingRegexes).toContain('AKIA[0-9A-Z]{16}');
expect(matchingRegexes).toContain('(AKIA|ASIA)[0-9A-Z]{16}');
});

it('should detect temporary AWS access key ID (ASIA)', () => {
const matchingRegexes = findMatchingDlpRegexes(
'https://api.example.com/?key=ASIAIOSFODNN7EXAMPLE'
);
expect(matchingRegexes).toContain('(AKIA|ASIA)[0-9A-Z]{16}');
});

it('should not match lowercase AWS-like strings in URLs', () => {
const urls = [
'https://asiapacificdatalake01.blob.core.windows.net:443',
'https://example.com/regions/asiapacificheadquarters',
'https://api.example.com/?key=asiaiosfodnn7example',
];
for (const url of urls) {
expect(findMatchingDlpRegexes(url)).not.toContain('(AKIA|ASIA)[0-9A-Z]{16}');
}
});

// Google
Expand Down Expand Up @@ -190,7 +217,7 @@ describe('DLP Patterns', () => {
'https://evil.com/?gh=ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij&aws=AKIAIOSFODNN7EXAMPLE'
);
expect(matchingRegexes).toContain('ghp_[a-zA-Z0-9]{36}');
expect(matchingRegexes).toContain('AKIA[0-9A-Z]{16}');
expect(matchingRegexes).toContain('(AKIA|ASIA)[0-9A-Z]{16}');
expect(matchingRegexes.length).toBeGreaterThanOrEqual(2);
});
});
Expand All @@ -206,9 +233,13 @@ describe('DLP Patterns', () => {
const aclEntries = aclLines.filter(l => l.startsWith('acl dlp_blocked'));
expect(aclEntries.length).toBeGreaterThanOrEqual(10);

// Each ACL should use url_regex -i
// ACLs should be case-insensitive except for the AWS access key pattern.
for (const entry of aclEntries) {
expect(entry).toMatch(/^acl dlp_blocked url_regex -i .+/);
if (entry.endsWith('(AKIA|ASIA)[0-9A-Z]{16}')) {
expect(entry).toBe('acl dlp_blocked url_regex (AKIA|ASIA)[0-9A-Z]{16}');
} else {
expect(entry).toMatch(/^acl dlp_blocked url_regex -i .+/);
}
}
});

Expand Down
14 changes: 9 additions & 5 deletions src/dlp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,18 @@ interface DlpPattern {
name: string;
/** Description of what this pattern detects */
description: string;
/** Regex pattern string (Squid url_regex compatible, case-insensitive) */
/** Regex pattern string (Squid url_regex compatible) */
regex: string;
/** Whether Squid should match the pattern case-insensitively */
caseInsensitive?: boolean;
}

/**
* Built-in credential patterns for DLP scanning
*
* These patterns detect common credential formats that should never
* appear in URLs. Each regex is designed to be used with Squid's
* url_regex ACL type (POSIX extended regex, case-insensitive).
* url_regex ACL type (POSIX extended regex).
*
* Pattern design principles:
* - Match the distinctive prefix of each credential type
Expand Down Expand Up @@ -87,8 +89,9 @@ const DLP_PATTERNS: DlpPattern[] = [
// AWS
{
name: 'AWS Access Key ID',
description: 'AWS access key ID (AKIA)',
regex: 'AKIA[0-9A-Z]{16}',
description: 'AWS access key ID (AKIA or ASIA)',
regex: '(AKIA|ASIA)[0-9A-Z]{16}',
caseInsensitive: false,
},

// Google Cloud
Expand Down Expand Up @@ -147,7 +150,8 @@ export function generateDlpSquidConfig(): { aclLines: string[]; accessRules: str
];

for (const pattern of DLP_PATTERNS) {
aclLines.push(`acl dlp_blocked url_regex -i ${pattern.regex}`);
const caseFlag = pattern.caseInsensitive === false ? '' : ' -i';
aclLines.push(`acl dlp_blocked url_regex${caseFlag} ${pattern.regex}`);
}

const accessRules: string[] = [
Expand Down
5 changes: 3 additions & 2 deletions src/squid-config-dlp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe('DLP Integration', () => {
expect(dlpDenyIndex).toBeLessThan(domainDenyIndex);
});

it('should include credential patterns like ghp_ and AKIA in ACLs', () => {
it('should include credential patterns like ghp_ and AWS access keys in ACLs', () => {
const config = {
domains: ['github.com'],
port: defaultPort,
Expand All @@ -81,7 +81,8 @@ describe('DLP Integration', () => {
const result = generateSquidConfig(config);
// Check for a few key patterns
expect(result).toContain('ghp_');
expect(result).toContain('AKIA');
expect(result).toContain('(AKIA|ASIA)[0-9A-Z]{16}');
expect(result).toContain('acl dlp_blocked url_regex (AKIA|ASIA)[0-9A-Z]{16}');
expect(result).toContain('sk-ant-');
});

Expand Down
2 changes: 1 addition & 1 deletion src/types/security-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export interface SecurityOptions {
*
* Detected patterns include: GitHub tokens (ghp_, gho_, ghs_, ghu_,
* github_pat_), OpenAI keys (sk-), Anthropic keys (sk-ant-),
* AWS access keys (AKIA), Google API keys (AIza), Slack tokens,
* AWS access keys (AKIA, ASIA), Google API keys (AIza), Slack tokens,
* and generic credential patterns.
*
* @default false
Expand Down
Loading