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:
@@ -0,0 +1,159 @@
|
||||
using FluentAssertions;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Policy.Snapshots;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Policy.Tests.Snapshots;
|
||||
|
||||
public sealed class SnapshotBuilderTests
|
||||
{
|
||||
private readonly ICryptoHash _hasher = DefaultCryptoHash.CreateForTests();
|
||||
|
||||
[Fact]
|
||||
public void Build_ValidInputs_CreatesManifest()
|
||||
{
|
||||
var builder = new SnapshotBuilder(_hasher)
|
||||
.WithEngine("test", "1.0", "abc123")
|
||||
.WithPolicy("policy-1", "sha256:xxx")
|
||||
.WithScoring("scoring-1", "sha256:yyy")
|
||||
.WithAdvisoryFeed("nvd", "2025-12-21", "sha256:zzz");
|
||||
|
||||
var manifest = builder.Build();
|
||||
|
||||
manifest.SnapshotId.Should().StartWith("ksm:sha256:");
|
||||
manifest.SnapshotId.Length.Should().Be("ksm:sha256:".Length + 64); // ksm:sha256: + 64 hex chars
|
||||
manifest.Sources.Should().HaveCount(1);
|
||||
manifest.Engine.Name.Should().Be("test");
|
||||
manifest.Engine.Version.Should().Be("1.0");
|
||||
manifest.Engine.Commit.Should().Be("abc123");
|
||||
manifest.Policy.PolicyId.Should().Be("policy-1");
|
||||
manifest.Scoring.RulesId.Should().Be("scoring-1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_MissingEngine_Throws()
|
||||
{
|
||||
var builder = new SnapshotBuilder(_hasher)
|
||||
.WithPolicy("policy-1", "sha256:xxx")
|
||||
.WithScoring("scoring-1", "sha256:yyy")
|
||||
.WithAdvisoryFeed("nvd", "2025-12-21", "sha256:zzz");
|
||||
|
||||
var act = () => builder.Build();
|
||||
|
||||
act.Should().Throw<InvalidOperationException>()
|
||||
.WithMessage("*Engine*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_MissingPolicy_Throws()
|
||||
{
|
||||
var builder = new SnapshotBuilder(_hasher)
|
||||
.WithEngine("test", "1.0", "abc123")
|
||||
.WithScoring("scoring-1", "sha256:yyy")
|
||||
.WithAdvisoryFeed("nvd", "2025-12-21", "sha256:zzz");
|
||||
|
||||
var act = () => builder.Build();
|
||||
|
||||
act.Should().Throw<InvalidOperationException>()
|
||||
.WithMessage("*Policy*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_MissingScoring_Throws()
|
||||
{
|
||||
var builder = new SnapshotBuilder(_hasher)
|
||||
.WithEngine("test", "1.0", "abc123")
|
||||
.WithPolicy("policy-1", "sha256:xxx")
|
||||
.WithAdvisoryFeed("nvd", "2025-12-21", "sha256:zzz");
|
||||
|
||||
var act = () => builder.Build();
|
||||
|
||||
act.Should().Throw<InvalidOperationException>()
|
||||
.WithMessage("*Scoring*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_NoSources_Throws()
|
||||
{
|
||||
var builder = new SnapshotBuilder(_hasher)
|
||||
.WithEngine("test", "1.0", "abc123")
|
||||
.WithPolicy("policy-1", "sha256:xxx")
|
||||
.WithScoring("scoring-1", "sha256:yyy");
|
||||
|
||||
var act = () => builder.Build();
|
||||
|
||||
act.Should().Throw<InvalidOperationException>()
|
||||
.WithMessage("*source*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_MultipleSources_OrderedByName()
|
||||
{
|
||||
var builder = new SnapshotBuilder(_hasher)
|
||||
.WithEngine("test", "1.0", "abc123")
|
||||
.WithPolicy("policy-1", "sha256:xxx")
|
||||
.WithScoring("scoring-1", "sha256:yyy")
|
||||
.WithAdvisoryFeed("z-source", "2025-12-21", "sha256:aaa")
|
||||
.WithAdvisoryFeed("a-source", "2025-12-21", "sha256:bbb")
|
||||
.WithAdvisoryFeed("m-source", "2025-12-21", "sha256:ccc");
|
||||
|
||||
var manifest = builder.Build();
|
||||
|
||||
manifest.Sources.Should().HaveCount(3);
|
||||
manifest.Sources[0].Name.Should().Be("a-source");
|
||||
manifest.Sources[1].Name.Should().Be("m-source");
|
||||
manifest.Sources[2].Name.Should().Be("z-source");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_WithPlugins_IncludesPlugins()
|
||||
{
|
||||
var builder = new SnapshotBuilder(_hasher)
|
||||
.WithEngine("test", "1.0", "abc123")
|
||||
.WithPolicy("policy-1", "sha256:xxx")
|
||||
.WithScoring("scoring-1", "sha256:yyy")
|
||||
.WithAdvisoryFeed("nvd", "2025-12-21", "sha256:zzz")
|
||||
.WithPlugin("reachability", "2.0", "analyzer")
|
||||
.WithPlugin("sbom", "1.5", "analyzer");
|
||||
|
||||
var manifest = builder.Build();
|
||||
|
||||
manifest.Plugins.Should().HaveCount(2);
|
||||
manifest.Plugins[0].Name.Should().Be("reachability");
|
||||
manifest.Plugins[1].Name.Should().Be("sbom");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_WithTrust_IncludesTrust()
|
||||
{
|
||||
var builder = new SnapshotBuilder(_hasher)
|
||||
.WithEngine("test", "1.0", "abc123")
|
||||
.WithPolicy("policy-1", "sha256:xxx")
|
||||
.WithScoring("scoring-1", "sha256:yyy")
|
||||
.WithAdvisoryFeed("nvd", "2025-12-21", "sha256:zzz")
|
||||
.WithTrust("trust-bundle", "sha256:trust123");
|
||||
|
||||
var manifest = builder.Build();
|
||||
|
||||
manifest.Trust.Should().NotBeNull();
|
||||
manifest.Trust!.BundleId.Should().Be("trust-bundle");
|
||||
manifest.Trust.Digest.Should().Be("sha256:trust123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_CaptureCurrentEnvironment_SetsEnvironment()
|
||||
{
|
||||
var builder = new SnapshotBuilder(_hasher)
|
||||
.WithEngine("test", "1.0", "abc123")
|
||||
.WithPolicy("policy-1", "sha256:xxx")
|
||||
.WithScoring("scoring-1", "sha256:yyy")
|
||||
.WithAdvisoryFeed("nvd", "2025-12-21", "sha256:zzz")
|
||||
.CaptureCurrentEnvironment();
|
||||
|
||||
var manifest = builder.Build();
|
||||
|
||||
manifest.Environment.Should().NotBeNull();
|
||||
manifest.Environment!.Platform.Should().NotBeNullOrEmpty();
|
||||
manifest.Environment.Locale.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user