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:
@@ -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);
|
||||
|
||||
78
tests/interop/StellaOps.Interop.Tests/Models.cs
Normal file
78
tests/interop/StellaOps.Interop.Tests/Models.cs
Normal 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);
|
||||
@@ -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>
|
||||
|
||||
@@ -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