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,195 @@
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Policy.Snapshots;
|
||||
|
||||
namespace StellaOps.Policy.Replay;
|
||||
|
||||
/// <summary>
|
||||
/// Resolves knowledge sources from snapshot descriptors.
|
||||
/// </summary>
|
||||
public sealed class KnowledgeSourceResolver : IKnowledgeSourceResolver
|
||||
{
|
||||
private readonly ISnapshotStore _snapshotStore;
|
||||
private readonly ILogger<KnowledgeSourceResolver> _logger;
|
||||
|
||||
public KnowledgeSourceResolver(
|
||||
ISnapshotStore snapshotStore,
|
||||
ILogger<KnowledgeSourceResolver>? logger = null)
|
||||
{
|
||||
_snapshotStore = snapshotStore ?? throw new ArgumentNullException(nameof(snapshotStore));
|
||||
_logger = logger ?? NullLogger<KnowledgeSourceResolver>.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a knowledge source to its actual content.
|
||||
/// </summary>
|
||||
public async Task<ResolvedSource?> ResolveAsync(
|
||||
KnowledgeSourceDescriptor descriptor,
|
||||
bool allowNetworkFetch,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
_logger.LogDebug("Resolving source {Name} ({Type})", descriptor.Name, descriptor.Type);
|
||||
|
||||
// Try bundled content first
|
||||
if (descriptor.InclusionMode != SourceInclusionMode.Referenced &&
|
||||
descriptor.BundlePath is not null)
|
||||
{
|
||||
var bundled = await ResolveBundledAsync(descriptor, ct).ConfigureAwait(false);
|
||||
if (bundled is not null)
|
||||
return bundled;
|
||||
}
|
||||
|
||||
// Try local store by digest
|
||||
var local = await ResolveFromLocalStoreAsync(descriptor, ct).ConfigureAwait(false);
|
||||
if (local is not null)
|
||||
return local;
|
||||
|
||||
// Network fetch not implemented yet (air-gap safe default)
|
||||
if (allowNetworkFetch && descriptor.Origin is not null)
|
||||
{
|
||||
_logger.LogWarning("Network fetch not implemented for {Name}", descriptor.Name);
|
||||
}
|
||||
|
||||
_logger.LogWarning("Failed to resolve source {Name} with digest {Digest}",
|
||||
descriptor.Name, descriptor.Digest);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<ResolvedSource?> ResolveBundledAsync(
|
||||
KnowledgeSourceDescriptor descriptor,
|
||||
CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var content = await _snapshotStore.GetBundledContentAsync(descriptor.BundlePath!, ct)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (content is null)
|
||||
return null;
|
||||
|
||||
// Verify digest
|
||||
var actualDigest = ComputeDigest(content);
|
||||
if (actualDigest != descriptor.Digest)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Bundled source {Name} digest mismatch: expected {Expected}, got {Actual}",
|
||||
descriptor.Name, descriptor.Digest, actualDigest);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ResolvedSource(
|
||||
descriptor.Name,
|
||||
descriptor.Type,
|
||||
content,
|
||||
SourceResolutionMethod.Bundled);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to resolve bundled source {Name}", descriptor.Name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ResolvedSource?> ResolveFromLocalStoreAsync(
|
||||
KnowledgeSourceDescriptor descriptor,
|
||||
CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var content = await _snapshotStore.GetByDigestAsync(descriptor.Digest, ct)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (content is null)
|
||||
return null;
|
||||
|
||||
return new ResolvedSource(
|
||||
descriptor.Name,
|
||||
descriptor.Type,
|
||||
content,
|
||||
SourceResolutionMethod.LocalStore);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to resolve source {Name} from local store", descriptor.Name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ComputeDigest(byte[] content)
|
||||
{
|
||||
var hash = SHA256.HashData(content);
|
||||
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolved knowledge source with content.
|
||||
/// </summary>
|
||||
public sealed record ResolvedSource(
|
||||
string Name,
|
||||
string Type,
|
||||
byte[] Content,
|
||||
SourceResolutionMethod Method);
|
||||
|
||||
/// <summary>
|
||||
/// Method used to resolve a source.
|
||||
/// </summary>
|
||||
public enum SourceResolutionMethod
|
||||
{
|
||||
Bundled,
|
||||
LocalStore,
|
||||
NetworkFetch
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for source resolution.
|
||||
/// </summary>
|
||||
public interface IKnowledgeSourceResolver
|
||||
{
|
||||
Task<ResolvedSource?> ResolveAsync(
|
||||
KnowledgeSourceDescriptor descriptor,
|
||||
bool allowNetworkFetch,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frozen inputs for replay.
|
||||
/// </summary>
|
||||
public sealed class FrozenInputs
|
||||
{
|
||||
public Dictionary<string, ResolvedSource> ResolvedSources { get; } = new();
|
||||
public IReadOnlyList<string> MissingSources { get; init; } = [];
|
||||
public bool IsComplete => MissingSources.Count == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builder for frozen inputs.
|
||||
/// </summary>
|
||||
public sealed class FrozenInputsBuilder
|
||||
{
|
||||
private readonly Dictionary<string, ResolvedSource> _sources = new();
|
||||
|
||||
public FrozenInputsBuilder AddSource(string name, ResolvedSource source)
|
||||
{
|
||||
_sources[name] = source;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FrozenInputs Build(IReadOnlyList<string> missingSources)
|
||||
{
|
||||
var inputs = new FrozenInputs
|
||||
{
|
||||
MissingSources = missingSources
|
||||
};
|
||||
|
||||
// Copy resolved sources
|
||||
foreach (var kvp in _sources)
|
||||
{
|
||||
inputs.ResolvedSources[kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
return inputs;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user