diff --git a/CHANGELOG.md b/CHANGELOG.md index f72ec6d..ff2c109 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 7ced54e..78c7574 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 diff --git a/data/rules/age.yml b/data/rules/age.yml index b85e4a1..f296a26 100644 --- a/data/rules/age.yml +++ b/data/rules/age.yml @@ -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: diff --git a/data/rules/fileio.yml b/data/rules/fileio.yml index 6bdd8dd..7bf255e 100644 --- a/data/rules/fileio.yml +++ b/data/rules/fileio.yml @@ -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: diff --git a/data/rules/github.yml b/data/rules/github.yml index 3bcb58f..90c9c3b 100644 --- a/data/rules/github.yml +++ b/data/rules/github.yml @@ -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: diff --git a/data/rules/gitlab.yml b/data/rules/gitlab.yml index e616a93..d7c3ca2 100644 --- a/data/rules/gitlab.yml +++ b/data/rules/gitlab.yml @@ -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 \ diff --git a/data/rules/google.yml b/data/rules/google.yml index f617845..812a903 100644 --- a/data/rules/google.yml +++ b/data/rules/google.yml @@ -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) (?: diff --git a/data/rules/hashes.yml b/data/rules/hashes.yml index f952d36..60b0f4a 100644 --- a/data/rules/hashes.yml +++ b/data/rules/hashes.yml @@ -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$[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' \ No newline at end of file diff --git a/data/rules/paypal.yml b/data/rules/paypal.yml index 4011eee..ef65d2d 100644 --- a/data/rules/paypal.yml +++ b/data/rules/paypal.yml @@ -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 diff --git a/data/rules/pem.yml b/data/rules/pem.yml index 23e0144..fb51a06 100644 --- a/data/rules/pem.yml +++ b/data/rules/pem.yml @@ -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 diff --git a/data/rules/postman.yml b/data/rules/postman.yml index 707b4e2..ae7ae7c 100644 --- a/data/rules/postman.yml +++ b/data/rules/postman.yml @@ -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} diff --git a/data/rules/privkey.yml b/data/rules/privkey.yml index 9815384..6a5fe32 100644 --- a/data/rules/privkey.yml +++ b/data/rules/privkey.yml @@ -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 diff --git a/data/rules/stripe.yml b/data/rules/stripe.yml index f127777..faaa1ab 100644 --- a/data/rules/stripe.yml +++ b/data/rules/stripe.yml @@ -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 diff --git a/data/rules/travisci.yml b/data/rules/travisci.yml index e23817e..3461fbb 100644 --- a/data/rules/travisci.yml +++ b/data/rules/travisci.yml @@ -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: diff --git a/src/update.rs b/src/update.rs index 8bfc11a..165120c 100644 --- a/src/update.rs +++ b/src/update.rs @@ -1,3 +1,20 @@ +// This module checks GitHub for a newer Kingfisher release and (optionally) +// self‑updates. Our release assets use short, user‑friendly 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 self‑update. +// +// 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), Apple‑Silicon 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 self‑update. /// /// * `base_url` lets tests point at a mock server. -/// * Self‑update is performed unless the user disabled it **or** the binary is a Homebrew install. +/// * Self‑update 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 { 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 self‑update"); - } - - 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 self‑update") + ); + } + + 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 + // self‑update code‑path (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 self‑update 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 user‑writable directory or re‑run 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 user‑writable directory or re‑run 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)