Improved Updater text. Cleaned up more rules and the examples included with them.

This commit is contained in:
Mick Grove 2025-06-26 14:29:36 -07:00
commit 37cdf1fb69
15 changed files with 180 additions and 66 deletions

View file

@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.
## [1.15.0]
- Ensuring temp files are cleaned up
- Applying visual style to the update check output
- Fixed bug in --self-update where it was looking for the incorrect binary name on GitHub releases
- Rule cleanup
## [1.14.0]
- Fixed several malformed rules

View file

@ -10,7 +10,7 @@ publish = false
[package]
name = "kingfisher"
version = "1.14.0"
version = "1.15.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View file

@ -1,7 +1,13 @@
rules:
- name: Age Recipient (X25519 public key)
id: kingfisher.age.1
pattern: '\b(age1[0-9a-z]{58})\b'
pattern: |
(?xi)
\b
(
age1[0-9a-z]{58}
)
\b
min_entropy: 3.3
confidence: medium
examples:
@ -13,7 +19,13 @@ rules:
- name: Age Identity (X22519 secret key)
id: kingfisher.age.2
pattern: '\b(AGE-SECRET-KEY-1[0-9A-Z]{58})\b'
pattern: |
(?xi)
\b
(
AGE-SECRET-KEY-1[0-9A-Z]{58}
)
\b
min_entropy: 3.3
confidence: medium
examples:

View file

@ -17,7 +17,7 @@ rules:
min_entropy: 3.3
confidence: medium
examples:
- fileio SECRETKEY = Z9Y8X7W6V5U4T3S2R1Q0.P9O8N7M6L5K4J3H2G1F
- fileio SECRETKEY = Z9Y8X7W6V5U4T3S2R1Q0.P9O8N7M6L5K4J3H2G1FV
- fileio.PRIVATE.TOKEN = F0E1D2C3B4A596877869.5E4D3C2B1A0Z9Y8X7W6V
- fileio_key = M8N6B4V2C0X9Z7L5K3J1.H2G4F6D8S0A9P7O5I3U1
validation:

View file

@ -88,7 +88,13 @@ rules:
- '"login"'
- name: GitHub App Token
id: kingfisher.github.3
pattern: '\b((?:ghu|ghs)_[A-Z0-9]{36})\b'
pattern: |
(?xi)
\b
(
(?:ghu|ghs)_[A-Z0-9]{36}
)
\b
examples:
- ' "token": "ghu_16C7e42F292c69C2E7C10c838347Ae178B4a",'
- |
@ -118,7 +124,13 @@ rules:
- '"login"'
- name: GitHub Refresh Token
id: kingfisher.github.4
pattern: '\b(ghr_[A-Z0-9]{76})\b'
pattern: |
(?xi)
\b
(
ghr_[A-Z0-9]{76}
)
\b
examples:
- ' "refresh_token": "ghr_1B4a2e77838347a7E420ce178F2E7c6912E169246c3CE1ccbF66C46812d16D5B1A9Dc86A1498",'
references:

View file

@ -36,7 +36,13 @@ rules:
- name: GitLab Runner Registration Token
id: kingfisher.gitlab.2
pattern: '\b(GR1348941[0-9A-Z_-]{20})(?:\b|$)'
pattern: |
(?xi)
\b
(
GR1348941[0-9A-Z_-]{20}
)
\b
examples:
- |
sudo gitlab-runner register \
@ -69,7 +75,13 @@ rules:
- name: GitLab Pipeline Trigger Token
id: kingfisher.gitlab.3
pattern: '\b(glptt-[0-9a-f]{40})\b'
pattern: |
(?xi)
\b
(
glptt-[0-9a-f]{40}
)
\b
examples:
- |
curl \

View file

@ -14,7 +14,7 @@ rules:
- name: Google OAuth Client Secret
id: kingfisher.google.2
pattern: |
(?x)
(?xi)
\b
(GOCSPX-[A-Z0-9_-]{28})
(?:[^A-Z0-9_-] | $)
@ -41,7 +41,7 @@ rules:
- name: Google OAuth Access Token
id: kingfisher.google.4
pattern: |
(?x)
(?xi)
\b
(ya29\.[0-9A-Z_-]{20,1024})
(?: [^0-9A-Z_-]|$)
@ -65,7 +65,7 @@ rules:
- name: Google OAuth Credentials
id: kingfisher.google.6
pattern: |
(?x)
(?xi)
\b
([0-9]+-[a-z0-9_]{32}\.apps\.googleusercontent\.com)
(?:

View file

@ -1,7 +1,7 @@
rules:
- name: Password Hash (md5crypt)
id: kingfisher.pwhash.1
pattern: '(\$1\$[./A-Z0-9]{8}\$[./A-Z0-9]{22})'
pattern: '(\$1\$[./A-Za-z0-9]{8}\$[./A-Za-z0-9]{22})'
references:
- https://en.wikipedia.org/wiki/Crypt_(C)#MD5-based_scheme
- https://unix.stackexchange.com/a/511017
@ -16,7 +16,7 @@ rules:
id: kingfisher.pwhash.2
# Format from Wikipedia:
# $2<a/b/x/y>$[cost]$[22 character salt][31 character hash]
pattern: '(\$2[abxy]\$\d+\$[./A-Z0-9]{53})'
pattern: '(\$2[abxy]\$\d+\$[./A-Za-z0-9]{53})'
references:
- https://en.wikipedia.org/wiki/Bcrypt
- https://hashcat.net/wiki/doku.php?id=example_hashes
@ -35,8 +35,8 @@ rules:
(
\$ 5
(?: \$ rounds=\d+ )?
\$ [./A-Z0-9]{8,16}
\$ [./A-Z0-9]{43}
\$ [./A-Za-z0-9]{8,16}
\$ [./A-Za-z0-9]{43}
)
references:
- https://en.wikipedia.org/wiki/Crypt_(C)#Key_derivation_functions_supported_by_crypt
@ -55,8 +55,8 @@ rules:
(
\$ 6
(?: \$ rounds=\d+ )?
\$ [./A-Z0-9]{8,16}
\$ [./A-Z0-9]{86}
\$ [./A-Za-z0-9]{8,16}
\$ [./A-Za-z0-9]{86}
)
references:
- https://en.wikipedia.org/wiki/Crypt_(C)#Key_derivation_functions_supported_by_crypt
@ -73,8 +73,8 @@ rules:
(?x)
(
\$ 8
\$ [./A-Z0-9]{8,16}
\$ [./A-Z0-9]{43}
\$ [./A-Za-z0-9]{8,16}
\$ [./A-Za-z0-9]{43}
)
references:
- https://en.wikipedia.org/wiki/Crypt_(C)#Key_derivation_functions_supported_by_crypt
@ -106,4 +106,4 @@ rules:
- '$krb5asrep$23$8cf8eb5287e28a4006c064892150c4fb$3e05ecc13548bec8e1eeb900dea5429cc6931bae9b8524490eb3a8801560871fe44355ed556202afbb39872e1bbb5c3c4f1b37dcd68fda89a23ebad917d4bbb0933edd94331598939e5d0c0c98c7e219a2e9dd6b877280d1bd7c51131413be577a167208bcc21e9fe7ae8f393278d740e72ca5c44c42d5cb0bf6bab0a36f1b88b7ddc4abbc6f152e652f6ba35c2955fb4132e11b7e566f3b422c3740f79847b77783d245a4e570b8a621b4ff6ff4815566446af70313ee78133707a76a4e4424783bd7c04920aa822a1a36b29f7e25cef186e6439fc46e42e23d6bd918969ef49b8388aef158e443b3a57dbde7ada631fbef7326f9046a9b'
- '$krb5asrep$23$c447eddaebf22ebf006a8fc6f986488c$eb3a17eb56287b474cecad5d4e0490d949977ba3f5015220bcd3080444d5601d67b76c5453b678e8527624e40c273bea4cfe4a7303e136b9bc3b9e63b6fb492ee4b4d2f830c5fa5a55466b57a678f708438f6712354a2deb851792b09270f4941966b82a2fd5ad8fa1fbd95a60b0f9bcd57774b3e55467a02ffcb3f1379104c24e468342f83df20b571e6f34f9a9842b43735d58d94514dcefa76719c0f5c7c3a3bfa770380924625aa0a3472d7c02d10dbb278fd946f7efcfe59a4d4cb7bdb9c5dbddc027611fe333d3ac940ec5b4ed43b55ab54b03cd2df0a9a2a7b5d235c226b259bd5ff8e0e49680351d4f0c4d13e258bc8d383cad6fc2711be0'
- '$krb5asrep$23$771adbc2397abddef676742924414f2b$2df6eb2d9c71820dc3fa2c098e071d920f0e412f5f12411632c5ee70e004da1be6f003b78661f8e4507e173552a52da751c45887c19bc1661ed334e0ccb4ef33975d4bd68b3d24746f281b4ca4fdf98fca0e50a8e845ad7d834e020c05b1495bc473b0295c6e9b94963cb912d3ff0f2f48c9075b0f52d9a31e5f4cc67c7af1d816b6ccfda0da5ccf35820a4d7d79073fa404726407ac840910357ef210fcf19ed81660106dfc3f4d9166a89d59d274f31619ddd9a1e2712c879a4e9c471965098842b44fae7ca6dd389d5d98b7fd7aca566ca399d072025e81cf0ef5075447687f80100307145fade7a8'
- '$krb5asrep$23$user@domain.com:3e156ada591263b8aab0965f5aebd837$007497cb51b6c8116d6407a782ea0e1c5402b17db7afa6b05a6d30ed164a9933c754d720e279c6c573679bd27128fe77e5fea1f72334c1193c8ff0b370fadc6368bf2d49bbfdba4c5dccab95e8c8ebfdc75f438a0797dbfb2f8a1a5f4c423f9bfc1fea483342a11bd56a216f4d5158ccc4b224b52894fadfba3957dfe4b6b8f5f9f9fe422811a314768673e0c924340b8ccb84775ce9defaa3baa0910b676ad0036d13032b0dd94e3b13903cc738a7b6d00b0b3c210d1f972a6c7cae9bd3c959acf7565be528fc179118f28c679f6deeee1456f0781eb8154e18e49cb27b64bf74cd7112a0ebae2102ac'
- '$krb5asrep$23$user@domain.com:3e156ada591263b8aab0965f5aebd837$007497cb51b6c8116d6407a782ea0e1c5402b17db7afa6b05a6d30ed164a9933c754d720e279c6c573679bd27128fe77e5fea1f72334c1193c8ff0b370fadc6368bf2d49bbfdba4c5dccab95e8c8ebfdc75f438a0797dbfb2f8a1a5f4c423f9bfc1fea483342a11bd56a216f4d5158ccc4b224b52894fadfba3957dfe4b6b8f5f9f9fe422811a314768673e0c924340b8ccb84775ce9defaa3baa0910b676ad0036d13032b0dd94e3b13903cc738a7b6d00b0b3c210d1f972a6c7cae9bd3c959acf7565be528fc179118f28c679f6deeee1456f0781eb8154e18e49cb27b64bf74cd7112a0ebae2102ac'

View file

@ -9,13 +9,13 @@ rules:
(?:.|[\n\r]){0,16}?
\b
(
A[A-Z0-9_-]{79,99}
A[A-Z0-9_-]{78,99}
)
\b
min_entropy: 3.5
visible: false
examples:
- paypal_client_id=AZJ6y8Dpr1TYbqAIdhkPzyhjXoY6m8GplL7C3zZ3lPrkTIdhkPzyhjXo_Dx3
- paypal_client_id=AZJ6y8Dpr1TYbqAIdhkPzyhjXoY6mIdhkPzyhjXoY6m8GplL7C3zZ3lPrkTIdhkPzyhjXo_Dx3IdhkPzyhjXoY6m
- name: PayPal OAuth Secret
id: kingfisher.paypal.2
@ -27,12 +27,12 @@ rules:
(?:.|[\n\r]){0,32}?
\b
(
[A-Z0-9_.-]{80,120}
[A-Z0-9_.-]{78,120}
)
\b
min_entropy: 3.5
examples:
- paypal_secret=EDe5J6y8Dpr1TYbqAIdhkPzyhjXoY6m8GplL7C3zZ3lPrkT1XlV6hYPSeJL5b1T1
- paypal_secret=EP0uwUsACKVPcbDRaXFYerX2ij6nbsha71cSdynuQWoSt1pIy4qtIs7gJQRmHwKXu5Icv3g1YQZzAywf
validation:
type: Http

View file

@ -5,7 +5,7 @@ rules:
(?x)
-----BEGIN\ .{0,20}\ ?PRIVATE\ KEY\ ?.{0,20}-----
\s*
( (?: [A-Z0-9+/=\s"',] | \\r | \\n ) {50,} )
( (?: [a-zA-Z0-9+/=\s"',] | \\r | \\n ) {50,} )
\s*
-----END\ .{0,20}\ ?PRIVATE\ KEY\ ?.{0,20}-----
min_entropy: 4.5
@ -57,9 +57,9 @@ rules:
| LS0tLS1CRUdJTiBEU0EgUFJJVkFURSBLRVktLS0t (?# prefix of base64 encoding of `-----BEGIN DSA PRIVATE KEY-----` )
| LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0t (?# prefix of base64 encoding of `-----BEGIN EC PRIVATE KEY-----` )
)
[A-Z0-9+/=]{50,}
[a-zA-Z0-9+/=]{50,}
)
(?: [^A-Z0-9+/=] | $ )
(?: [^a-zA-Z0-9+/=] | $ )
min_entropy: 4.5
confidence: high
prevalidated: false

View file

@ -2,7 +2,7 @@ rules:
- name: Postman API Key
id: kingfisher.postman.1
pattern: |
(?x)
(?xi)
\b
(
PMAK-[A-Z0-9]{24}-[A-Z0-9]{34}

View file

@ -2,7 +2,7 @@ rules:
- name: Contains encrypted RSA private key
id: kingfisher.privkey.1
pattern: |
(?x)
(?xi)
(?msi)
(
-----BEGIN\s
@ -45,7 +45,7 @@ rules:
- name: Contains Private Key
id: kingfisher.privkey.2
pattern: |
(?x)
(?xi)
(?ims)
(
-----BEGIN\s

View file

@ -5,8 +5,9 @@ rules:
pattern: |
(?xi)
(?:stripe|strp)
(?:.|[\n\r]){0,16}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
(?:.|[\n\r]){0,16}?
(
pk_live_
(?:[0-9A-Z]{6}){4,30}
@ -21,11 +22,11 @@ rules:
id: kingfisher.stripe.2
pattern: |
(?ix)
(?:^|[\s"'=])
(?xi)
(?:stripe|strp)
(?:.|[\n\r]){0,16}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
(?:.|[\n\r]){0,16}?
(
(?:
sk|rk

View file

@ -10,13 +10,13 @@ rules:
(?:.|[\n\r]){0,16}?
\b
(
[a-z0-9-_]{22}
[A-Z-_0-9]{22}
)
\b
min_entropy: 3.0
confidence: medium
examples:
- "travis_token splendid21RANDOMCONTENT_token"
- "travis_token splendid21RANDOMCONTEN"
validation:
type: Http
content:

View file

@ -1,3 +1,20 @@
// This module checks GitHub for a newer Kingfisher release and (optionally)
// selfupdates. Our release assets use short, userfriendly names such as
// `kingfisher-linux-arm64.tgz`, `kingfisher-darwin-x64.tgz`, etc. Those names
// do **not** match the full Rust target triple that the `self_update` crate
// expects (e.g. `aarch64-unknown-linux-musl`). We therefore map the compile
// time target to the corresponding asset suffix via `builder.target()`.
//
// Version handling logic covers three scenarios:
// 1. Running version == latest release → "up to date".
// 2. Running version > latest release → print a notice that the binary is
// **newer** than anything on GitHub (e.g. a dev build).
// 3. Latest release > running version → offer to selfupdate.
//
// All informational messages are printed with the
// `style_finding_active_heading` style so that they stand out alongside normal
// scan output.
use std::{
fs,
io::{ErrorKind, IsTerminal},
@ -5,9 +22,13 @@ use std::{
};
use self_update::{backends::github::Update, cargo_crate_version, errors::Error as UpdError};
use semver::Version;
use tracing::{error, info, warn};
use crate::{cli::global::GlobalArgs, reporter::styles::Styles};
use crate::{
cli::global::GlobalArgs,
reporter::styles::Styles,
};
/// Return `true` when the canonical executable path lives inside a Homebrew Cellar.
/// Works for Intel macOS (/usr/local/Cellar), AppleSilicon macOS (/opt/homebrew/Cellar)
@ -17,31 +38,38 @@ fn installed_via_homebrew() -> bool {
std::env::current_exe().ok().and_then(|p| fs::canonicalize(p).ok())
}
canonical_exe().map(|p| p.components().any(|c| c.as_os_str() == "Cellar")).unwrap_or(false)
canonical_exe()
.map(|p| p.components().any(|c| c.as_os_str() == "Cellar"))
.unwrap_or(false)
}
/// Check GitHub for a newer Kingfisher release.
/// Check GitHub for a newer Kingfisher release and optionally selfupdate.
///
/// * `base_url` lets tests point at a mock server.
/// * Selfupdate is performed unless the user disabled it **or** the binary is a Homebrew install.
/// * Selfupdate is skipped when the user disabled it **or** the binary is a
/// Homebrew install.
pub fn check_for_update(global_args: &GlobalArgs, base_url: Option<&str>) -> Option<String> {
if global_args.no_update_check {
return None;
}
let is_brew = installed_via_homebrew();
if is_brew {
info!("Homebrew install detected will notify about updates but not selfupdate");
}
info!("Checking for updates…");
// -------------------------------------------------------------
// Prepare colour/style helper so every message looks consistent
// -------------------------------------------------------------
// Decide once whether we want coloured output.
let use_color = std::io::stderr().is_terminal() && !global_args.quiet;
let styles = Styles::new(use_color);
let is_brew = installed_via_homebrew();
if is_brew {
info!(
"{}",
styles
.style_finding_active_heading
.apply_to("Homebrew install detected will notify about updates but not selfupdate")
);
}
info!(
"{}","Checking for updates…");
let mut builder = Update::configure();
builder
.repo_owner("mongodb")
@ -50,54 +78,101 @@ pub fn check_for_update(global_args: &GlobalArgs, base_url: Option<&str>) -> Opt
.show_download_progress(false)
.current_version(cargo_crate_version!());
// Allow tests to point at a mock HTTP server.
if let Some(url) = base_url {
builder.with_url(url);
}
// ──────────────────────────────────────────────────────
// Map the current Rust target triple to our simplified asset names.
// ──────────────────────────────────────────────────────
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
builder.target("linux-arm64");
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
builder.target("linux-x64");
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
builder.target("darwin-arm64");
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
builder.target("darwin-x64");
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
builder.target("windows-x64");
// Build the updater.
let Ok(updater) = builder.build() else {
warn!("Failed to configure update checker");
return None;
};
// Query GitHub.
let Ok(release) = updater.get_latest_release() else {
warn!("Failed to check for updates");
return None;
};
// ----------------------------
// Already on the latest version
// ----------------------------
if release.version == cargo_crate_version!() {
let plain = format!("Kingfisher {} is up to date", release.version);
let styled = styles.style_finding_active_heading.apply_to(&plain);
info!("{}", styled);
let running_v = cargo_crate_version!();
// ───────────── Case 1: running == latest ─────────────
if release.version == running_v {
let plain = format!("Kingfisher {running_v} is up to date");
info!("{}", styles.style_finding_active_heading.apply_to(&plain));
return Some(plain);
}
// ----------------------------
// A newer version is available
// ----------------------------
let plain = format!("New Kingfisher release {} available", release.version);
let styled = styles.style_finding_active_heading.apply_to(&plain);
info!("{}", styled);
// Try semantic version comparison. If parsing fails, fall back to the
// selfupdate codepath (which will treat the strings lexicographically).
if let (Ok(curr), Ok(latest)) = (
Version::parse(running_v),
Version::parse(&release.version),
) {
// ───────── Case 2: running > latest (dev build) ─────────
if curr > latest {
let plain = format!(
"Running Kingfisher {curr} which is newer than latest released {latest}"
);
info!("{}", styles.style_finding_active_heading.apply_to(&plain));
return Some(plain);
}
// else fall through to Case 3 (latest > running)
}
// Decide whether to perform the update in place.
// ───────────── Case 3: latest > running ─────────────
let plain = format!("New Kingfisher release {} available", release.version);
info!("{}", styles.style_finding_active_heading.apply_to(&plain));
// Attempt selfupdate when allowed and feasible.
if global_args.self_update && !is_brew {
match updater.update() {
Ok(status) => info!("Updated to version {}", status.version()),
Ok(status) => info!(
"{}",
styles
.style_finding_active_heading
.apply_to(&format!("Updated to version {}", status.version()))
),
Err(e) => match e {
UpdError::Io(ref io_err) if io_err.kind() == ErrorKind::PermissionDenied => {
warn!(
"Cannot replace the current binary permission denied.\n\
If you installed via a package manager, run its upgrade command.\n\
Otherwise reinstall to a userwritable directory or rerun with sudo."
"{}",
styles.style_finding_active_heading.apply_to(
"Cannot replace the current binary permission denied.\n\
If you installed via a package manager, run its upgrade command.\n\
Otherwise reinstall to a userwritable directory or rerun with sudo."
)
);
}
_ => error!("Failed to update: {e}"),
},
}
} else if is_brew {
info!("Run `brew upgrade kingfisher` to install the new version.");
info!(
"{}",
styles
.style_finding_active_heading
.apply_to("Run `brew upgrade kingfisher` to install the new version.")
);
}
Some(plain)