feat: add security sink detection patterns for JavaScript/TypeScript
- Introduced `sink-detect.js` with various security sink detection patterns categorized by type (e.g., command injection, SQL injection, file operations). - Implemented functions to build a lookup map for fast sink detection and to match sink calls against known patterns. - Added `package-lock.json` for dependency management.
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
namespace StellaOps.Interop.Tests;
|
||||
// -----------------------------------------------------------------------------
|
||||
// ToolManager.cs
|
||||
// Sprint: SPRINT_5100_0003_0001_sbom_interop_roundtrip
|
||||
// Task: T1 - Interop Test Harness
|
||||
// Description: Manages execution of external tools (Syft, Grype, cosign).
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Manages execution of external tools for interop testing.
|
||||
/// </summary>
|
||||
namespace StellaOps.Interop.Tests;
|
||||
|
||||
public sealed class ToolManager
|
||||
{
|
||||
private readonly string _workDir;
|
||||
@@ -15,110 +18,66 @@ public sealed class ToolManager
|
||||
_workDir = workDir;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that a tool is available and executable.
|
||||
/// </summary>
|
||||
public async Task<bool> VerifyToolAsync(string toolName, string testArgs, CancellationToken ct = default)
|
||||
public async Task VerifyToolAsync(string tool, string versionArg)
|
||||
{
|
||||
try
|
||||
var result = await RunAsync(tool, versionArg, CancellationToken.None);
|
||||
if (!result.Success)
|
||||
{
|
||||
var result = await RunAsync(toolName, testArgs, ct);
|
||||
return result.Success || result.ExitCode == 0; // Some tools return 0 even on --version
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
throw new InvalidOperationException(
|
||||
$"Tool '{tool}' is not available or failed verification: {result.Error}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run an external tool with arguments.
|
||||
/// </summary>
|
||||
public async Task<ToolResult> RunAsync(
|
||||
string toolName,
|
||||
string tool,
|
||||
string arguments,
|
||||
CancellationToken ct = default,
|
||||
int timeoutMs = 300000) // 5 minute default timeout
|
||||
CancellationToken ct,
|
||||
int timeoutSeconds = 300)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = toolName,
|
||||
Arguments = arguments,
|
||||
WorkingDirectory = _workDir,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var process = new Process { StartInfo = startInfo };
|
||||
var outputBuilder = new StringBuilder();
|
||||
var errorBuilder = new StringBuilder();
|
||||
|
||||
process.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Data != null)
|
||||
outputBuilder.AppendLine(e.Data);
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Data != null)
|
||||
errorBuilder.AppendLine(e.Data);
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = tool,
|
||||
Arguments = arguments,
|
||||
WorkingDirectory = _workDir,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
cts.CancelAfter(timeoutMs);
|
||||
var outputTask = process.StandardOutput.ReadToEndAsync(ct);
|
||||
var errorTask = process.StandardError.ReadToEndAsync(ct);
|
||||
|
||||
await process.WaitForExitAsync(cts.Token);
|
||||
var completed = await Task.WhenAny(
|
||||
process.WaitForExitAsync(ct),
|
||||
Task.Delay(TimeSpan.FromSeconds(timeoutSeconds), ct));
|
||||
|
||||
var output = outputBuilder.ToString();
|
||||
var error = errorBuilder.ToString();
|
||||
var exitCode = process.ExitCode;
|
||||
|
||||
return new ToolResult(
|
||||
Success: exitCode == 0,
|
||||
ExitCode: exitCode,
|
||||
Output: output,
|
||||
Error: string.IsNullOrWhiteSpace(error) ? null : error);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
try
|
||||
if (!process.HasExited)
|
||||
{
|
||||
if (!process.HasExited)
|
||||
process.Kill();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore kill failures
|
||||
process.Kill(entireProcessTree: true);
|
||||
return new ToolResult(false, "", "Process timed out");
|
||||
}
|
||||
|
||||
return new ToolResult(
|
||||
Success: false,
|
||||
ExitCode: -1,
|
||||
Output: outputBuilder.ToString(),
|
||||
Error: $"Tool execution timed out after {timeoutMs}ms");
|
||||
var output = await outputTask;
|
||||
var error = await errorTask;
|
||||
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
return new ToolResult(false, output, error);
|
||||
}
|
||||
|
||||
return new ToolResult(true, output);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new ToolResult(
|
||||
Success: false,
|
||||
ExitCode: -1,
|
||||
Output: outputBuilder.ToString(),
|
||||
Error: $"Tool execution failed: {ex.Message}");
|
||||
return new ToolResult(false, "", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record ToolResult(
|
||||
bool Success,
|
||||
int ExitCode,
|
||||
string Output,
|
||||
string? Error = null);
|
||||
|
||||
Reference in New Issue
Block a user