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,123 @@
using FluentAssertions;
using StellaOps.Policy.Gates;
using Xunit;
namespace StellaOps.Policy.Tests.Gates;
public sealed class BudgetLedgerTests
{
private readonly InMemoryBudgetStore _store = new();
private readonly BudgetLedger _ledger;
private readonly string _currentWindow;
public BudgetLedgerTests()
{
_ledger = new BudgetLedger(_store);
_currentWindow = DateTimeOffset.UtcNow.ToString("yyyy-MM");
}
[Fact]
public async Task GetBudget_CreatesDefaultWhenNotExists()
{
var budget = await _ledger.GetBudgetAsync("new-service");
budget.Should().NotBeNull();
budget.ServiceId.Should().Be("new-service");
budget.Tier.Should().Be(ServiceTier.CustomerFacingNonCritical);
budget.Allocated.Should().Be(200); // Default for Tier 1
budget.Consumed.Should().Be(0);
}
[Fact]
public async Task GetBudget_ReturnsExistingBudget()
{
var existing = CreateBudget("existing-service", consumed: 50);
await _store.CreateAsync(existing, CancellationToken.None);
var budget = await _ledger.GetBudgetAsync("existing-service", _currentWindow);
budget.Consumed.Should().Be(50);
}
[Fact]
public async Task Consume_DeductsBudget()
{
var initial = CreateBudget("test-service", consumed: 50);
await _store.CreateAsync(initial, CancellationToken.None);
var result = await _ledger.ConsumeAsync("test-service", 20, "release-1");
result.IsSuccess.Should().BeTrue();
result.Budget.Consumed.Should().Be(70);
result.Budget.Remaining.Should().Be(130);
result.Entry.Should().NotBeNull();
result.Entry!.RiskPoints.Should().Be(20);
}
[Fact]
public async Task Consume_FailsWhenInsufficientBudget()
{
var initial = CreateBudget("test-service", consumed: 190);
await _store.CreateAsync(initial, CancellationToken.None);
var result = await _ledger.ConsumeAsync("test-service", 20, "release-1");
result.IsSuccess.Should().BeFalse();
result.Error.Should().Contain("Insufficient");
}
[Fact]
public async Task GetHistory_ReturnsEntries()
{
await _ledger.GetBudgetAsync("test-service");
await _ledger.ConsumeAsync("test-service", 10, "release-1");
await _ledger.ConsumeAsync("test-service", 15, "release-2");
var history = await _ledger.GetHistoryAsync("test-service");
history.Should().HaveCount(2);
history.Should().Contain(e => e.ReleaseId == "release-1");
history.Should().Contain(e => e.ReleaseId == "release-2");
}
[Fact]
public async Task AdjustAllocation_IncreasesCapacity()
{
await _ledger.GetBudgetAsync("test-service");
var adjusted = await _ledger.AdjustAllocationAsync("test-service", 50, "earned capacity");
adjusted.Allocated.Should().Be(250); // 200 + 50
}
[Fact]
public async Task AdjustAllocation_DecreasesCapacity()
{
await _ledger.GetBudgetAsync("test-service");
var adjusted = await _ledger.AdjustAllocationAsync("test-service", -50, "incident penalty");
adjusted.Allocated.Should().Be(150); // 200 - 50
}
[Fact]
public async Task AdjustAllocation_DoesNotGoBelowZero()
{
await _ledger.GetBudgetAsync("test-service");
var adjusted = await _ledger.AdjustAllocationAsync("test-service", -500, "major penalty");
adjusted.Allocated.Should().Be(0);
}
private RiskBudget CreateBudget(string serviceId, int consumed) => new()
{
BudgetId = $"budget:{serviceId}:{_currentWindow}",
ServiceId = serviceId,
Tier = ServiceTier.CustomerFacingNonCritical,
Window = _currentWindow,
Allocated = 200,
Consumed = consumed,
UpdatedAt = DateTimeOffset.UtcNow
};
}

View File

@@ -0,0 +1,78 @@
using FluentAssertions;
using StellaOps.Policy.Gates;
using Xunit;
namespace StellaOps.Policy.Tests.Gates;
public sealed class GateLevelTests
{
[Theory]
[InlineData(GateLevel.G0, 2)]
[InlineData(GateLevel.G1, 5)]
[InlineData(GateLevel.G2, 6)]
[InlineData(GateLevel.G3, 7)]
[InlineData(GateLevel.G4, 6)]
public void GetRequirements_ReturnsCorrectCount(GateLevel level, int expectedCount)
{
var requirements = GateLevelRequirements.GetRequirements(level);
requirements.Should().HaveCount(expectedCount);
}
[Fact]
public void GetRequirements_G0_HasBasicCiOnly()
{
var requirements = GateLevelRequirements.GetRequirements(GateLevel.G0);
requirements.Should().Contain(r => r.Contains("Lint"));
requirements.Should().Contain(r => r.Contains("CI"));
}
[Fact]
public void GetRequirements_G1_HasUnitTestsAndReview()
{
var requirements = GateLevelRequirements.GetRequirements(GateLevel.G1);
requirements.Should().Contain(r => r.Contains("unit tests"));
requirements.Should().Contain(r => r.Contains("peer review"));
}
[Fact]
public void GetRequirements_G2_IncludesG1Requirements()
{
var requirements = GateLevelRequirements.GetRequirements(GateLevel.G2);
requirements.Should().Contain(r => r.Contains("G1"));
requirements.Should().Contain(r => r.Contains("Code owner", StringComparison.OrdinalIgnoreCase));
requirements.Should().Contain(r => r.Contains("feature flag", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void GetRequirements_G3_HasSecurityAndReleaseSign()
{
var requirements = GateLevelRequirements.GetRequirements(GateLevel.G3);
requirements.Should().Contain(r => r.Contains("Security scan", StringComparison.OrdinalIgnoreCase));
requirements.Should().Contain(r => r.Contains("release captain", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void GetRequirements_G4_HasFormalReviewAndCanary()
{
var requirements = GateLevelRequirements.GetRequirements(GateLevel.G4);
requirements.Should().Contain(r => r.Contains("Formal risk review"));
requirements.Should().Contain(r => r.Contains("Extended canary"));
}
[Theory]
[InlineData(GateLevel.G0, "No-risk")]
[InlineData(GateLevel.G1, "Low risk")]
[InlineData(GateLevel.G2, "Moderate risk")]
[InlineData(GateLevel.G3, "High risk")]
[InlineData(GateLevel.G4, "Very high risk")]
public void GetDescription_ContainsExpectedText(GateLevel level, string expectedText)
{
var description = GateLevelRequirements.GetDescription(level);
description.Should().Contain(expectedText);
}
}

View File

@@ -0,0 +1,85 @@
using FluentAssertions;
using StellaOps.Policy.Gates;
using Xunit;
namespace StellaOps.Policy.Tests.Gates;
public sealed class RiskBudgetTests
{
[Fact]
public void Budget_WithNoConsumption_IsGreen()
{
var budget = CreateBudget(allocated: 200, consumed: 0);
budget.Status.Should().Be(BudgetStatus.Green);
budget.Remaining.Should().Be(200);
budget.PercentageUsed.Should().Be(0);
}
[Fact]
public void Budget_With30PercentUsed_IsGreen()
{
var budget = CreateBudget(allocated: 200, consumed: 60);
budget.Status.Should().Be(BudgetStatus.Green);
budget.PercentageUsed.Should().Be(30);
}
[Fact]
public void Budget_With40PercentUsed_IsYellow()
{
var budget = CreateBudget(allocated: 200, consumed: 80);
budget.Status.Should().Be(BudgetStatus.Yellow);
budget.PercentageUsed.Should().Be(40);
}
[Fact]
public void Budget_With70PercentUsed_IsRed()
{
var budget = CreateBudget(allocated: 200, consumed: 140);
budget.Status.Should().Be(BudgetStatus.Red);
budget.PercentageUsed.Should().Be(70);
}
[Fact]
public void Budget_With100PercentUsed_IsExhausted()
{
var budget = CreateBudget(allocated: 200, consumed: 200);
budget.Status.Should().Be(BudgetStatus.Exhausted);
budget.Remaining.Should().Be(0);
}
[Fact]
public void Budget_Overconsumed_IsExhausted()
{
var budget = CreateBudget(allocated: 200, consumed: 250);
budget.Status.Should().Be(BudgetStatus.Exhausted);
budget.Remaining.Should().Be(-50);
}
[Theory]
[InlineData(ServiceTier.Internal, 300)]
[InlineData(ServiceTier.CustomerFacingNonCritical, 200)]
[InlineData(ServiceTier.CustomerFacingCritical, 120)]
[InlineData(ServiceTier.SafetyCritical, 80)]
public void DefaultAllocations_AreCorrect(ServiceTier tier, int expected)
{
var allocation = DefaultBudgetAllocations.GetMonthlyAllocation(tier);
allocation.Should().Be(expected);
}
private static RiskBudget CreateBudget(int allocated, int consumed) => new()
{
BudgetId = "budget:test:2025-01",
ServiceId = "test-service",
Tier = ServiceTier.CustomerFacingNonCritical,
Window = "2025-01",
Allocated = allocated,
Consumed = consumed,
UpdatedAt = DateTimeOffset.UtcNow
};
}

View File

@@ -0,0 +1,173 @@
using FluentAssertions;
using StellaOps.Policy.Gates;
using Xunit;
namespace StellaOps.Policy.Tests.Gates;
public sealed class RiskPointScoringTests
{
private readonly RiskPointScoring _scoring = new();
[Theory]
[InlineData(ServiceTier.Internal, 1)]
[InlineData(ServiceTier.CustomerFacingNonCritical, 3)]
[InlineData(ServiceTier.CustomerFacingCritical, 6)]
[InlineData(ServiceTier.SafetyCritical, 10)]
public void CalculateScore_UsesCorrectBaseScore(ServiceTier tier, int expectedBase)
{
var input = CreateInput(tier, DiffCategory.DocsOnly);
var result = _scoring.CalculateScore(input);
result.Breakdown.Base.Should().Be(expectedBase);
}
[Theory]
[InlineData(DiffCategory.DocsOnly, 1)]
[InlineData(DiffCategory.UiNonCore, 3)]
[InlineData(DiffCategory.ApiBackwardCompatible, 6)]
[InlineData(DiffCategory.DatabaseMigration, 10)]
[InlineData(DiffCategory.CryptoPayment, 15)]
public void CalculateScore_UsesCorrectDiffRisk(DiffCategory category, int expectedDiffRisk)
{
var input = CreateInput(ServiceTier.Internal, category);
var result = _scoring.CalculateScore(input);
result.Breakdown.DiffRisk.Should().Be(expectedDiffRisk);
}
[Fact]
public void CalculateScore_AddsOperationalContext()
{
var input = CreateInput(
ServiceTier.CustomerFacingNonCritical,
DiffCategory.DocsOnly,
context: new OperationalContext
{
HasRecentIncident = true,
ErrorBudgetBelow50Percent = true
});
var result = _scoring.CalculateScore(input);
result.Breakdown.OperationalContext.Should().Be(8); // 5 + 3
}
[Fact]
public void CalculateScore_SubtractsMitigations()
{
var input = CreateInput(
ServiceTier.CustomerFacingNonCritical,
DiffCategory.ApiBackwardCompatible,
mitigations: new MitigationFactors
{
HasFeatureFlag = true,
HasCanaryDeployment = true
});
var result = _scoring.CalculateScore(input);
result.Breakdown.Mitigations.Should().Be(6); // 3 + 3
}
[Fact]
public void CalculateScore_MinimumIsOne()
{
var input = CreateInput(
ServiceTier.Internal,
DiffCategory.DocsOnly,
mitigations: new MitigationFactors
{
HasFeatureFlag = true,
HasCanaryDeployment = true,
HasHighTestCoverage = true
});
var result = _scoring.CalculateScore(input);
result.Score.Should().Be(1);
}
[Theory]
[InlineData(5, GateLevel.G1)]
[InlineData(6, GateLevel.G2)]
[InlineData(12, GateLevel.G2)]
[InlineData(13, GateLevel.G3)]
[InlineData(20, GateLevel.G3)]
[InlineData(21, GateLevel.G4)]
public void CalculateScore_DeterminesCorrectGateLevel(int targetScore, GateLevel expectedGate)
{
// Use Tier 0 (base=1) + appropriate diff to hit target
var diffCategory = targetScore switch
{
<= 5 => DiffCategory.UiNonCore, // 1 + 3 = 4
<= 12 => DiffCategory.ApiBackwardCompatible, // 1 + 6 = 7
<= 20 => DiffCategory.InfraNetworking, // 1 + 15 = 16
_ => DiffCategory.CryptoPayment // 1 + 15 = 16, add context to get > 20
};
var context = targetScore > 20
? new OperationalContext { HasRecentIncident = true, InRestrictedWindow = true }
: OperationalContext.Default;
var input = CreateInput(ServiceTier.Internal, diffCategory, context: context);
var result = _scoring.CalculateScore(input);
result.RecommendedGate.Should().Be(expectedGate);
}
[Fact]
public void CalculateScore_EscalatesGateOnYellowBudget()
{
var input = CreateInput(
ServiceTier.CustomerFacingNonCritical,
DiffCategory.ApiBackwardCompatible,
context: new OperationalContext { BudgetStatus = BudgetStatus.Yellow });
var result = _scoring.CalculateScore(input);
// Base=3 + Diff=6 = 9 → G2, but Yellow escalates G2+ → G3
result.RecommendedGate.Should().Be(GateLevel.G3);
}
[Fact]
public void CalculateScore_EscalatesGateOnRedBudget()
{
var input = CreateInput(
ServiceTier.CustomerFacingNonCritical,
DiffCategory.DocsOnly,
context: new OperationalContext { BudgetStatus = BudgetStatus.Red });
var result = _scoring.CalculateScore(input);
// Base=3 + Diff=1 = 4 → G1, but Red escalates G1+ → G2
result.RecommendedGate.Should().Be(GateLevel.G2);
}
[Fact]
public void CalculateScore_MaxGateOnExhaustedBudget()
{
var input = CreateInput(
ServiceTier.Internal,
DiffCategory.DocsOnly,
context: new OperationalContext { BudgetStatus = BudgetStatus.Exhausted });
var result = _scoring.CalculateScore(input);
result.RecommendedGate.Should().Be(GateLevel.G4);
}
private static RiskScoreInput CreateInput(
ServiceTier tier,
DiffCategory category,
OperationalContext? context = null,
MitigationFactors? mitigations = null) => new()
{
Tier = tier,
DiffCategory = category,
Context = context ?? OperationalContext.Default,
Mitigations = mitigations ?? MitigationFactors.None
};
}