///|
pub(all) struct PrefixPattern {
id : String
prefix : String
minimum_tail : Int
maximum_tail : Int
severity : Severity
message : String
} derive(Eq, Debug)
///|
pub fn PrefixPattern::new(
id : String,
prefix : String,
minimum_tail : Int,
maximum_tail : Int,
severity : Severity,
message : String,
) -> PrefixPattern {
{ id, prefix, minimum_tail, maximum_tail, severity, message }
}
///|
pub fn builtin_prefix_patterns() -> Array[PrefixPattern] {
[
PrefixPattern::new(
"github-pat",
"ghp_",
30,
64,
Critical,
"GitHub personal access token",
),
PrefixPattern::new(
"github-oauth",
"gho_",
30,
64,
Critical,
"GitHub OAuth token",
),
PrefixPattern::new(
"github-user-token",
"ghu_",
30,
64,
Critical,
"GitHub user token",
),
PrefixPattern::new(
"github-server-token",
"ghs_",
30,
64,
Critical,
"GitHub server token",
),
PrefixPattern::new(
"github-refresh-token",
"ghr_",
30,
128,
Critical,
"GitHub refresh token",
),
PrefixPattern::new(
"gitlab-pat",
"glpat-",
16,
64,
Critical,
"GitLab personal access token",
),
PrefixPattern::new(
"openai-project-key",
"sk-proj-",
20,
180,
Critical,
"OpenAI project API key",
),
PrefixPattern::new("openai-key", "sk-", 20, 180, Critical, "API secret key"),
PrefixPattern::new(
"anthropic-key",
"sk-ant-",
20,
180,
Critical,
"Anthropic API key",
),
PrefixPattern::new(
"slack-bot",
"xoxb-",
20,
120,
Critical,
"Slack bot token",
),
PrefixPattern::new(
"slack-user",
"xoxp-",
20,
120,
Critical,
"Slack user token",
),
PrefixPattern::new(
"slack-app",
"xapp-",
20,
160,
Critical,
"Slack application token",
),
PrefixPattern::new(
"stripe-live-secret",
"sk_live_",
20,
120,
Critical,
"Stripe live secret key",
),
PrefixPattern::new(
"stripe-test-secret",
"sk_test_",
20,
120,
High,
"Stripe test secret key",
),
PrefixPattern::new(
"stripe-restricted",
"rk_live_",
20,
120,
Critical,
"Stripe restricted key",
),
PrefixPattern::new(
"npm-token",
"npm_",
20,
120,
Critical,
"npm access token",
),
PrefixPattern::new(
"pypi-token",
"pypi-",
30,
160,
Critical,
"PyPI upload token",
),
PrefixPattern::new(
"sendgrid-key",
"SG.",
20,
160,
Critical,
"SendGrid API key",
),
PrefixPattern::new(
"datadog-key",
"ddapi_",
20,
96,
Critical,
"Datadog API key",
),
PrefixPattern::new(
"grafana-token",
"glc_",
20,
128,
Critical,
"Grafana service token",
),
PrefixPattern::new(
"huggingface-token",
"hf_",
20,
100,
Critical,
"Hugging Face token",
),
PrefixPattern::new(
"digitalocean-token",
"dop_v1_",
20,
100,
Critical,
"DigitalOcean token",
),
PrefixPattern::new(
"linear-key",
"lin_api_",
20,
100,
Critical,
"Linear API key",
),
PrefixPattern::new(
"sentry-token",
"sntrys_",
20,
160,
Critical,
"Sentry authentication token",
),
PrefixPattern::new(
"cloudflare-token",
"v1.0-",
24,
160,
High,
"Cloudflare token candidate",
),
PrefixPattern::new(
"discord-token",
"mfa.",
20,
120,
Critical,
"Discord multi-factor token",
),
PrefixPattern::new(
"shopify-token",
"shpat_",
20,
100,
Critical,
"Shopify private app token",
),
PrefixPattern::new(
"shopify-shared-secret",
"shpss_",
20,
100,
Critical,
"Shopify shared secret",
),
PrefixPattern::new(
"shopify-custom-app",
"shpca_",
20,
100,
Critical,
"Shopify custom app token",
),
PrefixPattern::new(
"vault-service-token",
"hvs.",
20,
180,
Critical,
"HashiCorp Vault service token",
),
PrefixPattern::new(
"vault-batch-token",
"hvb.",
20,
180,
Critical,
"HashiCorp Vault batch token",
),
]
}
///|
fn valid_secret_tail(c : Char) -> Bool {
is_hex(c) || is_base64ish(c) || c == '.' || c == '~'
}
///|
fn scan_prefix_pattern(
chars : Array[Char],
pattern : PrefixPattern,
include_preview : Bool,
) -> Array[Finding] {
let findings : Array[Finding] = []
let needle = pattern.prefix.to_array()
let mut from = 0
while from < chars.length() {
let position = index_of_from(chars, needle, from)
if position < 0 {
break
}
let tail_start = position + needle.length()
let mut end = tail_start
while end < chars.length() &&
end - tail_start < pattern.maximum_tail &&
valid_secret_tail(chars[end]) {
end = end + 1
}
if end - tail_start >= pattern.minimum_tail {
let prefix_bonus = if needle.length() > 10 { 10 } else { needle.length() }
findings.push(
Finding::new(
pattern.id,
Prefix,
pattern.severity,
position,
end,
90 + prefix_bonus,
safe_preview(chars, position, end, include_preview),
pattern.message,
),
)
}
from = position + needle.length()
}
findings
}
///|
fn scan_all_prefixes(
chars : Array[Char],
include_preview : Bool,
) -> Array[Finding] {
let findings : Array[Finding] = []
for pattern in builtin_prefix_patterns() {
for finding in scan_prefix_pattern(chars, pattern, include_preview) {
findings.push(finding)
}
}
findings
}