Some checks failed
Build and Release / Create Release (push) Has been skipped
Build and Release / Unit Tests (push) Successful in 3m20s
Build and Release / Lint (push) Failing after 4m55s
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been skipped
Build and Release / Build Binaries (amd64, darwin, macos-latest) (push) Has been skipped
Build and Release / Build Binaries (arm64, darwin, macos-latest) (push) Has been skipped
Build and Release / Build Binaries (arm64, linux, linux-latest) (push) Has been skipped
Build and Release / Integration Tests (PostgreSQL) (push) Successful in 4m59s
393 lines
13 KiB
Go
393 lines
13 KiB
Go
// Copyright 2026 MarketAlly. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package secretscan
|
|
|
|
import "regexp"
|
|
|
|
// Severity represents the severity level of a detected secret
|
|
type Severity string
|
|
|
|
const (
|
|
SeverityCritical Severity = "critical"
|
|
SeverityHigh Severity = "high"
|
|
SeverityMedium Severity = "medium"
|
|
SeverityLow Severity = "low"
|
|
)
|
|
|
|
// Category represents the category of a detected secret
|
|
type Category string
|
|
|
|
const (
|
|
CategoryAPIKey Category = "api-key"
|
|
CategoryPrivateKey Category = "private-key"
|
|
CategoryPassword Category = "password"
|
|
CategoryToken Category = "token"
|
|
CategoryCertificate Category = "certificate"
|
|
CategoryConnectionString Category = "connection-string"
|
|
CategoryCredential Category = "credential"
|
|
CategorySecret Category = "secret"
|
|
)
|
|
|
|
// Pattern represents a secret detection pattern
|
|
type Pattern struct {
|
|
ID string
|
|
Name string
|
|
Description string
|
|
Regex *regexp.Regexp
|
|
Severity Severity
|
|
Category Category
|
|
FalsePositiveRegexes []*regexp.Regexp
|
|
}
|
|
|
|
// builtinPatterns contains all built-in secret detection patterns
|
|
var builtinPatterns = []Pattern{
|
|
// ============================================
|
|
// PRIVATE KEYS (Critical)
|
|
// ============================================
|
|
{
|
|
ID: "private-key-rsa",
|
|
Name: "RSA Private Key",
|
|
Description: "RSA private key in PEM format",
|
|
Regex: regexp.MustCompile(`-----BEGIN (?:RSA )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA )?PRIVATE KEY-----`),
|
|
Severity: SeverityCritical,
|
|
Category: CategoryPrivateKey,
|
|
},
|
|
{
|
|
ID: "private-key-openssh",
|
|
Name: "OpenSSH Private Key",
|
|
Description: "OpenSSH private key",
|
|
Regex: regexp.MustCompile(`-----BEGIN OPENSSH PRIVATE KEY-----[\s\S]*?-----END OPENSSH PRIVATE KEY-----`),
|
|
Severity: SeverityCritical,
|
|
Category: CategoryPrivateKey,
|
|
},
|
|
{
|
|
ID: "private-key-ec",
|
|
Name: "EC Private Key",
|
|
Description: "Elliptic Curve private key",
|
|
Regex: regexp.MustCompile(`-----BEGIN EC PRIVATE KEY-----[\s\S]*?-----END EC PRIVATE KEY-----`),
|
|
Severity: SeverityCritical,
|
|
Category: CategoryPrivateKey,
|
|
},
|
|
{
|
|
ID: "private-key-dsa",
|
|
Name: "DSA Private Key",
|
|
Description: "DSA private key",
|
|
Regex: regexp.MustCompile(`-----BEGIN DSA PRIVATE KEY-----[\s\S]*?-----END DSA PRIVATE KEY-----`),
|
|
Severity: SeverityCritical,
|
|
Category: CategoryPrivateKey,
|
|
},
|
|
{
|
|
ID: "private-key-pgp",
|
|
Name: "PGP Private Key",
|
|
Description: "PGP/GPG private key block",
|
|
Regex: regexp.MustCompile(`-----BEGIN PGP PRIVATE KEY BLOCK-----[\s\S]*?-----END PGP PRIVATE KEY BLOCK-----`),
|
|
Severity: SeverityCritical,
|
|
Category: CategoryPrivateKey,
|
|
},
|
|
|
|
// ============================================
|
|
// AWS (Critical)
|
|
// ============================================
|
|
{
|
|
ID: "aws-access-key",
|
|
Name: "AWS Access Key ID",
|
|
Description: "Amazon Web Services access key identifier",
|
|
Regex: regexp.MustCompile(`\b(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}\b`),
|
|
Severity: SeverityCritical,
|
|
Category: CategoryAPIKey,
|
|
},
|
|
{
|
|
ID: "aws-mws-key",
|
|
Name: "AWS MWS Key",
|
|
Description: "Amazon Marketplace Web Service key",
|
|
Regex: regexp.MustCompile(`(?i)amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`),
|
|
Severity: SeverityCritical,
|
|
Category: CategoryAPIKey,
|
|
},
|
|
|
|
// ============================================
|
|
// GOOGLE / GCP (Critical)
|
|
// ============================================
|
|
{
|
|
ID: "gcp-api-key",
|
|
Name: "Google API Key",
|
|
Description: "Google Cloud Platform API key",
|
|
Regex: regexp.MustCompile(`AIza[0-9A-Za-z_-]{35}`),
|
|
Severity: SeverityCritical,
|
|
Category: CategoryAPIKey,
|
|
},
|
|
{
|
|
ID: "gcp-service-account",
|
|
Name: "GCP Service Account",
|
|
Description: "Google Cloud service account key file",
|
|
Regex: regexp.MustCompile(`"type"\s*:\s*"service_account"[\s\S]*?"private_key"`),
|
|
Severity: SeverityCritical,
|
|
Category: CategoryPrivateKey,
|
|
},
|
|
|
|
// ============================================
|
|
// AZURE (Critical)
|
|
// ============================================
|
|
{
|
|
ID: "azure-connection-string",
|
|
Name: "Azure Connection String",
|
|
Description: "Azure service connection string",
|
|
Regex: regexp.MustCompile(`(?i)DefaultEndpointsProtocol=https?;AccountName=[^;]+;AccountKey=[A-Za-z0-9/+=]+`),
|
|
Severity: SeverityCritical,
|
|
Category: CategoryConnectionString,
|
|
},
|
|
|
|
// ============================================
|
|
// GITHUB (High)
|
|
// ============================================
|
|
{
|
|
ID: "github-token",
|
|
Name: "GitHub Token",
|
|
Description: "GitHub personal access token or OAuth token",
|
|
Regex: regexp.MustCompile(`\b(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}\b`),
|
|
Severity: SeverityHigh,
|
|
Category: CategoryToken,
|
|
},
|
|
|
|
// ============================================
|
|
// GITLAB (High)
|
|
// ============================================
|
|
{
|
|
ID: "gitlab-token",
|
|
Name: "GitLab Token",
|
|
Description: "GitLab personal access token",
|
|
Regex: regexp.MustCompile(`\bglpat-[A-Za-z0-9_-]{20,}\b`),
|
|
Severity: SeverityHigh,
|
|
Category: CategoryToken,
|
|
},
|
|
|
|
// ============================================
|
|
// STRIPE (Critical)
|
|
// ============================================
|
|
{
|
|
ID: "stripe-secret-key",
|
|
Name: "Stripe Secret Key",
|
|
Description: "Stripe API secret key",
|
|
Regex: regexp.MustCompile(`\bsk_live_[0-9a-zA-Z]{24,}\b`),
|
|
Severity: SeverityCritical,
|
|
Category: CategoryAPIKey,
|
|
},
|
|
{
|
|
ID: "stripe-test-key",
|
|
Name: "Stripe Test Key",
|
|
Description: "Stripe API test key (still sensitive)",
|
|
Regex: regexp.MustCompile(`\bsk_test_[0-9a-zA-Z]{24,}\b`),
|
|
Severity: SeverityMedium,
|
|
Category: CategoryAPIKey,
|
|
},
|
|
|
|
// ============================================
|
|
// SLACK (High)
|
|
// ============================================
|
|
{
|
|
ID: "slack-token",
|
|
Name: "Slack Token",
|
|
Description: "Slack API token",
|
|
Regex: regexp.MustCompile(`\bxox[baprs]-[0-9]{10,13}-[0-9]{10,13}[a-zA-Z0-9-]*\b`),
|
|
Severity: SeverityHigh,
|
|
Category: CategoryToken,
|
|
},
|
|
{
|
|
ID: "slack-webhook",
|
|
Name: "Slack Webhook URL",
|
|
Description: "Slack incoming webhook URL",
|
|
Regex: regexp.MustCompile(`https://hooks\.slack\.com/services/T[A-Z0-9]+/B[A-Z0-9]+/[A-Za-z0-9]+`),
|
|
Severity: SeverityHigh,
|
|
Category: CategoryCredential,
|
|
},
|
|
|
|
// ============================================
|
|
// TWILIO (High)
|
|
// ============================================
|
|
{
|
|
ID: "twilio-api-key",
|
|
Name: "Twilio API Key",
|
|
Description: "Twilio API key SID",
|
|
Regex: regexp.MustCompile(`\bSK[0-9a-fA-F]{32}\b`),
|
|
Severity: SeverityHigh,
|
|
Category: CategoryAPIKey,
|
|
},
|
|
|
|
// ============================================
|
|
// SENDGRID (High)
|
|
// ============================================
|
|
{
|
|
ID: "sendgrid-api-key",
|
|
Name: "SendGrid API Key",
|
|
Description: "SendGrid email API key",
|
|
Regex: regexp.MustCompile(`\bSG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}\b`),
|
|
Severity: SeverityHigh,
|
|
Category: CategoryAPIKey,
|
|
},
|
|
|
|
// ============================================
|
|
// NPM (High)
|
|
// ============================================
|
|
{
|
|
ID: "npm-token",
|
|
Name: "NPM Access Token",
|
|
Description: "NPM registry access token",
|
|
Regex: regexp.MustCompile(`\bnpm_[A-Za-z0-9]{36}\b`),
|
|
Severity: SeverityHigh,
|
|
Category: CategoryToken,
|
|
},
|
|
|
|
// ============================================
|
|
// DATABASE CONNECTION STRINGS (Critical)
|
|
// ============================================
|
|
{
|
|
ID: "postgres-uri",
|
|
Name: "PostgreSQL Connection URI",
|
|
Description: "PostgreSQL connection string with credentials",
|
|
Regex: regexp.MustCompile(`(?i)postgres(?:ql)?://[^:]+:[^@]+@[^/]+/[^\s"']+`),
|
|
Severity: SeverityCritical,
|
|
Category: CategoryConnectionString,
|
|
},
|
|
{
|
|
ID: "mysql-uri",
|
|
Name: "MySQL Connection URI",
|
|
Description: "MySQL connection string with credentials",
|
|
Regex: regexp.MustCompile(`(?i)mysql://[^:]+:[^@]+@[^/]+/[^\s"']+`),
|
|
Severity: SeverityCritical,
|
|
Category: CategoryConnectionString,
|
|
},
|
|
{
|
|
ID: "mongodb-uri",
|
|
Name: "MongoDB Connection URI",
|
|
Description: "MongoDB connection string with credentials",
|
|
Regex: regexp.MustCompile(`(?i)mongodb(?:\+srv)?://[^:]+:[^@]+@[^/]+`),
|
|
Severity: SeverityCritical,
|
|
Category: CategoryConnectionString,
|
|
},
|
|
|
|
// ============================================
|
|
// GENERIC PASSWORDS (High)
|
|
// ============================================
|
|
{
|
|
ID: "password-assignment",
|
|
Name: "Password Assignment",
|
|
Description: "Hardcoded password in code",
|
|
Regex: regexp.MustCompile(`(?i)(?:password|passwd|pwd|secret|api_?key|auth_?token|access_?token)\s*[:=]\s*['"][^'"]{8,}['"]`),
|
|
Severity: SeverityHigh,
|
|
Category: CategoryPassword,
|
|
FalsePositiveRegexes: []*regexp.Regexp{
|
|
regexp.MustCompile(`\$\{.*\}`), // Template variables
|
|
regexp.MustCompile(`process\.env`), // Environment references
|
|
regexp.MustCompile(`\{\{.*\}\}`), // Handlebars/mustache
|
|
regexp.MustCompile(`<.*>`), // Placeholder text
|
|
regexp.MustCompile(`(?i)example`), // Example text
|
|
regexp.MustCompile(`(?i)placeholder`),
|
|
},
|
|
},
|
|
|
|
// ============================================
|
|
// JWT (Medium)
|
|
// ============================================
|
|
{
|
|
ID: "jwt-token",
|
|
Name: "JSON Web Token",
|
|
Description: "JWT token (may contain sensitive claims)",
|
|
Regex: regexp.MustCompile(`\beyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*`),
|
|
Severity: SeverityMedium,
|
|
Category: CategoryToken,
|
|
},
|
|
|
|
// ============================================
|
|
// DISCORD (High)
|
|
// ============================================
|
|
{
|
|
ID: "discord-token",
|
|
Name: "Discord Bot Token",
|
|
Description: "Discord bot or user token",
|
|
Regex: regexp.MustCompile(`[MN][A-Za-z\d]{23,}\.[\w-]{6}\.[\w-]{27}`),
|
|
Severity: SeverityHigh,
|
|
Category: CategoryToken,
|
|
},
|
|
{
|
|
ID: "discord-webhook",
|
|
Name: "Discord Webhook URL",
|
|
Description: "Discord webhook URL",
|
|
Regex: regexp.MustCompile(`https://discord(?:app)?\.com/api/webhooks/[0-9]+/[A-Za-z0-9_-]+`),
|
|
Severity: SeverityHigh,
|
|
Category: CategoryCredential,
|
|
},
|
|
|
|
// ============================================
|
|
// TELEGRAM (High)
|
|
// ============================================
|
|
{
|
|
ID: "telegram-bot-token",
|
|
Name: "Telegram Bot Token",
|
|
Description: "Telegram Bot API token",
|
|
Regex: regexp.MustCompile(`\b[0-9]{8,10}:[A-Za-z0-9_-]{35}\b`),
|
|
Severity: SeverityHigh,
|
|
Category: CategoryToken,
|
|
},
|
|
|
|
// ============================================
|
|
// SHOPIFY (High)
|
|
// ============================================
|
|
{
|
|
ID: "shopify-token",
|
|
Name: "Shopify Access Token",
|
|
Description: "Shopify private app or custom app token",
|
|
Regex: regexp.MustCompile(`shpat_[a-fA-F0-9]{32}`),
|
|
Severity: SeverityHigh,
|
|
Category: CategoryToken,
|
|
},
|
|
|
|
// ============================================
|
|
// HEROKU (High)
|
|
// ============================================
|
|
{
|
|
ID: "heroku-api-key",
|
|
Name: "Heroku API Key",
|
|
Description: "Heroku platform API key",
|
|
Regex: regexp.MustCompile(`(?i)heroku.*['\"][0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}['\"]`),
|
|
Severity: SeverityHigh,
|
|
Category: CategoryAPIKey,
|
|
},
|
|
}
|
|
|
|
// genericFalsePositivePatterns are patterns that indicate false positives across all detections
|
|
var genericFalsePositivePatterns = []*regexp.Regexp{
|
|
regexp.MustCompile(`^[xX]+$`), // All x's (placeholder)
|
|
regexp.MustCompile(`^[0]+$`), // All zeros
|
|
regexp.MustCompile(`(?i)example`), // Contains "example"
|
|
regexp.MustCompile(`(?i)sample`), // Contains "sample"
|
|
regexp.MustCompile(`(?i)dummy`), // Contains "dummy"
|
|
regexp.MustCompile(`(?i)placeholder`), // Contains "placeholder"
|
|
regexp.MustCompile(`(?i)your[_-]?(api[_-]?)?key`), // "your_key", "your_api_key", etc.
|
|
regexp.MustCompile(`__[A-Z_]+__`), // Python dunder-like placeholders
|
|
}
|
|
|
|
// GetBuiltinPatterns returns all built-in secret detection patterns
|
|
func GetBuiltinPatterns() []Pattern {
|
|
return builtinPatterns
|
|
}
|
|
|
|
// IsFalsePositive checks if a match is a false positive
|
|
func IsFalsePositive(match string, pattern *Pattern) bool {
|
|
// Check pattern-specific false positives
|
|
for _, fp := range pattern.FalsePositiveRegexes {
|
|
if fp.MatchString(match) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Check generic false positives
|
|
for _, fp := range genericFalsePositivePatterns {
|
|
if fp.MatchString(match) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|