Files
SecretSecurity/SecretSecurity/Program.cs
T
2026-06-17 16:44:04 +01:00

255 lines
6.3 KiB
C#

using SecretSecurity;
using SecretSecurity.MethodExtensions;
using SecretSecurity.Models;
using Spectre.Console;
using System.Buffers;
using System.Diagnostics;
class Program
{
static List<string> _ignorePatterns = new();
static void Main(string[] args)
{
if (args.Length == 0)
{
PrintHelp();
return;
}
var arg = args[0].ToLower();
switch (arg)
{
case "scan":
PerformScan();
break;
case "install":
InstallCommand();
break;
default:
PrintHelp();
break;
}
}
static void LoadIgnoreFile()
{
if (!File.Exists(".secretsecurityignore"))
return;
_ignorePatterns = File.ReadAllLines(".secretsecurityignore")
.Where(x => !string.IsNullOrWhiteSpace(x))
.ToList();
}
static void InstallCommand()
{
var root = RunGit("rev-parse --show-toplevel").Trim();
var hookPath = Path.Combine(root, ".git", "hooks", "pre-commit");
var script = """
#!/bin/sh
SecretSecurity scan
if [ $? -ne 0 ]; then
echo "❌ SecretSecurity blocked commit"
exit 1
fi
""";
File.WriteAllText(hookPath, script);
Console.WriteLine("✅ SecretSecurity installed successfully");
}
static void PrintHelp()
{
Console.WriteLine(@"
SecretSecurity CLI
Commands:
scan Scan staged git changes
install Install git pre-commit hook
ignore Add ignore-rule
");
}
static void PerformScan()
{
LoadIgnoreFile();
AnsiConsole.MarkupLine("[cyan]🔍 SecretSecurity scanning...[/]\n");
var findings = GetFindings();
PrintFindings(findings);
bool shouldBlockCommit = findings.Any(f =>
f.Severity == Severity.High ||
f.Severity == Severity.Critical);
if (shouldBlockCommit)
{
AnsiConsole.Write(
new Panel("[red]Commit blocked: High/Critical secrets detected[/]")
.Header("SecretSecurity")
.Border(BoxBorder.Double)
);
var critical = findings
.Where(f => f.Severity is Severity.High or Severity.Critical)
.ToList();
foreach (var f in critical)
{
AnsiConsole.MarkupLine($"[red]• {f.Type}[/] in [grey]{f.File}:{f.Line}[/]");
}
Environment.Exit(1);
}
}
public static void PrintFindings(IEnumerable<SecretFinding> findings)
{
if (findings.Count() == 0)
{
AnsiConsole.MarkupLine("[green]✅ SecretSecurity: No secrets detected.[/]");
return;
}
AnsiConsole.Write(
new Panel($"[red]❌ {findings.Count()} issue(s) detected[/]")
.Border(BoxBorder.Rounded)
.Header("SecretSecurity Report")
);
foreach (var f in findings)
{
var color = f.Severity switch
{
Severity.Critical => "red",
Severity.High => "darkred",
Severity.Warning => "yellow",
_ => "grey"
};
AnsiConsole.MarkupLine($"""
[{color}]{f.Severity}[/]
📄 [bold]{f.File}:{f.Line}[/]
🔎 {f.ValuePreview}
────────────────────────────
""");
}
}
public static void PrintSeverity(Severity severity)
{
switch (severity)
{
case Severity.Critical:
Console.ForegroundColor = ConsoleColor.Red;
break;
case Severity.High:
Console.ForegroundColor = ConsoleColor.DarkRed;
break;
case Severity.Warning:
Console.ForegroundColor = ConsoleColor.Yellow;
break;
default:
Console.ForegroundColor = ConsoleColor.Gray;
break;
}
}
public static IEnumerable<SecretFinding> GetFindings()
{
var findings = new List<SecretFinding>();
var diffLines = GetStagedDiffWithContext();
foreach (var entry in diffLines)
{
var result = Validator.FindSecret(entry.Content);
if (result != null)
{
if (IsIgnored(entry.File))
continue;
if (IsDocumentationFile(entry.File))
continue;
result.File = entry.File;
result.Line = entry.Line;
findings.Add(result);
}
}
return findings;
}
public static List<DiffEntry> GetStagedDiffWithContext()
{
var output = RunGit("diff --cached -U0");
var lines = output.Split('\n');
var results = new List<DiffEntry>();
string currentFile = null;
int lineNumber = 0;
foreach (var line in lines)
{
// detect file
if (line.StartsWith("+++ b/"))
{
currentFile = line.Substring(6);
lineNumber = 0;
}
// detect added line
else if (line.StartsWith("+") && !line.StartsWith("+++"))
{
lineNumber++;
results.Add(new DiffEntry
{
File = currentFile ?? "unknown",
Line = lineNumber,
Content = line.Substring(1) // remove "+"
});
}
}
return results;
}
public static string RunGit(string args)
{
var process = new Process();
process.StartInfo.FileName = "git";
process.StartInfo.Arguments = args;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.Start();
return process.StandardOutput.ReadToEnd();
}
static bool IsIgnored(string file)
{
return _ignorePatterns.Any(p =>
file.Contains(p.Replace("*", "")));
}
static bool IsDocumentationFile(string file)
{
file = file.ToLowerInvariant();
return file.EndsWith(".md") ||
file.EndsWith(".txt") ||
file.Contains("readme") ||
file.Contains("docs");
}
}