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:
StellaOps Bot
2025-12-22 23:21:21 +02:00
parent 3ba7157b00
commit 5146204f1b
529 changed files with 73579 additions and 5985 deletions

View File

@@ -197,58 +197,3 @@ public sealed class InteropTestHarness : IAsyncLifetime
return Array.Empty<GrypeFinding>();
}
}
public enum SbomFormat
{
CycloneDx16,
Spdx30
}
public sealed record SbomResult(
bool Success,
string? Path = null,
SbomFormat? Format = null,
string? Content = null,
string? Digest = null,
string? Error = null)
{
public static SbomResult Failed(string error) => new(false, Error: error);
}
public sealed record AttestationResult(
bool Success,
string? ImageRef = null,
string? Error = null)
{
public static AttestationResult Failed(string error) => new(false, Error: error);
}
public sealed record GrypeScanResult(
bool Success,
IReadOnlyList<GrypeFinding>? Findings = null,
string? RawOutput = null,
string? Error = null)
{
public static GrypeScanResult Failed(string error) => new(false, Error: error);
}
public sealed record FindingsComparisonResult(
decimal ParityPercent,
bool IsWithinTolerance,
int StellaTotalFindings,
int GrypeTotalFindings,
int MatchingFindings,
int OnlyInStella,
int OnlyInGrype,
IReadOnlyList<(string VulnId, string Purl)> OnlyInStellaDetails,
IReadOnlyList<(string VulnId, string Purl)> OnlyInGrypeDetails);
public sealed record Finding(
string VulnerabilityId,
string PackagePurl,
string Severity);
public sealed record GrypeFinding(
string VulnerabilityId,
string PackagePurl,
string Severity);

View File

@@ -0,0 +1,78 @@
// -----------------------------------------------------------------------------
// Models.cs
// Sprint: SPRINT_5100_0003_0001_sbom_interop_roundtrip
// Task: T1, T7 - Interop Test Harness & Project Setup
// Description: Models for SBOM interoperability testing.
// -----------------------------------------------------------------------------
using System.Collections.Immutable;
using System.Security.Cryptography;
using System.Text;
namespace StellaOps.Interop.Tests;
public enum SbomFormat
{
CycloneDx16,
Spdx30
}
public sealed record SbomResult(
bool Success,
string? Path = null,
SbomFormat? Format = null,
string? Content = null,
string? Digest = null,
string? Error = null)
{
public static SbomResult Failed(string error) => new(false, Error: error);
}
public sealed record AttestationResult(
bool Success,
string? ImageRef = null,
string? Error = null)
{
public static AttestationResult Failed(string error) => new(false, Error: error);
}
public sealed record GrypeScanResult(
bool Success,
IReadOnlyList<GrypeFinding>? Findings = null,
string? RawOutput = null,
string? Error = null)
{
public static GrypeScanResult Failed(string error) => new(false, Error: error);
}
public sealed record GrypeFinding(
string VulnerabilityId,
string PackagePurl,
string Severity,
string? FixedIn = null);
public sealed record Finding(
string VulnerabilityId,
string PackagePurl,
string Severity);
public sealed record ToolResult(
bool Success,
string Output,
string? Error = null);
public sealed record FindingsComparisonResult(
decimal ParityPercent,
bool IsWithinTolerance,
int StellaTotalFindings,
int GrypeTotalFindings,
int MatchingFindings,
int OnlyInStella,
int OnlyInGrype,
IReadOnlyList<(string VulnId, string Purl)> OnlyInStellaDetails,
IReadOnlyList<(string VulnId, string Purl)> OnlyInGrypeDetails);
public sealed record VerifyResult(
bool Success,
string? PredicateDigest = null,
string? Error = null);

View File

@@ -5,19 +5,14 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<LangVersion>preview</LangVersion>
<RootNamespace>StellaOps.Interop.Tests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
@@ -26,7 +21,6 @@
<ItemGroup>
<Using Include="Xunit" />
<Using Include="FluentAssertions" />
<Using Include="System.Collections.Immutable" />
</ItemGroup>
</Project>

View File

@@ -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);