feat: Add native binary analyzer test utilities and implement SM2 signing tests
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled

- Introduced `NativeTestBase` class for ELF, PE, and Mach-O binary parsing helpers and assertions.
- Created `TestCryptoFactory` for SM2 cryptographic provider setup and key generation.
- Implemented `Sm2SigningTests` to validate signing functionality with environment gate checks.
- Developed console export service and store with comprehensive unit tests for export status management.
This commit is contained in:
StellaOps Bot
2025-12-07 13:12:41 +02:00
parent d907729778
commit e53a282fbe
387 changed files with 21941 additions and 1518 deletions

View File

@@ -1,6 +1,7 @@
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;

View File

@@ -0,0 +1,102 @@
namespace StellaOps.Scanner.Analyzers.Lang.Go.Internal;
/// <summary>
/// Represents evidence of a capability usage detected in Go source code.
/// </summary>
internal sealed record GoCapabilityEvidence
{
public GoCapabilityEvidence(
CapabilityKind kind,
string sourceFile,
int sourceLine,
string pattern,
string? snippet = null,
float confidence = 1.0f,
CapabilityRisk risk = CapabilityRisk.Low)
{
ArgumentException.ThrowIfNullOrWhiteSpace(sourceFile, nameof(sourceFile));
ArgumentException.ThrowIfNullOrWhiteSpace(pattern, nameof(pattern));
Kind = kind;
SourceFile = NormalizePath(sourceFile);
SourceLine = sourceLine;
Pattern = pattern;
Snippet = snippet;
Confidence = Math.Clamp(confidence, 0f, 1f);
Risk = risk;
}
/// <summary>
/// The capability category.
/// </summary>
public CapabilityKind Kind { get; }
/// <summary>
/// The source file where the capability is used.
/// </summary>
public string SourceFile { get; }
/// <summary>
/// The line number of the capability usage.
/// </summary>
public int SourceLine { get; }
/// <summary>
/// The function name or pattern matched.
/// </summary>
public string Pattern { get; }
/// <summary>
/// A snippet of the code (for context).
/// </summary>
public string? Snippet { get; }
/// <summary>
/// Confidence level (0.0 to 1.0).
/// </summary>
public float Confidence { get; }
/// <summary>
/// Risk level associated with this capability usage.
/// </summary>
public CapabilityRisk Risk { get; }
/// <summary>
/// Unique key for deduplication.
/// </summary>
public string DeduplicationKey => $"{Kind}|{SourceFile}|{SourceLine}|{Pattern}";
/// <summary>
/// Creates metadata entries for this evidence.
/// </summary>
public IEnumerable<KeyValuePair<string, string?>> CreateMetadata()
{
yield return new KeyValuePair<string, string?>("capability.kind", Kind.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.source", $"{SourceFile}:{SourceLine}");
yield return new KeyValuePair<string, string?>("capability.pattern", Pattern);
yield return new KeyValuePair<string, string?>("capability.risk", Risk.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.confidence", Confidence.ToString("F2", System.Globalization.CultureInfo.InvariantCulture));
if (!string.IsNullOrWhiteSpace(Snippet))
{
var truncated = Snippet.Length > 200 ? Snippet[..197] + "..." : Snippet;
yield return new KeyValuePair<string, string?>("capability.snippet", truncated);
}
}
/// <summary>
/// Converts to base LanguageComponentEvidence.
/// </summary>
public LanguageComponentEvidence ToLanguageEvidence()
{
return new LanguageComponentEvidence(
Kind: LanguageEvidenceKind.Metadata,
Source: SourceFile,
Locator: $"line:{SourceLine}",
Value: $"{Kind}:{Pattern}",
Sha256: null);
}
private static string NormalizePath(string path)
=> path.Replace('\\', '/');
}

View File

@@ -0,0 +1,171 @@
namespace StellaOps.Scanner.Analyzers.Lang.Go.Internal;
/// <summary>
/// Orchestrates capability scanning across Go source files.
/// </summary>
internal static class GoCapabilityScanBuilder
{
/// <summary>
/// Scans a Go module directory for capabilities.
/// </summary>
public static GoCapabilityScanResult ScanModule(string modulePath, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(modulePath);
if (!Directory.Exists(modulePath))
{
return GoCapabilityScanResult.Empty;
}
var allEvidences = new List<GoCapabilityEvidence>();
foreach (var goFile in EnumerateGoSourceFiles(modulePath))
{
cancellationToken.ThrowIfCancellationRequested();
try
{
var content = File.ReadAllText(goFile);
var relativePath = Path.GetRelativePath(modulePath, goFile);
var evidences = GoCapabilityScanner.ScanFile(content, relativePath);
allEvidences.AddRange(evidences);
}
catch (IOException)
{
// Skip inaccessible files
}
catch (UnauthorizedAccessException)
{
// Skip inaccessible files
}
}
// Deduplicate and sort for determinism
var finalEvidences = allEvidences
.DistinctBy(e => e.DeduplicationKey)
.OrderBy(e => e.SourceFile, StringComparer.Ordinal)
.ThenBy(e => e.SourceLine)
.ThenBy(e => e.Kind)
.ToList();
return new GoCapabilityScanResult(finalEvidences);
}
/// <summary>
/// Scans a Go project (discovered by GoProjectDiscoverer) for capabilities.
/// </summary>
public static GoCapabilityScanResult ScanProject(
GoProjectDiscoverer.GoProject project,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(project);
if (!Directory.Exists(project.RootPath))
{
return GoCapabilityScanResult.Empty;
}
return ScanModule(project.RootPath, cancellationToken);
}
/// <summary>
/// Scans a Go workspace (multiple modules) for capabilities.
/// </summary>
public static GoCapabilityScanResult ScanWorkspace(
GoProjectDiscoverer.GoProject workspaceProject,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(workspaceProject);
var allEvidences = new List<GoCapabilityEvidence>();
// Scan the root module
var rootResult = ScanModule(workspaceProject.RootPath, cancellationToken);
allEvidences.AddRange(rootResult.Evidences);
// Scan each workspace member
foreach (var memberPath in workspaceProject.WorkspaceMembers)
{
cancellationToken.ThrowIfCancellationRequested();
var memberFullPath = Path.Combine(workspaceProject.RootPath, memberPath);
if (Directory.Exists(memberFullPath))
{
var memberResult = ScanModule(memberFullPath, cancellationToken);
// Adjust paths to be relative to workspace root
foreach (var evidence in memberResult.Evidences)
{
var adjustedPath = Path.Combine(memberPath, evidence.SourceFile).Replace('\\', '/');
allEvidences.Add(new GoCapabilityEvidence(
evidence.Kind,
adjustedPath,
evidence.SourceLine,
evidence.Pattern,
evidence.Snippet,
evidence.Confidence,
evidence.Risk));
}
}
}
// Deduplicate and sort
var finalEvidences = allEvidences
.DistinctBy(e => e.DeduplicationKey)
.OrderBy(e => e.SourceFile, StringComparer.Ordinal)
.ThenBy(e => e.SourceLine)
.ThenBy(e => e.Kind)
.ToList();
return new GoCapabilityScanResult(finalEvidences);
}
/// <summary>
/// Scans specific Go source content.
/// </summary>
public static GoCapabilityScanResult ScanContent(string content, string filePath)
{
if (string.IsNullOrWhiteSpace(content))
{
return GoCapabilityScanResult.Empty;
}
var evidences = GoCapabilityScanner.ScanFile(content, filePath);
return new GoCapabilityScanResult(evidences.ToList());
}
private static IEnumerable<string> EnumerateGoSourceFiles(string rootPath)
{
var options = new EnumerationOptions
{
RecurseSubdirectories = true,
IgnoreInaccessible = true,
MaxRecursionDepth = 20
};
foreach (var file in Directory.EnumerateFiles(rootPath, "*.go", options))
{
// Skip test files
if (file.EndsWith("_test.go", StringComparison.OrdinalIgnoreCase))
{
continue;
}
// Skip vendor directory
if (file.Contains($"{Path.DirectorySeparatorChar}vendor{Path.DirectorySeparatorChar}") ||
file.Contains($"{Path.AltDirectorySeparatorChar}vendor{Path.AltDirectorySeparatorChar}"))
{
continue;
}
// Skip testdata directories
if (file.Contains($"{Path.DirectorySeparatorChar}testdata{Path.DirectorySeparatorChar}") ||
file.Contains($"{Path.AltDirectorySeparatorChar}testdata{Path.AltDirectorySeparatorChar}"))
{
continue;
}
yield return file;
}
}
}

View File

@@ -0,0 +1,227 @@
using System.Collections.Immutable;
using System.Globalization;
namespace StellaOps.Scanner.Analyzers.Lang.Go.Internal;
/// <summary>
/// Aggregates capability scan results from Go source code analysis.
/// </summary>
internal sealed class GoCapabilityScanResult
{
private readonly IReadOnlyList<GoCapabilityEvidence> _evidences;
private ILookup<CapabilityKind, GoCapabilityEvidence>? _byKind;
private ILookup<CapabilityRisk, GoCapabilityEvidence>? _byRisk;
private ILookup<string, GoCapabilityEvidence>? _byFile;
public GoCapabilityScanResult(IReadOnlyList<GoCapabilityEvidence> evidences)
{
_evidences = evidences ?? Array.Empty<GoCapabilityEvidence>();
}
/// <summary>
/// All capability evidences found.
/// </summary>
public IReadOnlyList<GoCapabilityEvidence> Evidences => _evidences;
/// <summary>
/// Gets whether any capabilities were detected.
/// </summary>
public bool HasCapabilities => _evidences.Count > 0;
/// <summary>
/// Gets evidences grouped by capability kind.
/// </summary>
public ILookup<CapabilityKind, GoCapabilityEvidence> EvidencesByKind
=> _byKind ??= _evidences.ToLookup(e => e.Kind);
/// <summary>
/// Gets evidences grouped by risk level.
/// </summary>
public ILookup<CapabilityRisk, GoCapabilityEvidence> EvidencesByRisk
=> _byRisk ??= _evidences.ToLookup(e => e.Risk);
/// <summary>
/// Gets evidences grouped by source file.
/// </summary>
public ILookup<string, GoCapabilityEvidence> EvidencesByFile
=> _byFile ??= _evidences.ToLookup(e => e.SourceFile, StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Gets all critical risk evidences.
/// </summary>
public IEnumerable<GoCapabilityEvidence> CriticalRiskEvidences
=> _evidences.Where(e => e.Risk == CapabilityRisk.Critical);
/// <summary>
/// Gets all high risk evidences.
/// </summary>
public IEnumerable<GoCapabilityEvidence> HighRiskEvidences
=> _evidences.Where(e => e.Risk == CapabilityRisk.High);
/// <summary>
/// Gets the set of detected capability kinds.
/// </summary>
public IReadOnlySet<CapabilityKind> DetectedKinds
=> _evidences.Select(e => e.Kind).ToHashSet();
/// <summary>
/// Gets the highest risk level found.
/// </summary>
public CapabilityRisk HighestRisk
=> _evidences.Count > 0
? _evidences.Max(e => e.Risk)
: CapabilityRisk.Low;
/// <summary>
/// Gets evidences for a specific capability kind.
/// </summary>
public IEnumerable<GoCapabilityEvidence> GetByKind(CapabilityKind kind)
=> EvidencesByKind[kind];
/// <summary>
/// Gets evidences at or above a specific risk level.
/// </summary>
public IEnumerable<GoCapabilityEvidence> GetByMinimumRisk(CapabilityRisk minRisk)
=> _evidences.Where(e => e.Risk >= minRisk);
/// <summary>
/// Creates metadata entries for the scan result.
/// </summary>
public IEnumerable<KeyValuePair<string, string?>> CreateMetadata()
{
yield return new KeyValuePair<string, string?>(
"capability.total_count",
_evidences.Count.ToString(CultureInfo.InvariantCulture));
// Count by kind (only emit non-zero)
foreach (var kindGroup in EvidencesByKind.OrderBy(g => g.Key.ToString(), StringComparer.Ordinal))
{
yield return new KeyValuePair<string, string?>(
$"capability.{kindGroup.Key.ToString().ToLowerInvariant()}_count",
kindGroup.Count().ToString(CultureInfo.InvariantCulture));
}
// Count by risk
var criticalCount = CriticalRiskEvidences.Count();
var highCount = HighRiskEvidences.Count();
var mediumCount = _evidences.Count(e => e.Risk == CapabilityRisk.Medium);
var lowCount = _evidences.Count(e => e.Risk == CapabilityRisk.Low);
yield return new KeyValuePair<string, string?>("capability.critical_risk_count", criticalCount.ToString(CultureInfo.InvariantCulture));
yield return new KeyValuePair<string, string?>("capability.high_risk_count", highCount.ToString(CultureInfo.InvariantCulture));
yield return new KeyValuePair<string, string?>("capability.medium_risk_count", mediumCount.ToString(CultureInfo.InvariantCulture));
yield return new KeyValuePair<string, string?>("capability.low_risk_count", lowCount.ToString(CultureInfo.InvariantCulture));
// Highest risk
if (_evidences.Count > 0)
{
yield return new KeyValuePair<string, string?>(
"capability.highest_risk",
HighestRisk.ToString().ToLowerInvariant());
}
// Detected capabilities as semicolon-separated list
if (DetectedKinds.Count > 0)
{
yield return new KeyValuePair<string, string?>(
"capability.detected_kinds",
string.Join(';', DetectedKinds.OrderBy(k => k.ToString(), StringComparer.Ordinal).Select(k => k.ToString().ToLowerInvariant())));
}
// Files with critical issues (first 10)
var criticalFiles = CriticalRiskEvidences
.Select(e => e.SourceFile)
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(f => f, StringComparer.Ordinal)
.ToList();
if (criticalFiles.Count > 0)
{
yield return new KeyValuePair<string, string?>(
"capability.critical_files",
string.Join(';', criticalFiles.Take(10)));
if (criticalFiles.Count > 10)
{
yield return new KeyValuePair<string, string?>(
"capability.critical_files_truncated",
"true");
}
}
// Unique patterns detected
var uniquePatterns = _evidences
.Select(e => e.Pattern)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Count();
yield return new KeyValuePair<string, string?>(
"capability.unique_pattern_count",
uniquePatterns.ToString(CultureInfo.InvariantCulture));
}
/// <summary>
/// Creates a summary of detected capabilities.
/// </summary>
public GoCapabilitySummary CreateSummary()
{
return new GoCapabilitySummary(
HasExec: EvidencesByKind[CapabilityKind.Exec].Any(),
HasFilesystem: EvidencesByKind[CapabilityKind.Filesystem].Any(),
HasNetwork: EvidencesByKind[CapabilityKind.Network].Any(),
HasEnvironment: EvidencesByKind[CapabilityKind.Environment].Any(),
HasSerialization: EvidencesByKind[CapabilityKind.Serialization].Any(),
HasCrypto: EvidencesByKind[CapabilityKind.Crypto].Any(),
HasDatabase: EvidencesByKind[CapabilityKind.Database].Any(),
HasDynamicCode: EvidencesByKind[CapabilityKind.DynamicCode].Any(),
HasReflection: EvidencesByKind[CapabilityKind.Reflection].Any(),
HasNativeCode: EvidencesByKind[CapabilityKind.NativeCode].Any(),
HasPluginLoading: EvidencesByKind[CapabilityKind.PluginLoading].Any(),
CriticalCount: CriticalRiskEvidences.Count(),
HighRiskCount: HighRiskEvidences.Count(),
TotalCount: _evidences.Count);
}
/// <summary>
/// Empty scan result with no capabilities detected.
/// </summary>
public static GoCapabilityScanResult Empty { get; } = new(Array.Empty<GoCapabilityEvidence>());
}
/// <summary>
/// Summary of detected Go capabilities.
/// </summary>
internal sealed record GoCapabilitySummary(
bool HasExec,
bool HasFilesystem,
bool HasNetwork,
bool HasEnvironment,
bool HasSerialization,
bool HasCrypto,
bool HasDatabase,
bool HasDynamicCode,
bool HasReflection,
bool HasNativeCode,
bool HasPluginLoading,
int CriticalCount,
int HighRiskCount,
int TotalCount)
{
/// <summary>
/// Creates metadata entries for the summary.
/// </summary>
public IEnumerable<KeyValuePair<string, string?>> CreateMetadata()
{
yield return new KeyValuePair<string, string?>("capability.has_exec", HasExec.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_filesystem", HasFilesystem.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_network", HasNetwork.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_environment", HasEnvironment.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_serialization", HasSerialization.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_crypto", HasCrypto.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_database", HasDatabase.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_dynamic_code", HasDynamicCode.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_reflection", HasReflection.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_native_code", HasNativeCode.ToString().ToLowerInvariant());
yield return new KeyValuePair<string, string?>("capability.has_plugin_loading", HasPluginLoading.ToString().ToLowerInvariant());
}
}

View File

@@ -0,0 +1,838 @@
using System.Collections.Immutable;
using System.Text.RegularExpressions;
namespace StellaOps.Scanner.Analyzers.Lang.Go.Internal;
/// <summary>
/// Scans Go source files for security-relevant capabilities.
/// Detects exec, filesystem, network, native code (CGO), and other dangerous patterns.
/// </summary>
internal static partial class GoCapabilityScanner
{
/// <summary>
/// Scans a Go source file for capabilities.
/// </summary>
public static IReadOnlyList<GoCapabilityEvidence> ScanFile(string content, string filePath)
{
if (string.IsNullOrWhiteSpace(content))
{
return [];
}
var evidences = new List<GoCapabilityEvidence>();
// Strip comments before scanning
var strippedContent = StripComments(content);
var lines = content.Split('\n');
var strippedLines = strippedContent.Split('\n');
// Track imports for context
var imports = ParseImports(content);
// Scan each line for capability patterns
for (var lineIndex = 0; lineIndex < strippedLines.Length; lineIndex++)
{
var strippedLine = strippedLines[lineIndex];
var originalLine = lineIndex < lines.Length ? lines[lineIndex] : strippedLine;
var lineNumber = lineIndex + 1;
// Skip empty lines
if (string.IsNullOrWhiteSpace(strippedLine))
{
continue;
}
// Check all pattern categories
CheckExecPatterns(strippedLine, originalLine, filePath, lineNumber, imports, evidences);
CheckFilesystemPatterns(strippedLine, originalLine, filePath, lineNumber, imports, evidences);
CheckNetworkPatterns(strippedLine, originalLine, filePath, lineNumber, imports, evidences);
CheckEnvironmentPatterns(strippedLine, originalLine, filePath, lineNumber, imports, evidences);
CheckSerializationPatterns(strippedLine, originalLine, filePath, lineNumber, imports, evidences);
CheckCryptoPatterns(strippedLine, originalLine, filePath, lineNumber, imports, evidences);
CheckDatabasePatterns(strippedLine, originalLine, filePath, lineNumber, imports, evidences);
CheckDynamicCodePatterns(strippedLine, originalLine, filePath, lineNumber, imports, evidences);
CheckReflectionPatterns(strippedLine, originalLine, filePath, lineNumber, imports, evidences);
CheckNativeCodePatterns(strippedLine, originalLine, filePath, lineNumber, imports, evidences);
CheckPluginPatterns(strippedLine, originalLine, filePath, lineNumber, imports, evidences);
}
// Deduplicate and sort for determinism
return evidences
.DistinctBy(e => e.DeduplicationKey)
.OrderBy(e => e.SourceFile, StringComparer.Ordinal)
.ThenBy(e => e.SourceLine)
.ThenBy(e => e.Kind)
.ToList();
}
private static HashSet<string> ParseImports(string content)
{
var imports = new HashSet<string>(StringComparer.Ordinal);
foreach (Match match in ImportPattern().Matches(content))
{
var importPath = match.Groups[1].Value;
imports.Add(importPath);
}
return imports;
}
private static void CheckExecPatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> imports, List<GoCapabilityEvidence> evidences)
{
// exec.Command - Critical
if (strippedLine.Contains("exec.Command") ||
strippedLine.Contains("exec.CommandContext"))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Exec,
filePath,
lineNumber,
"exec.Command",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// syscall.Exec - Critical
if (strippedLine.Contains("syscall.Exec") ||
strippedLine.Contains("syscall.ForkExec"))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Exec,
filePath,
lineNumber,
"syscall.Exec",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// os.StartProcess - Critical
if (strippedLine.Contains("os.StartProcess"))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Exec,
filePath,
lineNumber,
"os.StartProcess",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// Command.Run/Output/Start - High if already detected exec import
if (imports.Contains("os/exec") &&
(ExecRunPattern().IsMatch(strippedLine)))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Exec,
filePath,
lineNumber,
"cmd.Run/Output/Start",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.High));
}
}
private static void CheckFilesystemPatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> imports, List<GoCapabilityEvidence> evidences)
{
// os.Create/Open/OpenFile - Medium
if (strippedLine.Contains("os.Create(") ||
strippedLine.Contains("os.Open(") ||
strippedLine.Contains("os.OpenFile("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Filesystem,
filePath,
lineNumber,
"os.Open/Create",
GetSnippet(originalLine),
0.95f,
CapabilityRisk.Medium));
}
// os.Remove/RemoveAll - High
if (strippedLine.Contains("os.Remove(") ||
strippedLine.Contains("os.RemoveAll("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Filesystem,
filePath,
lineNumber,
"os.Remove/RemoveAll",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.High));
}
// os.Chmod/Chown - High
if (strippedLine.Contains("os.Chmod(") ||
strippedLine.Contains("os.Chown(") ||
strippedLine.Contains("os.Lchown("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Filesystem,
filePath,
lineNumber,
"os.Chmod/Chown",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.High));
}
// os.Symlink/Link - High
if (strippedLine.Contains("os.Symlink(") ||
strippedLine.Contains("os.Link("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Filesystem,
filePath,
lineNumber,
"os.Symlink/Link",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.High));
}
// os.Mkdir/MkdirAll - Medium
if (strippedLine.Contains("os.Mkdir(") ||
strippedLine.Contains("os.MkdirAll("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Filesystem,
filePath,
lineNumber,
"os.Mkdir",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// os.Rename - Medium
if (strippedLine.Contains("os.Rename("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Filesystem,
filePath,
lineNumber,
"os.Rename",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// ioutil (deprecated but still used) - Medium
if (strippedLine.Contains("ioutil.ReadFile(") ||
strippedLine.Contains("ioutil.WriteFile(") ||
strippedLine.Contains("ioutil.ReadDir("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Filesystem,
filePath,
lineNumber,
"ioutil",
GetSnippet(originalLine),
0.85f,
CapabilityRisk.Medium));
}
// os.ReadFile/WriteFile - Medium
if (strippedLine.Contains("os.ReadFile(") ||
strippedLine.Contains("os.WriteFile("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Filesystem,
filePath,
lineNumber,
"os.ReadFile/WriteFile",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
}
private static void CheckNetworkPatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> imports, List<GoCapabilityEvidence> evidences)
{
// net.Dial/DialContext - Medium
if (strippedLine.Contains("net.Dial(") ||
strippedLine.Contains("net.DialContext(") ||
strippedLine.Contains("net.DialTimeout("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Network,
filePath,
lineNumber,
"net.Dial",
GetSnippet(originalLine),
0.95f,
CapabilityRisk.Medium));
}
// net.Listen - Medium
if (strippedLine.Contains("net.Listen("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Network,
filePath,
lineNumber,
"net.Listen",
GetSnippet(originalLine),
0.95f,
CapabilityRisk.Medium));
}
// http.Get/Post/Client - Medium
if (strippedLine.Contains("http.Get(") ||
strippedLine.Contains("http.Post(") ||
strippedLine.Contains("http.NewRequest("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Network,
filePath,
lineNumber,
"http.Get/Post",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// http.Client.Do - Medium
if (imports.Contains("net/http") && strippedLine.Contains(".Do("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Network,
filePath,
lineNumber,
"http.Client.Do",
GetSnippet(originalLine),
0.8f,
CapabilityRisk.Medium));
}
// http.ListenAndServe - Medium
if (strippedLine.Contains("http.ListenAndServe(") ||
strippedLine.Contains("http.ListenAndServeTLS("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Network,
filePath,
lineNumber,
"http.ListenAndServe",
GetSnippet(originalLine),
0.95f,
CapabilityRisk.Medium));
}
// net.Resolver - Low
if (strippedLine.Contains("net.Resolver{") ||
strippedLine.Contains("net.LookupHost(") ||
strippedLine.Contains("net.LookupIP("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Network,
filePath,
lineNumber,
"net.Lookup",
GetSnippet(originalLine),
0.85f,
CapabilityRisk.Low));
}
}
private static void CheckEnvironmentPatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> imports, List<GoCapabilityEvidence> evidences)
{
// os.Getenv - Medium
if (strippedLine.Contains("os.Getenv(") ||
strippedLine.Contains("os.LookupEnv("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Environment,
filePath,
lineNumber,
"os.Getenv",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// os.Setenv - High
if (strippedLine.Contains("os.Setenv(") ||
strippedLine.Contains("os.Unsetenv("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Environment,
filePath,
lineNumber,
"os.Setenv",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.High));
}
// os.Environ - Medium
if (strippedLine.Contains("os.Environ("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Environment,
filePath,
lineNumber,
"os.Environ",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// os.ExpandEnv - Medium
if (strippedLine.Contains("os.ExpandEnv(") ||
strippedLine.Contains("os.Expand("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Environment,
filePath,
lineNumber,
"os.ExpandEnv",
GetSnippet(originalLine),
0.85f,
CapabilityRisk.Medium));
}
}
private static void CheckSerializationPatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> imports, List<GoCapabilityEvidence> evidences)
{
// encoding/gob - Medium (can be dangerous for untrusted data)
if (strippedLine.Contains("gob.NewDecoder(") ||
strippedLine.Contains("gob.NewEncoder("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Serialization,
filePath,
lineNumber,
"gob.Decoder/Encoder",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// json.Unmarshal/Marshal - Low
if (strippedLine.Contains("json.Unmarshal(") ||
strippedLine.Contains("json.Marshal(") ||
strippedLine.Contains("json.NewDecoder(") ||
strippedLine.Contains("json.NewEncoder("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Serialization,
filePath,
lineNumber,
"json",
GetSnippet(originalLine),
0.8f,
CapabilityRisk.Low));
}
// xml.Unmarshal - Medium (XXE potential)
if (strippedLine.Contains("xml.Unmarshal(") ||
strippedLine.Contains("xml.NewDecoder("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Serialization,
filePath,
lineNumber,
"xml.Unmarshal",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// yaml.Unmarshal - Medium
if (strippedLine.Contains("yaml.Unmarshal(") ||
strippedLine.Contains("yaml.NewDecoder("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Serialization,
filePath,
lineNumber,
"yaml.Unmarshal",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
}
private static void CheckCryptoPatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> imports, List<GoCapabilityEvidence> evidences)
{
// crypto/* - Low
if (strippedLine.Contains("sha256.New(") ||
strippedLine.Contains("sha512.New(") ||
strippedLine.Contains("md5.New(") ||
strippedLine.Contains("sha1.New("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Crypto,
filePath,
lineNumber,
"crypto/hash",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Low));
}
// crypto/aes, crypto/cipher - Low
if (strippedLine.Contains("aes.NewCipher(") ||
strippedLine.Contains("cipher.NewGCM(") ||
strippedLine.Contains("cipher.NewCBCEncrypter(") ||
strippedLine.Contains("cipher.NewCBCDecrypter("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Crypto,
filePath,
lineNumber,
"crypto/cipher",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Low));
}
// crypto/rsa - Low
if (strippedLine.Contains("rsa.GenerateKey(") ||
strippedLine.Contains("rsa.EncryptPKCS1v15(") ||
strippedLine.Contains("rsa.DecryptPKCS1v15(") ||
strippedLine.Contains("rsa.SignPKCS1v15("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Crypto,
filePath,
lineNumber,
"crypto/rsa",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Low));
}
// crypto/rand - Low
if (strippedLine.Contains("rand.Read(") ||
strippedLine.Contains("rand.Int("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Crypto,
filePath,
lineNumber,
"crypto/rand",
GetSnippet(originalLine),
0.85f,
CapabilityRisk.Low));
}
}
private static void CheckDatabasePatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> imports, List<GoCapabilityEvidence> evidences)
{
// database/sql - Medium
if (strippedLine.Contains("sql.Open(") ||
strippedLine.Contains("sql.OpenDB("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Database,
filePath,
lineNumber,
"sql.Open",
GetSnippet(originalLine),
0.95f,
CapabilityRisk.Medium));
}
// db.Query/Exec - Medium (potential SQL injection)
if (imports.Contains("database/sql") &&
(strippedLine.Contains(".Query(") ||
strippedLine.Contains(".QueryRow(") ||
strippedLine.Contains(".Exec(")))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Database,
filePath,
lineNumber,
"db.Query/Exec",
GetSnippet(originalLine),
0.85f,
CapabilityRisk.Medium));
}
// Raw SQL with string concatenation - High
if (RawSqlPattern().IsMatch(strippedLine))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Database,
filePath,
lineNumber,
"raw SQL concat",
GetSnippet(originalLine),
0.7f,
CapabilityRisk.High));
}
}
private static void CheckDynamicCodePatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> imports, List<GoCapabilityEvidence> evidences)
{
// reflect.Value.Call - High
if (strippedLine.Contains(".Call(") && imports.Contains("reflect"))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.DynamicCode,
filePath,
lineNumber,
"reflect.Value.Call",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.High));
}
// reflect.Value.MethodByName - High
if (strippedLine.Contains(".MethodByName("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.DynamicCode,
filePath,
lineNumber,
"reflect.MethodByName",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.High));
}
// text/template with Execute - Medium (template injection)
if ((imports.Contains("text/template") || imports.Contains("html/template")) &&
strippedLine.Contains(".Execute("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.DynamicCode,
filePath,
lineNumber,
"template.Execute",
GetSnippet(originalLine),
0.8f,
CapabilityRisk.Medium));
}
}
private static void CheckReflectionPatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> imports, List<GoCapabilityEvidence> evidences)
{
// reflect.TypeOf/ValueOf - Low
if (strippedLine.Contains("reflect.TypeOf(") ||
strippedLine.Contains("reflect.ValueOf("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Reflection,
filePath,
lineNumber,
"reflect.TypeOf/ValueOf",
GetSnippet(originalLine),
0.85f,
CapabilityRisk.Low));
}
// reflect.New - Medium
if (strippedLine.Contains("reflect.New("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Reflection,
filePath,
lineNumber,
"reflect.New",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.Medium));
}
// runtime.Caller/Callers - Low
if (strippedLine.Contains("runtime.Caller(") ||
strippedLine.Contains("runtime.Callers("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Reflection,
filePath,
lineNumber,
"runtime.Caller",
GetSnippet(originalLine),
0.8f,
CapabilityRisk.Low));
}
// runtime.FuncForPC - Low
if (strippedLine.Contains("runtime.FuncForPC("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.Reflection,
filePath,
lineNumber,
"runtime.FuncForPC",
GetSnippet(originalLine),
0.8f,
CapabilityRisk.Low));
}
}
private static void CheckNativeCodePatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> imports, List<GoCapabilityEvidence> evidences)
{
// import "C" - High (CGO)
if (CgoImportPattern().IsMatch(strippedLine))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.NativeCode,
filePath,
lineNumber,
"import \"C\"",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.High));
}
// unsafe.Pointer - Critical
if (strippedLine.Contains("unsafe.Pointer(") ||
strippedLine.Contains("unsafe.Sizeof(") ||
strippedLine.Contains("unsafe.Offsetof(") ||
strippedLine.Contains("unsafe.Alignof("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.NativeCode,
filePath,
lineNumber,
"unsafe.Pointer",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// //go:linkname directive - Critical
if (GoLinknamePattern().IsMatch(originalLine)) // Check original for comments
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.NativeCode,
filePath,
lineNumber,
"go:linkname",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// //go:noescape directive - High
if (GoNoescapePattern().IsMatch(originalLine))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.NativeCode,
filePath,
lineNumber,
"go:noescape",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.High));
}
// syscall.Syscall - Critical
if (strippedLine.Contains("syscall.Syscall(") ||
strippedLine.Contains("syscall.Syscall6(") ||
strippedLine.Contains("syscall.RawSyscall("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.NativeCode,
filePath,
lineNumber,
"syscall.Syscall",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
}
private static void CheckPluginPatterns(
string strippedLine, string originalLine, string filePath, int lineNumber,
HashSet<string> imports, List<GoCapabilityEvidence> evidences)
{
// plugin.Open - Critical
if (strippedLine.Contains("plugin.Open("))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.PluginLoading,
filePath,
lineNumber,
"plugin.Open",
GetSnippet(originalLine),
1.0f,
CapabilityRisk.Critical));
}
// plugin.Lookup - High
if (strippedLine.Contains(".Lookup(") && imports.Contains("plugin"))
{
evidences.Add(new GoCapabilityEvidence(
CapabilityKind.PluginLoading,
filePath,
lineNumber,
"plugin.Lookup",
GetSnippet(originalLine),
0.9f,
CapabilityRisk.High));
}
}
private static string StripComments(string content)
{
// Remove single-line comments
var result = SingleLineCommentPattern().Replace(content, "");
// Remove multi-line comments
result = MultiLineCommentPattern().Replace(result, "");
return result;
}
private static string GetSnippet(string line)
{
var trimmed = line.Trim();
return trimmed.Length > 150 ? trimmed[..147] + "..." : trimmed;
}
// Regex patterns
[GeneratedRegex(@"import\s+""([^""]+)""", RegexOptions.Multiline)]
private static partial Regex ImportPattern();
[GeneratedRegex(@"\.(Run|Output|CombinedOutput|Start)\s*\(")]
private static partial Regex ExecRunPattern();
[GeneratedRegex(@"(?i)(SELECT|INSERT|UPDATE|DELETE|DROP)\s+.*\+", RegexOptions.IgnoreCase)]
private static partial Regex RawSqlPattern();
[GeneratedRegex(@"import\s*""C""")]
private static partial Regex CgoImportPattern();
[GeneratedRegex(@"//go:linkname\s+")]
private static partial Regex GoLinknamePattern();
[GeneratedRegex(@"//go:noescape")]
private static partial Regex GoNoescapePattern();
[GeneratedRegex(@"//.*$", RegexOptions.Multiline)]
private static partial Regex SingleLineCommentPattern();
[GeneratedRegex(@"/\*[\s\S]*?\*/")]
private static partial Regex MultiLineCommentPattern();
}

View File

@@ -4,7 +4,7 @@
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>