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,259 @@
// -----------------------------------------------------------------------------
// UncertaintyStatementTests.cs
// Sprint: SPRINT_4300_0002_0002_unknowns_attestation_predicates
// Description: Unit tests for uncertainty attestation statements.
// -----------------------------------------------------------------------------
using System.Text.Json;
using StellaOps.Attestor.ProofChain.Builders;
using StellaOps.Attestor.ProofChain.Statements;
namespace StellaOps.Attestor.ProofChain.Tests.Statements;
/// <summary>
/// Unit tests for UncertaintyStatement and UncertaintyBudgetStatement.
/// </summary>
public sealed class UncertaintyStatementTests
{
private readonly StatementBuilder _builder = new();
private readonly DateTimeOffset _fixedTime = new(2025, 12, 22, 10, 0, 0, TimeSpan.Zero);
[Fact]
public void BuildUncertaintyStatement_SetsPredicateTypeAndSubject()
{
var subject = CreateSubject("image:demo@sha256:abc123", "abc123");
var predicate = new UncertaintyPayload
{
AggregateTier = "T2",
MeanEntropy = 0.45,
MarkerCount = 3,
RiskModifier = 1.25,
States = new[]
{
new UncertaintyStateEntry
{
Code = "U1",
Name = "MissingSymbolResolution",
Entropy = 0.5,
Tier = "T2",
MarkerKind = "missing_symbol"
},
new UncertaintyStateEntry
{
Code = "U2",
Name = "MissingPurl",
Entropy = 0.4,
Tier = "T3"
}
},
ComputedAt = _fixedTime
};
var statement = _builder.BuildUncertaintyStatement(subject, predicate);
Assert.Equal("https://in-toto.io/Statement/v1", statement.Type);
Assert.Equal("uncertainty.stella/v1", statement.PredicateType);
Assert.Single(statement.Subject);
Assert.Equal(subject.Name, statement.Subject[0].Name);
Assert.Equal("T2", statement.Predicate.AggregateTier);
Assert.Equal(0.45, statement.Predicate.MeanEntropy);
Assert.Equal(2, statement.Predicate.States.Count);
}
[Fact]
public void BuildUncertaintyBudgetStatement_SetsPredicateTypeAndSubject()
{
var subject = CreateSubject("image:demo@sha256:abc123", "abc123");
var predicate = new UncertaintyBudgetPayload
{
Environment = "production",
Passed = false,
Action = "block",
Budget = new BudgetDefinition
{
BudgetId = "prod-budget-v1",
TotalLimit = 5,
ReasonLimits = new Dictionary<string, int>
{
["U-RCH"] = 2,
["U-ID"] = 3
}
},
Observed = new BudgetObservation
{
TotalUnknowns = 8,
ByReasonCode = new Dictionary<string, int>
{
["U-RCH"] = 4,
["U-ID"] = 4
}
},
Violations = new[]
{
new BudgetViolationEntry
{
LimitType = "total",
Limit = 5,
Observed = 8,
Exceeded = 3,
Severity = "high"
}
},
EvaluatedAt = _fixedTime
};
var statement = _builder.BuildUncertaintyBudgetStatement(subject, predicate);
Assert.Equal("https://in-toto.io/Statement/v1", statement.Type);
Assert.Equal("uncertainty-budget.stella/v1", statement.PredicateType);
Assert.Single(statement.Subject);
Assert.Equal("production", statement.Predicate.Environment);
Assert.False(statement.Predicate.Passed);
Assert.Equal("block", statement.Predicate.Action);
Assert.NotNull(statement.Predicate.Violations);
Assert.Single(statement.Predicate.Violations);
}
[Fact]
public void UncertaintyStatement_RoundTripsViaJson()
{
var subject = CreateSubject("image:demo", "abc123");
var statement = _builder.BuildUncertaintyStatement(subject, new UncertaintyPayload
{
AggregateTier = "T3",
MeanEntropy = 0.25,
MarkerCount = 1,
RiskModifier = 1.1,
States = new[]
{
new UncertaintyStateEntry
{
Code = "U3",
Name = "UntrustedAdvisory",
Entropy = 0.25,
Tier = "T3"
}
},
ComputedAt = _fixedTime,
KnowledgeSnapshotId = "ksm:sha256:abc123"
});
var json = JsonSerializer.Serialize(statement);
var restored = JsonSerializer.Deserialize<UncertaintyStatement>(json);
Assert.NotNull(restored);
Assert.Equal(statement.PredicateType, restored.PredicateType);
Assert.Equal(statement.Subject[0].Name, restored.Subject[0].Name);
Assert.Equal(statement.Predicate.AggregateTier, restored.Predicate.AggregateTier);
Assert.Equal(statement.Predicate.MeanEntropy, restored.Predicate.MeanEntropy);
Assert.Equal(statement.Predicate.KnowledgeSnapshotId, restored.Predicate.KnowledgeSnapshotId);
}
[Fact]
public void UncertaintyBudgetStatement_RoundTripsViaJson()
{
var subject = CreateSubject("image:demo", "abc123");
var statement = _builder.BuildUncertaintyBudgetStatement(subject, new UncertaintyBudgetPayload
{
Environment = "staging",
Passed = true,
Action = "pass",
Budget = new BudgetDefinition
{
BudgetId = "staging-budget",
TotalLimit = 10
},
Observed = new BudgetObservation
{
TotalUnknowns = 3
},
EvaluatedAt = _fixedTime,
Message = "Budget check passed"
});
var json = JsonSerializer.Serialize(statement);
var restored = JsonSerializer.Deserialize<UncertaintyBudgetStatement>(json);
Assert.NotNull(restored);
Assert.Equal(statement.PredicateType, restored.PredicateType);
Assert.Equal(statement.Predicate.Environment, restored.Predicate.Environment);
Assert.True(restored.Predicate.Passed);
Assert.Equal("Budget check passed", restored.Predicate.Message);
}
[Fact]
public void UncertaintyBudgetStatement_WithExceptions_SerializesCorrectly()
{
var subject = CreateSubject("image:demo", "abc123");
var predicate = new UncertaintyBudgetPayload
{
Environment = "production",
Passed = true,
Action = "pass",
Budget = new BudgetDefinition
{
BudgetId = "prod-budget",
TotalLimit = 5
},
Observed = new BudgetObservation
{
TotalUnknowns = 7,
ByReasonCode = new Dictionary<string, int>
{
["U-RCH"] = 4,
["U-ID"] = 3
}
},
ExceptionsApplied = new[]
{
new BudgetExceptionEntry
{
ExceptionId = "EXC-2025-001",
CoveredReasons = new[] { "U-RCH" },
Justification = "Known limitation in reachability analysis",
ApprovedBy = "security-team",
ExpiresAt = _fixedTime.AddDays(30)
}
},
EvaluatedAt = _fixedTime
};
var statement = _builder.BuildUncertaintyBudgetStatement(subject, predicate);
var json = JsonSerializer.Serialize(statement, new JsonSerializerOptions { WriteIndented = true });
Assert.Contains("EXC-2025-001", json);
Assert.Contains("U-RCH", json);
Assert.Contains("security-team", json);
}
[Fact]
public void BuildUncertaintyStatement_NullSubject_Throws()
{
var predicate = new UncertaintyPayload
{
AggregateTier = "T4",
MeanEntropy = 0.05,
MarkerCount = 0,
RiskModifier = 1.0,
States = Array.Empty<UncertaintyStateEntry>(),
ComputedAt = _fixedTime
};
Assert.Throws<ArgumentNullException>(() => _builder.BuildUncertaintyStatement(null!, predicate));
}
[Fact]
public void BuildUncertaintyBudgetStatement_NullPredicate_Throws()
{
var subject = CreateSubject("image:demo", "abc123");
Assert.Throws<ArgumentNullException>(() => _builder.BuildUncertaintyBudgetStatement(subject, null!));
}
private static ProofSubject CreateSubject(string name, string sha256Digest)
=> new()
{
Name = name,
Digest = new Dictionary<string, string> { ["sha256"] = sha256Digest }
};
}

View File

@@ -0,0 +1,241 @@
// -----------------------------------------------------------------------------
// UnknownsBudgetPredicateTests.cs
// Sprint: SPRINT_5100_0004_0001_unknowns_budget_ci_gates
// Task: T6 - Unit Tests
// Description: Tests for UnknownsBudgetPredicate attestation integration.
// -----------------------------------------------------------------------------
using System.Collections.Immutable;
using System.Text.Json;
using System.Text.Json.Serialization;
using StellaOps.Attestor.ProofChain.Predicates;
namespace StellaOps.Attestor.ProofChain.Tests.Statements;
public sealed class UnknownsBudgetPredicateTests
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
[Fact]
public void PredicateType_IsCorrect()
{
Assert.Equal("unknowns-budget.stella/v1", UnknownsBudgetPredicate.PredicateType);
}
[Fact]
public void Create_WithinBudget_SetsCorrectProperties()
{
var predicate = new UnknownsBudgetPredicate
{
Environment = "prod",
TotalUnknowns = 3,
TotalLimit = 10,
IsWithinBudget = true,
PercentageUsed = 30m,
EvaluatedAt = DateTimeOffset.UtcNow
};
Assert.Equal("prod", predicate.Environment);
Assert.Equal(3, predicate.TotalUnknowns);
Assert.Equal(10, predicate.TotalLimit);
Assert.True(predicate.IsWithinBudget);
Assert.Equal(30m, predicate.PercentageUsed);
}
[Fact]
public void Create_ExceedsBudget_SetsCorrectProperties()
{
var predicate = new UnknownsBudgetPredicate
{
Environment = "prod",
TotalUnknowns = 15,
TotalLimit = 10,
IsWithinBudget = false,
PercentageUsed = 150m,
RecommendedAction = "Block",
Message = "Budget exceeded: 15 unknowns exceed limit of 10",
EvaluatedAt = DateTimeOffset.UtcNow
};
Assert.False(predicate.IsWithinBudget);
Assert.Equal("Block", predicate.RecommendedAction);
Assert.Contains("Budget exceeded", predicate.Message);
}
[Fact]
public void Create_WithViolations_SerializesCorrectly()
{
var violations = ImmutableArray.Create(
new BudgetViolationPredicate
{
ReasonCode = "Reachability",
Count = 5,
Limit = 3
},
new BudgetViolationPredicate
{
ReasonCode = "Identity",
Count = 2,
Limit = 1
}
);
var predicate = new UnknownsBudgetPredicate
{
Environment = "stage",
TotalUnknowns = 7,
TotalLimit = 5,
IsWithinBudget = false,
Violations = violations,
EvaluatedAt = DateTimeOffset.UtcNow
};
Assert.Equal(2, predicate.Violations.Length);
Assert.Equal("Reachability", predicate.Violations[0].ReasonCode);
Assert.Equal(5, predicate.Violations[0].Count);
}
[Fact]
public void Create_WithByReasonCode_SerializesCorrectly()
{
var byReasonCode = ImmutableDictionary.CreateRange(new[]
{
new KeyValuePair<string, int>("Reachability", 5),
new KeyValuePair<string, int>("Identity", 2),
new KeyValuePair<string, int>("VexConflict", 1)
});
var predicate = new UnknownsBudgetPredicate
{
Environment = "dev",
TotalUnknowns = 8,
TotalLimit = 20,
IsWithinBudget = true,
ByReasonCode = byReasonCode,
EvaluatedAt = DateTimeOffset.UtcNow
};
Assert.Equal(3, predicate.ByReasonCode.Count);
Assert.Equal(5, predicate.ByReasonCode["Reachability"]);
}
[Fact]
public void Serialize_ToJson_ProducesValidOutput()
{
var predicate = new UnknownsBudgetPredicate
{
Environment = "prod",
TotalUnknowns = 3,
TotalLimit = 10,
IsWithinBudget = true,
PercentageUsed = 30m,
EvaluatedAt = new DateTimeOffset(2025, 12, 22, 12, 0, 0, TimeSpan.Zero)
};
var json = JsonSerializer.Serialize(predicate, JsonOptions);
Assert.Contains("\"environment\": \"prod\"", json);
Assert.Contains("\"totalUnknowns\": 3", json);
Assert.Contains("\"totalLimit\": 10", json);
Assert.Contains("\"isWithinBudget\": true", json);
}
[Fact]
public void Deserialize_FromJson_RestoresProperties()
{
var json = """
{
"environment": "stage",
"totalUnknowns": 7,
"totalLimit": 5,
"isWithinBudget": false,
"percentageUsed": 140.0,
"recommendedAction": "Warn",
"violations": [
{
"reasonCode": "Reachability",
"count": 5,
"limit": 3
}
],
"evaluatedAt": "2025-12-22T12:00:00Z"
}
""";
var predicate = JsonSerializer.Deserialize<UnknownsBudgetPredicate>(json, JsonOptions);
Assert.NotNull(predicate);
Assert.Equal("stage", predicate.Environment);
Assert.Equal(7, predicate.TotalUnknowns);
Assert.Equal(5, predicate.TotalLimit);
Assert.False(predicate.IsWithinBudget);
Assert.Equal(140.0m, predicate.PercentageUsed);
Assert.Single(predicate.Violations);
Assert.Equal("Reachability", predicate.Violations[0].ReasonCode);
}
[Fact]
public void DeltaVerdictPredicate_IncludesUnknownsBudget()
{
var budget = new UnknownsBudgetPredicate
{
Environment = "prod",
TotalUnknowns = 2,
TotalLimit = 10,
IsWithinBudget = true,
EvaluatedAt = DateTimeOffset.UtcNow
};
var verdict = new DeltaVerdictPredicate
{
BeforeRevisionId = "rev-1",
AfterRevisionId = "rev-2",
HasMaterialChange = true,
PriorityScore = 0.5,
ComparedAt = DateTimeOffset.UtcNow,
UnknownsBudget = budget
};
Assert.NotNull(verdict.UnknownsBudget);
Assert.Equal("prod", verdict.UnknownsBudget.Environment);
Assert.True(verdict.UnknownsBudget.IsWithinBudget);
}
[Fact]
public void DeltaVerdictPredicate_WithoutUnknownsBudget_SerializesCorrectly()
{
var verdict = new DeltaVerdictPredicate
{
BeforeRevisionId = "rev-1",
AfterRevisionId = "rev-2",
HasMaterialChange = false,
PriorityScore = 0.0,
ComparedAt = DateTimeOffset.UtcNow,
UnknownsBudget = null
};
var json = JsonSerializer.Serialize(verdict, JsonOptions);
Assert.DoesNotContain("unknownsBudget", json);
}
[Fact]
public void BudgetViolationPredicate_Properties_AreCorrect()
{
var violation = new BudgetViolationPredicate
{
ReasonCode = "FeedGap",
Count = 10,
Limit = 5
};
Assert.Equal("FeedGap", violation.ReasonCode);
Assert.Equal(10, violation.Count);
Assert.Equal(5, violation.Limit);
}
}