- 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.
299 lines
9.8 KiB
C#
299 lines
9.8 KiB
C#
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
|
|
};
|
|
}
|
|
}
|