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,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 }
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user