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

@@ -0,0 +1,197 @@
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Cryptography;
using StellaOps.Policy.Replay;
using StellaOps.Policy.Snapshots;
using Xunit;
namespace StellaOps.Policy.Tests.Replay;
public sealed class ReplayEngineTests
{
private readonly ICryptoHash _hasher = DefaultCryptoHash.CreateForTests();
private readonly InMemorySnapshotStore _snapshotStore = new();
private readonly SnapshotService _snapshotService;
private readonly ReplayEngine _engine;
public ReplayEngineTests()
{
var idGenerator = new SnapshotIdGenerator(_hasher);
_snapshotService = new SnapshotService(
idGenerator,
_snapshotStore,
NullLogger<SnapshotService>.Instance);
var sourceResolver = new KnowledgeSourceResolver(
_snapshotStore,
NullLogger<KnowledgeSourceResolver>.Instance);
var verdictComparer = new VerdictComparer();
_engine = new ReplayEngine(
_snapshotService,
sourceResolver,
verdictComparer,
NullLogger<ReplayEngine>.Instance);
}
[Fact]
public async Task Replay_ValidSnapshot_ReturnsResult()
{
var snapshot = await CreateSnapshotAsync();
var request = new ReplayRequest
{
ArtifactDigest = "sha256:test123",
SnapshotId = snapshot.SnapshotId
};
var result = await _engine.ReplayAsync(request);
result.Should().NotBeNull();
result.SnapshotId.Should().Be(snapshot.SnapshotId);
result.ReplayedVerdict.Should().NotBeNull();
result.ReplayedAt.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromSeconds(5));
}
[Fact]
public async Task Replay_NonExistentSnapshot_ReturnsReplayFailed()
{
var request = new ReplayRequest
{
ArtifactDigest = "sha256:test123",
SnapshotId = "ksm:sha256:nonexistent"
};
var result = await _engine.ReplayAsync(request);
result.MatchStatus.Should().Be(ReplayMatchStatus.ReplayFailed);
result.DeltaReport.Should().NotBeNull();
result.DeltaReport!.Summary.Should().Contain("not found");
}
[Fact]
public async Task Replay_NoOriginalVerdict_ReturnsNoComparison()
{
var snapshot = await CreateSnapshotAsync();
var request = new ReplayRequest
{
ArtifactDigest = "sha256:test123",
SnapshotId = snapshot.SnapshotId,
OriginalVerdictId = null,
Options = new ReplayOptions { CompareWithOriginal = true }
};
var result = await _engine.ReplayAsync(request);
result.MatchStatus.Should().Be(ReplayMatchStatus.NoComparison);
}
[Fact]
public async Task Replay_SameInputs_ProducesDeterministicResult()
{
var snapshot = await CreateSnapshotAsync();
var request = new ReplayRequest
{
ArtifactDigest = "sha256:determinism-test",
SnapshotId = snapshot.SnapshotId
};
// Run multiple times
var results = new List<ReplayResult>();
for (var i = 0; i < 10; i++)
{
results.Add(await _engine.ReplayAsync(request));
}
// All results should have identical verdicts
var firstScore = results[0].ReplayedVerdict.Score;
var firstDecision = results[0].ReplayedVerdict.Decision;
results.Should().AllSatisfy(r =>
{
r.ReplayedVerdict.Score.Should().Be(firstScore);
r.ReplayedVerdict.Decision.Should().Be(firstDecision);
});
}
[Fact]
public async Task Replay_DifferentArtifacts_ProducesDifferentResults()
{
var snapshot = await CreateSnapshotAsync();
var request1 = new ReplayRequest
{
ArtifactDigest = "sha256:artifact-a",
SnapshotId = snapshot.SnapshotId
};
var request2 = new ReplayRequest
{
ArtifactDigest = "sha256:artifact-b",
SnapshotId = snapshot.SnapshotId
};
var result1 = await _engine.ReplayAsync(request1);
var result2 = await _engine.ReplayAsync(request2);
// Different inputs may produce different results
// (both are valid, just testing they can differ)
result1.ReplayedVerdict.ArtifactDigest.Should().NotBe(result2.ReplayedVerdict.ArtifactDigest);
}
[Fact]
public async Task Replay_RecordsDuration()
{
var snapshot = await CreateSnapshotAsync();
var request = new ReplayRequest
{
ArtifactDigest = "sha256:test123",
SnapshotId = snapshot.SnapshotId
};
var result = await _engine.ReplayAsync(request);
result.Duration.Should().BeGreaterThan(TimeSpan.Zero);
}
[Fact]
public async Task Replay_WithValidOriginalVerdictId_AttemptsComparison()
{
var snapshot = await CreateSnapshotAsync();
var request = new ReplayRequest
{
ArtifactDigest = "sha256:test123",
SnapshotId = snapshot.SnapshotId,
OriginalVerdictId = "verdict-not-found",
Options = new ReplayOptions { CompareWithOriginal = true }
};
var result = await _engine.ReplayAsync(request);
// Original verdict not implemented in test, so no comparison
result.MatchStatus.Should().Be(ReplayMatchStatus.NoComparison);
}
private async Task<KnowledgeSnapshotManifest> CreateSnapshotAsync()
{
var builder = new SnapshotBuilder(_hasher)
.WithEngine("stellaops-policy", "1.0.0", "abc123")
.WithPolicy("test-policy", "1.0", "sha256:policy123")
.WithScoring("test-scoring", "1.0", "sha256:scoring123")
.WithSource(new KnowledgeSourceDescriptor
{
Name = "test-feed",
Type = "advisory-feed",
Epoch = DateTimeOffset.UtcNow.ToString("o"),
Digest = "sha256:feed123",
InclusionMode = SourceInclusionMode.Referenced
});
return await _snapshotService.CreateSnapshotAsync(builder);
}
}

View File

@@ -0,0 +1,137 @@
using FluentAssertions;
using StellaOps.Policy.Replay;
using Xunit;
namespace StellaOps.Policy.Tests.Replay;
public sealed class ReplayReportTests
{
[Fact]
public void Build_CreatesReportWithRequiredFields()
{
var request = CreateRequest();
var result = CreateResult(ReplayMatchStatus.ExactMatch);
var report = new ReplayReportBuilder(request, result).Build();
report.ReportId.Should().StartWith("rpt:");
report.ArtifactDigest.Should().Be(request.ArtifactDigest);
report.SnapshotId.Should().Be(request.SnapshotId);
report.MatchStatus.Should().Be(ReplayMatchStatus.ExactMatch);
}
[Fact]
public void Build_ExactMatch_SetsDeterministicTrue()
{
var request = CreateRequest();
var result = CreateResult(ReplayMatchStatus.ExactMatch);
var report = new ReplayReportBuilder(request, result).Build();
report.IsDeterministic.Should().BeTrue();
report.DeterminismConfidence.Should().Be(1.0m);
}
[Fact]
public void Build_Mismatch_SetsDeterministicFalse()
{
var request = CreateRequest();
var result = CreateResult(ReplayMatchStatus.Mismatch);
var report = new ReplayReportBuilder(request, result).Build();
report.IsDeterministic.Should().BeFalse();
report.DeterminismConfidence.Should().Be(0.0m);
}
[Fact]
public void Build_MatchWithinTolerance_SetsHighConfidence()
{
var request = CreateRequest();
var result = CreateResult(ReplayMatchStatus.MatchWithinTolerance);
var report = new ReplayReportBuilder(request, result).Build();
report.IsDeterministic.Should().BeFalse();
report.DeterminismConfidence.Should().Be(0.9m);
}
[Fact]
public void Build_NoComparison_SetsMediumConfidence()
{
var request = CreateRequest();
var result = CreateResult(ReplayMatchStatus.NoComparison);
var report = new ReplayReportBuilder(request, result).Build();
report.DeterminismConfidence.Should().Be(0.5m);
}
[Fact]
public void AddRecommendation_AddsToList()
{
var request = CreateRequest();
var result = CreateResult(ReplayMatchStatus.ExactMatch);
var report = new ReplayReportBuilder(request, result)
.AddRecommendation("Test recommendation")
.Build();
report.Recommendations.Should().Contain("Test recommendation");
}
[Fact]
public void AddRecommendationsFromResult_MismatchAddsReviewRecommendation()
{
var request = CreateRequest();
var result = CreateResult(ReplayMatchStatus.Mismatch);
var report = new ReplayReportBuilder(request, result)
.AddRecommendationsFromResult()
.Build();
report.Recommendations.Should().Contain(r => r.Contains("delta report"));
}
[Fact]
public void AddRecommendationsFromResult_FailedAddsSnapshotRecommendation()
{
var request = CreateRequest();
var result = CreateResult(ReplayMatchStatus.ReplayFailed);
var report = new ReplayReportBuilder(request, result)
.AddRecommendationsFromResult()
.Build();
report.Recommendations.Should().Contain(r => r.Contains("snapshot"));
}
[Fact]
public void Build_IncludesTiming()
{
var request = CreateRequest();
var result = CreateResult(ReplayMatchStatus.ExactMatch) with
{
Duration = TimeSpan.FromMilliseconds(150)
};
var report = new ReplayReportBuilder(request, result).Build();
report.Timing.TotalDuration.Should().Be(TimeSpan.FromMilliseconds(150));
}
private static ReplayRequest CreateRequest() => new()
{
ArtifactDigest = "sha256:test123",
SnapshotId = "ksm:sha256:snapshot123",
OriginalVerdictId = "verdict-001"
};
private static ReplayResult CreateResult(ReplayMatchStatus status) => new()
{
MatchStatus = status,
ReplayedVerdict = ReplayedVerdict.Empty with { ArtifactDigest = "sha256:test123" },
SnapshotId = "ksm:sha256:snapshot123",
ReplayedAt = DateTimeOffset.UtcNow
};
}

View File

@@ -0,0 +1,127 @@
using FluentAssertions;
using StellaOps.Policy.Replay;
using Xunit;
namespace StellaOps.Policy.Tests.Replay;
public sealed class VerdictComparerTests
{
private readonly VerdictComparer _comparer = new();
[Fact]
public void Compare_IdenticalVerdicts_ReturnsExactMatch()
{
var verdict = CreateVerdict(decision: ReplayDecision.Pass, score: 85.5m);
var result = _comparer.Compare(verdict, verdict, VerdictComparisonOptions.Default);
result.MatchStatus.Should().Be(ReplayMatchStatus.ExactMatch);
result.IsDeterministic.Should().BeTrue();
result.DeterminismConfidence.Should().Be(1.0m);
result.Differences.Should().BeEmpty();
}
[Fact]
public void Compare_DifferentDecisions_ReturnsMismatch()
{
var original = CreateVerdict(decision: ReplayDecision.Pass);
var replayed = CreateVerdict(decision: ReplayDecision.Fail);
var result = _comparer.Compare(replayed, original, VerdictComparisonOptions.Default);
result.MatchStatus.Should().Be(ReplayMatchStatus.Mismatch);
result.IsDeterministic.Should().BeFalse();
result.Differences.Should().Contain(d => d.Field == "Decision");
}
[Fact]
public void Compare_ScoreWithinTolerance_ReturnsMatchWithinTolerance()
{
var original = CreateVerdict(score: 85.5000m);
var replayed = CreateVerdict(score: 85.5005m);
var result = _comparer.Compare(replayed, original,
new VerdictComparisonOptions { ScoreTolerance = 0.001m, TreatMinorAsMatch = true });
result.MatchStatus.Should().Be(ReplayMatchStatus.MatchWithinTolerance);
}
[Fact]
public void Compare_ScoreBeyondTolerance_ReturnsMismatch()
{
var original = CreateVerdict(score: 85.5m);
var replayed = CreateVerdict(score: 86.0m);
var result = _comparer.Compare(replayed, original,
new VerdictComparisonOptions { ScoreTolerance = 0.001m, CriticalScoreTolerance = 0.1m });
result.MatchStatus.Should().Be(ReplayMatchStatus.Mismatch);
result.Differences.Should().Contain(d => d.Field == "Score");
}
[Fact]
public void Compare_DifferentFindings_DetectsAddedAndRemoved()
{
var original = CreateVerdictWithFindings("CVE-2024-001", "CVE-2024-002");
var replayed = CreateVerdictWithFindings("CVE-2024-001", "CVE-2024-003");
var result = _comparer.Compare(replayed, original, VerdictComparisonOptions.Default);
result.MatchStatus.Should().Be(ReplayMatchStatus.Mismatch);
result.Differences.Should().Contain(d => d.Field == "Finding:CVE-2024-002" && d.ReplayedValue == "absent");
result.Differences.Should().Contain(d => d.Field == "Finding:CVE-2024-003" && d.OriginalValue == "absent");
}
[Fact]
public void Compare_SameFindings_DifferentOrder_ReturnsMatch()
{
var original = CreateVerdictWithFindings("CVE-2024-001", "CVE-2024-002", "CVE-2024-003");
var replayed = CreateVerdictWithFindings("CVE-2024-003", "CVE-2024-001", "CVE-2024-002");
var result = _comparer.Compare(replayed, original, VerdictComparisonOptions.Default);
result.MatchStatus.Should().Be(ReplayMatchStatus.ExactMatch);
}
[Fact]
public void Compare_ExtraFindings_DetectsAdditions()
{
var original = CreateVerdictWithFindings("CVE-2024-001");
var replayed = CreateVerdictWithFindings("CVE-2024-001", "CVE-2024-002");
var result = _comparer.Compare(replayed, original, VerdictComparisonOptions.Default);
result.MatchStatus.Should().Be(ReplayMatchStatus.Mismatch);
result.Differences.Should().ContainSingle(d => d.Field == "Finding:CVE-2024-002");
}
[Fact]
public void Compare_CalculatesCorrectConfidence()
{
var original = CreateVerdict(decision: ReplayDecision.Pass, score: 85.0m);
var replayed = CreateVerdict(decision: ReplayDecision.Fail, score: 75.0m);
var result = _comparer.Compare(replayed, original, VerdictComparisonOptions.Default);
result.DeterminismConfidence.Should().BeLessThan(1.0m);
result.DeterminismConfidence.Should().BeGreaterThanOrEqualTo(0m);
}
private static ReplayedVerdict CreateVerdict(
ReplayDecision decision = ReplayDecision.Pass,
decimal score = 85.0m) => new()
{
ArtifactDigest = "sha256:test123",
Decision = decision,
Score = score,
FindingIds = []
};
private static ReplayedVerdict CreateVerdictWithFindings(params string[] findingIds) => new()
{
ArtifactDigest = "sha256:test123",
Decision = ReplayDecision.Pass,
Score = 85.0m,
FindingIds = findingIds.ToList()
};
}