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,298 @@
|
||||
namespace StellaOps.VexLens.Trust.SourceTrust;
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="ITrustDecayService"/>.
|
||||
/// Applies time-based trust decay, recency bonuses, and revocation penalties.
|
||||
/// </summary>
|
||||
public sealed class TrustDecayService : ITrustDecayService
|
||||
{
|
||||
public DecayResult ApplyDecay(
|
||||
double baseScore,
|
||||
DateTimeOffset statementTimestamp,
|
||||
DecayContext context)
|
||||
{
|
||||
var age = context.EvaluationTime - statementTimestamp;
|
||||
var ageDays = age.TotalDays;
|
||||
var config = context.Configuration;
|
||||
|
||||
var (decayFactor, category) = CalculateDecayFactor(age, config);
|
||||
|
||||
// Reduce decay for statements with updates
|
||||
if (context.HasUpdates && context.UpdateCount > 0)
|
||||
{
|
||||
// Each update reduces effective age by 10%, up to 50% reduction
|
||||
var updateReduction = Math.Min(0.5, context.UpdateCount * 0.1);
|
||||
decayFactor = Math.Min(1.0, decayFactor + (1.0 - decayFactor) * updateReduction);
|
||||
}
|
||||
|
||||
var decayedScore = baseScore * decayFactor;
|
||||
|
||||
return new DecayResult
|
||||
{
|
||||
BaseScore = baseScore,
|
||||
DecayFactor = decayFactor,
|
||||
DecayedScore = decayedScore,
|
||||
AgeDays = ageDays,
|
||||
Category = category
|
||||
};
|
||||
}
|
||||
|
||||
public double CalculateRecencyBonus(
|
||||
DateTimeOffset lastUpdateTimestamp,
|
||||
RecencyBonusContext context)
|
||||
{
|
||||
var age = context.EvaluationTime - lastUpdateTimestamp;
|
||||
|
||||
if (age > context.RecencyWindow)
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Linear decrease from max bonus to 0 over the recency window
|
||||
var ratio = 1.0 - (age.TotalSeconds / context.RecencyWindow.TotalSeconds);
|
||||
return context.MaxBonus * ratio;
|
||||
}
|
||||
|
||||
public RevocationImpact CalculateRevocationImpact(
|
||||
RevocationInfo revocation,
|
||||
RevocationContext context)
|
||||
{
|
||||
if (!revocation.IsRevoked)
|
||||
{
|
||||
return new RevocationImpact
|
||||
{
|
||||
ShouldExclude = false,
|
||||
Penalty = 0.0,
|
||||
Explanation = "Statement is not revoked",
|
||||
RecommendedAction = RevocationAction.None
|
||||
};
|
||||
}
|
||||
|
||||
// Determine impact based on revocation type
|
||||
return revocation.RevocationType switch
|
||||
{
|
||||
RevocationType.Superseded when revocation.WasSuperseded => new RevocationImpact
|
||||
{
|
||||
ShouldExclude = true,
|
||||
Penalty = context.SupersededPenalty,
|
||||
Explanation = $"Statement superseded by {revocation.SupersededBy ?? "newer statement"}",
|
||||
RecommendedAction = RevocationAction.Replace
|
||||
},
|
||||
|
||||
RevocationType.Correction => new RevocationImpact
|
||||
{
|
||||
ShouldExclude = false,
|
||||
Penalty = context.CorrectionPenalty,
|
||||
Explanation = $"Statement corrected: {revocation.RevocationReason ?? "unspecified reason"}",
|
||||
RecommendedAction = RevocationAction.Penalize
|
||||
},
|
||||
|
||||
RevocationType.Withdrawn => new RevocationImpact
|
||||
{
|
||||
ShouldExclude = true,
|
||||
Penalty = context.RevocationPenalty,
|
||||
Explanation = $"Statement withdrawn: {revocation.RevocationReason ?? "unspecified reason"}",
|
||||
RecommendedAction = RevocationAction.Exclude
|
||||
},
|
||||
|
||||
RevocationType.Expired => new RevocationImpact
|
||||
{
|
||||
ShouldExclude = false,
|
||||
Penalty = context.RevocationPenalty * 0.5,
|
||||
Explanation = "Statement expired and was not renewed",
|
||||
RecommendedAction = RevocationAction.Review
|
||||
},
|
||||
|
||||
RevocationType.SourceRevoked => new RevocationImpact
|
||||
{
|
||||
ShouldExclude = true,
|
||||
Penalty = context.RevocationPenalty,
|
||||
Explanation = "Source has been revoked",
|
||||
RecommendedAction = RevocationAction.Exclude
|
||||
},
|
||||
|
||||
_ => new RevocationImpact
|
||||
{
|
||||
ShouldExclude = false,
|
||||
Penalty = context.RevocationPenalty * 0.75,
|
||||
Explanation = $"Statement revoked: {revocation.RevocationReason ?? "unknown reason"}",
|
||||
RecommendedAction = RevocationAction.Review
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public EffectiveTrustScore GetEffectiveScore(
|
||||
double baseScore,
|
||||
TrustScoreFactors factors,
|
||||
DateTimeOffset evaluationTime)
|
||||
{
|
||||
var adjustments = new List<TrustAdjustment>();
|
||||
var shouldExclude = false;
|
||||
|
||||
// Apply decay
|
||||
var decayConfig = factors.DecayConfiguration ?? DecayConfiguration.CreateDefault();
|
||||
var decayContext = new DecayContext
|
||||
{
|
||||
EvaluationTime = evaluationTime,
|
||||
Configuration = decayConfig,
|
||||
HasUpdates = factors.UpdateCount > 0,
|
||||
UpdateCount = factors.UpdateCount
|
||||
};
|
||||
|
||||
var decayResult = ApplyDecay(baseScore, factors.StatementTimestamp, decayContext);
|
||||
|
||||
adjustments.Add(new TrustAdjustment
|
||||
{
|
||||
Type = TrustAdjustmentType.Decay,
|
||||
Amount = decayResult.DecayedScore - baseScore,
|
||||
Reason = $"Time-based decay (age: {decayResult.AgeDays:F1} days, category: {decayResult.Category})"
|
||||
});
|
||||
|
||||
var effectiveScore = decayResult.DecayedScore;
|
||||
|
||||
// Apply recency bonus if recently updated
|
||||
var recencyBonus = 0.0;
|
||||
if (factors.LastUpdateTimestamp.HasValue)
|
||||
{
|
||||
var recencyContext = new RecencyBonusContext
|
||||
{
|
||||
EvaluationTime = evaluationTime
|
||||
};
|
||||
|
||||
recencyBonus = CalculateRecencyBonus(factors.LastUpdateTimestamp.Value, recencyContext);
|
||||
|
||||
if (recencyBonus > 0)
|
||||
{
|
||||
effectiveScore += recencyBonus;
|
||||
adjustments.Add(new TrustAdjustment
|
||||
{
|
||||
Type = TrustAdjustmentType.RecencyBonus,
|
||||
Amount = recencyBonus,
|
||||
Reason = "Recently updated statement"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Apply update bonus
|
||||
if (factors.UpdateCount > 1)
|
||||
{
|
||||
var updateBonus = Math.Min(0.05, factors.UpdateCount * 0.01);
|
||||
effectiveScore += updateBonus;
|
||||
adjustments.Add(new TrustAdjustment
|
||||
{
|
||||
Type = TrustAdjustmentType.UpdateBonus,
|
||||
Amount = updateBonus,
|
||||
Reason = $"Statement has been updated {factors.UpdateCount} times"
|
||||
});
|
||||
}
|
||||
|
||||
// Apply revocation penalty
|
||||
var revocationPenalty = 0.0;
|
||||
if (factors.Revocation != null)
|
||||
{
|
||||
var revocationContext = new RevocationContext
|
||||
{
|
||||
EvaluationTime = evaluationTime
|
||||
};
|
||||
|
||||
var revocationImpact = CalculateRevocationImpact(factors.Revocation, revocationContext);
|
||||
|
||||
if (revocationImpact.ShouldExclude)
|
||||
{
|
||||
shouldExclude = true;
|
||||
}
|
||||
|
||||
revocationPenalty = revocationImpact.Penalty;
|
||||
effectiveScore -= revocationPenalty;
|
||||
|
||||
adjustments.Add(new TrustAdjustment
|
||||
{
|
||||
Type = TrustAdjustmentType.RevocationPenalty,
|
||||
Amount = -revocationPenalty,
|
||||
Reason = revocationImpact.Explanation
|
||||
});
|
||||
}
|
||||
|
||||
// Clamp final score
|
||||
effectiveScore = Math.Clamp(effectiveScore, 0.0, 1.0);
|
||||
|
||||
return new EffectiveTrustScore
|
||||
{
|
||||
BaseScore = baseScore,
|
||||
EffectiveScore = effectiveScore,
|
||||
DecayFactor = decayResult.DecayFactor,
|
||||
RecencyBonus = recencyBonus,
|
||||
RevocationPenalty = revocationPenalty,
|
||||
ShouldExclude = shouldExclude,
|
||||
StalenessCategory = decayResult.Category,
|
||||
Adjustments = adjustments
|
||||
};
|
||||
}
|
||||
|
||||
private (double Factor, StalenessCategory Category) CalculateDecayFactor(
|
||||
TimeSpan age,
|
||||
DecayConfiguration config)
|
||||
{
|
||||
if (age <= TimeSpan.Zero)
|
||||
{
|
||||
return (1.0, StalenessCategory.Fresh);
|
||||
}
|
||||
|
||||
if (age < config.FreshThreshold)
|
||||
{
|
||||
return (1.0, StalenessCategory.Fresh);
|
||||
}
|
||||
|
||||
if (age < config.RecentThreshold)
|
||||
{
|
||||
var factor = CalculateCurveValue(
|
||||
age, config.FreshThreshold, config.RecentThreshold,
|
||||
1.0, 0.9, config.CurveType);
|
||||
return (factor, StalenessCategory.Recent);
|
||||
}
|
||||
|
||||
if (age < config.StaleThreshold)
|
||||
{
|
||||
var factor = CalculateCurveValue(
|
||||
age, config.RecentThreshold, config.StaleThreshold,
|
||||
0.9, 0.7, config.CurveType);
|
||||
return (factor, StalenessCategory.Aging);
|
||||
}
|
||||
|
||||
if (age < config.ExpiredThreshold)
|
||||
{
|
||||
var factor = CalculateCurveValue(
|
||||
age, config.StaleThreshold, config.ExpiredThreshold,
|
||||
0.7, config.MinDecayFactor, config.CurveType);
|
||||
return (factor, StalenessCategory.Stale);
|
||||
}
|
||||
|
||||
return (config.MinDecayFactor, StalenessCategory.Expired);
|
||||
}
|
||||
|
||||
private static double CalculateCurveValue(
|
||||
TimeSpan current,
|
||||
TimeSpan start,
|
||||
TimeSpan end,
|
||||
double startValue,
|
||||
double endValue,
|
||||
DecayCurveType curveType)
|
||||
{
|
||||
var progress = (current - start).TotalSeconds / (end - start).TotalSeconds;
|
||||
progress = Math.Clamp(progress, 0.0, 1.0);
|
||||
|
||||
return curveType switch
|
||||
{
|
||||
DecayCurveType.Linear =>
|
||||
startValue + (endValue - startValue) * progress,
|
||||
|
||||
DecayCurveType.Exponential =>
|
||||
startValue * Math.Pow(endValue / startValue, progress),
|
||||
|
||||
DecayCurveType.Step =>
|
||||
progress < 0.5 ? startValue : endValue,
|
||||
|
||||
_ => startValue + (endValue - startValue) * progress
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user