mirror of
https://github.com/omegaleo/SecretSecurity.git
synced 2026-11-08 17:43:29 +00:00
Initial implementation
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
github: omegaleo
|
||||
ko_fi: omegaleo
|
||||
@@ -0,0 +1,48 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '10.0.x'
|
||||
|
||||
- name: Extract version from tag
|
||||
id: version
|
||||
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build
|
||||
run: dotnet build SecretSecurity/SecretSecurity.csproj --configuration Release
|
||||
|
||||
- name: Pack NuGet tool
|
||||
run: >
|
||||
dotnet pack SecretSecurity/SecretSecurity.csproj
|
||||
--configuration Release
|
||||
--no-build
|
||||
-p:Version=${{ steps.version.outputs.VERSION }}
|
||||
--output ./nupkg
|
||||
|
||||
- name: Push to GitHub Packages
|
||||
run: >
|
||||
dotnet nuget push ./nupkg/*.nupkg
|
||||
--api-key ${{ secrets.GITHUB_TOKEN }}
|
||||
--source https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: ./nupkg/*.nupkg
|
||||
generate_release_notes: true
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
|
||||
# Build results
|
||||
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
x64/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.log
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.Publish.xml
|
||||
*.pubxml
|
||||
*.azurePubxml
|
||||
|
||||
# NuGet Packages Directory
|
||||
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||
packages/
|
||||
## TODO: If the tool you use requires repositories.config, also uncomment the next line
|
||||
!packages/repositories.config
|
||||
|
||||
# Windows Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Windows Store app package directory
|
||||
AppPackages/
|
||||
|
||||
# Others
|
||||
sql/
|
||||
*.Cache
|
||||
ClientBin/
|
||||
[Ss]tyle[Cc]op.*
|
||||
![Ss]tyle[Cc]op.targets
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.[Pp]ublish.xml
|
||||
|
||||
*.publishsettings
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file to a newer
|
||||
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
App_Data/*.mdf
|
||||
App_Data/*.ldf
|
||||
|
||||
# =========================
|
||||
# Windows detritus
|
||||
# =========================
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Mac desktop service store files
|
||||
.DS_Store
|
||||
|
||||
_NCrunch*
|
||||
|
||||
.vs
|
||||
.vscode
|
||||
.rider
|
||||
|
||||
test-files/
|
||||
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
bin/
|
||||
obj/
|
||||
*.min.js
|
||||
*.lock
|
||||
testfiles/
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Omega Leo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,96 @@
|
||||
# SecretSecurity
|
||||
|
||||
[](LICENSE)
|
||||
[](https://github.com/sponsors/omegaleo)
|
||||
[](https://ko-fi.com/omegaleo)
|
||||
|
||||
A .NET CLI tool that scans staged git changes for secrets, API keys, tokens, and sensitive credentials — and blocks commits when critical findings are detected.
|
||||
|
||||
## What it detects
|
||||
|
||||
| Type | Examples | Severity |
|
||||
|------|----------|----------|
|
||||
| Provider tokens | AWS access keys (`AKIA…`), Stripe live keys (`sk_live_…`), GitHub PATs (`ghp_…`), Slack tokens | Critical |
|
||||
| Private keys | RSA, EC, OpenSSH private key blocks | Critical |
|
||||
| Azure connection strings | `DefaultEndpointsProtocol`, `AccountKey` | Critical |
|
||||
| Explicit secret assignments | `api_key=`, `token=`, `secret:` with a value | High |
|
||||
| Database connection strings | General ADO.NET, Postgres/MySQL/MongoDB/Redis URLs | High |
|
||||
| Suspicious blobs | High-entropy strings in suspicious context | Warning |
|
||||
|
||||
Dev-safe connections (localhost, 127.0.0.1, integrated security) are automatically ignored.
|
||||
|
||||
## Installation
|
||||
|
||||
Requires [.NET 10 SDK](https://dotnet.microsoft.com/download) or later.
|
||||
|
||||
```bash
|
||||
dotnet tool install --global SecretSecurity --add-source https://nuget.pkg.github.com/omegaleo/index.json
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Install the pre-commit hook
|
||||
|
||||
Run once inside your repository:
|
||||
|
||||
```bash
|
||||
secretsecurity install
|
||||
```
|
||||
|
||||
This writes a `pre-commit` hook to `.git/hooks/` that runs `secretsecurity scan` automatically on every commit.
|
||||
|
||||
### Scan manually
|
||||
|
||||
```bash
|
||||
secretsecurity scan
|
||||
```
|
||||
|
||||
Scans all currently staged changes (`git diff --cached`). Exits with code `1` if any High or Critical findings are found, blocking the commit.
|
||||
|
||||
### Ignore files
|
||||
|
||||
Create a `.secretsecurityignore` file in your repo root with one pattern per line:
|
||||
|
||||
```
|
||||
tests/fixtures/
|
||||
mock_secrets.json
|
||||
```
|
||||
|
||||
Any staged file whose path contains a listed pattern is skipped.
|
||||
|
||||
## How it works
|
||||
|
||||
1. Runs `git diff --cached -U0` to get only the lines you are about to commit.
|
||||
2. Each added line is checked against a set of compiled regex patterns ordered by confidence.
|
||||
3. Matched values are partially masked in the output (e.g. `ghp_****Ab`).
|
||||
4. If any finding is High or Critical, the scan exits with code `1`, preventing the commit.
|
||||
|
||||
## Building from source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/omegaleo/SecretSecurity.git
|
||||
cd SecretSecurity
|
||||
dotnet build
|
||||
dotnet run --project SecretSecurity -- scan
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! To get started:
|
||||
|
||||
1. Fork the repository and create a branch from `main`.
|
||||
2. Make your changes and ensure the project builds (`dotnet build`).
|
||||
3. Open a pull request describing what you changed and why.
|
||||
|
||||
Please open an issue first for any significant change so we can discuss the approach before you invest time in it.
|
||||
|
||||
## Support
|
||||
|
||||
If SecretSecurity saves you from an accidental secret leak, consider supporting its development:
|
||||
|
||||
- [GitHub Sponsors](https://github.com/sponsors/omegaleo) — sponsor directly on GitHub
|
||||
- [Ko-fi](https://ko-fi.com/omegaleo) — buy me a coffee
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE.md](LICENSE) for details.
|
||||
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="SecretSecurity/SecretSecurity.csproj" />
|
||||
</Solution>
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace SecretSecurity.MethodExtensions
|
||||
{
|
||||
public static class ListExtensions
|
||||
{
|
||||
public static ReadOnlySpan<string> AsReadOnlySpan(this IEnumerable<string> strings)
|
||||
{
|
||||
return new ReadOnlySpan<string>(strings.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SecretSecurity.Models
|
||||
{
|
||||
public class DiffEntry
|
||||
{
|
||||
public string File { get; set; }
|
||||
public int Line { get; set; }
|
||||
public string Content { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SecretSecurity.Models
|
||||
{
|
||||
public enum FindingType
|
||||
{
|
||||
ApiKey,
|
||||
Token,
|
||||
PrivateKey,
|
||||
ConnectionString,
|
||||
CloudCredential,
|
||||
SuspiciousBlob
|
||||
}
|
||||
|
||||
public enum Severity
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
High,
|
||||
Critical
|
||||
}
|
||||
|
||||
public class SecretFinding
|
||||
{
|
||||
public string File { get; set; }
|
||||
public int Line { get; set; }
|
||||
public FindingType Type { get; set; } // "API Key", "Connection String"
|
||||
public string ValuePreview { get; set; } // partially masked
|
||||
public Severity Severity { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"SecretSecurity": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "scan"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<PackAsTool>true</PackAsTool>
|
||||
<ToolCommandName>secretsecurity</ToolCommandName>
|
||||
<PackageId>SecretSecurity</PackageId>
|
||||
<Version>1.0.0</Version>
|
||||
<Authors>Omega Leo</Authors>
|
||||
<Description>CLI tool for scanning secrets and sensitive data in files.</Description>
|
||||
<RepositoryUrl>https://github.com/omegaleo/SecretSecurity</RepositoryUrl>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Spectre.Console" Version="0.57.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,173 @@
|
||||
using SecretSecurity.Models;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SecretSecurity
|
||||
{
|
||||
public static class Validator
|
||||
{
|
||||
// 1. Explicit secret assignments (HIGH confidence)
|
||||
static Regex _explicitSecret = new Regex(
|
||||
@"(?i)(api[_-]?key|secret|token|access[_-]?key|auth[_-]?token)\s*[:=]\s*[\""']?(?<value>[a-z0-9_\-]{16,})[\""']?",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
// 2. Provider-specific tokens (VERY high confidence)
|
||||
static Regex _providerSecrets = new Regex(
|
||||
@"(AKIA[0-9A-Z]{16}|sk_live_[0-9a-zA-Z]{24,}|ghp_[0-9a-zA-Z]{36}|xox[baprs]-[0-9a-zA-Z-]{10,})",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
// 3. Private key blocks
|
||||
static Regex _privateKey = new Regex(
|
||||
@"-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
// 4. High entropy fallback (LIMITED usage only)
|
||||
static Regex _randomBlob = new Regex(
|
||||
@"\b[a-zA-Z0-9_\-]{40,}\b",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
// 5. General Connection Strings
|
||||
static Regex _generalConnString = new Regex(
|
||||
"(?i)\\b(server|data source|database|uid|user id|pwd|password)\\s*=\\s*[^;]+",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
// 6. Postgres Connection Strings
|
||||
static Regex _postgres = new Regex(
|
||||
"(?i)(postgres|mysql|mongodb|redis):\\/\\/[^\\\\s\\\"']+",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
// 7. Azure Connection Strings
|
||||
static Regex _azureConnString = new Regex(
|
||||
"(?i)(DefaultEndpointsProtocol|AccountName|AccountKey)=",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
public static SecretFinding? FindSecret(string line)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
return null;
|
||||
|
||||
// 1. Provider secrets (highest confidence)
|
||||
if (_providerSecrets.IsMatch(line))
|
||||
{
|
||||
return new SecretFinding
|
||||
{
|
||||
Type = FindingType.Token,
|
||||
Severity = Severity.Critical,
|
||||
ValuePreview = Mask(line)
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Private keys
|
||||
if (_privateKey.IsMatch(line))
|
||||
{
|
||||
return new SecretFinding
|
||||
{
|
||||
Type = FindingType.ApiKey,
|
||||
Severity = Severity.Critical,
|
||||
ValuePreview = "-----BEGIN **** KEY-----"
|
||||
};
|
||||
}
|
||||
|
||||
// 3. Explicit assignment secrets
|
||||
var explicitMatch = _explicitSecret.Match(line);
|
||||
if (explicitMatch.Success)
|
||||
{
|
||||
return new SecretFinding
|
||||
{
|
||||
Type = FindingType.ApiKey | FindingType.Token,
|
||||
Severity = Severity.High,
|
||||
ValuePreview = Mask(explicitMatch.Value)
|
||||
};
|
||||
}
|
||||
|
||||
// 4. Connection strings (general)
|
||||
if (_generalConnString.IsMatch(line))
|
||||
{
|
||||
if (IsSafeDevConnection(line))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SecretFinding
|
||||
{
|
||||
Type = FindingType.ConnectionString,
|
||||
Severity = Severity.High,
|
||||
ValuePreview = MaskConnection(line)
|
||||
};
|
||||
}
|
||||
|
||||
// 5. Postgres / DB URLs
|
||||
if (_postgres.IsMatch(line))
|
||||
{
|
||||
return new SecretFinding
|
||||
{
|
||||
Type = FindingType.ConnectionString,
|
||||
Severity = Severity.High,
|
||||
ValuePreview = Mask(line)
|
||||
};
|
||||
}
|
||||
|
||||
// 6. Azure connection strings
|
||||
if (_azureConnString.IsMatch(line))
|
||||
{
|
||||
return new SecretFinding
|
||||
{
|
||||
Type = FindingType.ConnectionString,
|
||||
Severity = Severity.Critical,
|
||||
ValuePreview = Mask(line)
|
||||
};
|
||||
}
|
||||
|
||||
// 7. Fallback entropy check (low confidence)
|
||||
if (_randomBlob.IsMatch(line) && LooksSuspicious(line))
|
||||
{
|
||||
return new SecretFinding
|
||||
{
|
||||
Type = FindingType.SuspiciousBlob,
|
||||
Severity = Severity.Warning,
|
||||
ValuePreview = Mask(line)
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static string Mask(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return value;
|
||||
|
||||
if (value.Length <= 10)
|
||||
return "****";
|
||||
|
||||
return value.Substring(0, 4) + "****" + value[^2..];
|
||||
}
|
||||
|
||||
static string MaskConnection(string conn)
|
||||
{
|
||||
return Regex.Replace(conn,
|
||||
"(?i)(password|pwd|user id|uid|key)=([^;]+)",
|
||||
"$1=****");
|
||||
}
|
||||
|
||||
static bool LooksSuspicious(string line)
|
||||
{
|
||||
// reduce false positives
|
||||
return line.Contains("=") ||
|
||||
line.Contains(":") ||
|
||||
line.Contains("token") ||
|
||||
line.Contains("key") ||
|
||||
line.Contains("secret");
|
||||
}
|
||||
|
||||
static bool IsSafeDevConnection(string line)
|
||||
{
|
||||
line = line.ToLowerInvariant();
|
||||
|
||||
return line.Contains("localhost") ||
|
||||
line.Contains("127.0.0.1") ||
|
||||
line.Contains("::1") ||
|
||||
line.Contains("database=mydb") && line.Contains("trusted_connection=true") ||
|
||||
line.Contains("integrated security=true");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
AWS_KEY=AKIAIOSFODNN7EXAMPLE
|
||||
AWS_SECRET=abcd1234abcd1234abcd1234abcd1234
|
||||
|
||||
GITHUB_TOKEN=ghp_ABCDEF1234567890ABCDEF1234567890AB
|
||||
|
||||
SLACK_TOKEN=xoxb-1234567890-abcdefghij
|
||||
|
||||
DATABASE_URL=postgres://admin:SuperSecret@localhost:5432/prod
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information"
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=localhost;Database=GameDb;Trusted_Connection=true;"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=mydb;Database=Prod;User Id=admin;Password=SuperSecret123;"
|
||||
},
|
||||
"Stripe": {
|
||||
"SecretKey": "sk_live_9x8y7z6w5v4u3t2s"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
public class Config
|
||||
{
|
||||
public string ApiKeyName = "access_key";
|
||||
public string TokenDescription = "used for authentication flow";
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
public class Startup
|
||||
{
|
||||
// safe config name
|
||||
public const string ApiKeyName = "access_key";
|
||||
|
||||
// suspicious placeholder
|
||||
private string tokenExample = "your_token_here_1234567890abcdef";
|
||||
|
||||
// real secret
|
||||
private string stripeKey = "sk_live_1234567890abcdef";
|
||||
|
||||
// connection string
|
||||
private string db = "Server=prod;Database=Main;User Id=admin;Password=SuperSecret;";
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"Logging": {
|
||||
"Level": "Debug"
|
||||
},
|
||||
"ApiKeyHint": "use environment variable API_KEY",
|
||||
|
||||
"Stripe": {
|
||||
"Secret": "sk_live_1234567890abcdef"
|
||||
},
|
||||
|
||||
"ConnectionStrings": {
|
||||
"Default": "postgres://user:pass@localhost/db"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
This file contains notes about API keys and tokens.
|
||||
|
||||
DO NOT COMMIT REAL VALUES.
|
||||
|
||||
Example:
|
||||
API_KEY=example_value_1234567890
|
||||
|
||||
Actual production key:
|
||||
ghp_ABCDEF1234567890ABCDEF1234567890AB
|
||||
@@ -0,0 +1,4 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA7examplefakekeydataaaaaaaaaaaaaaaaaaaaa
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1,7 @@
|
||||
public class Config
|
||||
{
|
||||
public string ApiKey = "sk_live_1234567890abcdef";
|
||||
public string Secret = "ghp_ABCDEF1234567890ABCDEF1234567890AB";
|
||||
|
||||
string conn = "Server=db;User Id=admin;Password=123456;";
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
public class Settings
|
||||
{
|
||||
public string ApiKeyPlaceholder = "your_api_key_here_123456789";
|
||||
public string TokenExample = "example_token_value_1234567890abcdef";
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# development environment
|
||||
|
||||
DATABASE_URL=postgres://user:password@localhost:5432/db
|
||||
API_KEY=replace_this_value
|
||||
SECRET_TOKEN=example_only_but_long_enough_1234567890
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
User login failed
|
||||
Auth token received: abcdef1234567890abcdef1234567890abcdef
|
||||
Request processed successfully
|
||||
Reference in New Issue
Block a user