Add unit tests for PhpFrameworkSurface and PhpPharScanner
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
- Implement comprehensive tests for PhpFrameworkSurface, covering scenarios such as empty surfaces, presence of routes, controllers, middlewares, CLI commands, cron jobs, and event listeners. - Validate metadata creation for route counts, HTTP methods, protected and public routes, and route patterns. - Introduce tests for PhpPharScanner, including handling of non-existent files, null or empty paths, invalid PHAR files, and minimal PHAR structures. - Ensure correct computation of SHA256 for valid PHAR files and validate the properties of PhpPharArchive, PhpPharEntry, and PhpPharScanResult.
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Node.Internal.Capabilities;
|
||||
|
||||
/// <summary>
|
||||
/// Orchestrates capability scanning across Node.js/JavaScript source files.
|
||||
/// </summary>
|
||||
internal static class NodeCapabilityScanBuilder
|
||||
{
|
||||
private static readonly string[] SourceExtensions = [".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx"];
|
||||
|
||||
/// <summary>
|
||||
/// Scans a Node.js project directory for capabilities.
|
||||
/// </summary>
|
||||
public static NodeCapabilityScanResult ScanProject(string projectPath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(projectPath);
|
||||
|
||||
if (!Directory.Exists(projectPath))
|
||||
{
|
||||
return NodeCapabilityScanResult.Empty;
|
||||
}
|
||||
|
||||
var allEvidences = new List<NodeCapabilityEvidence>();
|
||||
|
||||
foreach (var sourceFile in EnumerateSourceFiles(projectPath))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
var content = File.ReadAllText(sourceFile);
|
||||
var relativePath = Path.GetRelativePath(projectPath, sourceFile);
|
||||
var evidences = NodeCapabilityScanner.ScanFile(content, relativePath);
|
||||
allEvidences.AddRange(evidences);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// Skip inaccessible files
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
// Skip inaccessible files
|
||||
}
|
||||
}
|
||||
|
||||
// Deduplicate and sort for determinism
|
||||
var finalEvidences = allEvidences
|
||||
.DistinctBy(e => e.DeduplicationKey)
|
||||
.OrderBy(e => e.SourceFile, StringComparer.Ordinal)
|
||||
.ThenBy(e => e.SourceLine)
|
||||
.ThenBy(e => e.Kind)
|
||||
.ToList();
|
||||
|
||||
return new NodeCapabilityScanResult(finalEvidences);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scans a Node.js project from package.json location.
|
||||
/// </summary>
|
||||
public static NodeCapabilityScanResult ScanPackage(string packageJsonPath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(packageJsonPath);
|
||||
|
||||
var projectDir = File.Exists(packageJsonPath)
|
||||
? Path.GetDirectoryName(packageJsonPath) ?? packageJsonPath
|
||||
: packageJsonPath;
|
||||
|
||||
if (!Directory.Exists(projectDir))
|
||||
{
|
||||
return NodeCapabilityScanResult.Empty;
|
||||
}
|
||||
|
||||
var allEvidences = new List<NodeCapabilityEvidence>();
|
||||
|
||||
// Scan src directory if it exists
|
||||
var srcDir = Path.Combine(projectDir, "src");
|
||||
if (Directory.Exists(srcDir))
|
||||
{
|
||||
var result = ScanProject(srcDir, cancellationToken);
|
||||
allEvidences.AddRange(result.Evidences);
|
||||
}
|
||||
|
||||
// Scan lib directory if it exists
|
||||
var libDir = Path.Combine(projectDir, "lib");
|
||||
if (Directory.Exists(libDir))
|
||||
{
|
||||
var result = ScanProject(libDir, cancellationToken);
|
||||
allEvidences.AddRange(result.Evidences);
|
||||
}
|
||||
|
||||
// Scan root level .js files
|
||||
foreach (var ext in SourceExtensions)
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(projectDir, $"*{ext}", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Skip config files
|
||||
var fileName = Path.GetFileName(file);
|
||||
if (IsConfigFile(fileName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var content = File.ReadAllText(file);
|
||||
var relativePath = Path.GetRelativePath(projectDir, file);
|
||||
var evidences = NodeCapabilityScanner.ScanFile(content, relativePath);
|
||||
allEvidences.AddRange(evidences);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// Skip inaccessible files
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
// Skip inaccessible files
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no structured directories found, scan the whole project
|
||||
if (allEvidences.Count == 0)
|
||||
{
|
||||
return ScanProject(projectDir, cancellationToken);
|
||||
}
|
||||
|
||||
var finalEvidences = allEvidences
|
||||
.DistinctBy(e => e.DeduplicationKey)
|
||||
.OrderBy(e => e.SourceFile, StringComparer.Ordinal)
|
||||
.ThenBy(e => e.SourceLine)
|
||||
.ThenBy(e => e.Kind)
|
||||
.ToList();
|
||||
|
||||
return new NodeCapabilityScanResult(finalEvidences);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scans specific JavaScript/TypeScript source content.
|
||||
/// </summary>
|
||||
public static NodeCapabilityScanResult ScanContent(string content, string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
return NodeCapabilityScanResult.Empty;
|
||||
}
|
||||
|
||||
var evidences = NodeCapabilityScanner.ScanFile(content, filePath);
|
||||
return new NodeCapabilityScanResult(evidences.ToList());
|
||||
}
|
||||
|
||||
private static IEnumerable<string> EnumerateSourceFiles(string rootPath)
|
||||
{
|
||||
var options = new EnumerationOptions
|
||||
{
|
||||
RecurseSubdirectories = true,
|
||||
IgnoreInaccessible = true,
|
||||
MaxRecursionDepth = 30
|
||||
};
|
||||
|
||||
foreach (var ext in SourceExtensions)
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(rootPath, $"*{ext}", options))
|
||||
{
|
||||
// Skip node_modules
|
||||
if (file.Contains($"{Path.DirectorySeparatorChar}node_modules{Path.DirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.AltDirectorySeparatorChar}node_modules{Path.AltDirectorySeparatorChar}"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip dist/build output directories
|
||||
if (file.Contains($"{Path.DirectorySeparatorChar}dist{Path.DirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.DirectorySeparatorChar}build{Path.DirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.DirectorySeparatorChar}out{Path.DirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.AltDirectorySeparatorChar}dist{Path.AltDirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.AltDirectorySeparatorChar}build{Path.AltDirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.AltDirectorySeparatorChar}out{Path.AltDirectorySeparatorChar}"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip coverage directories
|
||||
if (file.Contains($"{Path.DirectorySeparatorChar}coverage{Path.DirectorySeparatorChar}") ||
|
||||
file.Contains($"{Path.AltDirectorySeparatorChar}coverage{Path.AltDirectorySeparatorChar}"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip hidden directories
|
||||
if (file.Contains($"{Path.DirectorySeparatorChar}.") ||
|
||||
file.Contains($"{Path.AltDirectorySeparatorChar}."))
|
||||
{
|
||||
// But allow .github, .vscode source files if they contain JS
|
||||
if (!file.Contains(".github") && !file.Contains(".vscode"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip test files (optional - can be useful for scanning)
|
||||
// if (IsTestFile(Path.GetFileName(file)))
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// Skip minified files
|
||||
var fileName = Path.GetFileName(file);
|
||||
if (fileName.Contains(".min.") || fileName.EndsWith(".min.js", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip config files
|
||||
if (IsConfigFile(fileName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsConfigFile(string fileName)
|
||||
{
|
||||
var configPatterns = new[]
|
||||
{
|
||||
"webpack.config",
|
||||
"rollup.config",
|
||||
"vite.config",
|
||||
"babel.config",
|
||||
"jest.config",
|
||||
"eslint.config",
|
||||
"prettier.config",
|
||||
"tsconfig",
|
||||
"jsconfig",
|
||||
".eslintrc",
|
||||
".prettierrc",
|
||||
".babelrc",
|
||||
"karma.conf",
|
||||
"protractor.conf",
|
||||
"gulpfile",
|
||||
"gruntfile",
|
||||
"postcss.config",
|
||||
"tailwind.config",
|
||||
"next.config",
|
||||
"nuxt.config",
|
||||
"svelte.config",
|
||||
"astro.config",
|
||||
"vitest.config"
|
||||
};
|
||||
|
||||
var lowerFileName = fileName.ToLowerInvariant();
|
||||
return configPatterns.Any(p => lowerFileName.Contains(p));
|
||||
}
|
||||
|
||||
// Uncomment if you want to skip test files
|
||||
// private static bool IsTestFile(string fileName)
|
||||
// {
|
||||
// var lowerFileName = fileName.ToLowerInvariant();
|
||||
// return lowerFileName.Contains(".test.") ||
|
||||
// lowerFileName.Contains(".spec.") ||
|
||||
// lowerFileName.Contains("_test.") ||
|
||||
// lowerFileName.Contains("_spec.") ||
|
||||
// lowerFileName.EndsWith(".test.js") ||
|
||||
// lowerFileName.EndsWith(".spec.js") ||
|
||||
// lowerFileName.EndsWith(".test.ts") ||
|
||||
// lowerFileName.EndsWith(".spec.ts");
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,538 @@
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Node.Internal.Capabilities;
|
||||
|
||||
/// <summary>
|
||||
/// Scans Node.js/JavaScript source files for security-relevant capabilities.
|
||||
/// Detects patterns for command execution, file I/O, network access,
|
||||
/// serialization, dynamic code evaluation, native addons, and more.
|
||||
/// </summary>
|
||||
internal static class NodeCapabilityScanner
|
||||
{
|
||||
// ========================================
|
||||
// EXEC - Command/Process Execution (Critical)
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] ExecPatterns =
|
||||
[
|
||||
// child_process module
|
||||
(new Regex(@"require\s*\(\s*['""]child_process['""]", RegexOptions.Compiled), "require('child_process')", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"from\s+['""]child_process['""]", RegexOptions.Compiled), "import child_process", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"child_process\s*\.\s*exec\s*\(", RegexOptions.Compiled), "child_process.exec", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"child_process\s*\.\s*execSync\s*\(", RegexOptions.Compiled), "child_process.execSync", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"child_process\s*\.\s*spawn\s*\(", RegexOptions.Compiled), "child_process.spawn", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"child_process\s*\.\s*spawnSync\s*\(", RegexOptions.Compiled), "child_process.spawnSync", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"child_process\s*\.\s*fork\s*\(", RegexOptions.Compiled), "child_process.fork", CapabilityRisk.High, 0.95f),
|
||||
(new Regex(@"child_process\s*\.\s*execFile\s*\(", RegexOptions.Compiled), "child_process.execFile", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"child_process\s*\.\s*execFileSync\s*\(", RegexOptions.Compiled), "child_process.execFileSync", CapabilityRisk.Critical, 1.0f),
|
||||
|
||||
// Destructured imports
|
||||
(new Regex(@"\{\s*(?:exec|execSync|spawn|spawnSync|fork|execFile)\s*\}", RegexOptions.Compiled), "destructured child_process", CapabilityRisk.Critical, 0.9f),
|
||||
|
||||
// Shell execution via execa, shelljs, etc.
|
||||
(new Regex(@"require\s*\(\s*['""]execa['""]", RegexOptions.Compiled), "require('execa')", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"require\s*\(\s*['""]shelljs['""]", RegexOptions.Compiled), "require('shelljs')", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"shell\s*\.\s*exec\s*\(", RegexOptions.Compiled), "shelljs.exec", CapabilityRisk.Critical, 0.9f),
|
||||
|
||||
// process.binding for internal access
|
||||
(new Regex(@"process\s*\.\s*binding\s*\(", RegexOptions.Compiled), "process.binding", CapabilityRisk.Critical, 0.95f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// FILESYSTEM - File/Directory Operations
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] FilesystemPatterns =
|
||||
[
|
||||
// fs module
|
||||
(new Regex(@"require\s*\(\s*['""]fs['""]", RegexOptions.Compiled), "require('fs')", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"require\s*\(\s*['""]fs/promises['""]", RegexOptions.Compiled), "require('fs/promises')", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"from\s+['""]fs['""]", RegexOptions.Compiled), "import fs", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"from\s+['""]fs/promises['""]", RegexOptions.Compiled), "import fs/promises", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"from\s+['""]node:fs['""]", RegexOptions.Compiled), "import node:fs", CapabilityRisk.Medium, 0.9f),
|
||||
|
||||
// Read operations
|
||||
(new Regex(@"fs\s*\.\s*readFile(?:Sync)?\s*\(", RegexOptions.Compiled), "fs.readFile", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"fs\s*\.\s*readdir(?:Sync)?\s*\(", RegexOptions.Compiled), "fs.readdir", CapabilityRisk.Medium, 0.8f),
|
||||
(new Regex(@"fs\s*\.\s*createReadStream\s*\(", RegexOptions.Compiled), "fs.createReadStream", CapabilityRisk.Medium, 0.85f),
|
||||
|
||||
// Write operations (higher risk)
|
||||
(new Regex(@"fs\s*\.\s*writeFile(?:Sync)?\s*\(", RegexOptions.Compiled), "fs.writeFile", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"fs\s*\.\s*appendFile(?:Sync)?\s*\(", RegexOptions.Compiled), "fs.appendFile", CapabilityRisk.High, 0.85f),
|
||||
(new Regex(@"fs\s*\.\s*createWriteStream\s*\(", RegexOptions.Compiled), "fs.createWriteStream", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"fs\s*\.\s*mkdir(?:Sync)?\s*\(", RegexOptions.Compiled), "fs.mkdir", CapabilityRisk.Medium, 0.8f),
|
||||
|
||||
// Delete operations (high risk)
|
||||
(new Regex(@"fs\s*\.\s*unlink(?:Sync)?\s*\(", RegexOptions.Compiled), "fs.unlink", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"fs\s*\.\s*rmdir(?:Sync)?\s*\(", RegexOptions.Compiled), "fs.rmdir", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"fs\s*\.\s*rm(?:Sync)?\s*\(", RegexOptions.Compiled), "fs.rm", CapabilityRisk.High, 0.9f),
|
||||
|
||||
// Permission operations
|
||||
(new Regex(@"fs\s*\.\s*chmod(?:Sync)?\s*\(", RegexOptions.Compiled), "fs.chmod", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"fs\s*\.\s*chown(?:Sync)?\s*\(", RegexOptions.Compiled), "fs.chown", CapabilityRisk.High, 0.9f),
|
||||
|
||||
// Symlink (can be used for path traversal)
|
||||
(new Regex(@"fs\s*\.\s*symlink(?:Sync)?\s*\(", RegexOptions.Compiled), "fs.symlink", CapabilityRisk.High, 0.85f),
|
||||
|
||||
// fs-extra
|
||||
(new Regex(@"require\s*\(\s*['""]fs-extra['""]", RegexOptions.Compiled), "require('fs-extra')", CapabilityRisk.Medium, 0.85f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// NETWORK - Network I/O
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] NetworkPatterns =
|
||||
[
|
||||
// Core modules
|
||||
(new Regex(@"require\s*\(\s*['""]net['""]", RegexOptions.Compiled), "require('net')", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"require\s*\(\s*['""]http['""]", RegexOptions.Compiled), "require('http')", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"require\s*\(\s*['""]https['""]", RegexOptions.Compiled), "require('https')", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"require\s*\(\s*['""]dgram['""]", RegexOptions.Compiled), "require('dgram')", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"require\s*\(\s*['""]tls['""]", RegexOptions.Compiled), "require('tls')", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"from\s+['""]node:(?:net|http|https|dgram|tls)['""]", RegexOptions.Compiled), "import node:network", CapabilityRisk.Medium, 0.9f),
|
||||
|
||||
// Socket operations
|
||||
(new Regex(@"net\s*\.\s*createServer\s*\(", RegexOptions.Compiled), "net.createServer", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"net\s*\.\s*createConnection\s*\(", RegexOptions.Compiled), "net.createConnection", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"net\s*\.\s*connect\s*\(", RegexOptions.Compiled), "net.connect", CapabilityRisk.Medium, 0.85f),
|
||||
|
||||
// HTTP operations
|
||||
(new Regex(@"http\s*\.\s*createServer\s*\(", RegexOptions.Compiled), "http.createServer", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"http\s*\.\s*request\s*\(", RegexOptions.Compiled), "http.request", CapabilityRisk.Medium, 0.8f),
|
||||
(new Regex(@"http\s*\.\s*get\s*\(", RegexOptions.Compiled), "http.get", CapabilityRisk.Medium, 0.8f),
|
||||
(new Regex(@"https\s*\.\s*request\s*\(", RegexOptions.Compiled), "https.request", CapabilityRisk.Medium, 0.8f),
|
||||
|
||||
// Fetch API
|
||||
(new Regex(@"\bfetch\s*\(", RegexOptions.Compiled), "fetch", CapabilityRisk.Medium, 0.75f),
|
||||
(new Regex(@"require\s*\(\s*['""]node-fetch['""]", RegexOptions.Compiled), "require('node-fetch')", CapabilityRisk.Medium, 0.85f),
|
||||
|
||||
// Axios, got, request
|
||||
(new Regex(@"require\s*\(\s*['""]axios['""]", RegexOptions.Compiled), "require('axios')", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"require\s*\(\s*['""]got['""]", RegexOptions.Compiled), "require('got')", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"require\s*\(\s*['""]request['""]", RegexOptions.Compiled), "require('request')", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"require\s*\(\s*['""]superagent['""]", RegexOptions.Compiled), "require('superagent')", CapabilityRisk.Medium, 0.85f),
|
||||
|
||||
// WebSocket
|
||||
(new Regex(@"require\s*\(\s*['""]ws['""]", RegexOptions.Compiled), "require('ws')", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"new\s+WebSocket\s*\(", RegexOptions.Compiled), "WebSocket", CapabilityRisk.Medium, 0.8f),
|
||||
|
||||
// DNS
|
||||
(new Regex(@"require\s*\(\s*['""]dns['""]", RegexOptions.Compiled), "require('dns')", CapabilityRisk.Low, 0.8f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// ENVIRONMENT - Environment Variables
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] EnvironmentPatterns =
|
||||
[
|
||||
(new Regex(@"process\s*\.\s*env\b", RegexOptions.Compiled), "process.env", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"process\s*\.\s*env\s*\[", RegexOptions.Compiled), "process.env[]", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"process\s*\.\s*env\s*\.\s*\w+", RegexOptions.Compiled), "process.env.*", CapabilityRisk.Medium, 0.85f),
|
||||
|
||||
// dotenv
|
||||
(new Regex(@"require\s*\(\s*['""]dotenv['""]", RegexOptions.Compiled), "require('dotenv')", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"dotenv\s*\.\s*config\s*\(", RegexOptions.Compiled), "dotenv.config", CapabilityRisk.Medium, 0.85f),
|
||||
|
||||
// process info
|
||||
(new Regex(@"process\s*\.\s*cwd\s*\(\s*\)", RegexOptions.Compiled), "process.cwd", CapabilityRisk.Low, 0.75f),
|
||||
(new Regex(@"process\s*\.\s*chdir\s*\(", RegexOptions.Compiled), "process.chdir", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"process\s*\.\s*argv\b", RegexOptions.Compiled), "process.argv", CapabilityRisk.Low, 0.7f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// SERIALIZATION - Data Serialization
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] SerializationPatterns =
|
||||
[
|
||||
// JSON.parse with reviver (potential code execution)
|
||||
(new Regex(@"JSON\s*\.\s*parse\s*\([^,)]+,\s*\w+", RegexOptions.Compiled), "JSON.parse with reviver", CapabilityRisk.Medium, 0.7f),
|
||||
|
||||
// Dangerous serializers - node-serialize is known vulnerable
|
||||
(new Regex(@"require\s*\(\s*['""]node-serialize['""]", RegexOptions.Compiled), "require('node-serialize')", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"serialize\s*\.\s*unserialize\s*\(", RegexOptions.Compiled), "node-serialize.unserialize", CapabilityRisk.Critical, 1.0f),
|
||||
|
||||
// serialize-javascript
|
||||
(new Regex(@"require\s*\(\s*['""]serialize-javascript['""]", RegexOptions.Compiled), "require('serialize-javascript')", CapabilityRisk.High, 0.85f),
|
||||
|
||||
// js-yaml (load is unsafe by default in older versions)
|
||||
(new Regex(@"require\s*\(\s*['""]js-yaml['""]", RegexOptions.Compiled), "require('js-yaml')", CapabilityRisk.Medium, 0.8f),
|
||||
(new Regex(@"yaml\s*\.\s*load\s*\(", RegexOptions.Compiled), "yaml.load", CapabilityRisk.High, 0.85f),
|
||||
|
||||
// Pickle-like serializers
|
||||
(new Regex(@"require\s*\(\s*['""]v8['""]", RegexOptions.Compiled), "require('v8')", CapabilityRisk.High, 0.85f),
|
||||
(new Regex(@"v8\s*\.\s*deserialize\s*\(", RegexOptions.Compiled), "v8.deserialize", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"v8\s*\.\s*serialize\s*\(", RegexOptions.Compiled), "v8.serialize", CapabilityRisk.Medium, 0.8f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// CRYPTO - Cryptographic Operations
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] CryptoPatterns =
|
||||
[
|
||||
(new Regex(@"require\s*\(\s*['""]crypto['""]", RegexOptions.Compiled), "require('crypto')", CapabilityRisk.Low, 0.85f),
|
||||
(new Regex(@"from\s+['""](?:node:)?crypto['""]", RegexOptions.Compiled), "import crypto", CapabilityRisk.Low, 0.85f),
|
||||
|
||||
// Specific crypto operations
|
||||
(new Regex(@"crypto\s*\.\s*createHash\s*\(", RegexOptions.Compiled), "crypto.createHash", CapabilityRisk.Low, 0.85f),
|
||||
(new Regex(@"crypto\s*\.\s*createCipher(?:iv)?\s*\(", RegexOptions.Compiled), "crypto.createCipher", CapabilityRisk.Low, 0.85f),
|
||||
(new Regex(@"crypto\s*\.\s*createDecipher(?:iv)?\s*\(", RegexOptions.Compiled), "crypto.createDecipher", CapabilityRisk.Low, 0.85f),
|
||||
(new Regex(@"crypto\s*\.\s*createSign\s*\(", RegexOptions.Compiled), "crypto.createSign", CapabilityRisk.Low, 0.85f),
|
||||
(new Regex(@"crypto\s*\.\s*createVerify\s*\(", RegexOptions.Compiled), "crypto.createVerify", CapabilityRisk.Low, 0.85f),
|
||||
(new Regex(@"crypto\s*\.\s*randomBytes\s*\(", RegexOptions.Compiled), "crypto.randomBytes", CapabilityRisk.Low, 0.8f),
|
||||
(new Regex(@"crypto\s*\.\s*pbkdf2\s*\(", RegexOptions.Compiled), "crypto.pbkdf2", CapabilityRisk.Low, 0.85f),
|
||||
|
||||
// Third-party crypto
|
||||
(new Regex(@"require\s*\(\s*['""]bcrypt['""]", RegexOptions.Compiled), "require('bcrypt')", CapabilityRisk.Low, 0.85f),
|
||||
(new Regex(@"require\s*\(\s*['""]argon2['""]", RegexOptions.Compiled), "require('argon2')", CapabilityRisk.Low, 0.85f),
|
||||
|
||||
// Weak crypto
|
||||
(new Regex(@"createHash\s*\(\s*['""](?:md5|sha1)['""]", RegexOptions.Compiled | RegexOptions.IgnoreCase), "Weak hash algorithm", CapabilityRisk.High, 0.9f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// DATABASE - Database Access
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] DatabasePatterns =
|
||||
[
|
||||
// SQL databases
|
||||
(new Regex(@"require\s*\(\s*['""]mysql2?['""]", RegexOptions.Compiled), "require('mysql')", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"require\s*\(\s*['""]pg['""]", RegexOptions.Compiled), "require('pg')", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"require\s*\(\s*['""]sqlite3['""]", RegexOptions.Compiled), "require('sqlite3')", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"require\s*\(\s*['""]better-sqlite3['""]", RegexOptions.Compiled), "require('better-sqlite3')", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"require\s*\(\s*['""]mssql['""]", RegexOptions.Compiled), "require('mssql')", CapabilityRisk.Medium, 0.9f),
|
||||
|
||||
// NoSQL databases
|
||||
(new Regex(@"require\s*\(\s*['""]mongodb['""]", RegexOptions.Compiled), "require('mongodb')", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"require\s*\(\s*['""]mongoose['""]", RegexOptions.Compiled), "require('mongoose')", CapabilityRisk.Medium, 0.9f),
|
||||
(new Regex(@"require\s*\(\s*['""]redis['""]", RegexOptions.Compiled), "require('redis')", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"require\s*\(\s*['""]ioredis['""]", RegexOptions.Compiled), "require('ioredis')", CapabilityRisk.Medium, 0.85f),
|
||||
|
||||
// Query execution
|
||||
(new Regex(@"\.query\s*\(\s*[`'""](?:SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER)", RegexOptions.Compiled | RegexOptions.IgnoreCase), "Raw SQL query", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"\.exec\s*\(\s*[`'""](?:SELECT|INSERT|UPDATE|DELETE)", RegexOptions.Compiled | RegexOptions.IgnoreCase), "Raw SQL exec", CapabilityRisk.High, 0.85f),
|
||||
|
||||
// SQL injection patterns - string concatenation
|
||||
(new Regex(@"[`'""](?:SELECT|INSERT|UPDATE|DELETE)\s+.*[`'""]\s*\+", RegexOptions.Compiled | RegexOptions.IgnoreCase), "SQL string concatenation", CapabilityRisk.Critical, 0.9f),
|
||||
(new Regex(@"\$\{.*\}.*(?:SELECT|INSERT|UPDATE|DELETE)", RegexOptions.Compiled | RegexOptions.IgnoreCase), "SQL template literal injection", CapabilityRisk.Critical, 0.85f),
|
||||
|
||||
// ORMs
|
||||
(new Regex(@"require\s*\(\s*['""]sequelize['""]", RegexOptions.Compiled), "require('sequelize')", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"require\s*\(\s*['""]typeorm['""]", RegexOptions.Compiled), "require('typeorm')", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"require\s*\(\s*['""]prisma['""]", RegexOptions.Compiled), "require('prisma')", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"require\s*\(\s*['""]knex['""]", RegexOptions.Compiled), "require('knex')", CapabilityRisk.Medium, 0.85f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// DYNAMIC CODE - Code Evaluation (Critical)
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] DynamicCodePatterns =
|
||||
[
|
||||
// eval - most dangerous
|
||||
(new Regex(@"\beval\s*\(", RegexOptions.Compiled), "eval", CapabilityRisk.Critical, 1.0f),
|
||||
|
||||
// Function constructor
|
||||
(new Regex(@"new\s+Function\s*\(", RegexOptions.Compiled), "new Function", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"Function\s*\(\s*[^)]+\)\s*\(", RegexOptions.Compiled), "Function()()", CapabilityRisk.Critical, 0.95f),
|
||||
|
||||
// vm module
|
||||
(new Regex(@"require\s*\(\s*['""]vm['""]", RegexOptions.Compiled), "require('vm')", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"from\s+['""](?:node:)?vm['""]", RegexOptions.Compiled), "import vm", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"vm\s*\.\s*runInContext\s*\(", RegexOptions.Compiled), "vm.runInContext", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"vm\s*\.\s*runInNewContext\s*\(", RegexOptions.Compiled), "vm.runInNewContext", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"vm\s*\.\s*runInThisContext\s*\(", RegexOptions.Compiled), "vm.runInThisContext", CapabilityRisk.Critical, 1.0f),
|
||||
(new Regex(@"vm\s*\.\s*Script\s*\(", RegexOptions.Compiled), "vm.Script", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"new\s+vm\s*\.\s*Script\s*\(", RegexOptions.Compiled), "new vm.Script", CapabilityRisk.Critical, 0.95f),
|
||||
|
||||
// setTimeout/setInterval with strings (eval-like)
|
||||
(new Regex(@"setTimeout\s*\(\s*['""`]", RegexOptions.Compiled), "setTimeout with string", CapabilityRisk.Critical, 0.9f),
|
||||
(new Regex(@"setInterval\s*\(\s*['""`]", RegexOptions.Compiled), "setInterval with string", CapabilityRisk.Critical, 0.9f),
|
||||
|
||||
// Template engines (can execute code)
|
||||
(new Regex(@"require\s*\(\s*['""]ejs['""]", RegexOptions.Compiled), "require('ejs')", CapabilityRisk.High, 0.8f),
|
||||
(new Regex(@"require\s*\(\s*['""]pug['""]", RegexOptions.Compiled), "require('pug')", CapabilityRisk.Medium, 0.75f),
|
||||
(new Regex(@"require\s*\(\s*['""]handlebars['""]", RegexOptions.Compiled), "require('handlebars')", CapabilityRisk.Medium, 0.7f),
|
||||
|
||||
// vm2 (sandbox escape vulnerabilities)
|
||||
(new Regex(@"require\s*\(\s*['""]vm2['""]", RegexOptions.Compiled), "require('vm2')", CapabilityRisk.High, 0.9f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// REFLECTION - Code Introspection
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] ReflectionPatterns =
|
||||
[
|
||||
// Reflect API
|
||||
(new Regex(@"Reflect\s*\.\s*(?:get|set|has|defineProperty|deleteProperty|apply|construct)\s*\(", RegexOptions.Compiled), "Reflect.*", CapabilityRisk.Medium, 0.8f),
|
||||
|
||||
// Proxy
|
||||
(new Regex(@"new\s+Proxy\s*\(", RegexOptions.Compiled), "new Proxy", CapabilityRisk.Medium, 0.8f),
|
||||
|
||||
// Property access via bracket notation with variables
|
||||
(new Regex(@"\[\s*\w+\s*\]\s*\(", RegexOptions.Compiled), "Dynamic property call", CapabilityRisk.Medium, 0.65f),
|
||||
|
||||
// Object introspection
|
||||
(new Regex(@"Object\s*\.\s*getOwnPropertyDescriptor\s*\(", RegexOptions.Compiled), "Object.getOwnPropertyDescriptor", CapabilityRisk.Low, 0.7f),
|
||||
(new Regex(@"Object\s*\.\s*getPrototypeOf\s*\(", RegexOptions.Compiled), "Object.getPrototypeOf", CapabilityRisk.Low, 0.7f),
|
||||
(new Regex(@"Object\s*\.\s*setPrototypeOf\s*\(", RegexOptions.Compiled), "Object.setPrototypeOf", CapabilityRisk.High, 0.85f),
|
||||
(new Regex(@"__proto__", RegexOptions.Compiled), "__proto__", CapabilityRisk.High, 0.9f),
|
||||
|
||||
// constructor access
|
||||
(new Regex(@"\.constructor\s*\(", RegexOptions.Compiled), ".constructor()", CapabilityRisk.High, 0.85f),
|
||||
(new Regex(@"\[['""]\s*constructor\s*['""]", RegexOptions.Compiled), "['constructor']", CapabilityRisk.High, 0.85f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// NATIVE CODE - Native Addons
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] NativeCodePatterns =
|
||||
[
|
||||
// Native addon loading
|
||||
(new Regex(@"require\s*\([^)]*\.node['""]?\s*\)", RegexOptions.Compiled), "require('.node')", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"process\s*\.\s*dlopen\s*\(", RegexOptions.Compiled), "process.dlopen", CapabilityRisk.Critical, 1.0f),
|
||||
|
||||
// N-API / node-addon-api
|
||||
(new Regex(@"require\s*\(\s*['""]node-addon-api['""]", RegexOptions.Compiled), "require('node-addon-api')", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"require\s*\(\s*['""]bindings['""]", RegexOptions.Compiled), "require('bindings')", CapabilityRisk.High, 0.9f),
|
||||
|
||||
// FFI
|
||||
(new Regex(@"require\s*\(\s*['""]ffi-napi['""]", RegexOptions.Compiled), "require('ffi-napi')", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"require\s*\(\s*['""]node-ffi['""]", RegexOptions.Compiled), "require('node-ffi')", CapabilityRisk.Critical, 0.95f),
|
||||
(new Regex(@"require\s*\(\s*['""]ref-napi['""]", RegexOptions.Compiled), "require('ref-napi')", CapabilityRisk.High, 0.9f),
|
||||
|
||||
// WebAssembly
|
||||
(new Regex(@"WebAssembly\s*\.\s*instantiate\s*\(", RegexOptions.Compiled), "WebAssembly.instantiate", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"WebAssembly\s*\.\s*compile\s*\(", RegexOptions.Compiled), "WebAssembly.compile", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"new\s+WebAssembly\s*\.\s*Module\s*\(", RegexOptions.Compiled), "new WebAssembly.Module", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"new\s+WebAssembly\s*\.\s*Instance\s*\(", RegexOptions.Compiled), "new WebAssembly.Instance", CapabilityRisk.High, 0.9f),
|
||||
];
|
||||
|
||||
// ========================================
|
||||
// OTHER - Worker threads, cluster, etc.
|
||||
// ========================================
|
||||
private static readonly (Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] OtherPatterns =
|
||||
[
|
||||
// Worker threads
|
||||
(new Regex(@"require\s*\(\s*['""]worker_threads['""]", RegexOptions.Compiled), "require('worker_threads')", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"from\s+['""](?:node:)?worker_threads['""]", RegexOptions.Compiled), "import worker_threads", CapabilityRisk.Medium, 0.85f),
|
||||
(new Regex(@"new\s+Worker\s*\(", RegexOptions.Compiled), "new Worker", CapabilityRisk.Medium, 0.8f),
|
||||
|
||||
// Cluster
|
||||
(new Regex(@"require\s*\(\s*['""]cluster['""]", RegexOptions.Compiled), "require('cluster')", CapabilityRisk.Medium, 0.8f),
|
||||
(new Regex(@"cluster\s*\.\s*fork\s*\(", RegexOptions.Compiled), "cluster.fork", CapabilityRisk.Medium, 0.85f),
|
||||
|
||||
// Process manipulation
|
||||
(new Regex(@"process\s*\.\s*exit\s*\(", RegexOptions.Compiled), "process.exit", CapabilityRisk.Medium, 0.8f),
|
||||
(new Regex(@"process\s*\.\s*kill\s*\(", RegexOptions.Compiled), "process.kill", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"process\s*\.\s*abort\s*\(", RegexOptions.Compiled), "process.abort", CapabilityRisk.High, 0.9f),
|
||||
|
||||
// Module loading
|
||||
(new Regex(@"require\s*\.\s*resolve\s*\(", RegexOptions.Compiled), "require.resolve", CapabilityRisk.Low, 0.7f),
|
||||
(new Regex(@"import\s*\(", RegexOptions.Compiled), "dynamic import()", CapabilityRisk.Medium, 0.75f),
|
||||
(new Regex(@"require\s*\(\s*\w+\s*\)", RegexOptions.Compiled), "require(variable)", CapabilityRisk.High, 0.85f),
|
||||
|
||||
// Inspector/debugger
|
||||
(new Regex(@"require\s*\(\s*['""]inspector['""]", RegexOptions.Compiled), "require('inspector')", CapabilityRisk.High, 0.9f),
|
||||
(new Regex(@"\bdebugger\b", RegexOptions.Compiled), "debugger statement", CapabilityRisk.Medium, 0.75f),
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Scans a Node.js source file for capability usages.
|
||||
/// </summary>
|
||||
public static IEnumerable<NodeCapabilityEvidence> ScanFile(string content, string filePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Strip comments for more accurate detection
|
||||
var cleanedContent = StripComments(content);
|
||||
var lines = cleanedContent.Split('\n');
|
||||
|
||||
for (var lineNumber = 0; lineNumber < lines.Length; lineNumber++)
|
||||
{
|
||||
var line = lines[lineNumber];
|
||||
var lineNum = lineNumber + 1;
|
||||
|
||||
// Exec patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, ExecPatterns, CapabilityKind.Exec))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Filesystem patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, FilesystemPatterns, CapabilityKind.Filesystem))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Network patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, NetworkPatterns, CapabilityKind.Network))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Environment patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, EnvironmentPatterns, CapabilityKind.Environment))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Serialization patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, SerializationPatterns, CapabilityKind.Serialization))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Crypto patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, CryptoPatterns, CapabilityKind.Crypto))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Database patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, DatabasePatterns, CapabilityKind.Database))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Dynamic code patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, DynamicCodePatterns, CapabilityKind.DynamicCode))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Reflection patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, ReflectionPatterns, CapabilityKind.Reflection))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Native code patterns
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, NativeCodePatterns, CapabilityKind.NativeCode))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
|
||||
// Other patterns (workers, process, etc.)
|
||||
foreach (var evidence in ScanPatterns(line, lineNum, filePath, OtherPatterns, CapabilityKind.Other))
|
||||
{
|
||||
yield return evidence;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<NodeCapabilityEvidence> ScanPatterns(
|
||||
string line,
|
||||
int lineNumber,
|
||||
string filePath,
|
||||
(Regex Pattern, string Name, CapabilityRisk Risk, float Confidence)[] patterns,
|
||||
CapabilityKind kind)
|
||||
{
|
||||
foreach (var (pattern, name, risk, confidence) in patterns)
|
||||
{
|
||||
if (pattern.IsMatch(line))
|
||||
{
|
||||
yield return new NodeCapabilityEvidence(
|
||||
kind: kind,
|
||||
sourceFile: filePath,
|
||||
sourceLine: lineNumber,
|
||||
pattern: name,
|
||||
snippet: line.Trim(),
|
||||
confidence: confidence,
|
||||
risk: risk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Strips single-line (//) and multi-line (/* */) comments from JavaScript source.
|
||||
/// </summary>
|
||||
private static string StripComments(string content)
|
||||
{
|
||||
var sb = new StringBuilder(content.Length);
|
||||
var i = 0;
|
||||
var inString = false;
|
||||
var inTemplate = false;
|
||||
var stringChar = '"';
|
||||
|
||||
while (i < content.Length)
|
||||
{
|
||||
// Handle escape sequences in strings
|
||||
if ((inString || inTemplate) && content[i] == '\\' && i + 1 < content.Length)
|
||||
{
|
||||
sb.Append(content[i]);
|
||||
sb.Append(content[i + 1]);
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle template literals
|
||||
if (!inString && content[i] == '`')
|
||||
{
|
||||
inTemplate = !inTemplate;
|
||||
sb.Append(content[i]);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle string literals (but not inside template literals)
|
||||
if (!inTemplate && (content[i] == '"' || content[i] == '\''))
|
||||
{
|
||||
if (!inString)
|
||||
{
|
||||
inString = true;
|
||||
stringChar = content[i];
|
||||
}
|
||||
else if (content[i] == stringChar)
|
||||
{
|
||||
inString = false;
|
||||
}
|
||||
sb.Append(content[i]);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip comments only when not in string/template
|
||||
if (!inString && !inTemplate)
|
||||
{
|
||||
// Single-line comment
|
||||
if (i + 1 < content.Length && content[i] == '/' && content[i + 1] == '/')
|
||||
{
|
||||
// Skip until end of line
|
||||
while (i < content.Length && content[i] != '\n')
|
||||
{
|
||||
i++;
|
||||
}
|
||||
if (i < content.Length)
|
||||
{
|
||||
sb.Append('\n');
|
||||
i++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Multi-line comment
|
||||
if (i + 1 < content.Length && content[i] == '/' && content[i + 1] == '*')
|
||||
{
|
||||
i += 2;
|
||||
while (i + 1 < content.Length && !(content[i] == '*' && content[i + 1] == '/'))
|
||||
{
|
||||
// Preserve newlines for line number accuracy
|
||||
if (content[i] == '\n')
|
||||
{
|
||||
sb.Append('\n');
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (i + 1 < content.Length)
|
||||
{
|
||||
i += 2; // Skip */
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append(content[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user