mirror of
https://github.com/omegaleo/SecretSecurity.git
synced 2026-11-08 17:43:29 +00:00
255 lines
6.3 KiB
C#
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");
|
|
}
|
|
} |