Cybersecurity-Projects/PROJECTS/intermediate/secrets-scanner/internal/rules/builtin_test.go

542 lines
16 KiB
Go

// ©AngelaMos | 2026
// builtin_test.go
package rules
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRegisterBuiltins(t *testing.T) {
t.Parallel()
reg := NewRegistry()
RegisterBuiltins(reg)
assert.GreaterOrEqual(t, reg.Len(), 70)
}
func TestBuiltinRuleIDs(t *testing.T) {
t.Parallel()
reg := NewRegistry()
RegisterBuiltins(reg)
seen := make(map[string]bool)
for _, r := range reg.All() {
assert.NotEmpty(t, r.ID)
assert.NotEmpty(t, r.Description)
assert.NotEmpty(t, r.Keywords)
assert.NotNil(t, r.Pattern)
assert.False(t, seen[r.ID], "duplicate ID: %s", r.ID)
seen[r.ID] = true
}
}
func TestBuiltinPatternMatches(t *testing.T) { //nolint:funlen,gocognit
t.Parallel()
tests := map[string]struct {
ruleID string
input string
wantMatch bool
wantGroup string
}{
"aws access key": { //nolint:gosec
ruleID: "aws-access-key-id",
input: `aws_key = "AKIAIOSFODNN7EXAMPLE"`,
wantMatch: true,
wantGroup: "AKIAIOSFODNN7EXAMPLE",
},
"aws access key ABIA prefix": {
ruleID: "aws-access-key-id",
input: `ABIAIOSFODNN7EXAMPL0`,
wantMatch: true,
wantGroup: "ABIAIOSFODNN7EXAMPL0",
},
"aws access key too short": {
ruleID: "aws-access-key-id",
input: `AKIA1234`,
wantMatch: false,
},
"github pat classic": {
ruleID: "github-pat-classic",
input: `ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij`,
wantMatch: true,
wantGroup: "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij",
},
"github pat fine-grained": {
ruleID: "github-pat-fine",
input: `github_pat_abcdefghijABCDEFGHIJKL_abcdefghij0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABC`,
wantMatch: true,
},
"github oauth": {
ruleID: "github-oauth-token",
input: `gho_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij`,
wantMatch: true,
wantGroup: "gho_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij",
},
"github app token": {
ruleID: "github-app-token",
input: `ghs_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij`,
wantMatch: true,
wantGroup: "ghs_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij",
},
"github refresh token": {
ruleID: "github-refresh-token",
input: `ghr_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij`,
wantMatch: true,
wantGroup: "ghr_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij",
},
"gitlab pat": {
ruleID: "gitlab-pat",
input: `glpat-xYz1234567890AbCdEfGh`,
wantMatch: true,
wantGroup: "glpat-xYz1234567890AbCdEfGh",
},
"gitlab pipeline trigger": {
ruleID: "gitlab-pipeline-trigger",
input: `glptt-abcdefghijklmnopqrstuvwxyz0123456789ABCD`,
wantMatch: true,
wantGroup: "glptt-abcdefghijklmnopqrstuvwxyz0123456789ABCD",
},
"gitlab runner token": {
ruleID: "gitlab-runner-token",
input: `glrt-xYz1234567890AbCdEfGh`,
wantMatch: true,
wantGroup: "glrt-xYz1234567890AbCdEfGh",
},
"gcp api key": {
ruleID: "gcp-api-key",
input: `AIzaSyDabcdefghij1234567890KLMNOPQRSTUV`,
wantMatch: true,
},
"gcp service account": {
ruleID: "gcp-service-account",
input: `"type" : "service_account"`,
wantMatch: true,
},
"gcp oauth secret": {
ruleID: "gcp-oauth-client-secret",
input: `GOCSPX-aBcDeFgHiJkLmNoPqRsTuVwXyZ01`,
wantMatch: true,
},
"azure storage key": {
ruleID: "azure-storage-key",
input: `AccountKey=` +
`abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`,
wantMatch: true,
},
"slack bot token": {
ruleID: "slack-bot-token",
input: `xoxb-1234567890-9876543210-AbCdEfGhIjKlMnOpQrStUvWx`,
wantMatch: true,
},
"slack webhook": { //nolint:gosec
ruleID: "slack-webhook",
input: `https://hooks.slack.com/services/T01234567/B01234567/AbCdEfGhIjKlMnOpQrStUvWx`,
wantMatch: true,
},
"stripe live key": { //nolint:gosec
ruleID: "stripe-live-secret",
input: `sk_live_4eC39HqLyjWDarjtT1zdp7dc`,
wantMatch: true,
wantGroup: "sk_live_4eC39HqLyjWDarjtT1zdp7dc",
},
"stripe test key": {
ruleID: "stripe-test-secret",
input: `sk_test_4eC39HqLyjWDarjtT1zdp7dc`, //nolint:gosec
wantMatch: true,
wantGroup: "sk_test_4eC39HqLyjWDarjtT1zdp7dc", //nolint:gosec
},
"stripe restricted live": { //nolint:gosec
ruleID: "stripe-live-restricted",
input: `rk_live_4eC39HqLyjWDarjtT1zdp7dc`,
wantMatch: true,
wantGroup: "rk_live_4eC39HqLyjWDarjtT1zdp7dc",
},
"stripe webhook secret": {
ruleID: "stripe-webhook-secret",
input: `whsec_MfKBGsXP8r7B2cGnQ9jT6KxL12AbCdEf`,
wantMatch: true,
},
"twilio api key": {
ruleID: "twilio-api-key",
input: `SK1234567890abcdef1234567890abcdef`,
wantMatch: true,
wantGroup: "SK1234567890abcdef1234567890abcdef",
},
"twilio account sid": {
ruleID: "twilio-account-sid",
input: `AC1234567890abcdef1234567890abcdef`,
wantMatch: true,
wantGroup: "AC1234567890abcdef1234567890abcdef",
},
"sendgrid api key": {
ruleID: "sendgrid-api-key",
input: `SG.aBcDeFgHiJkLmNoPqRsTuw.xYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMn`,
wantMatch: true,
},
"shopify access token": {
ruleID: "shopify-access-token",
input: `shpat_abcdef0123456789abcdef0123456789`,
wantMatch: true,
wantGroup: "shpat_abcdef0123456789abcdef0123456789",
},
"shopify custom app": {
ruleID: "shopify-custom-app",
input: `shpca_abcdef0123456789abcdef0123456789`,
wantMatch: true,
wantGroup: "shpca_abcdef0123456789abcdef0123456789",
},
"shopify private app": {
ruleID: "shopify-private-app",
input: `shppa_abcdef0123456789abcdef0123456789`,
wantMatch: true,
wantGroup: "shppa_abcdef0123456789abcdef0123456789",
},
"shopify shared secret": {
ruleID: "shopify-shared-secret",
input: `shpss_abcdef0123456789abcdef0123456789`,
wantMatch: true,
wantGroup: "shpss_abcdef0123456789abcdef0123456789",
},
"npm access token": {
ruleID: "npm-access-token",
input: `npm_aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789`,
wantMatch: true,
},
"pypi token": {
ruleID: "pypi-api-token",
input: `pypi-AgEIcHlwaS5vcmcCJDU5NTk5YTJhLWIwN2QtNDRkZi1iM` +
`jIxLTk2OWU4YmViZDM3NgACKlszLCJjODA0ZWE1OC1kMjFiLTQzMjMtYWR` +
`mNy0xZjQ4MGQ`,
wantMatch: true,
},
"rubygems api key": {
ruleID: "rubygems-api-key",
input: `rubygems_abcdef0123456789abcdef0123456789abcdef0123456789`,
wantMatch: true,
},
"docker hub pat": {
ruleID: "docker-hub-pat",
input: `dckr_pat_aBcDeFgHiJkLmNoPqRsTuVwXyZ01`,
wantMatch: true,
},
"jwt": {
ruleID: "jwt-token",
input: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.` +
`eyJzdWIiOiIxMjM0NTY3ODkwIn0.` +
`dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U`,
wantMatch: true,
},
"rsa private key": { //nolint:gosec
ruleID: "ssh-private-key-rsa",
input: `-----BEGIN RSA PRIVATE KEY-----`,
wantMatch: true,
},
"openssh private key": {
ruleID: "ssh-private-key-openssh",
input: `-----BEGIN OPENSSH PRIVATE KEY-----`, //nolint:gosec
wantMatch: true,
},
"ec private key": { //nolint:gosec
ruleID: "ssh-private-key-ec",
input: `-----BEGIN EC PRIVATE KEY-----`,
wantMatch: true,
},
"dsa private key": { //nolint:gosec
ruleID: "ssh-private-key-dsa",
input: `-----BEGIN DSA PRIVATE KEY-----`,
wantMatch: true,
},
"pgp private key": { //nolint:gosec
ruleID: "pgp-private-key",
input: `-----BEGIN PGP PRIVATE KEY BLOCK-----`,
wantMatch: true,
},
"pkcs8 private key": {
ruleID: "private-key-pkcs8",
input: `-----BEGIN PRIVATE KEY-----`,
wantMatch: true,
},
"generic password": {
ruleID: "generic-password",
input: `password = "my$ecretP@ss99"`,
wantMatch: true,
wantGroup: "my$ecretP@ss99",
},
"generic password no match on short": {
ruleID: "generic-password",
input: `password = "abc"`,
wantMatch: false,
},
"generic secret": {
ruleID: "generic-secret",
input: `secret_key = "xK9mP2vL5nQ8jR3tB7"`,
wantMatch: true,
wantGroup: "xK9mP2vL5nQ8jR3tB7",
},
"generic api key": {
ruleID: "generic-api-key",
input: `api_key = "aK9mP2vL5nQ8jR3tB7wX4cD"`,
wantMatch: true,
wantGroup: "aK9mP2vL5nQ8jR3tB7wX4cD",
},
"generic token": {
ruleID: "generic-token",
input: `access_token = "xK9mP2vL5nQ8jR3tB7wY"`,
wantMatch: true,
wantGroup: "xK9mP2vL5nQ8jR3tB7wY",
},
"postgres connection": { //nolint:gosec
ruleID: "postgres-connection",
input: `postgresql://admin:s3cr3t@db.example.com:5432/mydb`,
wantMatch: true,
},
"mysql connection": { //nolint:gosec
ruleID: "mysql-connection",
input: `mysql://root:password123@127.0.0.1:3306/app`,
wantMatch: true,
},
"mongodb connection": { //nolint:gosec
ruleID: "mongodb-connection",
input: `mongodb+srv://user:p4ssw0rd@cluster0.abc.mongodb.net/db`,
wantMatch: true,
},
"redis connection": { //nolint:gosec
ruleID: "redis-connection",
input: `redis://default:s3cret@redis.example.com:6379/0`,
wantMatch: true,
},
"hashicorp vault token": {
ruleID: "hashicorp-vault-token",
input: `hvs.CAESIGH3YzJfaBcDeFgHiJkLmNoPqR`,
wantMatch: true,
},
"digitalocean pat": {
ruleID: "digitalocean-pat",
input: `dop_v1_abcdef01234567890abcdef01234567890abcdef01234567890abcdef0123456`,
wantMatch: true,
wantGroup: "dop_v1_abcdef01234567890abcdef01234567890abcdef01234567890abcdef0123456",
},
"linear api key": {
ruleID: "linear-api-key",
input: `lin_api_aBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmN`,
wantMatch: true,
},
"age secret key": {
ruleID: "age-secret-key",
input: `AGE-SECRET-KEY-1QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ`,
wantMatch: true,
},
"doppler token": {
ruleID: "doppler-token",
input: `dp.st.aBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmN`,
wantMatch: true,
},
"grafana sa token": {
ruleID: "grafana-api-key",
input: `glsa_aBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeF_12345678`,
wantMatch: true,
},
"grafana cloud token": {
ruleID: "grafana-cloud-token",
input: `glc_aBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeF1`,
wantMatch: true,
},
"databricks token": {
ruleID: "databricks-token",
input: `dapi1234567890abcdef1234567890abcdef`,
wantMatch: true,
wantGroup: "dapi1234567890abcdef1234567890abcdef",
},
"huggingface token": {
ruleID: "huggingface-token",
input: `hf_aBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJ`,
wantMatch: true,
wantGroup: "hf_aBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJ",
},
"netlify token": {
ruleID: "netlify-token",
input: `nfp_aBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmN`,
wantMatch: true,
},
"postman api key": {
ruleID: "postman-api-key",
input: `PMAK-abcdef0123456789abcdef01-abcdef0123456789abcdef0123456789ab`,
wantMatch: true,
},
"figma pat": {
ruleID: "figma-pat",
input: `figd_aBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmN`,
wantMatch: true,
},
"flyio token": {
ruleID: "flyio-token",
input: `fo1_aBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmN`,
wantMatch: true,
},
"planetscale token": {
ruleID: "planetscale-token",
input: `pscale_tkn_aBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmN`,
wantMatch: true,
},
"replicate token": {
ruleID: "replicate-api-token",
input: `r8_aBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqR`,
wantMatch: true,
},
"sentry auth token": {
ruleID: "sentry-auth-token",
input: `sntrys_aBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHi`,
wantMatch: true,
},
"atlassian token": {
ruleID: "atlassian-api-token",
input: `ATATTaBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHiJkLmNoPqRsTuVwX`,
wantMatch: true,
},
"render api key": {
ruleID: "render-api-key",
input: `rnd_aBcDeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHi`,
wantMatch: true,
},
"launchdarkly sdk key": {
ruleID: "launchdarkly-sdk-key",
input: `sdk-a1b2c3d4-e5f6-7890-abcd-ef1234567890`,
wantMatch: true,
wantGroup: "sdk-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
},
"launchdarkly api key": {
ruleID: "launchdarkly-api-key",
input: `api-a1b2c3d4-e5f6-7890-abcd-ef1234567890`,
wantMatch: true,
wantGroup: "api-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
},
"okta api token": {
ruleID: "okta-api-token",
input: `OKTA_API_TOKEN = "00ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijkl"`,
wantMatch: true,
wantGroup: "00ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijkl",
},
"okta ssws header": {
ruleID: "okta-api-token",
input: `SSWS = "00xK9mP2vL5nQ8jR3tB7wX4cD6eF8gH0iJ2kL"`,
wantMatch: true,
wantGroup: "00xK9mP2vL5nQ8jR3tB7wX4cD6eF8gH0iJ2kL",
},
"supabase service key": {
ruleID: "supabase-service-key",
input: `SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJzZXJ2aWNlX3JvbGUifQ.xxxxxxxx`,
wantMatch: true,
wantGroup: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJvbGUiOiJzZXJ2aWNlX3JvbGUifQ.xxxxxxxx",
},
"supabase service key quoted": {
ruleID: "supabase-service-key",
input: `SUPABASE_SERVICE_ROLE_KEY="eyJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIn0.sig123"`,
wantMatch: true,
},
"no match on normal text": {
ruleID: "aws-access-key-id",
input: `this is just normal code`,
wantMatch: false,
},
}
reg := NewRegistry()
RegisterBuiltins(reg)
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()
rule, ok := reg.Get(tc.ruleID)
require.True(t, ok, "rule %s not found", tc.ruleID)
match := rule.Pattern.FindStringSubmatch(tc.input)
if tc.wantMatch {
require.NotNil(t, match,
"expected pattern match for rule %s", tc.ruleID)
if tc.wantGroup != "" && rule.SecretGroup > 0 {
require.Greater(t, len(match), rule.SecretGroup)
assert.Equal(t, tc.wantGroup,
match[rule.SecretGroup])
}
} else {
assert.Nil(t, match,
"unexpected match for rule %s", tc.ruleID)
}
})
}
}
func TestBuiltinKeywordMatches(t *testing.T) {
t.Parallel()
reg := NewRegistry()
RegisterBuiltins(reg)
tests := map[string]struct {
content string
wantIDs []string
}{
"aws content": { //nolint:gosec
content: "found AKIAIOSFODNN7EXAMPLE in config",
wantIDs: []string{"aws-access-key-id"},
},
"github content": {
content: "token = ghp_abc123",
wantIDs: []string{"github-pat-classic"},
},
"stripe content": {
content: "key = sk_live_abc123",
wantIDs: []string{"stripe-live-secret"},
},
"private key content": { //nolint:gosec
content: "-----BEGIN RSA PRIVATE KEY-----",
wantIDs: []string{"ssh-private-key-rsa"},
},
"postgres content": { //nolint:gosec
content: "DATABASE_URL=postgres://user:pass@host/db",
wantIDs: []string{"postgres-connection"},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()
matched := reg.MatchKeywords(tc.content)
ids := make([]string, len(matched))
for i, r := range matched {
ids[i] = r.ID
}
for _, wantID := range tc.wantIDs {
assert.Contains(t, ids, wantID)
}
})
}
}
func TestBuiltinNoFalsePositives(t *testing.T) {
t.Parallel()
reg := NewRegistry()
RegisterBuiltins(reg)
benignInputs := []string{
`import os`,
`func main() {}`,
`const x = 42`,
`// this is a comment`,
`print("hello world")`,
`if err != nil { return err }`,
`for i := 0; i < 10; i++ {}`,
}
for _, rule := range reg.All() {
for _, input := range benignInputs {
match := rule.Pattern.FindString(input)
assert.Empty(t, match,
"rule %s false positive on: %s", rule.ID, input)
}
}
}