///|
fn token_char(c : UInt16) -> Bool {
c is ('a'..<='z' | 'A'..<='Z' | '0'..<='9' | '_' | '-')
}
///|
fn placeholder(value : String) -> Bool {
let lower = value.to_lower()
if lower.contains("example") ||
lower.contains("placeholder") ||
lower.contains("changeme") ||
lower.contains("your_") ||
lower.contains("your-") ||
lower.contains("replace_me") ||
lower.contains("redacted") ||
lower.contains("dummy") {
return true
}
let tail = if value.has_prefix("sk-proj-") {
value[8:]
} else if value.has_prefix("github_pat_") {
value[11:]
} else if value.has_prefix("AKIA") ||
value.has_prefix("ASIA") ||
value.has_prefix("ghp_") {
value[4:]
} else if value.has_prefix("sk-") {
value[3:]
} else {
value[:]
}
tail.length() > 0 && tail.iter().all(c => c == tail.get_char(0).unwrap())
}
///|
fn assignment_value(value : String) -> Bool {
if value.length() < 8 || placeholder(value) {
return false
}
let lower = value.to_lower()
if lower.has_prefix("process.") ||
lower.has_prefix("os.") ||
lower.has_prefix("env.") ||
lower.has_prefix("config.") ||
lower.has_prefix("std::") ||
lower.has_prefix("getenv") ||
lower.has_prefix("string") ||
lower.has_prefix("option[") ||
lower.has_prefix("${") ||
lower.has_prefix("{{") {
return false
}
// A bare identifier or type is not evidence of a hard-coded credential.
let mut letters = false
let mut other = false
for c in value {
if c is ('a'..<='z' | 'A'..<='Z') {
letters = true
}
if c
is ('0'..<='9'
| '+'
| '/'
| '='
| '!'
| '@'
| '#'
| '$'
| '%'
| '^'
| '&'
| '*'
| '-') {
other = true
}
if c is ('(' | ')' | '[' | ']' | '{' | '}' | '<' | '>') {
return false
}
}
letters && other
}
///|
// The engine can read the closing quote in `"token=" + value + "\n"`
// as the opening quote of a credential. Filter only a concatenated reference
// whose preceding quote demonstrably closes a string on the same line.
fn concatenated_reference(text : String, start : Int32, end : Int32) -> Bool {
if start == 0 || end >= text.length() {
return false
}
let quote_at = start - 1
let quote = text[quote_at]
if !(quote is ('\'' | '"')) || text[end] != quote {
return false
}
let value = text[start:end].trim()
if value.length() < 3 || !value.has_prefix("+") || !value.has_suffix("+") {
return false
}
let reference = value[1:value.length() - 1].trim()
for part in reference.split(".") {
if part.is_empty() || !(part[0] is ('a'..<='z' | 'A'..<='Z' | '_' | '$')) {
return false
}
for c in part {
if !(c is ('a'..<='z' | 'A'..<='Z' | '0'..<='9' | '_' | '$')) {
return false
}
}
}
if reference.is_empty() {
return false
}
let mut line_start = quote_at
while line_start > 0 && !(text[line_start - 1] is ('\r' | '\n')) {
line_start -= 1
}
let mut active : UInt16? = None
let mut escaped = false
for i in line_start..<(quote_at + 1) {
let c = text[i]
match active {
Some(q) =>
if escaped {
escaped = false
} else if c == '\\' {
escaped = true
} else if c == q {
if i == quote_at {
return true
}
active = None
}
None => if c is ('\'' | '"') { active = Some(c) }
}
}
false
}
///|
fn rule_for(text : String, hit : @moon_scrub.Finding) -> String? {
let value = text[hit.start:hit.end].to_owned()
match hit.kind {
AccessToken => {
if (hit.start > 0 && token_char(text[hit.start - 1])) ||
placeholder(value) {
return None
}
if value.has_prefix("github_pat_") {
if value.length() == 93 {
Some("github-token")
} else {
None
}
} else if value.has_prefix("ghp_") ||
value.has_prefix("gho_") ||
value.has_prefix("ghu_") ||
value.has_prefix("ghs_") ||
value.has_prefix("ghr_") {
if value.length() == 40 {
Some("github-token")
} else {
None
}
} else if value.has_prefix("AKIA") || value.has_prefix("ASIA") {
if value.length() == 20 &&
value.iter().all(c => c is ('A'..<='Z' | '0'..<='9')) {
Some("aws-access-key")
} else {
None
}
} else if value.has_prefix("sk-proj-") || value.has_prefix("sk-svcacct-") {
if value.length() >= 48 && value.length() <= 256 {
Some("openai-key")
} else {
None
}
} else if value.has_prefix("sk-") && !value.has_prefix("sk-ant-") {
if value.length() == 51 {
Some("openai-key")
} else {
None
}
} else {
Some("access-token")
}
}
CredentialAssignment =>
if assignment_value(value) &&
!concatenated_reference(text, hit.start, hit.end) {
Some("credential-assignment")
} else {
None
}
PrivateKey => Some("pem-private-key")
BearerToken => if placeholder(value) { None } else { Some("bearer-token") }
Jwt => Some("jwt")
UrlCredential =>
if placeholder(value) {
None
} else {
Some("url-credential")
}
_ => None
}
}