preparing for v1.12

This commit is contained in:
Mick Grove 2025-06-24 17:17:16 -07:00
commit fc4aee9e41
249 changed files with 121395 additions and 0 deletions

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
testdata/* linguist-vendored

51
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,51 @@
---
name: "🐛 Bug report"
about: "Something isnt working as expected"
title: "[BUG] <short description>"
labels: [bug]
assignees: ''
---
### 📋 Checklist
- [ ] Im running **the latest `main` or a recent release**.
- [ ] Ive searched existing issuesand **no open issue covers this bug**.
- [ ] If this is a build problem, I attached the **full error log**.
---
### What version?
`kingfisher --version` output
### 🐞 What happened?
_A clear and concise description of what went wrong._
### ✅ What did you expect to happen?
_What *should* have happened?_
### 🔢 Reproduction steps
1. …
2. …
3. …
### 💻 Environment
| Item | Value |
| --------------- | ----- |
| OS / Distro | `<!-- e.g. Windows 11 22H2 / Ubuntu 24.04 → arm64 -->` |
| Rust toolchain | `rustc --version` → |
| kingfisher ver. | `<!-- kingfisher --version OR git rev-parse HEAD OR release tag -->` |
### 📎 Log / stack trace
<details>
<summary>Click to expand</summary>
```text
# Paste or drag-and-drop here
```
</details>

View file

@ -0,0 +1,34 @@
---
#### `.github/ISSUE_TEMPLATE/feature_request.md`
```md
---
name: "🚀 Feature request"
about: "Suggest an idea or improvement"
title: "[FEAT] <short description>"
labels: [enhancement]
assignees: ''
---
### 🌟 Is your feature request related to a problem?
_A short statement of *why* this feature matters. Example: “Cross-compiling on
macOS requires Homebrews outdated musl gcc; integrating Zig would remove that
dependency.”_
### 📝 Describe the solution youd like
_A clear, concise description of what you want to happen, including interface
changes, flags, or user-visible behavior._
### 🔄 Describe alternatives youve considered
- **Option A:**
- **Option B:**
### 📚 Additional context
_Anything else—diagrams, links, prior art, screenshots—that helps explain the
request._

42
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,42 @@
name: CI Pull Request
on:
pull_request:
branches:
- main
# This workflow runs on pull requests to the main branch
# It builds the project for 2 platforms, Linux arm64 and macOS arm64,
# and runs tests for each platform. All platforms tested on merge to main
jobs:
linux-arm64:
name: Linux arm64
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.85.0
profile: minimal
override: true
- uses: swatinem/rust-cache@v2
- name: Build (Makefile linux-arm64)
run: make ubuntu-arm64
- name: Run tests
run: make tests
macos-arm64:
name: macOS arm64
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.85.0
profile: minimal
override: true
- uses: swatinem/rust-cache@v2
- name: Build (Makefile darwin-arm64)
run: make darwin-arm64
- name: Run tests
run: make tests

199
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,199 @@
name: build-and-release
on:
push:
branches:
- main
jobs:
# ──────────────── Linux (via Makefile) ────────────────
linux-x64:
name: Linux x64
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.85.0
profile: minimal
override: true
- uses: swatinem/rust-cache@v2
- name: Build (Makefile linux-x64)
run: make linux-x64
- name: Move artifact to dist
shell: bash
run: |
mkdir -p dist
cp target/release/kingfisher-linux-x64.tgz dist/
- uses: actions/upload-artifact@v4
with:
name: kingfisher-linux-x64
path: dist/kingfisher-*linux-x64*.*
linux-arm64:
name: Linux arm64
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.85.0
profile: minimal
override: true
- uses: swatinem/rust-cache@v2
- name: Build (Makefile linux-arm64)
run: make linux-arm64
- name: Move artifact to dist
shell: bash
run: |
mkdir -p dist
cp target/release/kingfisher-linux-arm64.tgz dist/
- uses: actions/upload-artifact@v4
with:
name: kingfisher-linux-arm64
path: dist/kingfisher-*linux-arm64*.*
macos-x64:
name: macOS x64
runs-on: macos-13
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.85.0
profile: minimal
override: true
- uses: swatinem/rust-cache@v2
- name: Build Darwin x64
run: make darwin-x64
- name: Move artifacts to dist
shell: bash
run: |
mkdir -p dist
cp target/release/kingfisher-darwin-x64.tgz dist/
- uses: actions/upload-artifact@v4
with:
name: kingfisher-darwin-x64.tgz
path: dist/kingfisher-darwin-x64.tgz
macos-arm64:
name: macOS arm64
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.85.0
profile: minimal
override: true
- uses: swatinem/rust-cache@v2
- name: Build Darwin arm64
run: make darwin-arm64
- name: Move artifacts to dist
shell: bash
run: |
mkdir -p dist
cp target/release/kingfisher-darwin-arm64.tgz dist/
- uses: actions/upload-artifact@v4
with:
name: kingfisher-darwin-arm64.tgz
path: dist/kingfisher-darwin-arm64.tgz
# ──────────────── Windows ────────────────
windows:
name: Windows x64
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.85.0
profile: minimal
override: true
- name: Cache vcpkg artifacts
uses: actions/cache@v3
with:
# Adjust these paths if your vcpkg root is somewhere else
path: |
C:\vcpkg\buildtrees
C:\vcpkg\packages
C:\vcpkg\installed
key: ${{ runner.os }}-vcpkg-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.cmake') }}
restore-keys: |
${{ runner.os }}-vcpkg-
- uses: Swatinem/rust-cache@v2
- name: Build
run: .\buildwin.bat -force
shell: cmd
- name: Run tests
shell: pwsh
run: |
if (-not (Get-Command cargo-nextest -ErrorAction SilentlyContinue)) {
cargo install --locked cargo-nextest
}
Write-Host "▶ cargo nextest run --release --workspace --all-targets"
cargo nextest run --release --workspace --all-targets
- name: Move artifact to dist
shell: bash
run: |
mkdir -p dist
cp target/release/kingfisher-windows-x64.zip dist/
- uses: actions/upload-artifact@v4
with:
name: kingfisher-windows-x64
path: dist/kingfisher-*windows-x64*.*
# ──────────────── Draft public release ────────────────
release:
name: Public GitHub Release
needs: [linux-x64, linux-arm64, windows, macos-x64, macos-arm64] # wait for all builds to finish
runs-on: ubuntu-latest
permissions:
contents: write # allow release upload
steps:
- uses: actions/checkout@v4
- name: Read version from Cargo.toml
id: version
run: |
VERSION=$(grep -m1 '^version\s*=' Cargo.toml | cut -d '"' -f2)
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- uses: actions/download-artifact@v4
with:
path: target/release/kingfisher-*
merge-multiple: true
- name: Create release & upload assets
uses: ncipollo/release-action@v1
with:
tag: v${{ steps.version.outputs.version }}
name: "Kingfisher v${{ steps.version.outputs.version }}"
bodyFile: CHANGELOG.md # use existing changelog
generateReleaseNotes: false # turn off auto-notes
artifacts: target/release/**

205
.gitignore vendored Normal file
View file

@ -0,0 +1,205 @@
# Created by https://www.toptal.com/developers/gitignore/api/macos,visualstudiocode,rust
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,visualstudiocode,rust
*.log
*.sarif
*.profile.json
*.json
*.jsonl
*.bson
.prettierrc
custom.py
logs/*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Rust ###
# Generated by Cargo
# will have compiled files and executables
debug/
target/
bin/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# End of https://www.toptal.com/developers/gitignore/api/macos,visualstudiocode,rust
# Created by https://www.toptal.com/developers/gitignore/api/intellij,rust-analyzer
# Edit at https://www.toptal.com/developers/gitignore?templates=intellij,rust-analyzer
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/
# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
# Azure Toolkit for IntelliJ plugin
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
.idea/**/azureSettings.xml
### rust-analyzer ###
# Can be generated by other build systems other than cargo (ex: bazelbuild/rust_rules)
rust-project.json
.dockerignore
# End of https://www.toptal.com/developers/gitignore/api/intellij,rust-analyzer

21
CHANGELOG.md Normal file
View file

@ -0,0 +1,21 @@
# Changelog
All notable changes to this project will be documented in this file.
## [1.12.0]
- Added automatic update checks using GitHub releases
- New `--self-update` flag installs updates when available
- New `--no-update-check` flag disables update checks
- Updated rules
## [1.11.0] 2025-06-21
- Increased default value for number of scanning jobs to improve validation speed
- Fixed issue where some API responses (e.g. GitHub's `/user` endpoint) include required fields like `"name"` beyond the first 512 bytes. Truncating earlier causes `WordMatch` checks to fail even for active credentials. Increased the limit to keep a larger slice of the body while still bounding memory usage.
## [1.10.0] 2025-06-20
- Updated de-dupe fingerprint to include the content of the match
- Updated Makefile
- Adding GitHub Actions
## [1.9.0] 2025-06-16
- Initial public release of Kingfisher

204
Cargo.toml Normal file
View file

@ -0,0 +1,204 @@
[workspace.package]
edition = "2021"
rust-version = "1.83"
license = "Apache-2.0"
authors = ["Mick Grove <mick.grove@mongodb.com>"]
homepage = "https://github.com/mongodb/kingfisher"
repository = "https://github.com/mongodb/kingfisher"
publish = false
[package]
name = "kingfisher"
version = "1.12.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
authors.workspace = true
homepage.workspace = true
repository.workspace = true
publish.workspace = true
[dependencies]
clap = { version = "4.3", features = [
"cargo",
"derive",
"env",
"unicode",
"wrap_help",
] }
anyhow = "1.0"
bstr = { version = "1.0", features = ["serde"] }
fixedbitset = "0.5"
gix = { version = "0.72", features = ["max-performance", "serde", "blocking-network-client"] }
ignore = "0.4"
petgraph = "0.6"
roaring = "0.10"
schemars = "0.8"
serde = { version = "1.0", features = ["derive", "rc"] }
smallvec = { version = "1", features = [
"const_generics",
"const_new",
"union",
] }
tracing = "0.1.41"
indicatif = { version = "0.17", features = ["improved_unicode"] }
rayon = "1.10"
sha1 = "0.10.6"
hex = "0.4.3"
vectorscan-rs = "0.0.5"
regex = "1.10.6"
serde_json = "1.0.128"
lazy_static = "1.5.0"
url = "2.5.2"
include_dir = { version = "0.7", features = ["glob"] }
strum = { version = "0.26", features = ["derive"] }
sysinfo = "0.31.2"
reqwest = { version = "0.12", default-features = false, features = [
"json",
"gzip",
"brotli",
"deflate",
"stream",
"rustls-tls",
"blocking",
"multipart",
"rustls-tls",
] }
chrono = "0.4.38"
thiserror = "1.0.63"
tokio = { version = "1.39.2", features = ["full"] }
base64 = "0.22.1"
crossbeam-channel = "0.5.13"
indenter = "0.3.3"
serde-sarif = "0.4"
console = "0.15.8"
time = "0.3.36"
tempfile = "3.12.0"
num_cpus = "1.16.0"
once_cell = "1.19.0"
http = "1.1.0"
liquid = "0.26.4"
liquid-core = "0.26.4"
flate2 = "1.0.33"
brotli = "6.0.0"
thousands = "0.2.0"
base32 = "0.5.1"
crossbeam-skiplist = "0.1.3"
tokio-postgres = { version = "0.7", default-features = false, features = ["runtime"] }
mongodb = { version = "3.2", default-features = false, features = ["rustls-tls", "aws-auth", "compat-3-0-0", "dns-resolver"] }
bson = "2.13.0"
ring = "0.17.8"
pem = "3.0.4"
aws-config = "1.5.10"
aws-credential-types = "1.2.1"
aws-sdk-sts = "1.21.0"
aws-types = "1.3.3"
byteorder = "1.5.0"
parking_lot = "0.12.3"
octorust = "0.9.0"
reqwest-middleware = "0.4.1"
tracing-subscriber = {version = "0.3.19", features = ["env-filter"] }
tracing-core = "0.1.33"
tree-sitter = "0.24.4"
tree-sitter-bash = "0.23.3"
tree-sitter-c = "0.23.2"
tree-sitter-c-sharp = "0.23.1"
tree-sitter-cpp = "0.23.4"
tree-sitter-css = "0.23.1"
tree-sitter-go = "0.23.4"
tree-sitter-html = "0.23.2"
tree-sitter-java = "0.23.4"
tree-sitter-javascript = "0.23.1"
tree-sitter-php = "0.23.11"
tree-sitter-python = "0.23.4"
tree-sitter-ruby = "0.23.1"
tree-sitter-rust = "0.23.2"
tree-sitter-toml-ng = "0.7.0"
tree-sitter-typescript = "0.23.2"
tree-sitter-yaml = "0.6.1"
streaming-iterator = "0.1.9"
tree-sitter-regex = "0.24.3"
content_inspector = "0.2.4"
rustc-hash = "2.1.0"
term_size = "0.3.2"
bzip2 = "0.5.0"
zip = "2.2.2"
tar = "0.4.43"
xz2 = "0.1.7"
asar = "0.3.0"
blake3 = "1.5.5"
memmap2 = "0.9.5"
futures = "0.3.31"
dashmap = "6.1.0"
xxhash-rust = { version = "0.8.15", features = ["xxh3", "const_xxh3"] }
serde_yaml = "0.9.34"
hmac = "0.12.1"
sha2 = "0.10.8"
strum_macros = "0.27.1"
humantime = "2.2.0"
path-dedot = "3.1.1"
quick-xml = {version = "0.37.5", features = ["serde","serialize"] }
rustls = "0.23.26"
tokio-postgres-rustls = "0.13.0"
rustls-native-certs = "0.8.1"
predicates = "3.1.3"
assert_cmd = "2.0.17"
proptest = "1.6.0"
color-backtrace = "0.7.0"
gitlab = "0.1711.0"
mimalloc = {version = "0.1.46", features = ["override"]}
thread_local = "1.1.8"
crc32fast = "1.4.2"
bloomfilter = "3.0.1"
uuid = "1.17.0"
urlencoding = "2.1.3"
rand = "0.9.1"
percent-encoding = "2.3.1"
trust-dns-resolver = { version = "0.23.2", default-features = false, features = ["tokio-runtime"] }
atty = "0.2.14"
self_update = { version = "0.42.0", default-features = false, features = ["rustls"] }
[dependencies.tikv-jemallocator]
version = "0.6"
optional = true
[features]
default = ["use-mimalloc"]
use-mimalloc = ["mimalloc/override"]
use-jemalloc = ["tikv-jemallocator"]
system-alloc = [] # forces System allocator
[dev-dependencies]
pretty_assertions = "1.3"
temp-env = "0.3.6"
wiremock = "0.6.2"
git2 = "0.20.2"
rand_chacha = "0.9.0"
[profile.release]
debug = false
strip = "debuginfo"
opt-level = 3 # Maximum optimization for performance
lto = true # Enable Link Time Optimization
codegen-units = 1 # Optimize for size but slower compilation
# panic = "abort" # Remove unwind tables for panics
rpath = false # Don't embed path dependencies
incremental = false
[profile.dev]
opt-level = 0
debug = true
incremental = true
codegen-units = 256
[patch.crates-io]
vectorscan-rs = { path = "vendor/vectorscan-rs/vectorscan-rs" }
vectorscan-rs-sys = { path = "vendor/vectorscan-rs/vectorscan-rs-sys" }
[profile.profiling]
inherits = "release"
debug = true

201
LICENSE Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

429
Makefile Normal file
View file

@ -0,0 +1,429 @@
SHELL := /usr/bin/env bash
.SHELLFLAGS := -eu -o pipefail -c
# Detect project name from Cargo.toml
PROJECT_NAME := $(shell grep '^name' Cargo.toml | cut -d '"' -f 2)
# Determine OS and whether to use gtar on darwin
OS := $(shell uname)
ifneq ($(OS),darwin)
USE_GTAR := 0
TAR_CMD := tar
TAR_OPTS := $(shell if tar --help 2>/dev/null | grep -q -- '--no-xattrs'; then echo '--no-xattrs -czf'; else echo '-czf'; fi)
else
ifneq ($(shell command -v gtar 2>/dev/null),)
USE_GTAR := 1
TAR_CMD := gtar
TAR_OPTS := --no-xattrs -czf
else
USE_GTAR := 0
TAR_CMD := tar
TAR_OPTS := -czf
endif
endif
ifeq ($(OS),darwin)
export HOMEBREW_NO_INSTALL_CLEANUP=1
export HOMEBREW_NO_ENV_HINTS=1
endif
# detect host architecture and map to our target suffixes
UNAME_M := $(shell uname -m)
ifeq ($(UNAME_M),x86_64)
ARCH := x64
else ifeq ($(UNAME_M),amd64)
ARCH := x64
else ifeq ($(UNAME_M),arm64)
ARCH := arm64
else ifeq ($(UNAME_M),aarch64)
ARCH := arm64
else
$(error Unsupported architecture: $(UNAME_M))
endif
ARCHIVE_CMD = $(TAR_CMD) $(TAR_OPTS)
SUDO_CMD := $(shell command -v sudo 2>/dev/null)
.PHONY: default help create-dockerignore ubuntu-x64 ubuntu-arm64 linux-x64 linux-arm64 darwin-arm64 darwin-x64 windows-x64 windows \
linux darwin all list-archives check-docker check-rust clean tests
default: help
help:
@echo "Available targets:"
@echo " create-dockerignore"
@echo " linux-x64"
@echo " linux-arm64"
@echo " linux"
@echo " darwin-arm64"
@echo " darwin-x64"
@echo " darwin"
@echo " windows-x64"
@echo " windows"
@echo " all"
@echo " list-archives"
@echo " tests"
create-dockerignore:
@echo "target/" > .dockerignore
@echo ".git/" >> .dockerignore
@echo ".vscode/" >> .dockerignore
@echo "bin/" >> .dockerignore
.PHONY: setup-zig
setup-zig:
@command -v zig >/dev/null 2>&1 || { \
echo "⬇️ Installing Zig 0.14.0 …"; \
if $(SUDO_CMD) apt-get update -qq && \
$(SUDO_CMD) apt-get install -y --no-install-recommends zig 2>/dev/null ; then \
echo "✓ Zig installed via apt"; \
else \
echo "⚠️ Package 'zig' not in apt repos falling back to manual install"; \
arch=$$(uname -m); \
case "$$arch" in \
x86_64) pkg=zig-linux-x86_64-0.14.0 ;; \
aarch64|arm64) pkg=zig-linux-aarch64-0.14.0 ;; \
*) echo "Unsupported architecture: $$arch"; exit 1 ;; \
esac; \
curl -L -o /tmp/zig.tar.xz https://ziglang.org/download/0.14.0/$${pkg}.tar.xz; \
tar -C /tmp -xf /tmp/zig.tar.xz; \
$(SUDO_CMD) mv /tmp/$${pkg} /opt/zig; \
$(SUDO_CMD) ln -sf /opt/zig/zig /usr/local/bin/zig; \
echo "✓ Zig installed to /usr/local/bin/zig"; \
fi; \
}
@if [ -f "$$HOME/.cargo/env" ]; then . $$HOME/.cargo/env; fi && \
(cargo zigbuild --help >/dev/null 2>&1 || { \
echo "⬇️ Installing cargo-zigbuild …"; \
cargo install --locked cargo-zigbuild; \
})
# ============= BAREMETAL BUILDS (Check Rust first, install if missing) =============
#
# -------------------------------------------------------------------------------------------------
# ubuntu-x64 — native static build for x86_64-unknown-linux-musl via Zig. Tested on Ubuntu 24.04.
# -------------------------------------------------------------------------------------------------
ubuntu-x64: setup-zig # ensures Zig & cargo-zigbuild exist
@echo "Checking Rust toolchain…"
@$(MAKE) check-rust || { \
echo "🦀 Installing Rust 1.85.0 …"; \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y; \
. $$HOME/.cargo/env; \
rustup toolchain install 1.85.0; \
rustup default 1.85.0; \
}
@echo "📦 Installing build dependencies (musl, cmake, etc.)…"
@$(SUDO_CMD) DEBIAN_FRONTEND=noninteractive apt-get update -qq
@$(SUDO_CMD) DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential musl-tools musl-dev cmake pkg-config \
zlib1g-dev libbz2-dev liblzma-dev libboost-all-dev \
patch perl ragel
@echo "🔨 Building $(PROJECT_NAME) for x86_64-unknown-linux-musl …"
@. $$HOME/.cargo/env && \
rustup target add x86_64-unknown-linux-musl && \
export PKG_CONFIG_ALLOW_CROSS=1 && \
cargo zigbuild --release --target x86_64-unknown-linux-musl
@echo "🗜️ Packaging archive …"
@cd target/x86_64-unknown-linux-musl/release && \
find ./$(PROJECT_NAME) -type f -executable -exec sha256sum {} \; > CHECKSUM.txt
@mkdir -p target/release
@cp target/x86_64-unknown-linux-musl/release/$(PROJECT_NAME) target/release/
@cp target/x86_64-unknown-linux-musl/release/CHECKSUM.txt target/release/CHECKSUM-linux-x64.txt
@cd target/release && \
rm -rf $(PROJECT_NAME)-linux-x64.tgz && \
$(ARCHIVE_CMD) $(PROJECT_NAME)-linux-x64.tgz $(PROJECT_NAME) CHECKSUM-linux-x64.txt && \
sha256sum $(PROJECT_NAME)-linux-x64.tgz >> CHECKSUM-linux-x64.txt
$(MAKE) list-archives
# -------------------------------------------------------------------------------------------------
# ubuntu-arm64 — native cross-compile to aarch64-unknown-linux-musl via Zig. Tested on Ubuntu 24.04.
# -------------------------------------------------------------------------------------------------
ubuntu-arm64: setup-zig # ensures Zig & cargo-zigbuild exist
@echo "Checking Rust toolchain…"
@$(MAKE) check-rust || { \
echo "🦀 Installing Rust 1.85.0 …"; \
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y; \
. $$HOME/.cargo/env; \
rustup toolchain install 1.85.0; \
rustup default 1.85.0; \
}
@echo "📦 Installing build dependencies (musl, cmake, etc.)…"
@$(SUDO_CMD) DEBIAN_FRONTEND=noninteractive apt-get update -qq
@$(SUDO_CMD) DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential musl-tools musl-dev cmake pkg-config \
zlib1g-dev libbz2-dev liblzma-dev libboost-all-dev \
patch perl ragel
@echo "🔨 Building $(PROJECT_NAME) for aarch64-unknown-linux-musl …"
@. $$HOME/.cargo/env && \
rustup target add aarch64-unknown-linux-musl && \
export PKG_CONFIG_ALLOW_CROSS=1 && \
cargo zigbuild --release --target aarch64-unknown-linux-musl
@echo "🗜️ Packaging archive …"
@cd target/aarch64-unknown-linux-musl/release && \
find ./$(PROJECT_NAME) -type f -executable -exec sha256sum {} \; > CHECKSUM.txt
@mkdir -p target/release
@cp target/aarch64-unknown-linux-musl/release/$(PROJECT_NAME) target/release/
@cp target/aarch64-unknown-linux-musl/release/CHECKSUM.txt target/release/CHECKSUM-linux-arm64.txt
@cd target/release && \
rm -rf $(PROJECT_NAME)-linux-arm64.tgz && \
$(ARCHIVE_CMD) $(PROJECT_NAME)-linux-arm64.tgz $(PROJECT_NAME) CHECKSUM-linux-arm64.txt && \
sha256sum $(PROJECT_NAME)-linux-arm64.tgz >> CHECKSUM-linux-arm64.txt
$(MAKE) list-archives
darwin-arm64:
@echo "Checking Rust for darwin-arm64..."
@$(MAKE) check-rust || ( \
echo "Rust not found or out-of-date. Installing via Homebrew..." && \
brew install rust \
)
@brew install boost cmake gcc libpcap pkg-config ragel sqlite coreutils gnu-tar || true
@rustup target add aarch64-apple-darwin
cargo build --release --target aarch64-apple-darwin --features system-alloc
@cd target/aarch64-apple-darwin/release && \
find ./$(PROJECT_NAME) -type f -not -name "*.d" -not -name "*.rlib" -exec shasum -a 256 {} \; > CHECKSUM.txt
@mkdir -p target/release
@cp target/aarch64-apple-darwin/release/$(PROJECT_NAME) target/release/
@cp target/aarch64-apple-darwin/release/CHECKSUM.txt target/release/CHECKSUM-darwin-arm64.txt
@cd target/release && \
rm -rf $(PROJECT_NAME)-darwin-arm64.tgz && \
$(ARCHIVE_CMD) $(PROJECT_NAME)-darwin-arm64.tgz $(PROJECT_NAME) CHECKSUM-darwin-arm64.txt && \
if [ -f $(PROJECT_NAME)-darwin-arm64.tgz ]; then \
shasum -a 256 $(PROJECT_NAME)-darwin-arm64.tgz >> CHECKSUM-darwin-arm64.txt; \
fi
$(MAKE) list-archives
darwin-x64:
@echo "Checking Rust for darwin-x64..."
@$(MAKE) check-rust || ( \
echo "Rust not found or out-of-date. Installing via Homebrew..." && \
brew install rust \
)
@brew install boost cmake gcc libpcap pkg-config ragel sqlite coreutils gnu-tar || true
@rustup target add x86_64-apple-darwin
source $$HOME/.cargo/env && cargo build --release --target x86_64-apple-darwin --features system-alloc
@cd target/x86_64-apple-darwin/release && \
find ./$(PROJECT_NAME) -type f -not -name "*.d" -not -name "*.rlib" -exec shasum -a 256 {} \; > CHECKSUM.txt
@mkdir -p target/release
@cp target/x86_64-apple-darwin/release/$(PROJECT_NAME) target/release/
@cp target/x86_64-apple-darwin/release/CHECKSUM.txt target/release/CHECKSUM-darwin-x64.txt
@cd target/release && \
rm -rf $(PROJECT_NAME)-darwin-x64.tgz && \
$(ARCHIVE_CMD) $(PROJECT_NAME)-darwin-x64.tgz $(PROJECT_NAME) CHECKSUM-darwin-x64.txt && \
if [ -f $(PROJECT_NAME)-darwin-x64.tgz ]; then \
shasum -a 256 $(PROJECT_NAME)-darwin-x64.tgz >> CHECKSUM-darwin-x64.txt; \
fi
$(MAKE) list-archives
windows-x64:
ifeq ($(OS),Windows_NT)
@echo "Detected Windows host."
else
$(error "This target can only run on Windows.")
endif
buildwin.bat -force
#
# ============= DOCKER-BASED BUILDS =============
# #
linux-x64: check-docker create-dockerignore
@mkdir -p target/release
docker run --platform linux/amd64 --rm \
-v "$$(pwd):/src" -w /src rust:1.85-alpine sh -eu -c '\
apk add --no-cache \
musl-dev \
gcc g++ make cmake pkgconfig \
zlib-dev zlib-static \
bzip2-dev bzip2-static \
xz-dev xz-static \
boost-dev linux-headers \
patch perl ragel && \
git openssl-dev curl && \
\
cargo test --workspace --all-targets --release ; \
\
rustup target add x86_64-unknown-linux-musl && \
\
export PKG_CONFIG_ALLOW_CROSS=1 ; \
export RUSTFLAGS="-C target-feature=+crt-static" ; \
\
cargo build --release --target x86_64-unknown-linux-musl && \
cd target/x86_64-unknown-linux-musl/release && \
find "./$(PROJECT_NAME)" -type f -executable \
-not -name "*.d" -not -name "*.rlib" \
-exec sha256sum {} \; > CHECKSUM.txt \
'
@cd target/release && \
rm -rf $(PROJECT_NAME)-linux-x64.tgz && \
cp ../x86_64-unknown-linux-musl/release/$(PROJECT_NAME) . && \
cp ../x86_64-unknown-linux-musl/release/CHECKSUM.txt CHECKSUM-linux-x64.txt && \
tar --no-xattrs -czf $(PROJECT_NAME)-linux-x64.tgz \
$(PROJECT_NAME) CHECKSUM-linux-x64.txt && \
rm $(PROJECT_NAME) && \
sha256sum $(PROJECT_NAME)-linux-x64.tgz >> CHECKSUM-linux-x64.txt
$(MAKE) list-archives
linux-arm64: check-docker create-dockerignore
@mkdir -p target/release
docker run --platform linux/arm64 --rm \
-v "$$(pwd):/src" -w /src rust:1.85-alpine sh -eu -c '\
apk add --no-cache \
musl-dev \
gcc g++ make cmake pkgconfig \
zlib-dev zlib-static \
bzip2-dev bzip2-static \
xz-dev xz-static \
boost-dev linux-headers \
patch perl ragel && \
git openssl-dev curl && \
\
rustup target add aarch64-unknown-linux-musl && \
\
cargo test --workspace --all-targets --release ; \
\
export PKG_CONFIG_ALLOW_CROSS=1 ; \
export RUSTFLAGS="-C target-feature=+crt-static" ; \
\
cargo build --release --target aarch64-unknown-linux-musl && \
\
cd target/aarch64-unknown-linux-musl/release && \
find "./$(PROJECT_NAME)" -type f -executable \
-not -name "*.d" -not -name "*.rlib" \
-exec sha256sum {} \; > CHECKSUM.txt \
'
@cd target/release && \
rm -rf $(PROJECT_NAME)-linux-arm64.tgz && \
cp ../aarch64-unknown-linux-musl/release/$(PROJECT_NAME) . && \
cp ../aarch64-unknown-linux-musl/release/CHECKSUM.txt CHECKSUM-linux-arm64.txt && \
tar --no-xattrs -czf $(PROJECT_NAME)-linux-arm64.tgz \
$(PROJECT_NAME) CHECKSUM-linux-arm64.txt && \
rm $(PROJECT_NAME) && \
sha256sum $(PROJECT_NAME)-linux-arm64.tgz >> CHECKSUM-linux-arm64.txt
$(MAKE) list-archives
# ============= AGGREGATE TARGETS =============
#
windows: windows-x64
@echo "# Windows builds:" > target/release/CHECKSUMS-windows.txt
@echo -e "\n# x86_64-windows build:" >> target/release/CHECKSUMS-windows.txt
@cat target/release/CHECKSUM-windows-x64.txt >> target/release/CHECKSUMS-windows.txt
@echo -e "\nBuilt Windows archives:"
@ls -lh target/release/*.tgz
@echo -e "\nWindows Checksums:"
@cat target/release/CHECKSUMS-windows.txt
linux:
$(MAKE) linux-$(ARCH)
linux-all: linux-x64 linux-arm64
@echo "# Linux builds:" > target/release/CHECKSUMS-linux.txt
@echo -e "\n# x86_64-linux build:" >> target/release/CHECKSUMS-linux.txt
@cat target/release/CHECKSUM-linux-x64.txt >> target/release/CHECKSUMS-linux.txt
@echo -e "\n# arm64-linux build:" >> target/release/CHECKSUMS-linux.txt
@cat target/release/CHECKSUM-linux-arm64.txt >> target/release/CHECKSUMS-linux.txt
@echo -e "\nBuilt Linux archives:"
@ls -lh target/release/*.tgz
@echo -e "\nLinux Checksums:"
@cat target/release/CHECKSUMS-linux.txt
darwin:
$(MAKE) darwin-$(ARCH)
darwin-all: darwin-arm64 darwin-x64
@echo "# darwin builds:" > target/release/CHECKSUMS-darwin.txt
@echo -e "\n# arm64-darwin build:" >> target/release/CHECKSUMS-darwin.txt
@cat target/release/CHECKSUM-darwin-arm64.txt >> target/release/CHECKSUMS-darwin.txt
@echo -e "\n# x86_64-darwin build:" >> target/release/CHECKSUMS-darwin.txt
@cat target/release/CHECKSUM-darwin-x64.txt >> target/release/CHECKSUMS-darwin.txt
@echo -e "\nBuilt darwin archives:"
@ls -lh target/release/*.tgz
@echo -e "\ndarwin Checksums:"
@cat target/release/CHECKSUMS-darwin.txt
all: linux darwin
@echo "# All builds:" > target/release/CHECKSUMS.txt
@echo -e "\n# Linux builds:" >> target/release/CHECKSUMS.txt
@cat target/release/CHECKSUMS-linux.txt >> target/release/CHECKSUMS.txt
@echo -e "\n# darwin builds:" >> target/release/CHECKSUMS.txt
@cat target/release/CHECKSUMS-darwin.txt >> target/release/CHECKSUMS.txt
@echo -e "\nBuilt archives:"
@ls -lh target/release/*.tgz
@echo -e "\nCombined Checksums:"
@cat target/release/CHECKSUMS.txt
list-archives:
@echo -e "\n=== Built archives ==="
@found=0; \
for f in target/release/*.tgz; do \
if [ -e "$$f" ]; then \
found=1; \
realpath "$$f"; \
fi; \
done; \
if [ $$found -eq 0 ]; then \
echo "No archives found."; \
fi
check-docker:
@command -v docker >/dev/null 2>&1 || { \
echo "Docker is not installed. Please install Docker."; \
exit 1; \
}
check-rust:
@version=$$(rustc --version 2>/dev/null | awk '{print $$2}'); \
if [ -z "$$version" ]; then \
echo "Rust not found."; \
exit 1; \
fi; \
required=1.85.0; \
if [ $$(printf '%s\n' "$$required" "$$version" | sort -V | head -n1) != "$$required" ]; then \
echo "Rust version $$version is older than required $$required."; \
exit 1; \
else \
echo "Rust version $$version is acceptable."; \
fi
tests:
@echo "🔍 checking for cargo-nextest …"
@if command -v cargo-nextest >/dev/null 2>&1; then \
echo "✅ cargo-nextest already present"; \
else \
echo "📦 installing cargo-nextest …"; \
cargo install --locked cargo-nextest || true; \
fi
@echo "▶ running tests …"; \
if command -v cargo-nextest >/dev/null 2>&1; then \
cargo nextest run --workspace --all-targets; \
else \
echo "⚠️ cargo-nextest unavailable falling back to cargo test"; \
cargo test --workspace --all-targets; \
fi
clean:
@echo "Cleaning build artifacts..."
cargo clean
rm -f .dockerignore
notices:
@echo "Generating third-party notices..."
@cargo install cargo-bundle-licenses
@cargo bundle-licenses --format yaml --output THIRD_PARTY_NOTICES
evergreen-patch:
@evergreen patch --project kingfisher --variants all --tasks build

308
README.md Normal file
View file

@ -0,0 +1,308 @@
# Kingfisher
<p align="center">
<img src="docs/kingfisher_logo.png" alt="Kingfisher Logo" width="126" height="173" style="vertical-align: right;" />
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
Kingfisher is a blazingly fast secretscanning and validation tool built in Rust. It combines Intels hardwareaccelerated Hyperscan regex engine with languageaware parsing via TreeSitter, and **ships with hundreds of builtin rules** to detect, validate, and triage secrets before they ever reach production
</p>
**MongoDB Blog**: [Introducing Kingfisher: Real-Time Secret Detection and Validation](https://www.mongodb.com/blog/post/product-release-announcements/introducing-kingfisher-real-time-secret-detection-validation)
## Key Features
- **Performance**: Multithreaded, Hyperscanpowered scanning for massive codebases
- **LanguageAware Accuracy**: AST parsing in 20+ languages via TreeSitter reduces contextless regex matches. see [docs/PARSING.md](/docs/PARSING.md)
- **Built-In Validation**: Hundreds of built-in detection rules, many with live-credential validators that call the relevant service APIs (AWS, Azure, GCP, Stripe, etc.) to confirm a secret is active. You can extend or override the library by adding YAML-defined rules on the command line—see [docs/RULES.md](/docs/RULES.md) for details
- **Git History Scanning**: Scan local repos, remote GitHub/GitLab orgs/users, or arbitrary GitHub/GitLab repos
## Getting Started
### Installation
On macOS, you can simply
```bash
brew install kingfisher
```
Pre-built binaries are also available on the [Releases](https://github.com/mongodb/kingfisher/releases) section of this page.
Or you may compile for your platform via `make`:
```bash
# NOTE: Requires Docker
make linux
```
```bash
# macOS
make darwin
```
```bash
# Windows x64 --- requires building from a Windows host with Visual Studio installed
./buildwin.bat -force
```
```bash
# Build all targets
make linux-all # builds both x64 and arm64
make darwin-all # builds both x64 and arm64
make all # builds for every OS and architecture supported
```
# Write Custom Rules!
Kingfisher ships with hundreds of rules with HTTP and servicespecific validation checks (AWS, Azure, GCP, etc.) to confirm if a detected string is a live credential.
However, you may want to add your own custom rules, or modify a detection to better suit your needs / environment.
First, review [docs/RULES.md](/docs/RULES.md) to learn how to create custom Kingfisher rules.
Once you've done that, you can provide your custom rules (defined in a YAML file) and provide it to Kingfisher at runtime --- no recompiling required!
# Usage
## Basic Examples
> **Note**  `kingfisher scan` detects whether the input is a Git repository or a plain directory—no extra flags required.
### Scan with secret validation
```bash
kingfisher scan /path/to/code
## NOTE: This path can refer to:
# 1. a local git repo
# 2. a directory with many git repos
# 3. or just a folder with files and subdirectories
## To explicitly prevent scanning git commit history add:
# `--git-history=none`
```
### Scan a directory containing multiple Git repositories
```bash
kingfisher scan /projects/monorepodir
```
### Scan a Git repository without validation
```bash
kingfisher scan ~/src/myrepo --no-validate
```
### Display only secrets confirmed active by thirdparty APIs
```bash
kingfisher scan ./service --only-valid
```
### Output JSON and capture to a file
```bash
kingfisher scan . --format json | tee kingfisher.json
```
### Output SARIF directly to disk
```bash
kingfisher scan . --format sarif --output findings.sarif
```
### Pipe any text directly into Kingfisher by passing `-`
```bash
cat /path/to/file.py | kingfisher scan -
```
### Scan using a rule *family* with one flag
*(prefix matching: `--rule kingfisher.aws` loads `kingfisher.aws.*`)*
```bash
# Only apply AWS-related rules (kingfisher.aws.1 + kingfisher.aws.2)
kingfisher scan /path/to/repo --rule kingfisher.aws
```
---
## Scanning GitHub
### Scan GitHub organisation (requires `KF_GITHUB_TOKEN`)
```bash
kingfisher scan --github-organization my-org
```
### Scan remote GitHub repository
```bash
kingfisher scan --git-url https://github.com/org/repo.git
# Optionally provide a GitHub Token
KF_GITHUB_TOKEN="ghp_…" kingfisher scan --git-url https://github.com/org/private_repo.git
```
---
## Scanning GitLab
### Scan GitLab group (requires `KF_GITLAB_TOKEN`)
```bash
kingfisher scan --gitlab-group my-group
```
### Scan GitLab user
```bash
kingfisher scan --gitlab-user johndoe
```
### Scan remote GitLab repository by URL
```bash
kingfisher scan --git-url https://gitlab.com/group/project.git
```
### List GitLab repositories
```bash
kingfisher gitlab repos list --group my-group
```
---
## Environment Variables for Tokens
| Variable | Purpose |
|---------------------|---------------------------------------|
| `KF_GITHUB_TOKEN` | GitHub Personal Access Token |
| `KF_GITLAB_TOKEN` | GitLab Personal Access Token |
Set them temporarily per command:
```bash
KF_GITLAB_TOKEN="glpat-…" kingfisher scan --gitlab-group my-group
```
Or export for the session:
```bash
export KF_GITLAB_TOKEN="glpat-…"
```
*If no token is provided Kingfisher still works for public repositories.*
---
## Exit Codes
| Code | Meaning |
|------|-------------------------------------|
| 0 | No findings |
| 200 | Findings discovered |
| 205 | Validated findings discovered |
---
### Update Checks
Kingfisher checks for newer releases on GitHub each time it starts and exits, printing whether a new version is available. Use `--self-update` to automatically download and replace the binary when an update is found. Add `--no-update-check` to disable these checks entirely.
---
### List Builtin Rules
```bash
kingfisher rules list
```
### To scan using **only** your own `my_rules.yaml` you could run:
```bash
kingfisher scan \
--load-builtins=false \
--rules-path path/to/my_rules.yaml \
./src/
```
### To add your rules alongside the builtins:
```bash
kingfisher scan \
--rules-path ./custom-rules/ \
--rules-path my_rules.yml \
~/path/to/project-dir/
```
## Other Examples
```bash
# Check custom rules - this ensures all regular expressions compile, and can match the rule's `examples` in the YML file
kingfisher rules check --rules-path ./my_rules.yml
# List GitHub repos
kingfisher github repos list --user my-user
kingfisher github repos list --organization my-org
```
## Notable Scan Options
- `--no-dedup`: Report every occurrence of a finding (disable the default de-duplicate behavior)
- `--confidence <LEVEL>`: (low|medium|high)
- `--min-entropy <VAL>`: Override default threshold
- `--no-binary`: Skip binary files
- `--no-extract-archives`: Do not scan inside archives
- `--extraction-depth <N>`: Specifies how deep nested archives should be extracted and scanned (default: 2)
- `--redact`: Replaces discovered secrets with a one-way hash for secure output
## Finding Fingerprint
The document below details the four-field formula (rule SHA-1, origin label, start & end offsets) hashed with XXH3-64 to create Kingfishers 64-bit finding fingerprint, and explains how this ID powers safe deduplication; plus how `--no-dedup` can be used shows every raw match.
See ([docs/FINGERPRINT.md](docs/FINGERPRINT.md))
## CLI Options
```bash
kingfisher scan --help
```
## Business Value
By integrating Kingfisher into your development lifecycle, you can:
- **Prevent Costly Breaches**
Early detection of embedded credentials avoids expensive incident response, legal fees, and reputation damage
- **Automate Compliance**
Enforce secretscanning policies across GitOps, CI/CD, and pull requests to help satisfy SOC 2, PCIDSS, GDPR, and other standards
- **Reduce Noise, Focus on Real Threats**
Validation logic filters out false positives and highlights only active, valid secrets (`--only-valid`)
- **Accelerate Dev Workflows**
Run in parallel across dozens of languages, integrate with GitHub Actions or any pipeline, and shift security left to minimize delays
## The Risk of Leaked Secrets
Embedding credentials in code repositories is a pervasive, everpresent risk that leads directly to data breaches:
1. **Uber (2016)**
- *Incident*: Attackers stole GitHub credentials, retrieved an AWS key from a developers private repo, and accessed data on 57 million riders and 600 000 drivers.
- *Sources*: [BBC News](https://www.bbc.com/news/technology-42075306), [Ars Technica](https://arstechnica.com/tech-policy/2017/11/report-uber-paid-hackers-100000-to-keep-2016-data-breach-quiet/)
2. **AWS**
- *Incident*: An AWS engineer accidentally published log files and CloudFormation templates containing AWS key pairs (including “rootkey.csv”) to a public GitHub repo.
- *Sources*: [The Register](https://www.theregister.com/2020/01/23/aws_engineer_credentials_github/), [UpGuard](https://www.upguard.com/breaches/identity-and-access-misstep-how-an-amazon-engineer-exposed-credentials-and-more)
3. **Infosys**
- *Incident*: Infosys published an internal PyPI package embedding a FullAdminAccess AWS key for a Johns Hopkins data bucket; the key remained active for over a year.
- *Sources*: [The Stack](https://www.thestack.technology/infosys-leak-aws-key-exposed-on-pypi/), [Tom Forbes Blog](https://tomforb.es/blog/infosys-leak/)
4. **Microsoft**
- *Incident*: Microsofts AI research GitHub repo included an overly permissive Azure SAS token, exposing 38 TB of private data (workstation backups, 30,000+ Teams messages).
- *Sources*: [Wiz Blog](https://www.wiz.io/blog/38-terabytes-of-private-data-accidentally-exposed-by-microsoft-ai-researchers), [TechCrunch](https://techcrunch.com/2023/09/18/microsoft-ai-researchers-accidentally-exposed-terabytes-of-internal-sensitive-data/)
5. **GitHub**
- *Incident*: GitHub discovered its RSA SSH host private key was briefly exposed in a public repository and rotated it out of caution.
- *Sources*: [GitHub Blog](https://github.blog/news-insights/company-news/we-updated-our-rsa-ssh-host-key/)
Left unchecked, leaked secrets can lead to unauthorized access, pivoting within your environment, regulatory fines, and branddamaging incident response costs.
# Benchmark Results
See ([docs/COMPARISON.md](docs/COMPARISON.md))
# Roadmap
- More rules
- Auto-updater
- Packages for Linux (deb, rpm)
- Please file a [feature request](https://github.com/mongodb/kingfisher/issues) if you have specific features you'd like added
# License
[Apache2 License](LICENSE)

73305
THIRD_PARTY_NOTICES Normal file

File diff suppressed because one or more lines are too long

140
buildwin.bat Normal file
View file

@ -0,0 +1,140 @@
@echo off
REM This script builds a Windows x64 release binary and creates a tarball with checksum.
REM It requires vcpkg to be installed at root of C: drive (https://github.com/microsoft/vcpkg).
REM This script will install Rust (using chocolatey) if it is not already installed.
REM
REM Call with -force to clone and bootstrap vcpkg if it is not found
REM
setlocal
REM Set your Cargo project name manually here if desired:
set "PROJECT_NAME=kingfisher"
REM Optional check for OS:
if NOT "%OS%"=="Windows_NT" (
echo This script must be run on Windows.
exit /b 1
)
if "%VCINSTALLDIR%"=="" (
echo VCINSTALLDIR not set - attempting auto-detection…
for %%P in (
"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC"
"C:\Program Files\Microsoft Visual Studio\2022\Professional\VC"
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC"
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC"
) do (
if exist "%%~P\Auxiliary\Build\vcvars64.bat" (
set "VCINSTALLDIR=%%~P"
echo Found Visual C++ Build Tools at: %%~P
goto :vc_found
)
)
echo ERROR: Could not find a suitable Visual Studio installation.
echo Install “Desktop development with C++” or set VCINSTALLDIR.
exit /b 1
)
:vc_found
REM Strip trailing backslash if present
if "%VCINSTALLDIR:~-1%"=="\" set "VCINSTALLDIR=%VCINSTALLDIR:~0,-1%"
echo Initialising MSVC environment…
call "%VCINSTALLDIR%\Auxiliary\Build\vcvars64.bat" || (
echo ERROR: Failed to initialise MSVC toolchain.
exit /b 1
)
REM Locate vcpkg.exe
where vcpkg.exe >nul 2>nul
if %ERRORLEVEL% NEQ 0 (
if exist "%HOMEDRIVE%\vcpkg\vcpkg.exe" (
set "VCPKG_EXE=%HOMEDRIVE%\vcpkg\vcpkg.exe"
echo Found vcpkg at: %VCPKG_EXE%
) else (
if "%~1"=="-force" (
echo Cloning and bootstrapping vcpkg...
if exist "%HOMEDRIVE%\vcpkg" (
rmdir /s /q "%HOMEDRIVE%\vcpkg"
)
git clone https://github.com/microsoft/vcpkg.git "%HOMEDRIVE%\vcpkg"
pushd "%HOMEDRIVE%\vcpkg"
dir
call .\bootstrap-vcpkg.bat
set "VCPKG_EXE=%CD%\vcpkg.exe"
popd
echo Installed vcpkg at: %VCPKG_EXE%
) else (
echo ERROR: vcpkg not found. Please install it or re-run script with -force.
exit /b 1
)
)
) else (
for /f "tokens=*" %%i in ('where vcpkg.exe') do (
set "VCPKG_EXE=%%i"
goto :found_vcpkg
)
:found_vcpkg
echo Found vcpkg at: %VCPKG_EXE%
)
REM Check if LOCALAPPDATA starts with a drive letter, if not set it to APPDATA
if /I not "%LOCALAPPDATA:~1,1%"==":" (
echo LOCALAPPDATA does not start with a drive letter. Setting it to APPDATA.
set "LOCALAPPDATA=%APPDATA%"
)
echo Installing hyperscan via vcpkg...
set
"%HOMEDRIVE%\vcpkg\vcpkg.exe" install hyperscan:x64-windows
set "LIBHS_NO_PKG_CONFIG=1"
REM Point vectorscan-rs-sys at the Hyperscan install from vcpkg
set "HYPERSCAN_ROOT=%HOMEDRIVE%\vcpkg\installed\x64-windows"
set "LIB=%HYPERSCAN_ROOT%\lib;%LIB%"
set "INCLUDE=%HYPERSCAN_ROOT%\include;%INCLUDE%"
REM Check for Rust, install if missing
where rustc.exe >nul 2>nul
if %ERRORLEVEL% NEQ 0 (
echo Installing Rust...
choco install rust-ms -y
choco install cmake -y --installargs "ADD_CMAKE_TO_PATH=System"
call refreshenv
) else (
echo Rust is already installed.
)
echo Building for Windows x64...
cargo build --release --target x86_64-pc-windows-msvc || (
echo Cargo build failed.
exit /b 1
)
echo Generating CHECKSUM.txt...
powershell -Command ^
"Get-FileHash .\target\x86_64-pc-windows-msvc\release\%PROJECT_NAME%.exe -Algorithm SHA256 | Out-File .\target\x86_64-pc-windows-msvc\release\CHECKSUM.txt"
if not exist "target\release" mkdir "target\release"
copy /Y "target\x86_64-pc-windows-msvc\release\%PROJECT_NAME%.exe" "target\release\" >nul
copy /Y "target\x86_64-pc-windows-msvc\release\CHECKSUM.txt" "target\release\CHECKSUM-windows-x64.txt" >nul
cd target\release
echo Creating archive: %PROJECT_NAME%-windows-x64.zip
if exist "%PROJECT_NAME%-windows-x64.zip" del /f /q "%PROJECT_NAME%-windows-x64.zip"
powershell -Command "Compress-Archive -Path '%PROJECT_NAME%.exe','CHECKSUM-windows-x64.txt' -DestinationPath '%PROJECT_NAME%-windows-x64.zip' -Force"
if exist "%PROJECT_NAME%-windows-x64.zip" (
REM -- append the ZIPs SHA-256 to the existing checksum file ----
certutil -hashfile "%PROJECT_NAME%-windows-x64.zip" SHA256 >> "CHECKSUM-windows-x64.txt"
echo Created: %PROJECT_NAME%-windows-x64.zip
) else (
echo ERROR: Archive not created.
)
echo Archives in target\release:
dir /b *.zip 2>nul || echo None found.
endlocal
exit /b 0

7
data/default/ignore.conf Normal file
View file

@ -0,0 +1,7 @@
# This file lists gitignore-style patterns: https://git-scm.com/docs/gitignore
#
# These patterns control which paths Kingfisher will scan.
**/objects/pack/pack-*.pack
**/objects/pack/pack-*.idx
**/packed-refs

View file

@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Name of the application (used for pid lookup, log and plot filenames)
APP_NAME="kingfisher"
# Remove old log (if it exists) so we start fresh
LOG_FILE="${APP_NAME}_mem.log"
PLOT_FILE="${APP_NAME}_rss.png"
[ -f "$LOG_FILE" ] && rm "$LOG_FILE"
[ -f "$PLOT_FILE" ] && rm "$PLOT_FILE"
# Find the main PID
main_pid=$(pidof "$APP_NAME")
if [[ -z "$main_pid" ]]; then
echo "Error: no running process named '$APP_NAME' found."
exit 1
fi
# Determine page size in KiB
page_kb=$(( $(getconf PAGESIZE) / 1024 ))
# Monitor loop: sum VmRSS across parent + direct children
while [[ -d /proc/$main_pid ]]; do
ts=$(date +%s)
pids="$main_pid $(pgrep -P $main_pid)"
total_kb=0
for pid in $pids; do
if rss_kb=$(awk '/VmRSS/ {print $2}' /proc/$pid/status 2>/dev/null); then
total_kb=$(( total_kb + rss_kb ))
fi
done
# Convert KiB → MiB (2 decimal places) and log
total_mb=$(awk "BEGIN {printf \"%.2f\", $total_kb/1024}")
echo "$ts $total_mb" >> "$LOG_FILE"
sleep 0.5
done
# Once monitoring ends, generate the plot
gnuplot -persist <<EOF
set terminal pngcairo size 1280,720
set output "$PLOT_FILE"
set xdata time
set timefmt "%s"
set format x "%H:%M:%S"
set xlabel "Time"
set ylabel "RSS (MiB)"
set title "${APP_NAME^} Memory Usage"
set grid
plot "$LOG_FILE" using 1:2 with lines linewidth 2 title "RSS"
EOF
echo "Memory log written to $LOG_FILE"
echo "Plot generated as $PLOT_FILE"

2
data/default/rule_cleanup/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.venv
.venv/*

View file

@ -0,0 +1,108 @@
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.9"
# dependencies = ["PyYAML>=6.0", "idna>=3.7"]
# ///
import os
import re
import socket
from pathlib import Path
from urllib.parse import urlparse
import idna
try:
import yaml
except ModuleNotFoundError as exc:
raise SystemExit(
"PyYAML isnt installed.\n"
"Run the script with `uv run …` or add PyYAML to the dependency list."
) from exc
RULES_DIR = Path(os.path.expanduser("../../data/rules"))
URL_KEY_RE = re.compile(r"url$", re.IGNORECASE) # keys literally named “url”
TEMPLATE_RE = re.compile(r"\{\{.*?\}\}") # strip Liquid placeholders
DOMAIN_RE = re.compile(r"^(?:[a-z][a-z0-9+\-.]*://)?([^/]+)", re.I)
def find_yaml_files(root: Path):
yield from root.rglob("*.yml")
yield from root.rglob("*.yaml")
def extract_domains(obj):
"""Recursively yield every domain appearing in any 'url' key."""
if isinstance(obj, dict):
for k, v in obj.items():
if URL_KEY_RE.fullmatch(str(k)) and isinstance(v, str):
cleaned = TEMPLATE_RE.sub("", v).strip()
parsed = urlparse(cleaned).netloc
if not parsed:
m = DOMAIN_RE.match(cleaned)
if not m: # value wasnt a URL/host ignore
continue
parsed = m.group(1)
domain = (
parsed.split("@")[-1] # drop any creds
.split(":")[0] # drop port
.lstrip(".")
.rstrip(".")
.lower()
)
if domain and "{{" not in domain: # ignore Liquid tokens
yield domain
else:
yield from extract_domains(v)
elif isinstance(obj, list):
for item in obj:
yield from extract_domains(item)
def domain_active(domain: str) -> bool:
"""Return True iff *domain* resolves via DNS."""
if not domain:
return False
try:
ascii_domain = idna.encode(domain).decode() # puny-encode if needed
socket.gethostbyname(ascii_domain)
return True
except (socket.gaierror, UnicodeError, idna.IDNAError):
return False
def main():
# list of (yaml_path, [dead_domain, …])
inactive_files: list[tuple[Path, list[str]]] = []
for yml_path in find_yaml_files(RULES_DIR):
try:
docs = yaml.safe_load_all(yml_path.read_text())
except yaml.YAMLError as e:
print(f"⚠️ Skipping {yml_path} (YAML error: {e})")
continue
domains: set[str] = set()
for doc in docs:
if doc is not None:
domains.update(extract_domains(doc))
if not domains:
continue
dead = sorted({d for d in domains if not domain_active(d)})
if dead:
inactive_files.append((yml_path, dead))
if inactive_files:
print("YAML files with at least one non-resolving domain:")
for path, dead in inactive_files:
print(f" - {path}: {', '.join(dead)}")
else:
print("✅ All domains resolve.")
if __name__ == "__main__":
main()

31
data/rules/adafruitio.yml Normal file
View file

@ -0,0 +1,31 @@
rules:
- name: Adafruit IO Key
id: kingfisher.adafruitio.1
pattern: |
(?xi)
\b
(
aio_
[a-zA-Z0-9]{28}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- ADAFRUIT_AIO_KEY = "aio_giXk31KzM05IVxHRwJwtpNGClUE5"
validation:
type: Http
content:
request:
url: https://io.adafruit.com/api/v2/kingfishermdb/feeds/
headers:
X-AIO-Key: "{{ TOKEN }}"
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
- type: WordMatch
words:
- '"username":"kingfishermdb"'

71
data/rules/adobe.yml Normal file
View file

@ -0,0 +1,71 @@
rules:
- name: Adobe Stock API Key
id: kingfisher.adobe.1
pattern: |
(?xi)
\b
adobe
(?:.|[\n\r]){0,32}?
\b
(
[A-F0-9]{32}
)
\b
min_entropy: 3.5
examples:
- adobeKey = 1a2b3c4d5e6f7890abcdef1234567890
references:
- https://developer.adobe.com/stock/docs/getting-started/03-api-authentication/
validation:
type: Http
content:
request:
headers:
x-api-key: '{{ TOKEN }}'
x-product: '{{ PRODUCTID }}'
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
- type: WordMatch
words:
- '"error_code":"403003"'
negative: true
url: https://stock.adobe.io/Rest/Media/1/Search/Files?locale=en_US&search_parameters%5Bwords%5D=cats%20in%20costume
depends_on_rule:
- rule_id: "kingfisher.adobe.2"
variable: PRODUCTID
- name: Adobe IO Product ID
id: kingfisher.adobe.2
pattern: |
(?xi)
\b
adobe
(?:.|[\n\r]){0,64}?
(
[a-z0-9]{12}
)
\b
min_entropy: 2.0
visible: false
examples:
- adobeProduct = lV9ASPsd2P3d
- name: Adobe OAuth Client Secret
id: kingfisher.adobe.3
pattern: |
(?xi)
\b
(
p8e-[A-Z0-9-]{32}
)
(?:[^A-Z0-9-]|$)
min_entropy: 3.5
examples:
- |
{
"client_credentials": {
"client_id": "a65b0146769d433a835f36660881db50",
"client_secret": "p8e-ibndcvsmAp9ZgPBZ606FSlYIZVlsZ-g5"
},

30
data/rules/age.yml Normal file
View file

@ -0,0 +1,30 @@
rules:
- name: Age Recipient (X25519 public key)
id: kingfisher.age.1
pattern: '\b(age1[0-9a-z]{58})\b'
min_entropy: 3.3
confidence: medium
examples:
- 'age1zvkyg2lqzraa2lnjvqej32nkuu0ues2s82hzrye869xeexvn73equnujwj'
references:
- https://age-encryption.org
- https://htmlpreview.github.io/?https://github.com/FiloSottile/age/blob/main/doc/age.1.html
- https://github.com/C2SP/C2SP/blob/8b6a842e0360d35111c46be2a8019b2276295914/age.md#the-x25519-recipient-type
- name: Age Identity (X22519 secret key)
id: kingfisher.age.2
pattern: '\b(AGE-SECRET-KEY-1[0-9A-Z]{58})\b'
min_entropy: 3.3
confidence: medium
examples:
- |
# created: 2022-09-26T21:55:47-05:00
# public key: age1epzmwwzw8n09slh0c7z1z52x43nnga7lkksx3qrh07tqz5v7lcys45428t
this is the 'AGE-SECRET-KEY-1HJCRJVK7EE3A5N8CRP8YSEUGZKNW90Y5UR2RGYAS8L279LFP6LCQU5ADNR'
- 'AGE-SECRET-KEY-1HJCRJVK7EE3A5N8CRP8YSEUGZKNW90Y5UR2RGYAS8L279LFP6LCQUEGAEX'
references:
- https://age-encryption.org
- https://htmlpreview.github.io/?https://github.com/FiloSottile/age/blob/main/doc/age.1.html
- https://github.com/C2SP/C2SP/blob/8b6a842e0360d35111c46be2a8019b2276295914/age.md#the-x25519-recipient-type
categories:
- secret

37
data/rules/airbrake.yml Normal file
View file

@ -0,0 +1,37 @@
rules:
- name: Airbrake User Key
id: kingfisher.airbrake.1
pattern: |
(?x)(?i)
\b
airbrake
(?:.|[\n\r]){0,16}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,16}?
(
[a-zA-Z0-9-]{40}
)
\b
min_entropy: 4.5
confidence: medium
examples:
- airbrake secretKey= a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6q7R8s9T0
- "airbrakeToken: 'a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6q7R8s9T0'"
references:
- https://docs.airbrake.io/docs/devops-tools/api/#list-projects-v4
validation:
type: Http
content:
request:
headers:
Content-Type: application/json
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
- words:
- '"id"'
type: WordMatch
url: https://api.airbrake.io/api/v4/projects?key={{ TOKEN }}

63
data/rules/airtable.yml Normal file
View file

@ -0,0 +1,63 @@
rules:
- name: Airtable Personal Access Token
id: kingfisher.airtable.1
pattern: |
(?xi)
\b
(
pat
[a-z0-9]{14}
\.
[a-z0-9]{62,66}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- airtable_pat = patXfsZBLOgxbJqSi.fd6210cfb2e0b2049bd8e7fa0e69f26a3a704412af6fcbd93a097c42507fc893
categories:
- api
- secret
references:
- https://airtable.com/developers/web/api/authentication
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.airtable.com/v0/meta/whoami
- name: Airtable OAuth Token
id: kingfisher.airtable.2
pattern: |
(?xi)
\b
(
[a-zA-Z0-9]+\.v1\.[a-zA-Z0-9_-]+\.[a-f0-9]+
)
\b
min_entropy: 3.5
confidence: medium
examples:
- example.v1.XYZ123_ABC.abcdef123456
references:
- https://airtable.com/developers/web/api/oauth-reference
validation:
type: Http
content:
request:
headers:
Authorization: "Bearer {{ TOKEN }}"
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.airtable.com/v0/meta/whoami

36
data/rules/aiven.yml Normal file
View file

@ -0,0 +1,36 @@
rules:
- name: Aiven API Key
id: kingfisher.aiven.1
pattern: |
(?x)
(?i)
aiven
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[a-z0-9/+=]{372}
)
min_entropy: 3.3
confidence: medium
examples:
- FLASK_APP_AIVEN_API_KEY = "MOLXG502hGM9DsUSyvKVf2cx8zXEdBesHZLSqXnMj4agm9jLx4gpC9R+z26CX4tKgrIpjvR9dgorE/DzVxxH79Pd+mspIHgxkf7fL4eLxuFvl4RrvX9CWS7nMnfB9uDiM80AtGykzHm8KKr76I7UY8Az/i3x2OG5gFhH0+2AT0Qr75T1JbNF0IiPSjI3MQ0A1+k1b2DW2dwdNnYKEewrNjhVHre8sYLzMUE5Y+FIs8OFdpAm4YNUb283iVJjEcxT8AtMhmOrziMkmWn0haxjhT2qdxgnafGJidF0Dl/NIN+4o1WokQSyhHH1glhNV5wZcG4Po/KP3aPSRnrFE0+GZ6322TrWo1btS5mv+FKkS6gKq0zEfA=="
references:
- https://aiven.io/docs/tools/api
validation:
type: Http
content:
request:
headers:
Authorization: aivenv1 {{ TOKEN }}
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
- words:
- '"project_membership"'
type: WordMatch
url: https://api.aiven.io/v1/project

52
data/rules/algolia.yml Normal file
View file

@ -0,0 +1,52 @@
rules:
- name: Algolia Admin API Key
id: kingfisher.algolia.1
pattern: |
(?x)
(?i)
algolia
(?:.|[\n\r]){0,32}?
\b
(
[a-z0-9]{32}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- algolia_api_key = "ij1mut5oe606wlrf5z4u8u31264z3gag"
validation:
type: Http
content:
request:
headers:
X-Algolia-API-Key: '{{ TOKEN }}'
X-Algolia-Application-Id: '{{ APPID }}'
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://{{ APPID }}-dsn.algolia.net/1/keys/{{ TOKEN }}
depends_on_rule:
- rule_id: "kingfisher.algolia.2"
variable: APPID
- name: Algolia Application ID
id: kingfisher.algolia.2
pattern: |
(?x)
(?i)
algolia
(?:.|[\n\r]){0,16}?
\b
(
[A-Z0-9]{10}
)
\b
min_entropy: 2.0
visible: false
confidence: medium
examples:
- algolia_app_id = "WRB8YLFW7Y"

35
data/rules/alibaba.yml Normal file
View file

@ -0,0 +1,35 @@
rules:
- name: Alibaba Access Key ID
id: kingfisher.alibabacloud.1
pattern: |
(?x)
(?i)
\b
(
LTAI[a-z0-9]{17,21}
)
\b
min_entropy: 4.0
confidence: medium
examples:
- LTAI8x2NiGqfyJGx7eLDhp12
- LTAI5GqyJGhp12ad31L5hpix
- name: Alibaba Access Key Secret
id: kingfisher.alibabacloud.2
pattern: |
(?x)
(?i)
\b
alibaba
(?:.|[\n\r]){0,16}?
\b
(
[a-z0-9]{30}
)
\b
min_entropy: 4.2
confidence: medium
examples:
- alibaba_secret = 7jkWdTjKLnSlGddwPR5gBn65PHcZG6
- alibaba-token = aJHKLnSlGddwPR5g7jkWdTBn65PHc5

51
data/rules/anthropic.yml Normal file
View file

@ -0,0 +1,51 @@
rules:
- name: Anthropic API Key
id: kingfisher.anthropic.1
pattern: |
(?x)
(?i)
\b
(
sk-ant-api
\d{2,4}
-
[\w\-]{93}
AA
)
\b
min_entropy: 3.3
confidence: medium
examples:
- sk-ant-api668-Clm512odot9WDD7itfUU9R880nefA1EtYZDbpE-C9b0XQEWpqFKf9DQUo03vOfXl16oSmyar1CLF1SzV3YzpZJ6bahcpLAA
categories:
- api
- secret
references:
- https://docs.anthropic.com/claude/reference/authentication
validation:
type: Http
content:
request:
body: |
{
"model": "claude-3-haiku-20240307",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "respond only with 'success'"}
]
}
headers:
Content-Type: application/json
anthropic-version: "2023-06-01"
x-api-key: '{{ TOKEN }}'
method: POST
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
- report_response: true
- type: WordMatch
words:
- '"type":"invalid_request_error"'
url: https://api.anthropic.com/v1/messages

41
data/rules/anypoint.yml Normal file
View file

@ -0,0 +1,41 @@
rules:
- name: Anypoint API Key
id: kingfisher.anypoint.1
pattern: |
(?x)
(?i)
anypoint
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[0-9a-z]{8}
-
[0-9a-z]{4}
-
[0-9a-z]{4}
-
[0-9a-z]{4}
-
[0-9a-z]{12}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- anypoint_key = "33333333-cccc-dddd-eeee-444444444444"
references:
- https://www.postman.com/salesforce-developers/salesforce-developers/documentation/oj0opxa/mulesoft-anypoint-platform-apis
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
method: GET
response_matcher:
- status:
- 200
type: StatusMatch
url: https://anypoint.mulesoft.com/accounts/api/me

View file

@ -0,0 +1,57 @@
rules:
- name: Artifactory Access Token
id: kingfisher.artifactory.1
pattern: |
(?x)
(?i)
\b
(
AKC[a-zA-Z0-9]{64,74}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- |
export HOMEBREW_ARTIFACTORY_API_TOKEN=AKCp8igrDNFerC357m4422e4tmu7xB983QLPxJhKFcSMfoux2RFvp8rc4jC8t9ncdmYCMFD8W
export HOMEBREW_ARTIFACTORY_API_USER=kashorn
- 'jfrog rt dl --url=http://localhost:8071/artifactory --apikey=AKCp2WXX7SDvcsmny528sSDnaB3zACkNQoRcD8D1WmxhMV9gk6Wp8mVWC8bh38kJQbXagUT8Z generic-local/hello.txt'
validation:
type: Http
content:
request:
headers:
X-JFrog-Art-Api: '{{ TOKEN }}'
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
- type: JsonValid
url: https://{{ JFROGURL }}/artifactory/api/repositories
depends_on_rule:
- rule_id: "kingfisher.artifactory.2"
variable: JFROGURL
- name: Artifactory JFrog URL
id: kingfisher.artifactory.2
pattern: |
(?x)
\b
(
[a-z0-9]
(?:
[a-z0-9\-]{0,61}
[a-z0-9]
)?
\.jfrog\.io
)
\b
min_entropy: 3.5
visible: false
confidence: medium
examples:
- mycompany.jfrog.io
- my-company-name.jfrog.io
- a.jfrog.io

89
data/rules/asana.yml Normal file
View file

@ -0,0 +1,89 @@
rules:
- name: Asana Client ID
id: kingfisher.asana.1
pattern: |
(?x)
(?i)
\b
asana
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[0-9]{16}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- asana_key = "8195513847023883"
- "ASANA_API_TOKEN: 1234567890123456"
references:
- https://developers.asana.com/docs/authentication
- name: Asana Client Secret
id: kingfisher.asana.2
pattern: |
(?x)
(?i)
\b
asana
(?:.|[\n\r]){0,64}?
\b
(
[a-z0-9]{30,40}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- "asana :'20c2F0d03201af478ca1aBE9515A1A4FEfb'"
- ASANA_PAT = 1234567890abcdef1234567890abcdef12
- name: Asana OAuth / Personal Access Token
id: kingfisher.asana.3
pattern: |
(?x)
(?i)
\b
asana
(?:.|[\n\r]){0,64}?
\b
(
[01]{1,}
\/
[0-9a-f]{16,32}
(?:
:
[a-z0-9]{32,64}
)?
)
\b
min_entropy: 3.5
confidence: medium
examples:
- asana_pat = 1/1248440223456784:d3d7e52e5c4a5d4c9bc424d2d882324d
- asana token = 0/d6f1e29e5b4b4d8c9bb419b2d882154d
categories:
- api
- key
- asana
references:
- https://developers.asana.com/docs/personal-access-token#example
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
method: GET
response_matcher:
- report_response: true
- match_all_words: true
type: WordMatch
words:
- 'data:'
- email
- name
url: https://app.asana.com/api/1.0/users/me

41
data/rules/atlassian.yml Normal file
View file

@ -0,0 +1,41 @@
rules:
- name: Atlassian API token
id: kingfisher.atlassian.1
pattern: |
(?x)
(?i)
\b
atlassian
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[a-z0-9]{24}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- Atlassian_key = "DjayBenyJrtpvydFCzAphcqc"
- "ATLASSIAN_API_TOKEN:'abcdef1234567890abcdef12'"
references:
- https://developer.atlassian.com/cloud/admin/organization/rest/api-group-orgs/#api-v1-orgs-get
validation:
type: Http
content:
request:
url: https://api.atlassian.com/me
headers:
Authorization: "Bearer {{ TOKEN }}"
Accept: "application/json"
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
- type: WordMatch
words:
- "Unauthorized"
negative: true

87
data/rules/auth0.yml Normal file
View file

@ -0,0 +1,87 @@
rules:
- name: Auth0 Client ID
id: kingfisher.auth0.1
pattern: |
(?x)
(?i)
\b
auth0
(?:.|[\n\r]){0,32}?
\b
(
[a-z0-9_-]{32,60}
)
\b
min_entropy: 3.5
confidence: medium
visible: false
examples:
- auth0_client_id = 'dBpXUZS34Rhg6WUKTLLj3E9yWGY6IJgV'
- 'auth0ClientId: ''Hf5sS5kLmNm6Lmvoc1BwXryX49bPD4L0'''
- AUTH0_CLIENT_ID='abcdef1234567890abcdef1234567890abcdef12'
references:
- https://auth0.com/docs/get-started/applications/application-settings
- name: Auth0 Client Secret
id: kingfisher.auth0.2
pattern: |
(?x)
(?i)
\b
auth0
(?:.|[\n\r]){0,16}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,64}?
\b
(
[a-z0-9_-]{64,}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- 'auth0_client_secret: ''v7qhPJv1fDRoUwBjktpWgS8LPVr-hXWOI8tFnRtWrU3jNYZf1hW3hfJjA8rEER9D'''
- auth0ClientSecret = 'YOUR_VERY_LONG_AND_SECURE_CLIENT_SECRET_GOES_HERE_12345678900000'
- AUTH0_CLIENT_SECRET='abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890'
validation:
type: Http
content:
request:
url: https://{{ DOMAIN }}/oauth/token
headers:
Content-Type: application/x-www-form-urlencoded
body: |
grant_type=authorization_code&client_id={{ CLIENT_ID }}&client_secret={{ CLIENT_SECRET }}&code=AUTHORIZATION_CODE&redirect_uri=undefined
method: POST
response_matcher:
- report_response: true
- type: WordMatch
words:
- "access_denied"
- "invalid_grant"
depends_on_rule:
- rule_id: kingfisher.auth0.1
variable: CLIENTID
- rule_id: "kingfisher.auth0.3"
variable: DOMAIN
- name: Auth0 Domain
id: kingfisher.auth0.3
pattern: |
(?x)
(?i)
\b
(
[a-z0-9]
[a-z0-9._-]*
auth0\.com
)
\b
min_entropy: 3.5
visible: false
confidence: medium
examples:
- https://mycompany.us.auth0.com/
- 'auth0Domain: ''example-org.auth0.com'''
- AUTH0_DOMAIN=myapp.eu.auth0.com
categories:
- domain
- auth0

74
data/rules/aws.yml Normal file
View file

@ -0,0 +1,74 @@
rules:
- name: AWS Access Key ID
id: kingfisher.aws.1
pattern: |
(?x)
(?i)
\b
(
(?:AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)
[0-9A-Z]{16}
)
\b
min_entropy: 3.2
visible: false
confidence: medium
examples:
- ASIAOZW6VBVAZFJHJLQA
- name: AWS Secret Access Key
id: kingfisher.aws.2
pattern: |
(?xi)
(?:
\b
(?:AWS|AMAZON|AMZN|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)
(?:.|[\n\r]){0,32}?
\b
(
[A-Za-z0-9/+=]{40}
)
\b
|
\b(?:AWS|AMAZON|AMZN|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)
(?:.|[\n\r]){0,96}?
(?:SECRET|PRIVATE|ACCESS)
(?:.|[\n\r]){0,16}?
(?:KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[A-Za-z0-9/+=]{40}
)
\b
)
min_entropy: 4.5
confidence: medium
examples:
- foo.backup.archive.aws.secretkey=sBmHlDFrNcsz35N+LRjwlUxF8/wypT4tiJCQ0wP4
- '"awsSecretKey":"3lyTWqHMt5UySny2drdPYheRTEzrNux8Cn5JWFHL"'
- '"\"awsSecretKey\":\"3lyTWqHMt5UySny2drdPYheRTEzrNux8Cn5JWFHL\"," +'
- |
"Whiteboard" : {
"type" : "aws-s3",
"config" : {
"accessKeyId" : "AKIAIVOURJN3SXRRLZFQ",
"region" : "us-east-1",
"secretAccessKey" : "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"
},
validation:
type: AWS
depends_on_rule:
- rule_id: kingfisher.aws.1
variable: AKID
- name: AWS Session Token
id: kingfisher.aws.4
pattern: '(?i)(?:aws.?session|aws.?session.?token|aws.?token)["''`]?\s{0,30}(?::|=>|=)\s{0,30}["''`]?([a-z0-9/+=]{16,200})[^a-z0-9/+=]'
min_entropy: 3.3
confidence: medium
examples:
- |
export AWS_ACCESS_KEY_ID="I08BCX2ACV45ED1DOC9J"
export AWS_SECRET_ACCESS_KEY="0qk+o7XctJMmG6ydO8537c9+TofLJU1K0PiVBXSg"
export AWS_SESSION_TOKEN="eyJhbGciOiJIUzUxMi53InR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJJMDhCQ1gySkpWNDVFRDFET0M5SiIsImFjciI6Ij53LCJhdWQiOiJhY2NvdW50IiwiYXV0aF90aW1lIjowLCJhenAiOiJtaW5pbyIsImVtYWlsIjoiYWlkYW4uY29wZUBnbWFpbC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImV4cCI6MTU4MDUwMDIzOCwiZmFtaWx5X25hbWUiOiJDb3BlIiwiZ2l2ZW5fbmFtZSI6IkFpZGFuIENvcGUiLCJpYXQiOjE1ODA0OTk5MzgsImlzcyI6Imh0dHBzOi8vYXV0aHN0Zy5wb3BkYXRhLmJjLmNhL2F1dGgvcmVhbG1zL3NhbXBsZSIsImp0aSI6IjU5ZTM5ODAxLWQxMmUtNDVhYS04NmQzLWVhMmNmZDU0NmE2MiIsIm1pbmlvX3BvbGljeSI6ImRhdGFzZXRfMV9ybyIsIm5hbWUiOiJBaWRhbiBDb3BlIENvcGUiLCJuYmYiOjAsInByZWZlcnJlZF91c2VybmFtZSI6ImFjb3BlLTk5LXQwNSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2Vzc2lvbl9zdGF0ZSI6IjcxYjczZWJjLThlMzMtNGMyMi04NmE2LWI0MzhhNDM4ZmI2MiIsInN1YiI6IjVkOTBlOTgzLTA1NDItNDYyYS1hZWIwLWYxZWVmNjcwYzdlNSIsInR5cCI6IkJlYXJlciJ9.J-a9PORJToz7MUrnPQlOywcqtVMNkXy53Gedp_V4PW-Gbf1_BAMjwuw_X7fKRd6hkNfEn43CKKju7muzi_d1Ig"

87
data/rules/azure.yml Normal file
View file

@ -0,0 +1,87 @@
rules:
- name: Azure Connection String
id: kingfisher.azure.1
pattern: |
(?xi)
(?: AccountName | SharedAccessKeyName | SharedSecretIssuer) \s*=\s* ([^;]{1,80}) \s*;\s*
.{0,10}\s*
(?: AccountKey | SharedAccessKey | SharedSecretValue) \s*=\s* ([^;]{1,100})
(?: ;|$ )
min_entropy: 3.3
confidence: medium
examples:
- |
# Azure Storage Connection String
AzureWebJobsStorage=DefaultEndpointsProtocol=https;AccountName=hanatour9833;AccountKey=6jqh42QQjWWBwoPGGR/Jr0PZjhBMZVbHm/gkhEfHvOj8aV6+oI8ed6ZAAwB5a6993WqyQDiuJJB0QpseJwqYxw==;EndpointSuffix=core.windows.net
- |
DefaultEndpointsProtocol=http;AccountName=testacc1;
AccountKey=1gy3lpE7Du1j5ljKiupgKzywSw2isjsa69sfsdfsdsgfsgfdgfdgfd/YThisv/OVVLfIOv9kQ==;
BlobEndpoint=http://127.0.0.1:8440/testacc1;
TableEndpoint=http://127.0.0.1:8440/testacc1;
QueueEndpoint=http://127.0.0.1:8440/testacc1;
- |
"IOTHUB_CONNECTION_STRING": {
"value": "HostName=d1-vi-ioth521.azure-devices.net;SharedAccessKeyName=registryReadWrite;SharedAccessKey=S8ii67l3Gd1Ba69az78iP9UksewzhjvUfh1DIuDs30w="
}
- |
"AZURE_STORAGE_CONNECTION_STRING": {
"value": "DefaultEndpointsProtocol=https;AccountName=d1biblobstor521;AccountKey=NjEwGHd9+piK+iCi2C2XURWPmeDDjif9UKN1HAszYptL4iQ+yD7/dgjLMZc3VOpURsa53aJ4HZfbVWzL429C5g==;EndpointSuffix=core.windows.net"
}
negative_examples:
- 'InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=ai.contoso.com;'
- 'InstrumentationKey=00000000-0000-0000-0000-000000000000;IngestionEndpoint=https://custom.com:111/;LiveEndpoint=https://custom.com:222/;ProfilerEndpoint=https://custom.com:333/;SnapshotEndpoint=https://custom.com:444/;'
references:
- https://azure.microsoft.com/en-us/blog/windows-azure-web-sites-how-application-strings-and-connection-strings-work/
- https://docs.microsoft.com/en-us/azure/storage/common/storage-configure-connection-string
- https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-sas#best-practices-when-using-sas
categories:
- api
- fuzzy
- secret
- name: Azure App Configuration Connection String
id: kingfisher.azure.2
pattern: |
(?x)
(https://[a-zA-Z0-9-]+\.azconfig\.io);
Id=(.{4}-.{2}-.{2}:[a-zA-Z0-9+/]{18,22});
Secret=([a-zA-Z0-9+/]{36,50}=)
min_entropy: 3.3
confidence: medium
examples:
- 'Endpoint=https://foo-nonprod-appconfig.azconfig.io;Id=ABCD-E6-s0:tl6ABcdefGHi7kLMno/p;Secret=abCD1EF+GHIJxLMnOPqRSa53VWX05zaBCdE/fg9hi4k='
- 'https://foo-nonprod-appconfig.azconfig.io;Id=ABCD-E6-s0:tl6ABcdefGHi7kLMno/p;Secret=abCD1EF+GHIJxLMnOA53ST8uVWX05zaBCdE/fg9hi4k='
- 'Endpoint=https://appconfig-test01.azconfig.io;Id=09pv-l0-s0:opFCQMC6+9485xJgN5Ws;Secret=GcoEA53t7GLRNJ910M46IrbHO/Vg0tt4HujRdsaCoTY='
- ' private static string appConfigurationConnectionString = "Endpoint=https://appcs-fg-pwc.azconfig.io;Id=pi5x-l9-s0:SZLlhHA53Nz2MpAl04cU;Secret=CQ+mlfQqkzfZv4XA53gigJ/seeXMKwNsqW/rM3wmtuE=";'
negative_examples:
- |
text:
az appconfig feature delete --connection-string Endpoint=https://contoso.azconfig.io;Id=xxx;Secret=xxx --feature color --label MyLabel
references:
- https://docs.microsoft.com/en-us/azure/azure-app-configuration/
- https://docs.microsoft.com/en-us/azure/azure-app-configuration/howto-best-practices
- https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/appconfiguration/azure-appconfiguration/azure/appconfiguration/_utils.py
categories:
- api
- fuzzy
- secret
- name: Azure Personal Access Token
id: kingfisher.azure.3
pattern: |
(?x)
(?i: ADO_PAT | pat_token | personal_?access_?token | \$token )
\s* = \s*
["']
([a-z0-9]{52})
["']
min_entropy: 3.3
confidence: medium
examples:
- |
var personalAccessToken = "zpczok4kqgnw5prpfy3ehiylbqvgbjfkdiqkejsxqamy7qbkep7q"; // Provide a value or retrieve it from configuration
- |
$token = "58oo4mvqr2tpw7b4w3loeckwfu5o6nw3sihfckvlwoxgqimlddza"
- |
if __name__ == "__main__":
ado_pat = "iyfmob6xjrfmit67anxbot64umfx2clwx7dz5ynxi4q2z3uqegvq"

View file

@ -0,0 +1,32 @@
rules:
- name: Azure DevOps Personal Access Token
id: kingfisher.azure.devops.1
pattern: |
(?xi)
\b
azure
(?:.|[\n\r]){0,32}?
(
[a-z0-9]{75}AZDO[a-z0-9]{5}
)
\b
min_entropy: 3
confidence: medium
examples:
- azure devops pat = FBdFol081crwkIHWJH2yiqDDyrFjVSi7HWl22hN2hTYfsB8NlGDpJQQJ77BAACAAAAAAAAAAAAASAZDOBucT
references:
- https://learn.microsoft.com/en-us/rest/api/azure/devops/profile/profiles/get?view=azure-devops-rest-7.1&tabs=HTTP
- https://learn.microsoft.com/en-us/azure/devops/release-notes/2024/general/sprint-241-update
validation:
type: Http
content:
request:
headers:
Authorization: 'Basic {{ ":" | append: TOKEN | b64enc }}'
method: GET
url: https://app.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=7.1-preview.1
response_matcher:
- report_response: true
- type: StatusMatch
status:
- 200

View file

@ -0,0 +1,58 @@
rules:
- name: Azure OpenAI API Key
id: kingfisher.azureopenai.1
pattern: |
(?xi)
\b
azure
(?:.|[\n\r]){0,8}?
(?:openai)
(?:.|[\n\r]){0,16}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,8}?
(
[a-f0-9]{32}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- azure_openai_key=1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d
- azure_openai_secret=abcdef0123456789abcdef0123456789
references:
- https://learn.microsoft.com/en-us/rest/api/azureopenai/models/list?view=rest-azureopenai-2024-10-21&tabs=HTTP
validation:
type: Http
content:
request:
method: GET
url: 'https://{{ AZUREHOST }}/openai/models?api-version=2024-10-21'
headers:
Api-Key: '{{ TOKEN }}'
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
- type: WordMatch
words:
- '"object":'
depends_on_rule:
- rule_id: kingfisher.azureopenai.host.1
variable: AZUREHOST
- name: Azure OpenAI Host
id: kingfisher.azureopenai.host.1
pattern: |
(?xi)
\b
(
[a-z0-9-]+
\.openai\.azure\.com
)
\b
min_entropy: 2.0
visible: false
confidence: medium
examples:
- my-instance.openai.azure.com
- mycompany-east-us.openai.azure.com

View file

@ -0,0 +1,55 @@
rules:
- name: Azure Search Query Key
id: kingfisher.azuresearch.key.1
pattern: |
(?xi)
\b
azure
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
(
[0-9a-zA-Z]{52}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- azure_search_key = XK8TnSRDXsoxiFYiH6Ix2aBC6jvWozd9Ida1yNjWgFHSjeDlblDK
references:
- https://learn.microsoft.com/en-us/rest/api/searchservice/search-documents
validation:
type: Http
content:
request:
method: GET
url: '{{ AZUREURL }}/docs?search=%2a&$top=0&api-version=2024-07-01'
headers:
api-key: '{{ TOKEN }}'
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
depends_on_rule:
- rule_id: kingfisher.azuresearch.url.1
variable: AZUREURL
- name: Azure Search URL
id: kingfisher.azuresearch.url.1
pattern: |
(?x)
\b
azure
(?:.|[\n\r]){0,32}?
https:\/\/
(
[0-9a-z]{5,40}
\.search\.windows\.net
\/indexes\/
[0-9a-z]{5,40}
)
\b
min_entropy: 3.0
confidence: medium
examples:
- azure_search_url=https://myservice.search.windows.net/indexes/myindex

View file

@ -0,0 +1,42 @@
rules:
- name: Azure Storage Account Name
id: kingfisher.azurestorage.name.1
pattern: |
(?x)
(?:
(?i:
(?:Account|Storage)
(?:[._-]Account)?
[._-]?Name
)
(?:.|[\n\r]){0,20}?
([a-z0-9]{3,24})
|
([a-z0-9]{3,24})
(?i:\.blob\.core\.windows\.net)
)\b
min_entropy: 2.5
visible: false
confidence: medium
examples:
- storage_name=mystorageaccount123
- mystorageaccount.blob.core.windows.net
- name: Azure Storage Account Key
id: kingfisher.azurestorage.key.1
pattern: |
(?x)
(?i:(?:Access|Account|Storage)[_.-]?Key)
(?:.|[\n\r]){0,25}?
(
[a-zA-Z0-9+\\/-]{86,88}={0,2}
)
min_entropy: 4.0
confidence: medium
examples:
- AccountKey=Xy9aB8cD7eF6gH5iJ4kL3mN2oP1qR0sT9uV8wX7yZ6aB5cD4eF3gH2iJ1kL0mN9oP8qR7sT6uV5wX4yZ3aB2cD1eF0gH9iJ8kL7mN6oP5q==\
validation:
type: AzureStorage
depends_on_rule:
- rule_id: kingfisher.azurestorage.name.1
variable: AZURENAME

View file

@ -0,0 +1,36 @@
rules:
- name: Baremetrics API Key
id: kingfisher.baremetrics.1
pattern: |
(?x)
(?i)
\b
baremetrics
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[a-z0-9_-]{25}
)
\b
min_entropy: 3.3
confidence: medium
references:
- https://developers.baremetrics.com/reference/sources
examples:
- baremetrics_api_key = "12345abcdef67890abcdef123"
- "BAREMETRICS_API_KEY:'abcde12345fghij67890klmno'"
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.baremetrics.com/v1/sources

32
data/rules/beamer.yml Normal file
View file

@ -0,0 +1,32 @@
rules:
- name: Beamer API token
id: kingfisher.beamer.1
pattern: |
(?xi)
\b
beamer
(?:.|[\n\r]){0,64}?
\b
(
b_[a-zA-Z0-9=_\\/\\\-+]{44}
)
min_entropy: 3.0
confidence: medium
examples:
- beamer = b_ByDfulghxvvmHbArJSFfQhxemJPQHOwplxuydlKEEbfe
- "BEAMER_key = 'b_ByDfulghxvvmHbArJSFfQhxemJPQHOwplxuydlKEEbfe'"
references:
- https://getbeamer-api.pages.dev/
validation:
type: Http
content:
request:
headers:
Beamer-Api-Key: '{{ TOKEN }}'
method: GET
response_matcher:
- report_response: true
type: WordMatch
words:
- '"name":'
url: https://api.getbeamer.com/v0/ping

65
data/rules/bitbucket.yml Normal file
View file

@ -0,0 +1,65 @@
rules:
- name: Bitbucket Client ID
id: kingfisher.bitbucket.1
pattern: |
(?x)
(?i)
\b
bitbucket
(?:.|[\n\r]){0,16}?
(?:client|id)
(?:.|[\n\r]){0,16}?
\b
([a-z0-9]{30,40})
\b
min_entropy: 3.5
confidence: medium
examples:
- bitbucket_id=byl2nhrv34zaclukjhvomlvjabkujf
- bitbucket.client.ID=abcdefghij1234567890klmnopqrst
validation:
type: Http
content:
request:
headers:
Authorization: Basic {{ TOKEN | b64enc }}
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.bitbucket.org/2.0/user
- name: Bitbucket Secret
id: kingfisher.bitbucket.3
pattern: |
(?x)
(?i)
\b
bitbucket
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[a-z0-9+_\-+]{44}
)
min_entropy: 3.5
confidence: medium
examples:
- bitbucket_key=HedmnK9h6KD_eh9KK8FlI9ahUc8WfaNZ4gulbrtN2ouV
- bitbucket_secret=kd8j2h4jf9s8mf6l4k9j2h4jf9s8mf6l4k9j2h4jf9s8mf6l
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.bitbucket.org/2.0/user

75
data/rules/blynk.yml Normal file
View file

@ -0,0 +1,75 @@
rules:
- name: Blynk Device Access Token
id: kingfisher.blynk.1
pattern: |
(?x)
https://(?:fra1\.|lon1\.|ny3\.|sgp1\.|blr1\.)*blynk\.cloud/external/api/[a-zA-Z0-9/]*\?token=
([a-zA-Z0-9_\-]{32})
&
min_entropy: 3.3
confidence: medium
examples:
- curl "https://blynk.cloud/external/api/get?token=Rps15JICmtRVbFyS_95houlLbm6xIQ2L&V1"
- curl "https://fra1.blynk.cloud/external/api/get?token=Rps15JICmtRVbFyS_95houlLbm6xIQ2L&V1"
- curl "https://lon1.blynk.cloud/external/api/get?token=Rps15JICmtRVbFyS_95houlLbm6xIQ2L&V1"
- curl "https://blynk.cloud/external/api/update/property?token=Rps15JICmtRVbFyS_95houlLbm6xIQ2L&pin=v1&isDisabled=true"
- name: Blynk Organization Access Token
id: kingfisher.blynk.2
pattern: |
(?x)
https://(?:fra1\.|lon1\.|ny3\.|sgp1\.|blr1\.)*blynk\.cloud/api/[a-zA-Z0-9_\-\s/\\]*
-H\s*"Authorization:\s*Bearer\s*
([a-zA-Z0-9_\-]{40})
"
min_entropy: 3.3
confidence: medium
examples:
- 'curl https://fra1.blynk.cloud/api/organization/profile -H "Authorization: Bearer eIdWHQqRfFmvP5LDDh-IGxPUzi7I27HthzCPAVmS"'
- |
curl https://fra1.blynk.cloud/api/organization/profile \
-H "Authorization: Bearer eIdWHQqRfFmvP5LDDh-IGxPUzi7I27HthzCPAVmS"
- name: Blynk Organization Access Token
id: kingfisher.blynk.3
pattern: |
(?x)
-H\s*"Authorization:\s*Bearer\s*
([a-zA-Z0-9_\-]{40})
"[\s\\]*https://(?:fra1\.|lon1\.|ny3\.|sgp1\.|blr1\.)*blynk\.cloud/api
min_entropy: 3.3
confidence: medium
examples:
- 'curl -H "Authorization: Bearer eIdWHQqRfFmvP5LDDh-IGxPUzi7I27HthzCPAVmS" https://fra1.blynk.cloud/api/organization/profile'
- |
curl -H "Authorization: Bearer eIdWHQqRfFmvP5LDDh-IGxPUzi7I27HthzCPAVmS" \
https://fra1.blynk.cloud/api/organization/profile
- name: Blynk Organization Client Credentials
id: kingfisher.blynk.8
pattern: |
(?x)
https://(?:fra1\.|lon1\.|ny3\.|sgp1\.|blr1\.)*blynk\.cloud/oauth2/[a-zA-Z0-9_\-\s/\\?=&]*
(oa2-client-id_[a-zA-Z0-9_\-]{32})
(?: : | &client_secret= )
([a-zA-Z0-9_\-]{40})
min_entropy: 3.3
confidence: medium
examples:
- 'curl -X POST https://fra1.blynk.cloud/oauth2/token?grant_type=client_credentials -u oa2-client-id_zmNtW-D0Toqpz4AZnBLCIlklBrz9ynU-:5uC5Y4Mcvdl5rB56rBmxnvB4DZgiIpcyTPbOoEWp'
- |
curl -X POST https://fra1.blynk.cloud/oauth2/token?grant_type=client_credentials \
-u oa2-client-id_zmNtW-D0Toqpz4AZnBLCIlklBrz9ynU-:5uC5Y4Mcvdl5rB56rBmxnvB4DZgiIpcyTPbOoEWp
- 'curl -X POST https://fra1.blynk.cloud/oauth2/token?grant_type=client_credentials&client_id=oa2-client-id_zmNtW-D0Toqpz4AZnBLCIlklBrz9ynU-&client_secret=5uC5Y4Mcvdl5rB56rBmxnvB4DZgiIpcyTPbOoEWp'
- name: Blynk Organization Client Credentials
id: kingfisher.blynk.9
pattern: |
(?x)
\b
(oa2-client-id_[a-zA-Z0-9_\-]{32})
:([a-zA-Z0-9_\-]{40})
[\s\\]*https://(fra1\.|lon1\.|ny3\.|sgp1\.|blr1\.)*blynk\.cloud/oauth2
min_entropy: 3.3
confidence: medium
examples:
- 'curl -X POST -u oa2-client-id_zmNtW-D0Toqpz4AZnBLCIlklBrz9ynU-:5uC5Y4Mcvdl5rB56rBmxnvB4DZgiIpcyTPbOoEWp https://fra1.blynk.cloud/oauth2/token?grant_type=client_credentials'
- |
curl -X POST -u oa2-client-id_zmNtW-D0Toqpz4AZnBLCIlklBrz9ynU-:5uC5Y4Mcvdl5rB56rBmxnvB4DZgiIpcyTPbOoEWp \
https://fra1.blynk.cloud/oauth2/token?grant_type=client_credentials

84
data/rules/circleci.yml Normal file
View file

@ -0,0 +1,84 @@
rules:
- name: CircleCI API Personal Access Token
id: kingfisher.circleci.1
pattern: |
(?x)
(?i)
\b
(
CCIPAT_
[a-z0-9]{4}
[a-z]{5}
[a-z0-9]{3}
[0-9]{3}
[a-z]{2}
[A-Z]{2}
[0-9]{1}
[a-z]{1}
[a-z0-9]{1}
[0-9]{1}
[a-z]{1}
_
[a-z0-9]{40}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- CircleCI_PAT = "CCIPAT_lZyPAuThWn2G908ssDT0g33e_t7qh0r5hrvsqzmuraqzduq6qco5onxgrtcn7y2z4"
- |
export CIRCLECI_TOKEN=CCIPAT_lZyPAuThWn2G908ssDT0g33e_t7qh0r5hrvsqzmuraqzduq6qco5onxgrtcn7y2z4
references:
- https://circleci.com/docs/api-developers-guide/#using-the-api-securely-wtih-curl
validation:
type: Http
content:
request:
headers:
Accept: application/json
Circle-Token: '{{ TOKEN }}'
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
- type: WordMatch
words:
- '"id"'
url: https://circleci.com/api/v2/me
- name: CircleCI API Project Token
id: kingfisher.circleci.2
pattern: |
(?xi)
\b
circleci
(?:.|[\n\r]){0,64}?
(
[a-f0-9]{40}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- circleci_project_secret = 'Ca61263Bf9A4DcEECd00EdAAacb4eaEe74e8682f'
- "CIRCLECI_API_TOKEN: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
references:
- https://circleci.com/docs/api/v1/index.html#projects
validation:
type: Http
content:
request:
headers:
Circle-Token: '{{ TOKEN }}'
Accept: application/json
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
- type: WordMatch
words:
- '"vcs_url"'
url: https://circleci.com/api/v1.1/projects

78
data/rules/cloudflare.yml Normal file
View file

@ -0,0 +1,78 @@
rules:
- name: Cloudflare API Token
id: kingfisher.cloudflare.1
pattern: |
(?x)
(?i)
\b
cloudflare
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[a-z0-9_-]{38,42}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- cloudflareAPIKey = A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0
- |
CLOUDFLARE_API_TOKEN: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0'
cloudflare_key="B1C2D3E4F5G6H7I8J9K0L1M2N3O4P5Q6R7S8T9U0V1"
references:
- https://developers.cloudflare.com/api/resources/user/subresources/tokens/methods/verify/
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
Accept: application/json
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.cloudflare.com/client/v4/user/tokens/verify
- name: Cloudflare CA Key
id: kingfisher.cloudflare.2
pattern: |
(?xi)
\b
(?:cloudflare|x-auth-user-service-key)
(?:.|[\n\r]){0,64}?
(
v1\.0-[a-z0-9._-]{160,}
)
["'`]?
\b
min_entropy: 4.5
confidence: medium
examples:
- 'X-Auth-User-Service-Key = v1.0-e26de050e02ddeaeef6de8d5ee267df5e78f68666ddd0ee76f22d26a0d20756f-eda77de60e8e76077e162727656787de2005d25e2f6e502e2d067657ed65722eade065275001a0f6f6e521e5e1fd76a6e8d7e2d6da8a2ee01e66e061e22570e2-07f2ede0aed78e82e8d2e620aaef8656d81e762266d7d226a205de7e18e2256a'
- |
cloudflare_service_key: "v1.0-e26de050e02ddeaeef6de8d5ee267df5e78f68666ddd0ee76f22d26a0d20756f-eda77de60e8e76077e162727656787de2005d25e2f6e502e2d067657ed65722eade065275001a0f6f6e521e5e1fd76a6e8d7e2d6da8a2ee01e66e061e22570e2-07f2ede0aed78e82e8d2e620aaef8656d81e762266d7d226a205de7e18e2256a"
references:
- https://developers.cloudflare.com/api/keys/
- https://developers.cloudflare.com/fundamentals/api/get-started/keys/
categories:
- api
- secret
validation:
type: Http
content:
request:
headers:
Content-Type: application/json
X-Auth-User-Service-Key: '{{ TOKEN }}'
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.cloudflare.com/client/v4/certificates?per_page=1

39
data/rules/cloudsight.yml Normal file
View file

@ -0,0 +1,39 @@
rules:
- name: CloudSight API Key
id: kingfisher.cloudsight.1
pattern: |
(?x)
(?i)
\b
cloudsight
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[a-z0-9]{20,24}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- cloudsight api_key = 2d0d3432369b6b53e7621f
- |
CLOUDSIGHT_API_KEY: 'a1b2c3d4e5f6g7h8i9j0k1l2'
cloudsight_secret="3m4n5o6p7q8r9s0t1u2v3w4x5"
references:
- https://cloudsight.docs.apiary.io/#introduction/authentication
- https://cloudsight.ai/api
validation:
type: Http
content:
request:
headers:
Authorization: CloudSight {{ TOKEN }}
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.cloudsightapi.com/image_requests

33
data/rules/codacy.yml Normal file
View file

@ -0,0 +1,33 @@
rules:
- name: Codacy API Key
id: kingfisher.codacy.1
pattern: |
(?xi)
\b
codacy
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[0-9A-Za-z]{20,24}
)
min_entropy: 3.5
confidence: medium
examples:
- codacySECRET=a1b2c3d4e5f6g7h8i9j0
- codacyACCESS_KEY k1l2m3n4o5p6q7r8s9t0
validation:
type: Http
content:
request:
headers:
Accept: application/json
api-token: '{{ TOKEN }}'
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://app.codacy.com/api/v3/user/organizations

View file

@ -0,0 +1,40 @@
rules:
- name: CodeClimate Reporter ID
id: kingfisher.codeclimate.1
pattern: |
(?x)
(?: CODECLIMATE| CC_TEST_REPORTER_ID)
(?:.|[\n\r]){0,64}?
(
[a-f0-9]{64}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- ' - RAILS_ENV=test CODECLIMATE_REPO_TOKEN=d37a8b9e09642cb73cfcf4e1284815fc3d6a55a7714110187ac59856ae4ab5ad'
- |
- uses: paambaati/codeclimate-action@v2.2.4
env:
CC_TEST_REPORTER_ID: 945dfb58a832d233a3caeb84e3e6d3be212e8c7abcb48117fce63b9adcb43647
- CODECLIMATE_API_TOKEN=d37a8b9e09642cb73cfcf4e1284815fc3d6a55a7714110187ac59856ae4ab5ad
- CODECLIMATE_API_TOKEN="d37a8b9e09642cb73cfcf4e1284815fc3d6a55a7714110187ac59856ae4ab5ad"
references:
- https://developer.codeclimate.com/#overview
validation:
type: Http
content:
request:
method: GET
url: 'https://api.codeclimate.com/v1/orgs'
headers:
Accept: 'application/vnd.api+json'
Authorization: 'Token token={{ TOKEN }}'
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
- type: WordMatch
words:
- '"id":'

55
data/rules/confluent.yml Normal file
View file

@ -0,0 +1,55 @@
rules:
- name: Confluent Client ID
id: kingfisher.confluent.1
pattern: |
(?x)(?i)
\b(?:confluent|ccloud|cpdev|kafka)
(?:.|[\n\r]){0,32}?
\b
(
[a-zA-Z0-9]{16}
)
\b
min_entropy: 3
confidence: medium
visible: false
examples:
- confluent client_id=ABCD1234EFGH5678
- kafka_client=WXYZ9876MNOP5432
references:
- https://docs.confluent.io/cloud/current/access-management/authenticate/api-keys.html
- name: Confluent API Secret
id: kingfisher.confluent.2
pattern: |
(?x)(?i)
(?:confluent|ccloud|cpdev|kafka)
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[a-zA-Z0-9\+/]{64}
)
min_entropy: 3.3
confidence: medium
examples:
- confluent secret=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ab
- kafka_token=ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyzABCD
references:
- https://docs.confluent.io/cloud/current/api.html#tag/API-Keys-(iamv2)/operation/getIamV2ApiKey
validation:
type: Http
content:
request:
headers:
Authorization: 'Basic {{ CLIENTID | append: ":" | append: TOKEN | b64enc }}'
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.confluent.cloud/iam/v2/api-keys/{{ CLIENTID }}
depends_on_rule:
- rule_id: "kingfisher.confluent.1"
variable: CLIENTID

11
data/rules/crates.io.yml Normal file
View file

@ -0,0 +1,11 @@
rules:
- name: crates.io API Key
id: kingfisher.cratesio.1
pattern: '\b(cio[a-zA-Z0-9]{32})\b'
min_entropy: 3.3
confidence: medium
examples:
- 'Bearer: ciotgp8BGZBlX192iExSQPm0SrUlBunG8zd'
references:
- https://crates.io/data-access
- https://github.com/rust-lang/crates.io/blob/master/src/util/token.rs

View file

@ -0,0 +1,26 @@
rules:
- name: Credentials in a URL
id: kingfisher.credentials.1
pattern: |
(?x)
(?i)
https?:\/\/
(
[a-z0-9._~-]+
)
:
(
[a-z0-9._~-]+
)
@
(
[a-z0-9.-]+
)
(
\/
[a-z0-9\/._~-]*
)?
min_entropy: 3.0
confidence: low
examples:
- https://eaRIWNkE:qyOIhJiM@j2LYY414Q5cCYD

83
data/rules/databricks.yml Normal file
View file

@ -0,0 +1,83 @@
rules:
- name: Databricks API token
id: kingfisher.databricks.1
pattern: |
(?x)
(?i)
(
dapi
[a-f0-9]{32}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- "DATABRICKS_TOKEN: 'dapicd295a7be286969133e18a58e4afe7bd-3'"
- "dapif21ee53d2b3648c2a1ed38953312a203"
categories:
- api
- secret
references:
- https://docs.databricks.com/dev-tools/api/latest/authentication.html
- name: Databricks API Token
id: kingfisher.databricks.2
pattern: |
(?xi)
\b
(
dapi[0-9a-f]{32}
)
(-\d)?
\b
min_entropy: 3.3
confidence: medium
examples:
- dapiabcd1234abcd1234abcd1234abcd1234
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://{{ DOMAIN }}/api/2.0/clusters/list
depends_on_rule:
- rule_id: "kingfisher.databricks.2"
variable: DOMAIN
- name: Databricks Domain
id: kingfisher.databricks.3
pattern: |
(?x)
\b
(
[a-z0-9-]+
(?:
\.[a-z0-9\-]+
)*
\.
(
cloud\.databricks\.com |
gcp\.databricks\.com |
azurewebsites\.net
)
)
\b
min_entropy: 3.5
visible: false
confidence: medium
examples:
- company-name.cloud.databricks.com
categories:
- cloud
- infrastructure
references:
- https://docs.databricks.com/workspace/workspace-details.html
- https://docs.gcp.databricks.com/workspace/workspace-details.html
- https://docs.microsoft.com/en-us/azure/databricks/scenarios/what-is-azure-databricks

64
data/rules/datadog.yml Normal file
View file

@ -0,0 +1,64 @@
rules:
- name: Datadog API Key
id: kingfisher.datadog.1
pattern: |
(?xi)
\b
(?:datadog|dd-|dd_)
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[a-z0-9]{32}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- dd-apikey-dd52c29224affe29d163c6bf99e5c34f
- datadog-secrettoken-0024a29224affe29d173c0bf99e5a89d
references:
- https://docs.datadoghq.com/account_management/api-app-keys/
validation:
type: Http
content:
request:
headers:
Accept: application/json
DD-API-KEY: '{{ TOKEN }}'
DD-APPLICATION-KEY: '{{ APPKEY }}'
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.datadoghq.com/api/v2/current_user
depends_on_rule:
- rule_id: kingfisher.datadog.2
variable: APPKEY
- name: Datadog Application Secret
id: kingfisher.datadog.2
pattern: |
(?xi)
\b
(?:
dd[_-]?\w{0,8}[_-]?(?:key|secret) |
datadog |
dog
)
(?:.|[\n\r]){0,64}?
\b
(
[a-z0-9]{40}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- dd_secret_key-3c0c3965368a6b10f7640dbda46abfdca981c2d3
- datadog_token = BzHpkcs7LujMb3Q1vLRRjbpBNxxYV0ousumYoKJS
references:
- https://docs.datadoghq.com/account_management/api-app-keys/

View file

@ -0,0 +1,23 @@
rules:
- name: Dependency-Track API Key
id: kingfisher.dtrack.1
pattern: '\b(odt_[A-Za-z0-9]{32,255})\b'
min_entropy: 3.3
confidence: medium
examples:
- 'odt_KTJlDq2AGGGlqG4riKdT7p980AW8RlU5'
- 'odt_ABCDDq2AGxGlrF4ribBT7p98AOM9TlU8'
- 'odt_FHxhQGh77JAHHIYpZ818UQ0aYjXIdMIxxgeR'
# validation:
# type: Http
# content:
# request:
# headers:
# Authorization: "Bearer {{ TOKEN }}"
# method: GET
# response_matcher:
# - report_response: true
# - status:
# - 200
# type: StatusMatch
# url: https://dependencytrack.example.com/api/v1/token/verify

View file

@ -0,0 +1,67 @@
rules:
- name: DigitalOcean API Key
id: kingfisher.digitalocean.1
pattern: |
(?x)
\b
(
(?:dop|doo)_v1_
[a-f0-9]{64}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- dop_v1_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
- 'token = "dop_v1_ef0e04edc13918192246e0c90f0735c7f4db7a5a036a857e48d6cc98f1c9576b"'
categories:
- api
- secret
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.digitalocean.com/v2/projects?per_page=1
- name: DigitalOcean Refresh Token
id: kingfisher.digitalocean.2
pattern: |
(?x)
\b
(
dor_v1_
[a-f0-9]{64}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- dor_v1_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
- ' "refresh_token": "dor_v1_d6ce5b93104521c47be0b580e9296454ef4a319b02b5513469f0ec71d99af2e2",'
validation:
type: Http
content:
request:
method: POST
url: https://cloud.digitalocean.com/v1/oauth/token
headers:
Content-Type: application/json
Accept: application/json
body: |
{
"grant_type": "refresh_token",
"refresh_token": "{{ TOKEN }}"
}
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: JsonValid

77
data/rules/discord.yml Normal file
View file

@ -0,0 +1,77 @@
rules:
- name: Discord Webhook URL
id: kingfisher.discord.1
pattern: |
(?xi)
(
https://discord\.com/api/webhooks/
\d{18}
)/
(
[0-9a-z_\-]{68}
)
min_entropy: 3.5
confidence: medium
examples:
- https://discord.com/api/webhooks/123456789012345678/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdef
categories:
- api
- webhook
validation:
type: Http
content:
request:
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: '{{ TOKEN }}'
- name: Discord Bot Token
id: kingfisher.discord.2
pattern: |
(?xi)
\b
(
[MNO][A-Za-z0-9_-]{23}\.[A-Za-z0-9_-]{6}\.[A-Za-z0-9_-]{27}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- 'apikey: MC0dBvKTjD0rY0cV8i37CjBf.uLHQPq.Nb1Ok1mEhe-3iTdrGOuegj29yQR'
validation:
type: Http
content:
request:
headers:
Authorization: Bot {{ BOTID }}
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://discord.com/api/v8/users/@me
depends_on_rule:
- rule_id: "kingfisher.discord.3"
variable: BOTID
- name: Discord Bot ID
id: kingfisher.discord.3
pattern: |
(?xi)
\b
(?:discord|botid|bot_id)
(?:.|[\n\r]){0,64}?
\b
(
\d{17,19}
)
\b
min_entropy: 3.5
visible: false
confidence: medium
examples:
- discord = 12345678901234567
- 'bot_id: "123456789012345678"'

17
data/rules/django.yml Normal file
View file

@ -0,0 +1,17 @@
rules:
- name: Django Secret Key
id: kingfisher.django.1
pattern: |
(?x)
[DJANGO]\w{0,8}SECRET_KEY
.{1,16}?
\b
(
[a-zA-Z0-9*!$@\#&_%^-]{45,55}
)
\b
min_entropy: 4.5
confidence: medium
examples:
- os.environ.get('DJANGO_SECRET_KEY','wwf*2#86t64!fgh6yav$aoeuo@u2o@fy&*gg76q!&%6x_wbduad')
- DJANGO_SECRET_KEY = "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z"

32
data/rules/dockerhub.yml Normal file
View file

@ -0,0 +1,32 @@
rules:
- name: Docker Hub Personal Access Token
id: kingfisher.dockerhub.1
pattern: |
(?x)
\b
(
dckr_pat_[a-zA-Z0-9_-]{27}
)
(?: $ | [^a-zA-Z0-9_-] )
min_entropy: 3.3
confidence: medium
examples:
- docker login -u gemesa -p dckr_pat_hc8VxYclixyTr2rDFsa2rqzkP3Y
- docker login -u gemesa -p dckr_pat_tkzBYxjNNC3R_Yg6jd_O-G8FbrJ
- docker login -u gemesa -p dckr_pat_1q8yKET1VDJTpfCwseUDzT8vFh-
references:
- https://docs.docker.com/reference/api/hub/latest/#tag/access-tokens/paths/~1v2~1access-tokens~1%7Buuid%7D/get
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
Accept: application/json
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://hub.docker.com/v2/access-tokens?page_size=1

179
data/rules/doppler.yml Normal file
View file

@ -0,0 +1,179 @@
rules:
- name: Doppler CLI Token
id: kingfisher.doppler.1
pattern: |
(?x)
\b
(dp\.ct\.[a-zA-Z0-9]{40,44})
\b
min_entropy: 3.3
confidence: medium
examples:
- dp.ct.bAqhcVzrhy5cRHkOlNTc0Ve6w5NUDCpcutm8vGE9myi
references:
- https://docs.doppler.com/reference/api
- https://docs.doppler.com/reference/auth-token-formats
- https://docs.doppler.com/reference/auth-me
validation:
type: Http
content:
request:
method: GET
url: https://api.doppler.com/v3/projects
headers:
Authorization: Bearer {{ TOKEN }}
Accept: application/json
response_matcher:
- report_response: true
- type: StatusMatch
status:
- 200
- name: Doppler Personal Token
id: kingfisher.doppler.2
pattern: |
(?x)
\b
(dp\.pt\.[a-zA-Z0-9]{40,44})
\b
min_entropy: 3.3
confidence: medium
examples:
- dp.pt.bAqhcVzrhy5cRHkOlNTc0Ve6w5NUDCpcutm8vGE9myi
references:
- https://docs.doppler.com/reference/api
- https://docs.doppler.com/reference/auth-token-formats
- https://docs.doppler.com/reference/auth-me
validation:
type: Http
content:
request:
method: GET
url: https://api.doppler.com/v3/projects
headers:
Authorization: Bearer {{ TOKEN }}
Accept: application/json
response_matcher:
- report_response: true
- type: StatusMatch
status:
- 200
- name: Doppler Service Token
id: kingfisher.doppler.3
pattern: |
(?x)
\b
(dp\.st\.(?:[a-z0-9\-_]{2,35}\.)?[a-zA-Z0-9]{40,44})
\b
min_entropy: 3.3
confidence: medium
examples:
- dp.st.dev.bAqhcVzrhy5cRHkOlNTc0Ve6w5NUDCpcutm8vGE9myi
references:
- https://docs.doppler.com/reference/api
- https://docs.doppler.com/reference/auth-token-formats
- https://docs.doppler.com/reference/auth-me
validation:
type: Http
content:
request:
method: GET
url: https://api.doppler.com/v3/me
headers:
Authorization: Bearer {{ TOKEN }}
Accept: application/json
response_matcher:
- report_response: true
- type: StatusMatch
status:
- 200
- name: Doppler Service Account Token
id: kingfisher.doppler.4
pattern: |
(?x)
\b
(dp\.sa\.[a-zA-Z0-9]{40,44})
\b
min_entropy: 3.3
confidence: medium
examples:
- dp.sa.bAqhcVzrhy5cRHkOlNTc0Ve6w5NUDCpcutm8vGE9myi
references:
- https://docs.doppler.com/reference/api
- https://docs.doppler.com/reference/auth-token-formats
- https://docs.doppler.com/reference/auth-me
validation:
type: Http
content:
request:
method: GET
url: https://api.doppler.com/v3/me
headers:
Authorization: Bearer {{ TOKEN }}
Accept: application/json
response_matcher:
- report_response: true
- type: StatusMatch
status:
- 200
- name: Doppler SCIM Token
id: kingfisher.doppler.5
pattern: |
(?x)
\b
(dp\.scim\.[a-zA-Z0-9]{40,44})
\b
min_entropy: 3.3
confidence: medium
examples:
- dp.scim.bAqhcVzrhy5cRHkOlNTc0Ve6w5NUDCpcutm8vGE9myi
references:
- https://docs.doppler.com/reference/api
- https://docs.doppler.com/reference/auth-token-formats
- https://docs.doppler.com/reference/auth-me
validation:
type: Http
content:
request:
method: GET
url: https://api.doppler.com/v3/scim/v2/ServiceProviderConfig
headers:
Authorization: Bearer {{ TOKEN }}
Accept: application/json
response_matcher:
- report_response: true
- type: StatusMatch
status:
- 200
- name: Doppler Audit Token
id: kingfisher.doppler.6
pattern: |
(?x)
\b
(dp\.audit\.[a-zA-Z0-9]{40,44})
\b
min_entropy: 3.3
confidence: medium
examples:
- dp.audit.bAqhcVzrhy5cRHkOlNTc0Ve6w5NUDCpcutm8vGE9myi
references:
- https://docs.doppler.com/reference/api
- https://docs.doppler.com/reference/auth-token-formats
- https://docs.doppler.com/reference/auth-me
validation:
type: Http
content:
request:
method: GET
url: https://api.doppler.com/v3/auditlogs
headers:
Authorization: Bearer {{ TOKEN }}
Accept: application/json
response_matcher:
- report_response: true
- type: StatusMatch
status:
- 200

33
data/rules/dynatrace.yml Normal file
View file

@ -0,0 +1,33 @@
rules:
- name: Dynatrace Token
id: kingfisher.dynatrace.1
pattern: |
(?xi)
\b
(
dt0[a-z][0-9]{2}
\.
[A-Z0-9]{24}
\.
[A-Z0-9]{64}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- dt0c01.ST2EY72KQINMH574WMNVI7YN.G3DFPBEJYMODIDAEX454M7YWBUVEFOWKPRVMWFASS64NFH52PX6BNDVFFM572RZM
references:
- https://docs.dynatrace.com/docs/discover-dynatrace/references/dynatrace-api/basics/dynatrace-api-authentication
validation:
type: Http
content:
request:
method: GET
url: https://api.dynatrace.com/v2/release-notes
headers:
Authorization: "Api-Token {{ TOKEN }}"
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: JsonValid

33
data/rules/easypost.yml Normal file
View file

@ -0,0 +1,33 @@
rules:
- name: EasyPost API token
id: kingfisher.easypost.1
pattern: |
(?x)
\b
(
EZ[AT]K
[a-zA-Z0-9]{54}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- '"EZTKXxNbJDeDLDyrXuIgHd3cr1YmP7MFqY9cHAPYMOXhUN8nJ671JKaGME"'
- EZAK1234abcd5678efgh9012ijkl3456mnop7890qrst1234uvwx5678yz
categories:
- api
- secret
validation:
type: Http
content:
request:
headers:
Authorization: 'Basic {{ TOKEN | append: ":" | b64enc }}'
Content-Type: application/json
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.easypost.com/v2/shipments?page_size=5

81
data/rules/facebook.yml Normal file
View file

@ -0,0 +1,81 @@
rules:
- name: Facebook App ID
id: kingfisher.facebook.1
pattern: |
(?x)
(?i)
\b
(?:facebook|fb)
(?:.|[\n\r]){0,8}?
(?:APP|APPLICATION)
(?:.|[\n\r]){0,16}?
\b
(
\d{15}
)
\b
min_entropy: 2.0
visible: false
confidence: medium
examples:
- '"facebook String appId = "294790898041575"; String appSecret = "ce3f9f0362bbe5ab01dfc8ee565e4372"'
- 'fb_app_id: 123456789012345'
- 'FACEBOOK_APPLICATION_ID=123456789012345'
- name: Facebook Secret Key
id: kingfisher.facebook.2
pattern: |
(?x)(?i)
\b (?: facebook | fb )
.?
(?: api | app | application | client | consumer | customer | secret | key )
.?
(?: key | oauth | sec | secret )?
.{0,2} \s{0,20} .{0,2} \s{0,20} .{0,2}
\b ([a-z0-9]{32}) \b
examples:
- ' # config.facebook.key = "34cebc81c056a21bc66e212f947d73ec"'
- " var fbApiKey = '0278fc1adf6dc1d82a156f306ce2c5cc';"
- ' fbApiKey: "171e84fd57f430fc59afa8fad3dbda2a",'
- '"facebook appSecret = "ce3f9f0362bbe5ab01dfc8ee565e4372"'
validation:
type: Http
content:
request:
headers:
Content-Type: application/json
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: >-
https://graph.facebook.com/v19.0/oauth/access_token
?client_id={{ APIID }}
&client_secret={{ TOKEN }}
&grant_type=client_credentials
depends_on_rule:
- rule_id: kingfisher.facebook.1
variable: APIID
- name: Facebook Access Token
id: kingfisher.facebook.3
pattern: |
(?x)
(?:
\b
(?:facebook|fb\b)
(?:.|[\n\r]){0,32}?
(?:access_token|access[\s-]token)
(?:.|[\n\r]){0,32}?
)?
\b
(EAACEdEose0cBA[a-zA-Z0-9]{20,})
\b
min_entropy: 3.3
confidence: medium
examples:
- "url = 'https://graph.facebook.com/me/friends?access_token=EAACEdEose0cBAD5XZCz5JXYvqyeJzcSvFZC42toHiWyfjhcZCMZBZCpE3uRJnEBsrhUEMRK1wWs6SsdiDCaCI1mYwyoNuMix2XZCpvsKbZB9TumtZBlcLeIpl4pa931Ce9rTinEAhtyVVZAAZAX4NmfpBUqWtzCRC0fX5GZBn7ZC28mPKAZDZD'"
- 'fb_access_token: "EAACEdEose0cBAMZD123456789abcdefghijklmnopqrstuvwxyz"'
- 'FACEBOOK_ACCESS_TOKEN=EAACEdEose0cBAZAQW123456789abcdefghijklmnopqrstuvwxyzASDFGHJKL'

32
data/rules/fastly.yml Normal file
View file

@ -0,0 +1,32 @@
rules:
- name: Fastly API token
id: kingfisher.fastly.1
pattern: |
(?x)(?i)
\b
fastly
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
([a-z0-9_-]{32})
\b
min_entropy: 3.5
confidence: medium
examples:
- 'Fastly token: fgsb3ef237afd6c1b9d91f81cdba64f3'
references:
- https://developer.fastly.com/reference/api/#authentication
validation:
type: Http
content:
request:
headers:
Fastly-Key: '{{ TOKEN }}'
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.fastly.com/current_user

58
data/rules/figma.yml Normal file
View file

@ -0,0 +1,58 @@
rules:
- name: Figma Personal Access Token
id: kingfisher.figma.1
pattern: |
(?x)
\b
(
figd_[A-Za-z0-9_-]{38,42}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- figma pat = figd_rh1234567890123456789012345678901234abcd
- "figma access token: figd_1234567890123456789012345678901234abcdef"
references:
- https://www.figma.com/developers/api#users
validation:
type: Http
content:
request:
headers:
X-Figma-Token: '{{ TOKEN }}'
method: GET
url: https://api.figma.com/v1/me
- name: Figma Personal Access Header Token
id: kingfisher.figma.2
pattern: |
(?x)(?i)
figma
(?:.|[\n\r]){0,32}?
\b
(
[0-9A-F]{4}
-[0-9A-F]{8}
(?:-[0-9A-F]{4}){3}
-[0-9A-F]{12}
)
\b
examples:
- "--header='X-Figma-Token: 1394-0ca7a5be-8e22-40ee-8c40-778d41ab2313'"
references:
- https://www.figma.com/developers/api#users
validation:
type: Http
content:
request:
headers:
X-Figma-Token: '{{ TOKEN }}'
method: GET
url: https://api.figma.com/v1/me
response_matcher:
- report_response: true
- type: WordMatch
words:
- "Invalid token"
negative: true

39
data/rules/fileio.yml Normal file
View file

@ -0,0 +1,39 @@
rules:
- name: FileIO Secret Key
id: kingfisher.fileio.1
pattern: |
(?xi)
\b
fileio
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,16}?
\b
(
[A-Z0-9]{16}
(?:\.[A-Z0-9]{7}){2}
\.[A-Z0-9]{8}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- fileio SECRETKEY = Z9Y8X7W6V5U4T3S2R1Q0.P9O8N7M6L5K4J3H2G1F
- fileio.PRIVATE.TOKEN = F0E1D2C3B4A596877869.5E4D3C2B1A0Z9Y8X7W6V
- fileio_key = M8N6B4V2C0X9Z7L5K3J1.H2G4F6D8S0A9P7O5I3U1
validation:
type: Http
content:
request:
method: GET
url: https://file.io/api/v2/account
headers:
Authorization: "Bearer {{ TOKEN }}"
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: HeaderMatch
header: content-type
expected: ["application/json"]
- type: JsonValid

65
data/rules/finicity.yml Normal file
View file

@ -0,0 +1,65 @@
rules:
- name: Finicity API token
id: kingfisher.finicity.1
pattern: |
(?xi)
\b
finicity
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[a-f0-9]{32}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- finicity_api_token = "5600675afe3e1a0fa76d5b99b30ca7a8"
validation:
type: Http
content:
request:
method: GET
url: https://api.finicity.com/aggregation/v2/transactions
headers:
Content-Type: application/json
x-finicity-app-key: '{{ TOKEN }}'
response_matcher:
- report_response: true
- type: WordMatch
words:
- '"transactions":'
- name: Finicity client secret
id: kingfisher.finicity.2
pattern: |
(?x)
(?i)
\b
finicity
(?:.|[\n\r]){0,64}?
\b
(
[a-z0-9]{20}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- finicity-clientsecret = 'geruj4LFyu0NBeBOnQvo'
validation:
type: Http
content:
request:
method: POST
url: https://api.finicity.com/aggregation/v2/customers
headers:
Content-Type: application/json
x-finicity-secret: '{{ TOKEN }}'
response_matcher:
- report_response: true
- type: StatusMatch
status:
- 200

30
data/rules/frame.io.yml Normal file
View file

@ -0,0 +1,30 @@
rules:
- name: Frame.io API token
id: kingfisher.frame.io.1
pattern: |
(?xi)
\b
(
fio-u-(?:[A-Z0-9_-]{16}){4}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- fio-u-TaWoPIBovaGCbBkUtGPKWS0D3cu254VA33IFCCrtwl8J2Dtq2pMJ9MvNHmNoL2XX
- ffio-u-TaWoPIBovaGCbBkUtGPKWS0D3cu254VA33IFCCrtwl8J2Dtq2pMJ9MvNHmNoL2XX
references:
- https://developer.frame.io/api/reference/operation/getMe/
validation:
type: Http
content:
request:
headers:
Authorization: "Bearer {{ TOKEN }}"
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.frame.io/v2/me

61
data/rules/gcp.yml Normal file
View file

@ -0,0 +1,61 @@
rules:
- name: GCP API Token
id: kingfisher.gcp.1
pattern: |
(?x)
(?m)
(?i)
(?s)
(
\{[^{}]*
\"auth_provider_x509_cert_url\":
.{0,512}?
}
)
|
\{
(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*
"auth_provider_x509_cert_url":\s*".+?"
(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*
\}
min_entropy: 4.5
confidence: high
examples:
- '{"auth_provider_x509_cert_url":DFD5dC97C711CE1E078FD0A5CBCE1fE895D44abb47faeD9280db6137a35EDdfBd173Dbf57B1e54c10dD9B8E36bdE4816EedEF6c55D9ca4FC14dEf71c9bNd6a5aF9c46EC6BdfCD4f7fEeb2B9E96A8CfBD9a965bbaDAACe0d09ffe19Fc5BF3B6c9636A9b35d0cFcfC0ECb118edf8E2dEae3f1D3C36F5Da9DBe562f3E2deb9D20f4ndDC106bdEdBeaB5629362eE6d0C1b90e84A6C281C14c2ade40c3AF17ACeEfe0e998d5f3BaadbB7D479ab3cEbc8Be263Dc07dF2}'
- |-
{
"admin": {
"credential": {
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"client_email": "firebase-example@firebase-example.iam.gserviceaccount.com",
"client_id": "218284793146200123456",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-example-a1mrc%40firebase-example.iam.gserviceaccount.com",
"database_url": "https://example.firebaseio.com"
}
}
}
validation:
type: GCP
- name: GCP Private Key ID
id: kingfisher.gcp.3
pattern: |
(?x)
(?i)
\b
gcp
(?:
.{0,20}?
(?:key|secret)
)
.{0,12}
[=:]
\s{0,8}
["']?
([0-9a-z]{35,40})
["']?
\b
min_entropy: 3.5
confidence: medium
examples:
- gcp_secret = ANzaSy0c3475372a7b10f7740dbda47abfdca42

196
data/rules/generic.yml Normal file
View file

@ -0,0 +1,196 @@
rules:
- name: Generic Secret
id: kingfisher.generic.1
pattern: |
(?xi)
secret
.{0,20}
\b
([0-9a-z]{32,64})
\b
min_entropy: 3.3
confidence: low
examples:
- ' private static String CLIENT_SECRET = "6fb1cff7690db9ac066cadbbde8e3c078efdabcf";'
- name: Generic API Key
id: kingfisher.generic.2
pattern: |
(?xi)
(?: api_key | apikey | access_key | accesskey )
(?:.|[\n\r]){0,8}?
\b
([0-9a-z][0-9a-z\-._/+]{30,62}[0-9a-z])
\b
min_entropy: 3.3
confidence: low
examples:
- 'API_KEY = "951bc382db9abad29c68634761dd6e19"'
- "buildConfigField 'String' , 'API_KEY' , '\"951bc382db9cfee29c68634761dd6e19\"'\tAPI_KEY\t"
- name: Generic Username and Password
id: kingfisher.generic.3
pattern: |
(?xi)
(?: username | user)
\b
(?:.|[\n\r]){0,16}?
(?: password | pass )
(?:.|[\n\r]){0,16}?
["'] ([^"']{5,30}) ["']
min_entropy: 3.3
confidence: low
examples:
- |
credential = UsernamePasswordCredential(
client_id='da34859b-2ae4-48c3-bfe0-1b28b7cf2eed',
username='donjuandemarco',
password='1qay@WXS????',
tenant_id='bc877b20-f135-4c13-a266-8ed26b8f0f4b')
- |
hostname = '10.11.12.13'
username = 'donjuandemarco@example.com'
password = '`123QWERasdf'
- |
hostname = '10.11.12.13'
USERNAME = 'donjuandemarco@example.com'
# some comment
# some other comment
PASS = '`123QWERasdf'
- |
user = 'abuser' # some comment
password = 'abuser123456' # some other comment
- |
user = 'Aladdin'
password = 'open sesame'
- name: Generic Username and Password
id: kingfisher.generic.4
pattern: |
(?xi)
(?: username | user)
\b
(?:.|[\n\r]){0,16}?
(?: password | pass )
(?:.|[\n\r]){0,16}?
(\S{5,30})
(?: \s | $ )
min_entropy: 3.3
confidence: low
examples:
- |
user = Aladdin
password = open_sesame
- |
user = Aladdin
// some comment
// some other comment
password = open_sesame
- ":authn_dbd_params => 'host=db_host port=3306 user=apache password=###### dbname=apache_auth',"
- name: Generic Password
id: kingfisher.generic.5
pattern: |
(?x)(?i)
password
\b
(?:.|[\n\r]){0,16}?
["']
([^$<%@.,\s'"(){}&/\#\-][^\s'"(){}/]{4,}) (?# password )
["']
min_entropy: 3.3
confidence: low
categories: [fuzzy, generic, secret]
examples:
- |
password = "super$ecret"
- |
password="super$ecret"
- |
String usernamePassword = "application:" + appKey + ":" + appSecret;
- |
my_password: "super$ecret"
- |
"password": "super$ecret",
- |
my_password := "super$ecret"
- |
password => "super$ecret"
- |
"ApplicationServicesConnection" : {
"ServiceAddress" : "https://services-dev.examples.com",
"AdminPassword" : "thisismypassword"
}
- |
private const string DevFolkoosComPfxPassword = "thisismypassword";
- |
"password": "YOURPASSWROD"
- |
create_random_name('sfrp-cli-cert2', 24),
'cluster_name': self.create_random_name('sfrp-cli-', 24),
'vm_password': "Pass123!@#",
'policy_path': os.path.join(TEST_DIR, 'policy.json')
})
- name: Weak Password Pattern
id: kingfisher.weak_password.1
pattern: |
(?xi)
\b
(
blink\d{3,6}
|correcthorsebatterystaple\d{0,6}
|letmein\d{1,6}
|newpass\d{1,6}
|p@ssw0rd\d{0,6}
|pa55word\d{0,6}
|pass4now\d{0,6}
|password\d{1,6}
|qwer\d{4,6}
|qwerty\d{3,6}
|trustno\d{1,6}
)
\b
min_entropy: 1.0
confidence: low
examples:
- password123
- blink5678
- letmein42
- p@ssw0rd99
- qwerty456
- name: Generic Username and Password
id: kingfisher.generic.8
pattern: |
(?xi)
(?: db_user | db_USERNAME | db_name)
\b
(?:.|[\n\r]){0,8}?
["'] ([^"']{5,40}) ["']
(?:.|[\n\r]){0,32}?
(
db_password | db_pass
\b
(?:.|[\n\r]){0,16}?
["'] [^"']{5,40}
) ["']
min_entropy: 3.3
confidence: low
examples:
- |
credential = UsernamePasswordCredential(
client_id='da34859b-2ae4-48c3-bfe0-1b28b7cf2eed',
username='donjuandemarco',
password='1qay@WXS????',
tenant_id='bc877b20-f135-4c13-a266-8ed26b8f0f4b')
- |
hostname = '10.11.12.13'
username = 'donjuandemarco@example.com'
password = '`123QWERasdf'
- |
hostname = '10.11.12.13'
USERNAME = 'donjuandemarco@example.com'
# some comment
# some other comment
PASS = '`123QWERasdf'
- |
user = 'abuser' # some comment
password = 'abuser123456' # some other comment
- |
user = 'Aladdin'
password = 'open sesame'

205
data/rules/github.yml Normal file
View file

@ -0,0 +1,205 @@
rules:
- name: GitHub Personal Access Token
id: kingfisher.github.1
pattern: |
(?x)
(?i)
\b
(
(?: # for token prefixes
ghp| # Personal Access Token
gho| # OAuth Token
ghu| # GitHub App User-to-Server Token
ghs| # GitHub App Server-to-Server Token
ghr| # Refresh Token
github_pat # Alternative format for Personal Access Token
)_
(?: # for token body
[a-z0-9_]{35,235} # 35 to 235 lowercase alphanumeric characters or underscores
)
)
\b
min_entropy: 3.5
examples:
- "GITHUB_KEY=ghp_XIxB7KMNdAr3zqWtQqhE94qglHqOzn1D1stg"
- "let g:gh_token='ghp_4U3LSowpDx8XvYE7A8GH56oxU5aWnY2mzIbV'"
- |
## git developer settings
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
- "oauth_token: gho_fq75OMU7UVbS9pTZmoCCzJT6TM5d1w099FgG"
- "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"
references:
- https://docs.github.com/en/rest/users?apiVersion=2022-11-28
validation:
type: Http
content:
request:
method: POST
url: https://api.github.com/graphql
headers:
Authorization: token {{ TOKEN }}
Accept: application/vnd.github+json
Content-Type: application/json
body: |
{
"query": "{ viewer { login } }"
}
response_matcher:
- report_response: true
- match_all_words: true
type: WordMatch
words:
- '"login"'
- name: GitHub OAuth Access Token
id: kingfisher.github.2
pattern: |
(?x)
\b
(
gho_
[a-zA-Z0-9]{36}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- ' "url": "git+https://FelipeMestre:gho_psT9pqNFsehnc4se0ZzzR0HBxapxZD35hNHi@github.com/gontarz/PW_2021_Website-FelipeMestre.git"'
- ' oauth_token: gho_fq75OMU7UVbS9pTZmoCCzJT6TM5d1w099FgG'
references:
- https://docs.github.com/en/rest/users?apiVersion=2022-11-28
validation:
type: Http
content:
request:
method: POST
url: https://api.github.com/graphql
headers:
Authorization: token {{ TOKEN }}
Accept: application/vnd.github+json
Content-Type: application/json
body: |
{
"query": "{ viewer { login } }"
}
response_matcher:
- report_response: true
- match_all_words: true
type: WordMatch
words:
- '"login"'
- name: GitHub App Token
id: kingfisher.github.3
pattern: '\b((?:ghu|ghs)_[a-zA-Z0-9]{36})\b'
examples:
- ' "token": "ghu_16C7e42F292c69C2E7C10c838347Ae178B4a",'
- |
Example usage:
git clone http://ghs_RguXIkihJjwHAP6eXEYxaPNvywurTr5IOAbg@github.com/username/repo.git
references:
- https://docs.github.com/en/rest/users?apiVersion=2022-11-28
validation:
type: Http
content:
request:
method: POST
url: https://api.github.com/graphql
headers:
Authorization: token {{ TOKEN }}
Accept: application/vnd.github+json
Content-Type: application/json
body: |
{
"query": "{ viewer { login } }"
}
response_matcher:
- report_response: true
- match_all_words: true
type: WordMatch
words:
- '"login"'
- name: GitHub Refresh Token
id: kingfisher.github.4
pattern: '\b(ghr_[a-zA-Z0-9]{76})\b'
examples:
- ' "refresh_token": "ghr_1B4a2e77838347a7E420ce178F2E7c6912E169246c3CE1ccbF66C46812d16D5B1A9Dc86A1498",'
references:
- https://docs.github.com/en/rest/users?apiVersion=2022-11-28
validation:
type: Http
content:
request:
method: POST
url: https://api.github.com/graphql
headers:
Authorization: token {{ TOKEN }}
Accept: application/vnd.github+json
Content-Type: application/json
body: |
{
"query": "{ viewer { login } }"
}
response_matcher:
- report_response: true
- match_all_words: true
type: WordMatch
words:
- '"login"'
- name: GitHub Client ID
id: kingfisher.github.5
pattern: |
(?x)(?i)
(?:github)
.?
(?: api | app | application | client | consumer | customer )?
.?
(?: id | identifier | key )
.{0,2} \s{0,20} .{0,2} \s{0,20} .{0,2}
\b ([a-z0-9]{20}) \b
examples:
- |
GITHUB_CLIENT_ID=ac58d6da7d7a84c039b7
GITHUB_SECRET=37d02377a3e9d849e18704c3ec883f9c5787d857
- name: GitHub Secret Key
id: kingfisher.github.6
pattern: |
(?x)(?i)
github
.?
(?: api | app | application | client | consumer | customer | secret | key )
.?
(?: key | oauth | sec | secret )?
.{0,2} \s{0,20} .{0,2} \s{0,20} .{0,2}
\b ([a-z0-9]{40}) \b
examples:
- |
GITHUB_CLIENT_ID=ac58d6da7d7a84c039b7
GITHUB_SECRET=37d02377a3e9d849e18704c3ec883f9c5787d857
- name: GitHub Personal Access Token (fine-grained permissions)
id: kingfisher.github.7
pattern: |
(?x)
\b
(github_pat_[0-9a-zA-Z_]{82})
\b
examples:
- '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'
validation:
type: Http
content:
request:
method: POST
url: https://api.github.com/graphql
headers:
Authorization: token {{ TOKEN }}
Accept: application/vnd.github+json
Content-Type: application/json
body: |
{
"query": "{ viewer { login } }"
}
response_matcher:
- report_response: true
- match_all_words: true
type: WordMatch
words:
- '"login"'

89
data/rules/gitlab.yml Normal file
View file

@ -0,0 +1,89 @@
rules:
- name: GitLab Private Token
id: kingfisher.gitlab.1
pattern: |
(?x)
\b
(
glpat-
[0-9a-zA-Z_-]{20}
)
(?:\b|$)
min_entropy: 3.5
confidence: medium
examples:
- glpat-kSaPeOD_-T0JxMi3p28B
- |
docker build -t tweedledee \
-f Dockerfile \
--build-arg 'GO_REPO_TOKEN=glpat-tFrjFXD7soVU2fqxuDMh' \
references:
- https://docs.gitlab.com/api/users/#get-your-user-status
validation:
type: Http
content:
request:
headers:
PRIVATE-TOKEN: '{{ TOKEN }}'
method: GET
response_matcher:
- report_response: true
- type: WordMatch
words:
- '"message"'
url: https://gitlab.com/api/v4/user/status
- name: GitLab Runner Registration Token
id: kingfisher.gitlab.2
pattern: '\b(GR1348941[0-9a-zA-Z_-]{20})(?:\b|$)'
examples:
- |
sudo gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
--registration-token "GR1348941_iAgdMy7a3NhZaa5oNoH" \
--executor "docker" \
--docker-image ubuntu:latest \
--description "docker-runner" \
--tag-list "docker, CICD, App" \
--run-untagged="true" \
--locked="false" \
--access-level="not_protected"
validation:
type: Http
content:
request:
method: POST
headers:
Content-Type: application/x-www-form-urlencoded
Accept: application/json
body: token={{ TOKEN }}
response_matcher:
- report_response: true
- type: StatusMatch
status: [200, 201]
url: https://gitlab.com/api/v4/runners/verify
- name: GitLab Pipeline Trigger Token
id: kingfisher.gitlab.3
pattern: '\b(glptt-[0-9a-f]{40})\b'
examples:
- |
curl \
-X POST \
--fail \
--no-progress-meter \
-F token=glptt-0d66598d696a02da33fb65e2a041f607c68ea50d \
-F ref=main
validation:
type: Http
content:
request:
headers:
PRIVATE-TOKEN: '{{ TOKEN }}'
method: GET
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
url: https://gitlab.com/api/v4/ci/pipeline_triggers/{{ TOKEN }}

40
data/rules/gocardless.yml Normal file
View file

@ -0,0 +1,40 @@
rules:
- name: GoCardless API Token
id: kingfisher.gocardless.1
pattern: |
(?xi)
\b
gocardless
(?:.|[\n\r]){0,16}?
\b
(
live_
[A-Za-z0-9=_-]{16}
(?:[A-Za-z0-9=_-]{8}){3}
[A-Za-z0-9=_-]{0,2}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- 'gocardless_token = "live_8uq9fsUA28SqKT=CTsQxgKrqB6_7QV5tA39I8y5H'
- GOCARDLESS_LIVE_KEY = "live_cpo0k9jbnb2djeaq=tga45ua_bnhev5ivv294a6cs"
categories:
- api
- payment
- identifier
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
Accept: application/json
GoCardless-Version: "2015-07-06"
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.gocardless.com/customers?limit=1

96
data/rules/google.yml Normal file
View file

@ -0,0 +1,96 @@
rules:
- name: Google Client ID
id: kingfisher.google.1
pattern: '(?i)\b([0-9]+-[a-z0-9_]{32})\.apps\.googleusercontent\.com'
min_entropy: 3.3
confidence: medium
visible: false
examples:
- " 'clientID' : '231545488769-4d1mcev9vifvlncrern52id2pqqf5u5l.apps.googleusercontent.com',"
- " //$google_client_id = '244082345999-o6m8f1pmb1e76tjfj9v7b96j31e53ps5.apps.googleusercontent.com';"
- " GOOGLE_OAUTH2_CLIENT_ID = '607830223128-4qgthc7ofdqce232dk690t5jgkm1ce33.apps.googleusercontent.com'"
- ' $cordovaOauth.google("653512027492-5u9blotr1521fa0lo1172nhv4pmqgttq.apps.googleusercontent.com", ["email"]).then(function(result) {'
- name: Google OAuth Client Secret
id: kingfisher.google.2
pattern: |
(?x)
\b
(GOCSPX-[a-zA-Z0-9_-]{28})
(?:[^a-zA-Z0-9_-] | $)
min_entropy: 3.3
confidence: medium
examples:
- 'const CLIENTSECRET = "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"'
- name: Google OAuth Client Secret
id: kingfisher.google.3
pattern: |
(?x)(?i)
client.?secret .{0,10}
\b
([a-z0-9_-]{24})
(?: [^a-z0-9_-] |$)
min_entropy: 3.3
confidence: medium
examples:
- '"client_secret":"aaaaaaaaaaaaaaaaaaaaaaa-"'
- " //$google_client_secret = 'fnhqAakzWrX-mtFQ4PRdMoy0';"
- " 'clientSecret' : 'Ufvuj-d6alhwGKvvLh_8Nq0K'"
- name: Google OAuth Access Token
id: kingfisher.google.4
pattern: |
(?x)
\b
(ya29\.[0-9A-Za-z_-]{20,1024})
(?: [^0-9A-Za-z_-]|$)
min_entropy: 3.3
confidence: medium
examples:
- |
const setupCredentials = () => {
const { encryptedData, iv } = encrypt({
expiry_date: 1642441058842,
access_token:
'ya29.A0ARrdaM--PV_87ebjywDJpXKb77NBFJl16meVUapYdfNv6W6ZzCu947fNaPaRjbDbOIIcp6f49cMaX5ndK9TAFnKwlVqz3nrK9nLKqgyDIhYsIq47smcAIZkK56SWPx3X3DwAFqRu2UPojpd2upWwo-3uJrod',
// This token is linked to a test Google account (typebot.test.user@gmail.com)
refresh_token:
'1//039xWRt8YaYa3CgYIARAAGAMSNwF-L9Iru9FyuTrDSa7lkSceggPho83kJt2J29Ga91EhT1C6XV1vmo6bQS9puL_R2t8FIwR3gek',
})
- |
-- Clear login if it's a new connection.
--propertyTable.access_token = 'ya29.Ci_UA7aEsvT6-oVI8f96kvB6i8oO13WgdZUviLaCVtpEPYZqhQcQycR-u2X9xtmYGA'
- name: Google OAuth Credentials
id: kingfisher.google.6
pattern: |
(?x)
\b
([0-9]+-[a-z0-9_]{32}\.apps\.googleusercontent\.com)
(?:
(?s).{0,40}
)
\b
(?:
(GOCSPX-[a-zA-Z0-9_-]{28})
|
(?:
(?i) client.?secret .{0,10} \b ([a-zA-Z0-9_-]{24})
)
)
(?:[^a-zA-Z0-9_-] | $)
min_entropy: 3.3
confidence: medium
examples:
- |
const CLIENT_ID = '304167046824-45h8no7j0s38akv998nivvb7i17ckqeh.apps.googleusercontent.com';
const CLIENT_SECRET = '1QcFpNjHoAf4_XczYwhYicTl';
- |
public static GAPIS_CREDENTIALS = {
// 1. Generate credentials: https://console.cloud.google.com/apis/
// 2. Create OAuth page and set spreadsheets and drive.metadata.readonly scopes
client_id: '132261435625-69ubohrvppjr9hcc5t9uighsb7j2cqhv.apps.googleusercontent.com',
client_secret: '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',
redirect_uri: `http://localhost:${Config.OAUTH_HTTP_PORT}/oauth2callback`
};

View file

@ -0,0 +1,32 @@
rules:
- name: Google OAuth2 Access Token
id: kingfisher.google.oauth2.1
pattern: |
(?xi)
\b
(
ya29\.(?i:[a-z0-9_-]{30,})
)
\b
min_entropy: 3.5
confidence: medium
examples:
- "ya29.A0ARrdaM9Ra8K7R9AcxA1PpIMLVQ021H0TL0PRh2s_HH0_tn5gCSSf"
- "ya29.Cj0KCQjwgLr5BRC3ARIsAKEd0AR9_Fg5fjV5pVXZlFYobyfz7Bb9SyOv"
validation:
type: Http
content:
request:
headers:
Authorization: "Bearer {{ TOKEN }}"
Accept: application/json
method: GET
url: https://www.googleapis.com/oauth2/v3/userinfo
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
- type: WordMatch
words:
- '"email":'

25
data/rules/jwt.yml Normal file
View file

@ -0,0 +1,25 @@
rules:
- name: JSON Web Token (base64url-encoded)
id: kingfisher.jwt.1
pattern: |
(?x)
\b
(
ey[a-zA-Z0-9_-]{12,} (?# header )
\.
ey[a-zA-Z0-9_-]{12,} (?# payload )
\.
[a-zA-Z0-9_-]{12,} (?# signature )
)
(?:[^a-zA-Z0-9_-]|$) (?# this instead of a \b anchor because that doesn't play nicely with `-` )
min_entropy: 3.3
confidence: medium
examples:
- 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmEmMjLiuyu5CSpyHI'
- 'NUCLEAR_SERVICES_ANON_KEY=eyJhbGciOiJIUzI1NiIsEnR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFqcnVqc2lzY2Nzdnl2am5xdG5xIiwicm9sZSI6ImEub24iLCJpYXQiOjE2NTY1OTY0NjEsImV4cCI6MTk3MjE3MjQ2MX0.WQWcwBAQFNE259f2o8ruFln_UMLTFEnEaUD7KHrs9Aw'
references:
- https://en.wikipedia.org/wiki/JSON_Web_Token
- https://datatracker.ietf.org/doc/html/rfc7519
- https://en.wikipedia.org/wiki/Base64#URL_applications
- https://datatracker.ietf.org/doc/html/rfc4648
- https://developer.okta.com/blog/2018/06/20/what-happens-if-your-jwt-is-stolen

96
data/rules/mongodb.yml Normal file
View file

@ -0,0 +1,96 @@
rules:
- name: MongoDB API Private Key
id: kingfisher.mongodb.1
pattern: |
(?x)
(?i)
(?:
(?:\b|_|-|\.)
(?:mongodb|atlas)
(?:\b|_|-|\.)
)
.{0,1000}?
(?:private|priv|secret|auth|pass|key)
(?:.|[\n\r]){0,32}?
(
[a-fA-F0-9]{8}
-
[a-fA-F0-9]{4}
-
[a-fA-F0-9]{4}
-
[a-fA-F0-9]{4}
-
[a-fA-F0-9]{12}
)
min_entropy: 3.7
examples:
- ATLAS_PRIVATE_KEY=4b18315e-6b7d-4337-b449-5d38f5a189ec
validation:
type: Http
content:
request:
headers:
Accept: application/vnd.atlas.2023-02-01+json
Content-Type: application/json
method: GET
digest: '{{ PUBKEY | append: ":" | append: TOKEN }}'
response_matcher:
- report_response: true
- match_all_words: true
type: WordMatch
words:
- '"orgId":'
- '"id":'
url: https://cloud.mongodb.com/api/atlas/v2/groups
depends_on_rule:
- rule_id: "kingfisher.mongodb.2"
variable: PUBKEY
- name: MongoDB API PUBLIC Key
id: kingfisher.mongodb.2
pattern: |
(?xi)
(?:
(?:\b|_|-|\.)
(?:mongodb|atlas)
(?:\b|_|-|\.)
)
(?:public|pub|user|id)
(?:.|[\n\r]){0,4}?
(
[a-zA-Z]+
)
(?:$|[^a-zA-Z0-9/+=-])
min_entropy: 2.0
confidence: medium
visible: false
examples:
- 'mongodb-public: qj4Zrh8e6A'
- name: MongoDB URI Connection String
id: kingfisher.mongodb.3
pattern: |
(?x)
(?i)
\b
(
mongodb(?:\+srv)?://[\S]{3,50}:(?:[\S]{3,88})@[-.%\w/:]+
)
\b
min_entropy: 3
examples:
- client = mongoc_client_new ("mongodb+srv://someuser:hunter2@my-atlas-rd941.mongodb.net/test?retryWrites=true&w=majority");
- "mongodb+srv://user:passw0rd@cluster0.something.mongodb.net/"
- "mongodb://mongoadmin:contoso@something.foo.mongodb.net/myFirstDatabase"
- name: MongoDB Atlas Service Account Token
id: kingfisher.mongodb.4
pattern: |
(?xi)
\b
(
mdb_sa_sk_[0-9a-zA-Z_-]{6}[0-9a-zA-Z]{34}
)
\b
min_entropy: 3.5
examples:
- mdb_sa_sk_BdIX_jLzut2WTgglKzKvSgWMDDj5hEoTqdwOyLOL

7
data/rules/mysql.yaml Normal file
View file

@ -0,0 +1,7 @@
rules:
- name: MySQL URI with Credentials
id: kingfisher.mysql.1
pattern: (?xi)\bmysql:\/\/[a-z0-9]+:([a-z0-9!@\#$%^&*()_+{}|:<>?=\\-]+)@[a-z0-9.]+:[0-9]+\/[a-z0-9]+\b
min_entropy: 3.5
examples:
- CONNECTION_URI="mysql://nimda:m42p!o@2wd@google.com:5434/elephant"

73
data/rules/odbc.yml Normal file
View file

@ -0,0 +1,73 @@
rules:
- name: Credentials in ODBC Connection String
id: kingfisher.odbc.1
pattern: |
(?x)(?i)
(?: User | User\ Id | UserId | Uid) \s*=\s* ([^\s;]{3,100}) \s* ;
[\ \t]* .{0,10} [\ \t]*
(?: Password | Pwd) \s*=\s* ([^\t\ ;]{3,100}) \s* (?: [;] | $)
min_entropy: 3.3
confidence: medium
examples:
- |
//Database Info
$host = "localhost";
$database = "NHOHVA";
$user = "mg1021"; $password = "goodspec";
- |
//Database Info
$host = "localhost";
$database = "NHOHVA";
$user = "mg1021"; $password = goodspec;
- 'Server=host;Port=5432;User Id=username;Password=secret;Database=databasename;'
- 'Server=host;Port=5432;SomeOtherKey=SomeOtherValue;User Id=username;Password=secret;Database=databasename;'
- 'Data Source=190.190.200.100,1433;Network Library=DBMSSOCN;Initial Catalog=myDataBase;User ID=myUsername;Password=myPassword;'
- 'Data Source=190.190.200.100,1433;Network_library=DBMSSOCN;Initial Catalog=myDataBase;User ID=myUsername;Password=myPassword;'
- 'Provider=SQLNCLI;Server=myServerName,myPortNumber;Database=myDataBase;Uid=myUsername;Pwd=myPassword;'
- |
adoConn.Open("Provider=SQLOLEDB.1;User ID=specialbill_user; " & "Password =specialbill_user;Initial Catalog=SpecialBill_PROD;Data Source=uszdba01;")
- |
"driver={SQL Server};server=(#{datastore['DBHOST']});database=#{datastore['DBNAME']};uid=#{datastore['DBUID']};pwd=#{datastore['DBPASSWORD']}"
negative_examples:
- 'def login(self, user = "", password = "", domain = ""):'
- |
if datastore['VERBOSE']
text = ''
text << "User=#{username}, "
text << "Password=#{password}, "
text << "Domain=#{domain}, "
text << "Full Name=#{full_name}, "
text << "E-mail=#{e_mail}"
print_good(text)
- |
if (len < ulen + wlen + 2)
break;
user = (char *) (p + 1);
pwd = (char *) (p + ulen + 2);
p += ulen + wlen + 2;
- |
/* Set default values */
server = xmalloc(sizeof(*server));
server->user = "anonymous";
server->password = "busybox@";
- |
System.out.println("Here we go...");
String url = "jdbc:msf:sql://127.0.0.1:8080/sample";
String userid = "userid";
String password = "password";
- |
char *domain = NULL;
char *user = NULL;
char *password = NULL;
- |
<?php
\$user = \$_POST["username"];
\$pwd = \$_POST["password"];
\$otherdata = \$_POST["otherdata"];
?>
references:
- https://docs.aws.amazon.com/redshift/latest/mgmt/configure-odbc-connection.html
- https://docs.microsoft.com/en-us/azure/data-explorer/kusto/api/connection-strings/kusto
- https://docs.microsoft.com/en-us/azure/mariadb/howto-connection-string
- https://docs.microsoft.com/en-us/azure/mysql/single-server/how-to-connection-string
- https://www.connectionstrings.com/

55
data/rules/okta.yml Normal file
View file

@ -0,0 +1,55 @@
rules:
- name: Okta API Token
id: kingfisher.okta.1
pattern: |
(?xi)
(?s)
(?:okta|ssws)
(?:.|[\n\r]){0,64}?
\b
(
00[a-z0-9_-]{39}[a-z0-9_]
)
min_entropy: 3.3
examples:
- okta_api_token=00hqNORUpnTcdFWA5WEM4YwOkw6RXeFw21lFDRKmY1
- 'okta_api_token = 00aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
- 'OKTA_API_KEY = "00-aaaaaaaaaaaaa-aaaaaaaaaaaaaaaaaaaaaaaaa"'
- 'okta_secret: 00QCjAl4MlV-WPXM-ABCDEFGHIJKL-0HmjFx-vbGua'
- 'Authorization: SSWS 00QCjAl4MlV-WPXM-ABCDEFGHIJKL-0HmjFx-vbGua'
- |
variable "corp_okta_api_token" {
default = "004EWTpRQT_HJtG_nL-agxacgzYHjxPcF99kJsFzWg"
}
validation:
type: Http
content:
request:
headers:
Accept: application/json
Authorization: SSWS {{ TOKEN }}
Content-Type: application/json
method: GET
response_matcher:
- report_response: true
- match_all_words: true
type: WordMatch
words:
- activated
url: https://{{ DOMAIN }}/api/v1/users/me
depends_on_rule:
- rule_id: "kingfisher.okta.2"
variable: DOMAIN
- name: Okta Domain
id: kingfisher.okta.2
pattern: |
(?xi)
\b
(
[a-z0-9-]{1,40}\.okta(?:preview|-emea)?\.com
)
\b
min_entropy: 3
visible: false
examples:
- company-name.okta.com

30
data/rules/openai.yml Normal file
View file

@ -0,0 +1,30 @@
rules:
- name: OpenAI API Key
id: kingfisher.openai.1
pattern: |
(?x)
\b
(
sk-[a-zA-Z0-9]{48}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- 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
- curl https://api.openai.com/v1/images/generations -H 'Content-Type application/json' -H "Authorization Bearer sk-mxIt5s1tyfCJyIKHwrqOT4BlbkFJT3VVmv6VdSwB7XXIq1TO"
references:
- https://help.openai.com/en/articles/9132009-how-can-i-view-the-users-or-organizations-associated-with-an-api-key
validation:
type: Http
content:
request:
headers:
Authorization: "Bearer {{ TOKEN }}"
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://api.openai.com/v1/me

68
data/rules/pem.yml Normal file
View file

@ -0,0 +1,68 @@
rules:
- name: PEM-Encoded Private Key
id: kingfisher.pem.1
pattern: |
(?x)
-----BEGIN\ .{0,20}\ ?PRIVATE\ KEY\ ?.{0,20}-----
\s*
( (?: [a-zA-Z0-9+/=\s"',] | \\r | \\n ) {50,} )
\s*
-----END\ .{0,20}\ ?PRIVATE\ KEY\ ?.{0,20}-----
min_entropy: 4.5
confidence: high
prevalidated: false
examples:
- |
-----BEGIN RSA PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAIEAtDSHFO5tfN+jYMJuiNvBaplkSI3eFqKMLOvXyVu+dmSEic6xyKWQ
qjQiFpXogArvAq2tBxWOq7F+a6rNhDKdICD2amRwDHqKD1bzXVSZ5c1XnpCFsBiQaEyX2i
qyjScnntFHIpTCVHNxILDxsStocj64YS0C7hfCGVhft/Ts/O0AAAIQJOKnUyTip1MAAAAH
c3NoLXJzYQAAAIEAtDSHFO5tfN+jYMJuiNvBaplkSI3eFqKMLOvXyVu+dmSEic6xyKWQqj
QiFpXogArvAq2tBxWOq7F+a6rNhDKdICD2amRwDHqKD1bzXVSZ5c1XnpCFsBiQaEyX2iqy
jScnntFHIpTCVHNxILDxsStocj6Cf99C7hfCGVhft/Ts/O0AAAADAQABAAAAgBcaTN8gGi
VSPo3fH3CoS8mw1KyAk6JvQG1Z5xZHjsl65YsNVrmUkFFh0aT3nxEbVb0QKwineN0GKmD/
Ss3R91a573gzli7TJPFCHhhBbE7FRC4KQMTc1/UANwFYQVcfZ4n9IVHr3jiWToSY3XbC66
Zcd0sg+d+YRjIxUktuNFHBAAAAQQCOOKbSUJAWzcTDbxImwDCAfBMlEeMAnJrwobL/zxbT
GhKdnqnomoreFdYL8vOcOlwZG0hUKIA6AM1GsMzp6aCwAAAAQQDmAABpOQnkDy8v8kTDhP
dW3lAqRGOU4WRWj7WystQv/VjuJpceekhOyhNJBuNHDKZ3IT1agAZHIhhL+webE2S1AAAA
QQDIk4H1agCohlHUg50PcyKzE/zZ85Gw0ErTmgqIIGd4B1AqUtjwVe1qFoqHuZPtq2cbVF
1HTHh6GX//J6rKWVJZAAAAGWJsYXJzZW5AYnJhZGZvcmRzLW1icC5sYW4B
-----END RSA PRIVATE KEY-----
- |
"-----BEGIN RSA PRIVATE KEY-----" +
"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn" +
"NhAAAAAwEAAQAAAIEAtDSHFO5tfN+jYMJuiNvBaplkSI3eFqKMLOvXyVu+dmSEic6xyKWQ" +
"qjQiFpXogArvAq2tBxWOq7F+a6rNhDKdICD2amRwDHqKD1bzXVSZ5c1XnpCFsBiQaEyX2i" +
"qyjScnntFHIpTCVHNxILDxsStocj6Cf99C7hfCGVhft/Ts/O0AAAIQJOKnUyTip1MAAAAH" +
"c3NoLXJzYQAAAIEAtDSHFO5tfN+jYMJuiNvBaplkSI3eFqKMLOvXyVu+dmSEic6xyKWQqj" +
"QiFpXogArvAq2tBxWOq7F+a6rNhDKdICD2amRwDHqKD1bzXVSZ5c1XnpCFsBiQaEyX2iqy" +
"jScnntFHIpTCVHNxILDxsStocj6Cf99C7hfCGVhft/Ts/O0AAAADAQABAAAAgBcaTN8gGi" +
"VSPo3fH3CoS8mw1KyAk6JvQG1Z5xZHjsl65YsNVrmUkFFh0aT3nxEbVb0QKwineN0GKmD/" +
"Ss3R91a573gzli7TJPFCHhhBbE7FRC4KQMTc1/UANwFYQVcfZ4n9IVHr3jiWToSY3XbC66" +
"Zcd0sg+d+YRjIxUktuNFHBAAAAQQCOOKbSUJAWzcTDbxImwDCAfBMlEeMAnJrwobL/zxbT
GhKdnqnomoreFdYL8vOcOlwZG0hUKIA6AM1GsMzp6aCwAAAAQQDmAABpOQnkDy8v8kTDhP
dW3lAqRGOU4WRWj7WystQv/VjuJpceekhOyhNJBuNHDKZ3IT1agAZHIhhL+webE2S1AAAA
QQDIk4H1agCohlHUg50PcyKzE/zZ85Gw0ErTmgqIIGd4B1AqUtjwVe1qFoqHuZPtq2cbVF
1HTHh6GX//J6rKWVJZAAAAGWJsYXJzZW5AYnJhZGZvcmRzLW1icC5sYW4B
-----END RSA PRIVATE KEY-----
- |
"-----BEGIN RSA PRIVATE KEY-----\r\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn\r\nNhAAAAAwEAAQAAAIEAtDSHFO5tfN+jYMJuiNvBaplkSI3eFqKMLOvXyVu+dmSEic6xyKWQ\r\nqjQiFpXogArvAq2tBxWOq7F+a6rNhDKdICD2amRwDHqKD1bzXVSZ5c1XnpCFsBiQaEyX2i\r\nqyjScnntFHIpTCVHNxILDxsStocj6Cf99C7hfCGVhft/Ts/O0AAAIQJOKnUyTip1MAAAAH\r\nc3NoLXJzYQAAAIEAtDSHFO5tfN+jYMJuiNvBaplkSI3eFqKMLOvXyVu+dmSEic6xyKWQqj\r\nQiFpXogArvAq2tBxWOq7F+a6rNhDKdICD2amRwDHqKD1bzXVSZ5c1XnpCFsBiQaEyX2iqy\r\njScnntFHIpTCVHNxILDxsStocj6Cf99C7hfCGVhft/Ts/O0AAAADAQABAAAAgBcaTN8gGi\r\nVSPo3fH3CoS8mw1KyAk6JvQG1Z5xZHjsl65YsNVrmUkFFh0aT3nxEbVb0QKwineN0GKmD/\r\nSs3R91a573gzli7TJPFCHhhBbE7FRC4KQMTc1/UANwFYQVcfZ4n9IVHr3jiWToSY3XbC66\r\nZcd0sg+d+YRjIxUktuNFHBAAAAQQCOOKbSUJAWzcTDbxImwDCAfBMlEeMAnJrwobL/zxbT\r\nGhKdnqnomoreFdYL8vOcOlwZG0hUKIA6AM1GsMzp6aCwAAAAQQDmAABpOQnkDy8v8kTDhP\r\ndW3lAqRGOU4WRWj7WystQv/VjuJpceekhOyhNJBuNHDKZ3IT1agAZHIhhL+webE2S1AAAA\r\nQQDIk4H1agCohlHUg50PcyKzE/zZ85Gw0ErTmgqIIGd4B1AqUtjwVe1qFoqHuZPtq2cbVF\r\n1HTHh6GX//J6rKWVJZAAAAGWJsYXJzZW5AYnJhZGZvcmRzLW1icC5sYW4B\r\n-----END RSA PRIVATE KEY-----"
- name: Base64-PEM-Encoded Private Key
id: kingfisher.pem.2
pattern: |
(?x)
(
(?: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0t (?# prefix of base64 encoding of `-----BEGIN RSA PRIVATE KEY-----` )
| LS0tLS1CRUdJTiBEU0EgUFJJVkFURSBLRVktLS0t (?# prefix of base64 encoding of `-----BEGIN DSA PRIVATE KEY-----` )
| LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0t (?# prefix of base64 encoding of `-----BEGIN EC PRIVATE KEY-----` )
)
[a-zA-Z0-9+/=]{50,}
)
(?: [^a-zA-Z0-9+/=] | $ )
min_entropy: 4.5
confidence: high
prevalidated: false
examples:
- 'PRIVATE_KEY_B64=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBb3kxWFh1VkFRcHFIYlFFMDVta2hyTmcvMTI0Ri8ySzlPYW5pelpUWlVVaEswOFU4CkxhaC9SbVVsWHFRMDEvU255aktGOWZqUDhFcU1OZ1dpamUzYmVwL3RPOVpTMEFUMi9PVlJXeS9TOG52RDQ5WTMKenMxMktSbERhR2lZc0RsYUZrbHJkeDQ4RWhRVmdHN3hmWE1jaC9OejJzc2FEby9kRkNBOW80TkZZQWUzM2UveApWNVo1UHNkWkl6dkNZQVlCNDRoUEtpN3JXRE1IbFdzM1kvVkVtQXMzSzVNK2QvL3QzRHB4WnBEbWJERGdYa2w2CjZUdDh3VXloUVZ3MkZpMStobTF1T2QwYjFkaW9aNko2OXNTT2JOZXpSR3YxYjdZaFltT0JKL1JBbHN5ZHoxTmgKVXpXT1lYV0Z1OGJrOU9JM3lQMEc0TE84QjhtbWRldE1RVVoyelFJREFRQUJBb0lCQUN2ckhUUHVVZ0JiSlE0QwpvQ0ZQdEgrWDZIN3NIdk1ndVR0VzdUTlYxN1BYMkVQdE53ZzI3S0tld0pNYmNSbWF3THBjSk5BU09xMDY4MGZxCjlsaHE1NEsybnB4WFVBeXErV3NSc1hid2hUODhibm5aQTBaRzZJR2hTaEpFN0t1cGxBU2htQ29FV2ppbmJTNFgKTGlvTW5HWSs4VFMzSzNrMTRWUDBaWUtuNXprMERHZnFBMEo0VTRXSmxUeGwrTWZxd0pJOTlrcTdHbFVlZkdncQpuK3Q1d2NrV3BPbTd5TUJjZTlTSXlmTm54bnU3TkZYQm50VTN5RGxSUThWUWZmNEtRMzJCaWNiYlJWemR1TThNCnNxMU5CZWNzL0EzUXRvdG1nWUc4d094ZXpNS3Iyays2QzB2NmlFc0h5T0lmR25GWktSZDJFd0dnWlo3aytURHUKUUYrcjd1VUNnWUVBMkRqNUJoYmpybDFRNTZya3BhTGFvVldRV1Y5YUYzUUJtNlNZM2VQYmlvY2JNR2k1ak1ESQpkSjdJVXlLYUljK3BNV1RQYlBmVUd2WmNENlczZDFBNUNUSnFuWHVuVlY3czRqaWJ6WDZUbjhNM3IrMHZTZnNZCmdPMHBtRFpndlNqaVZTRUNBQTZFOFUxQ1lFZU5KUDFDOW12cGJVNzJRTEpndWp3M3JMb2oyYmNDZ1lFQXdUSXYKOUNSeWNOQXRBbDcvUHdWZGh5eXRvVHBSRnZDSU1HSVk5SjMxZ3lva0ZlaFQvWjQ4WkF6anl6ZTBSUXYzdGUxTQoveVJMQkVETGkwbEtrZFVXckVkaVR3dm1KdkpwMDZ0OEdCbERsK25ycXVLWTFxVThDbTR5cis4QzZtRThkVnZrClNINXBhRXptOERFTE1wSjhGVTZFYnhmZHZjRzZmSGx6dnVnZmc1c0NnWUFFQ1BRa3QvS2h3MTRLSkxkRm5BZG0KY1ZsVFFhTkZ3c1Z3NlI1dExaNWdOR3MrZVFYVmFaZVVEWTZCZHFqWHJxOWltNVgvVzVTYXVEUTVtb2NVOCt0TQpqNk5Mc3c0SldzOGkzWm1TdVNUNkcwT0R4ZkpXK0JlWitGTUpZeUpsQlVsTCsyUzFLWkF6akpTTGhXcE40V2dKCmZ6UUk5U3RGUTg3b1NzMWpMTW9VZXdLQmdGOE9CMlFURHErTTdhaE4vejROc0wvU2JyZDJEdkcvZFBLQlFaQVIKcS90V0g1MGJ5ejlzdkgvcGk2YXdDS1UwUnpPZXh4UjkwZDhNMWxqNHZaVFZDQ3ZKajRnZTdhVlovbEdqL1JHSwpWS1NJOW1nRXgzaE1vaWJybzByR3lXTnlaaUhFRGFUUmRhRll2UU9PemRpYkZDd1RqcnR1UGE2Z2c5VzhtQU5sCkNDUmpBb0dBSTRIbnpyV3kzaU5kR2xqVnh4bW1DN1V0c0MvajJBUEZpcHc0ZHJ0U2NsMDFRZzF5WkowbDNBTk4KOU5lTmVSUUFzN3pFTng2T1B1SzlxYy83T1ROMTJKaHdoUTIzdXZwNjZjV0krdTRjcVpOZTJyZVFVVWVmM3psbQpMcXRmOU50VHp5M3pjMGZQcGoxQnBlRmxHSG9SVDhjVHpBWjFTeGwyZWChazlqS2RVeDQ9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t'
- ' "privateKey": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBbUhKOEJHdTFYZUZ4aENVQXBrNHNSTVI4RnRTdGtyMEx0OWtWTGNSUjRFWitiOWhHCmR0blJpOFhqV3d5MU5zMHliMkJMdHBpVHZKSFVKTUphWXluZ2ZkZnZhcWhocm1yYm5vV0pLQkxmeUxwTXFNS1EKQ3RialFxbnVrQURJUWVQd2ZGeTNpVHkxd1JkRC9zTUs1U0VtV0Fxb0pZQk50eTFZZzA2UzVkYVlPM2xjY3hrYQpQWjRjcm9McWF6Ny9tU3dDVTR5VWRSb3h4WVF4VG1MZXg5M2tqU09TTmdpK0FXc0lCbjV3UHI0VHNuVHFSeWpIClN2aEdMdk9YREpRYWZRdk56WjFSL1FYMzlOQk9xOEVKZW5pWXdaUm9uNVcvNVhMYW94MFFyUGhrY1BES3A5SVUKeHpJakUwWlNmMStUK1FFbTQ3TkFtSnhvZjFhdGRFVzZDTCtheHdJREFRQUJBb0lCQUQ3enI4REhsWnFSK1NWZgpmbGd1bWRzLzVCb3Rjd3ZRWXlGbFZIaVV4RmEvNVlCY0tDVDJKN0QzWTc1NmplNTJaK2hVTkkvUGk5cG53ZG40CkpBa2xCdDRRcUg0NzBES05UK216TFFOT1gvanM3YkVXdnhLcTBDZjhNbFptN0V0QlRGS2VtdS9pRVJBT2duYVcKcGs0ZUZVNXdBQ1dVU1FObWgxR1p4ZEdCZjFXM1VjUnQxcFRvOEtQTDluZm4vSGJiRFNsQkNVL3VIcWd2TSt2cApmTE03bzRIVDZ1K1ZzU00rWGZqeDhpeE5ZRHdoalNuKzQyZm13d1d3ZzJISHUrdUozZ1pUSWQwRUI1VW9hdUNjCjZUTlVtcEJscjU5UGFmVkZRWUY1S3VxaHJXKzVQaWpHcHBZcXg4Ynl6aFpOQzkwZnl5V0NXcXg2eGFZVm5OdzgKNkJmUXM2a0NnWUVBeVlyRVg1NU1RTzJnWDY2TGwxaGJDMzNzWk1OZzloVG1SK1doSTFjNksvbFZ1TFoyL0RPdwpsYTZ6eHdBU204Z0ZyVUFYbUljV2h2b3FwWGVzNWZzOVZKeDlNT0ZVYVBrckRPQllnY1laMUR6VVNVOHc3SSttCnlyV3hRUkRNajhvSGpRbHVpM0s2MzZucm5RajhxOGkvQ2dranVPcHJGZnliMzVEMFlDdjVXZzBDZ1lFQXdhT3cKRWFhN0l1MjFGa08vbmFjdVhjSnBhNkVlUTNqZFNlNlRQaXZ6bVVXU0haeGJuUy9XSnJaRjQwSExzUWxOZHl0ZgpNTTBKZFU0VmMyR0NVc1pMYjdQSmJwdVRqRERSSHJXV1pCMnhiemF0K3A3N2RzNWlOcXFRcTZ6M0syUVh4Y3ZTCis5am5VZXpDU2Y0N1R1OWNTTW96V3hTMW82b1BPSFdHVFRvdHR5TUNnWUFQdWc1Y3o4TnZoWnR3Ry9TMG1LWnkKSFI5bk5YL0pkQlFNSkRVUXh1dTVKcm16c2psU3NNM2t3RDh6RmlSZGw1d3B5c2lNbEc0RGxsM2hqNWNrVXhpVQpFNm9KT0d3WHpPbTVGWUNTajl6UUhQY0x5V3d0NlgvQWJiRXBQS0JaMEJBS3gyT2k2ZzcvQ1FsanRhSFIzZFphCmVDQWJlOTlqVmRUcit5bTJuM2ZUdVFLQmdBMm5TZ25rbEx0Z3dXMEJkK2hZMm1jWUJ6RGttbXF0Z2dUdGdvcFcKdFFWd3AxM1pJWWlTeituSTNtS295QUVDbytpc01Ua1NyQUVPY1dyQ1RGc2p5anZsRkdYdEtGa3hNLzJUVmpoVwo4NlRnMlNHYnhpVlpaZ2x1dTJhdmVub2Z3NkZadnRXdE5KcE5OR0hkUURkUG4xVXVsTEp1WW1SWTRGdmR4WXQ2CmQ3QzdBb0dBRUsvalFiZ0l3OXFLQUNOZ0JySnB1cU5Ham9JajFoQTRlb29DMXp1bFEyZUpnZ2J5OTBpSDg2VzEKM0xyOVZMVFkyc2JKTzlqekZVR0lOL01BOEhYQTE1a2grZHRibkRsdFRFZGNnenBCRzhCQUZRQ3hQWnBGWHhtZgpDUmhXN1l6RW1IeWJ4R0toR3NOK2M3NUhKTHZFSWwrRTh6eitXRk9xT240dkJXU1ZwSnc9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==",'

36
data/rules/postgres.yml Normal file
View file

@ -0,0 +1,36 @@
rules:
- name: Postgres URL with hardcoded password
id: kingfisher.postgres.1
pattern: |
(?xi)
(?:
postgres
(?:ql)?
| postgis
)
:\/\/
(?:
[\w]+
)
:
(?:
[^\@]+
)
@
(?:
[^:\/]+
)
:
(?:
\d+
)
min_entropy: 3.3
confidence: medium
examples:
- CONNECTION_URI="postgres://postgres:s2Tf2k@rLMy@google.com:5434/elephant"
- Connection URI= postgresql://nimda:vg498hwegw1udp6s@db-postgresql-nyc1-64297-do-user-1243723-0.db.ondigitalocean.com:25060/defaultdb?sslmode=require
- CONNECTION_URI="postgres://postgres:s2Tf2k@rLMy@google.com:5434/elephant"
- CONNECTION_URI="postgis://postgres:s2Tf2k@rLMy@google.com:5434/elephant"
- CONNECTION_URI="postgis://postgres:s2Tf2k@rLMy@google.com:5434/elephant"
validation:
type: Postgres

104
data/rules/privkey.yml Normal file
View file

@ -0,0 +1,104 @@
rules:
- name: Contains encrypted RSA private key
id: kingfisher.privkey.1
pattern: |
(?x)
(?msi)
(
-----BEGIN\s
(?:RSA)\s
PRIVATE\sKEY
(\sBLOCK)?
-----
)
[\r\n]
Proc-Type:.*?ENCRYPTED
[\r\n]
DEK-INFO.*?
[\r\n][\r\n]
[a-z0-9/+=\r\n\\n]{32,}?
-----END\s
(?:RSA)\s
PRIVATE\sKEY
(\sBLOCK)?
-----
min_entropy: 4.5
confidence: high
prevalidated: false
examples:
- |-
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,1F77CE6d2Bb6B18537633Ec3aD093b9C
kyx3
wbfxsauty36i7t
i9z9jp/m7wiptdmxpx2zh
wehs0oz1abuede+tq4ama6kjrywq0
xw2mbgz1fmxw+8ub4rf2wkm/qlvnopukbitesk2+
b55+04h565guxlu+8u4+mynsoqgahx0p5jg4h1vo0lpw/ru1qe60p4nz7635xeel868y72bsysa5/90qkhnt1bkxcqyqkmw3
949mmahkh/cd9+f+x9wprv4mtq02ks1pzpb6zz6t
+ril
frnc129xvp11ndqbyjqlg3jf9ovlb1qula84ftj8m
-----END RSA PRIVATE KEY-----
- name: Contains Private Key
id: kingfisher.privkey.2
pattern: |
(?x)
(?ims)
(
-----BEGIN\s
(?:
RSA |
PGP |
DSA |
OPENSSH |
ENCRYPTED |
EC
)?
\s{0,1}
PRIVATE\sKEY
(\sBLOCK)?
-----
[a-z0-9 /+=\r\n\\n]{32,}?
-----END\s
(?:
RSA |
PGP |
DSA |
OPENSSH |
ENCRYPTED
)?
\s{0,1}
PRIVATE\sKEY
(\sBLOCK)?
-----
)
min_entropy: 4.5
confidence: high
prevalidated: false
examples:
- |
-----BEGIN PRIVATE KEY-----
34fNwLAJYISQf4pT4wgMrzN1p3kdMYLTEYXw12k+SlKL=QLKQj=SP=hOpCG5Cdj/Hkk0ARyOgoZbJycYA
8D9r9K2m6N1ZaUT96UrFqjlL9nAqmZ+13D82H1CYLKy0NOAY3XBLzLk46HZd8na2
-----END PRIVATE KEY-----
- |
-----BEGIN RSA PRIVATE KEY-----
34fNwLAJYISQf4pT4wgMrzN1p3kdMYLTEYXw12k+SlKL=QLKQj=SP=hOpCG5Cdj/Hkk0ARyOgoZbJycYA
8D9r9K2m6N1ZaUT96UrFqjlL9nAqmZ+13D82H1CYLKy0NOAY3XBLzLk46HZd8na2
-----END RSA PRIVATE KEY-----
- |-
-----BEGIN ENCRYPTED PRIVATE KEY-----
FPaBqPQWx0nxTJbSrPavSaNtmAyAH6etkjZs0/In
-----END ENCRYPTED PRIVATE KEY-----
- |-
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7a7kN8LymUu8Z
8D9r9K2m6N1ZaUT96UrFqjlL9nAqmZ+13D82H1CYLKy0NOAY3XBLzLk46HZd8na2
-----END PRIVATE KEY-----
- |-
-----BEGIN ENCRYPTED PRIVATE KEY BLOCK-----
V75NeIrlsI80Gf0aTS2RZQvEcUQ3n6XwFnOvB/O5rRv3HGqvptc3P3n0bxfEg5KA
-----END ENCRYPTED PRIVATE KEY BLOCK-----

27
data/rules/psexec.yml Normal file
View file

@ -0,0 +1,27 @@
rules:
- name: Credentials in PsExec
id: kingfisher.psexec.1
pattern: |
(?xi)
psexec .{0,100}
-u \s* (\S+) \s+ (?# username )
-p \s* (\S+) (?# password )
min_entropy: 3.3
confidence: medium
categories: [fuzzy, secret]
examples:
- 'cmd.exe /C PSEXEC \\10.0.94.120 -u Administrator -p dev_admin CMD /C ECHO'
- 'PSEXEC.EXE \\LocalComputerIPAddress -u DOMAIN\my-user -p mypass CMD'
- 'psExec \\OAIJCTDU8024272 -u User -p $Password -i -d calc.exe'
- |
:: satmodel2
%RUNTIMEDIR%\PsExec.exe \\satmodel2 -u SATMODEL2\MTCPB -p %nothing% -i 2 -c -f %TEMP%\psexec_helper.bat %RUNTIMEDIR% .\JavaOnly_runNode2.cmd
%RUNTIMEDIR%\pslist.exe \\satmodel2 java
if %ERRORLEVEL% NEQ 0 goto done
- |
ASSEMBLE THE BATCH FILE TO COPY THE FILE ACROSS THE DOMAIN
start PsExec.exe /accepteula @C:\share$\comps1.txt -u DOMAIN\ADMINISTRATOR -p PASSWORD cmd /c COPY "\PRIMARY DOMAIN CONTROLLER\share$\fx166.exe" "C:\windows\temp\"
SAVE IT AS "COPY.BAT"
- 'system("psexec \\\\192.168.3.77 -u Administrator -p braksha shutdown -r -f -t 0");'
references:
- https://learn.microsoft.com/en-us/sysinternals/downloads/psexec

64
data/rules/pypi.yml Normal file
View file

@ -0,0 +1,64 @@
rules:
- name: PyPI Upload Token
id: kingfisher.pypi.1
pattern: |
(?x)
\b
(
pypi-AgEIcHlwaS5vcmc[a-zA-Z0-9_-]{50,}
)
(?:[^a-zA-Z0-9_-]|$)
min_entropy: 3.3
confidence: medium
examples:
- '# password = pypi-AgEIcHlwaS5vcmcABCD1234efgh5678ijklmnopqrst9098UVWXYZabcd1234EFGHIJKL'
validation:
type: Http
content:
request:
method: POST
url: https://upload.pypi.org/legacy/
response_is_html: true
response_matcher:
- report_response: true
- type: WordMatch
words:
- "isn't allowed to upload to project"
headers:
Authorization: 'Basic {{ "__token__:" | append: TOKEN | b64enc }}'
multipart:
parts:
- name: name
type: text
content: "my-package"
- name: version
type: text
content: "0.0.1"
- name: filetype
type: text
content: "sdist"
- name: metadata_version
type: text
content: "2.1"
- name: summary
type: text
content: "A simple example package"
- name: home_page
type: text
content: "https://github.com/yourusername/my_package"
- name: sha256_digest
type: text
content: "0447379dd46c4ca8b8992bda56d07b358d015efb9300e6e16f224f4536e71d64"
- name: md5_digest
type: text
content: "9b4036ab91a71124ab9f1d32a518e2bb"
- name: :action
type: text
content: "file_upload"
- name: protocol_version
type: text
content: "1"
- name: content
type: file
content: "path/to/my_package-0.0.1.tar.gz"
content_type: "application/octet-stream"

40
data/rules/rabbitmq.yml Normal file
View file

@ -0,0 +1,40 @@
rules:
- name: RabbitMQ Credential
id: kingfisher.rabbitmq.1
pattern: |
(?xi)
\b
(?:
amqps?
)
:\/\/
[\S]{3,50}
:
(
[\S]{3,50}
)
@
[-.%\w\/:]+
\b
min_entropy: 3.5
confidence: medium
examples:
- amqp://user:password@rabbitmq.example.com/queue
- amqps://admin:3eCa3P@192.168.1.10:5671/vhost
# validation:
# type: Http
# content:
# request:
# url: '{{ URL }}'
# headers:
# Custom-header: '{{ TOKEN }}'
# method: GET
# response_matcher:
# - report_response: true
# - status:
# - 200
# type: StatusMatch
# - report_response: true
# - type: WordMatch
# words:
# - '"connected":true'

53
data/rules/react.yml Normal file
View file

@ -0,0 +1,53 @@
rules:
- name: React App Username
id: kingfisher.reactapp.1
pattern: |
(?x)(?i)
\b
REACT_APP (?: _[A-Z0-9]+)* _USER (?: NAME)? (?# variable name )
\s* = \s*
['"]?
( [^\s'"$]{3,} ) (?# value )
(?: [\s'"$] | $ )
min_entropy: 3.3
confidence: medium
examples:
- '# REACT_APP_GUEST_USERNAME=guest'
- '# REACT_APP_USER=postgres'
- 'REACT_APP_AUTH_USER=postgres'
- 'REACT_APP_AUTH_USERNAME=bowie'
- ' REACT_APP_AUTH_USERNAME=bowie # some comment'
- 'REACT_APP_MAILER_USERNAME=smtp_username # Enter your SMTP email username'
negative_examples:
- 'REACT_APP_FRONTEND_LOGIN_FORGOT_USERNAME=$REACT_APP_MATRIX_BASE_URL/classroom/#/forgot_username'
categories: [fuzzy, identifier]
references:
- https://create-react-app.dev/docs/adding-custom-environment-variables/
- https://stackoverflow.com/questions/48699820/how-do-i-hide-an-api-key-in-create-react-app
- name: React App Password
id: kingfisher.reactapp.2
pattern: |
(?x)(?i)
\b
REACT_APP (?: _[A-Z0-9]+)* _PASS (?: WORD)? (?# variable name )
\s* = \s*
['"]?
( [^\s'"$]{6,} ) (?# value )
(?: [\s'"$] | $ )
min_entropy: 3.3
confidence: medium
examples:
- '# REACT_APP_GUEST_PASSWORD=mycoin!1'
- '# REACT_APP_PASS=whiteduke'
- 'REACT_APP_AUTH_PASS=whiteduke'
- 'REACT_APP_AUTH_PASSWORD=whiteduke'
- ' REACT_APP_AUTH_PASSWORD=whiteduke # some comment'
- 'REACT_APP_MAILER_PASSWORD=smtp_password # Enter your SMTP email password'
negative_examples:
- ' const password = process.env.REACT_APP_FIREBASE_DEV_PASSWORD || "not-set"'
- 'REACT_APP_FRONTEND_LOGIN_FORGOT_PASSWORD=$REACT_APP_MATRIX_BASE_URL/classroom/#/forgot_password'
categories: [fuzzy, secret]
references:
- https://create-react-app.dev/docs/adding-custom-environment-variables/
- https://stackoverflow.com/questions/48699820/how-do-i-hide-an-api-key-in-create-react-app

35
data/rules/recaptcha.yml Normal file
View file

@ -0,0 +1,35 @@
rules:
- name: reCAPTCHA API Key
id: kingfisher.recaptcha.1
pattern: |
(?xi)
recaptcha
(?:.|[\n\r]){0,16}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
6l[c-f][a-z0-9_-].{36}
)
min_entropy: 3
confidence: medium
examples:
- recaptcha apikey = 6Lcr--w-BBBBBw-w-w----w-w-www-www--ww-w-
- recaptcha_secret = 6Lcw--w-AAAAAw-w-w----w-w-www-www--ww-w-
validation:
type: Http
content:
request:
headers:
Content-Type: application/x-www-form-urlencoded
body: |
secret={{ TOKEN }}
response=test
method: POST
response_matcher:
- report_response: true
- match_all_words: true
type: WordMatch
words:
- '"success": true'
url: https://www.google.com/recaptcha/api/siteverify

117
data/rules/slack.yml Normal file
View file

@ -0,0 +1,117 @@
rules:
- name: Slack App Token
id: kingfisher.slack.1
pattern: |
(?x)
(?i)
(?:
.{0,24}[=:]
\s{0,8}
)?
(
xapp-
[0-9]{1,3}-
[0-9a-z]{10,15}-
[0-9a-z]{10,15}-
[0-9a-z]{10,66}
)
\b
min_entropy: 3.5
examples:
- xapp-1-A05V64V7F2B-5062360157732-9f01726eebe77df2c096a65e95acdd02107b2c1e92ca341cff27ca271b7251b4
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
Content-Type: application/json; charset=utf-8
method: POST
response_matcher:
- report_response: true
- type: WordMatch
words:
- '"ok":true'
url: https://slack.com/api/auth.test
- name: Slack Token
id: kingfisher.slack.2
pattern: |
(?x)
(?i)
\b
(
xox[pbarose]
[-0-9]{0,3}-
[0-9a-z]{6,15}-
[0-9a-z]{6,15}-
[-0-9a-z]{6,66}
)\b
|
(
xoxe\.xox[bparose]-
\d-
[A-Z0-9]{155,170}
)\b
|
(
xoxe-\d-
[A-Z0-9]{140,150}
)\b
min_entropy: 3.5
examples:
- xoxb-853BAAEE-1B2eDb6A4c75-01bB6Da1CE3E98f6fED5AeC07Dc3E94C
- xoxe.xoxp-1-Mi0yLTIxNTE3NzA2MDYtNTEyOTQ2NTEzMDYxMy03MTkwNzY4NDc5ODc3LTcxOTM1NTk2ODMxODctZGE3NWQ0NmZiNTk3MTU1ZDA4ZTQwZTYxOWY2Njc3YTgzNzNhNDkxMzg4ZWZiYzljMjRkOWZhODgxYzA0NGI2ZQ
- xoxe-1-My0xLTIxNTE3NzA2MDYtNzE5MDc2ODQ3OTg3Ny03MjE2NDY0MzYxNDcyLTliOWNhYzZhODU3ODc3NjAxZGM4ODg2ZWRkMmE2MTc3ZDA3ODY4ZmUzZDg2NzEyZTU0Zjk2ZTYzMWMxZTVmOTA
references:
- https://api.slack.com/methods/auth.test
validation:
type: Http
content:
request:
headers:
Authorization: Bearer {{ TOKEN }}
Content-Type: application/json; charset=utf-8
method: POST
response_matcher:
- report_response: true
- type: WordMatch
words:
- '"ok":true'
url: https://slack.com/api/auth.test
- name: Slack Webhook
id: kingfisher.slack.4
pattern: |
(?x)
(?i)
\b
(
https://hooks\.slack\.com/services/
T[a-z0-9_-]{8,12}/ # Team ID
B[a-z0-9_-]{8,12}/ # Bot ID
[a-z0-9_-]{20,30} # Webhook token
)
\b
min_entropy: 3.3
examples:
- https://hooks.slack.com/services/TY40v9sZ9/BxIqhIXIi/NGUyXK6nK7HMAqd0ASzXluoV
- https://hooks.slack.com/services/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
validation:
type: Http
content:
request:
headers:
Content-Type: application/json
method: POST
response_matcher:
- report_response: true
type: WordMatch
words:
- ok
- invalid_payload
- type: WordMatch
words:
- "invalid_token"
negative: true
url: '{{ TOKEN }}'

View file

@ -0,0 +1,31 @@
rules:
- name: Supabase API Key
id: kingfisher.supabase.1
pattern: |
(?xi)
\b
sbp_
(
[a-z0-9_-]{40}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- sbp_abcd1234efgh5678ijkl9012mnop3456qrst7890
- sbp_1234567890abcdefghij1234567890klmnopqrst
references:
- https://supabase.com/docs/reference/api/v1-get-an-organization
validation:
type: Http
content:
request:
headers:
Authorization: "Bearer {{ TOKEN }}"
method: GET
url: https://api.supabase.com/v1/organizations
response_matcher:
- report_response: true
- type: StatusMatch
status:
- 200

31
data/rules/tailscale.yml Normal file
View file

@ -0,0 +1,31 @@
rules:
- name: Tailscale API Key
id: kingfisher.tailscale.1
pattern: |
(?x)
(?i)
\b
(
tskey-[a-z]+-[A-Za-z0-9_-]{20,24}
)
\b
min_entropy: 3.0
confidence: medium
examples:
- tskey-secret-12345678-abcd
- tskey-api-abcdefg-123456789
references:
- https://tailscale.com/kb/1215/oauth-clients
validation:
type: Http
content:
request:
method: GET
url: https://api.tailscale.com/api/v2/tailnet/-/devices
headers:
Authorization: "Bearer {{ TOKEN }}"
Accept: application/json
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]

34
data/rules/travisci.yml Normal file
View file

@ -0,0 +1,34 @@
rules:
- name: Travis CI Token
id: kingfisher.travisci.1
pattern: |
(?x)
(?i)
\b
travis
(?:.|[\\n\r]){0,16}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,16}?
\b
(
[a-z0-9-_]{22}
)
\b
min_entropy: 3.0
confidence: medium
examples:
- "travis_token splendid21RANDOMCONTENT_token"
validation:
type: Http
content:
request:
method: GET
url: https://api.travis-ci.com/repos?limit=1
headers:
Authorization: token {{ TOKEN }}
Accept: application/vnd.travis-ci.3+json
Travis-API-Version: "3"
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]

58
data/rules/twilio.yml Normal file
View file

@ -0,0 +1,58 @@
rules:
- name: Twilio API ID
id: kingfisher.twilio.1
pattern: |
(?x)
\b
(
(?:SK|AC)[a-fA-F0-9]{32}
)
\b
min_entropy: 3.5
examples:
- |
const twilioAccountSid = 'AC712594f590c0d8ace55c04858f7398f9' // Your Account SID from www.twilio.com/console
const twilioApiKeySID = 'SK9b4cc552783500ace5414a1ed3e9fd1a'
const twilioApiKeySecret = 'l6LUelKF2BUtMLace5oShZSmRppadYqI'
- |
// https://www.twilio.com/console/video/dev-tools/api-keys
'API' => env('TWILIO_API','SK6e84981d07ace5c9df33e1ab043a2fb2'),
'API_KEY' => env('TWILIO_API_KEY', 'wbTs1SUt6Aace5eKeNCxuYvJa6PhaRd0')
- name: Twilio API Key
id: kingfisher.twilio.2
pattern: |
(?x)
(?i)
\b
twilio
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[a-z0-9]{32}
)
\b
min_entropy: 3.0
examples:
- Twilio_key=Cd2Bd1dE1201aE2DFFEcfeBafCc3c31D
validation:
type: Http
content:
request:
headers:
Accept: '*/*'
Authorization: "Basic {{ TWILIOID | append: ':' | append: TOKEN | b64enc }}"
Content-Type: application/x-www-form-urlencoded
method: GET
response_matcher:
- report_response: true
- match_all_words: true
type: WordMatch
words:
- '"first_page_uri":'
- '"accounts":'
url: https://api.twilio.com/2010-04-01/Accounts.json
depends_on_rule:
- rule_id: "kingfisher.twilio.1"
variable: TWILIOID

31
data/rules/uri.yml Normal file
View file

@ -0,0 +1,31 @@
rules:
- name: URI with Username and Secret
id: kingfisher.uri.1
pattern: |
(?xi)
(https?)://
[a-z][a-z0-9+\-.]*
:
[a-z0-9\-._~%!$&'()*,;=]+
@
(?:[a-z0-9\-._~%]+|\[[a-f0-9:.]+\]|\[v[a-f0-9][a-z0-9\-._~%!$&'()*,;=:]+\])
(:?[0-9]+)?
(?:/[a-z0-9\-._~%!$&'()*,;=:@]*)* # Match path
/?
(?:\?[a-z0-9\-._~%!$&'()*,;=:@/?]*)?
(?:\#[a-z0-9\-._~%!$&'()*,;=:@/?]*)?
min_entropy: 4.0
confidence: medium
examples:
- https://username:secret@example.com/path
validation:
type: Http
content:
request:
method: GET
url: '{{ TOKEN }}'
response_matcher:
- report_response: true
type: StatusMatch
status:
- 200

65
docs/COMPARISON.md Normal file
View file

@ -0,0 +1,65 @@
### Runtime Comparison (seconds)
*Lower runtimes are better.*
| Repository | Kingfisher Runtime | TruffleHog Runtime | GitLeaks Runtime | detect-secrets Runtime |
|------------|--------------------|--------------------|------------------|------------------------|
| croc | 2.64 | 10.36 | 3.10 | 0.16 |
| rails | 8.75 | 24.19 | 24.24 | 0.48 |
| ruby | 22.93 | 132.68 | 61.37 | 0.79 |
| gitlab | 135.41 | 325.93 | 350.84 | 5.04 |
| django | 6.91 | 227.63 | 59.50 | 0.61 |
| lucene | 15.62 | 89.11 | 76.24 | 0.66 |
| mongodb | 25.37 | 174.93 | 175.80 | 2.74 |
| linux | 205.19 | 597.51 | 548.96 | 5.49 |
| typescript | 64.99 | 183.04 | 232.34 | 4.23 |
### Validated/Verified Findings Comparison
Note: For GitLeaks and detect-secrets, validated/verified counts are not available.
| Repository | Kingfisher Validated | TruffleHog Verified | GitLeaks Verified | detect-secrets Verified |
|------------|----------------------|---------------------|-------------------|-------------------------|
| croc | 0 | 0 | 0 | 0 |
| rails | 0 | 0 | 0 | 0 |
| ruby | 0 | 0 | 0 | 0 |
| gitlab | 6 | 6 | 0 | 0 |
| django | 0 | 0 | 0 | 0 |
| lucene | 0 | 0 | 0 | 0 |
| mongodb | 0 | 0 | 0 | 0 |
| linux | 0 | 0 | 0 | 0 |
| typescript | 0 | 0 | 0 | 0 |
### Network Requests Comparison
*'Network Requests' shows the total number of HTTP calls made during a scan. Since Gitleaks and detectsecrets dont validate secrets, they never make any network requests.*
| Repository | Kingfisher Network Requests | TruffleHog Network Requests | GitLeaks Network Requests | detect-secrets Network Requests |
|------------|-----------------------------|-----------------------------|---------------------------|----------------------------------|
| croc | 0 | 17 | 0 | 0 |
| rails | 1 | 25 | 0 | 0 |
| ruby | 3 | 33 | 0 | 0 |
| gitlab | 17 | 15624 | 0 | 0 |
| django | 0 | 66 | 0 | 0 |
| lucene | 0 | 116 | 0 | 0 |
| mongodb | 1 | 191 | 0 | 0 |
| linux | 0 | 287 | 0 | 0 |
| typescript | 0 | 10 | 0 | 0 |
### QuickChart.io Visualizations
#### Runtime Chart
*Lower runtimes are better*
![Runtime Comparison](https://quickchart.io/chart?c=%7B%22type%22%3A%22bar%22%2C%22data%22%3A%7B%22labels%22%3A%5B%22croc%22%2C%22rails%22%2C%22ruby%22%2C%22gitlab%22%2C%22django%22%2C%22lucene%22%2C%22mongodb%22%2C%22linux%22%2C%22typescript%22%5D%2C%22datasets%22%3A%5B%7B%22label%22%3A%22Kingfisher%22%2C%22data%22%3A%5B3.087692041%2C9.816560542%2C22.222204459%2C129.921919875%2C6.748027708%2C18.650581459%2C27.47587625%2C204.192040875%2C62.877494792%5D%7D%2C%7B%22label%22%3A%22TruffleHog%22%2C%22data%22%3A%5B17.667027792%2C24.4969155%2C133.286264708%2C335.819256375%2C248.135664708%2C91.367231833%2C180.311266375%2C585.00584475%2C182.478392708%5D%7D%2C%7B%22label%22%3A%22GitLeaks%22%2C%22data%22%3A%5B2.845539417%2C19.704876208%2C46.658975%2C285.6701695%2C22.446593958%2C53.793195375%2C174.406220375%2C517.420016958%2C164.260176625%5D%7D%2C%7B%22label%22%3A%22detect-secrets%22%2C%22data%22%3A%5B0.703465916%2C0.783118209%2C1.231432834%2C8.751082041%2C1.120182458%2C1.019824708%2C4.737797875%2C8.402164%2C7.170617042%5D%7D%5D%7D%2C%22options%22%3A%7B%22scales%22%3A%7B%22yAxes%22%3A%5B%7B%22ticks%22%3A%7B%22beginAtZero%22%3Atrue%7D%7D%5D%7D%2C%22title%22%3A%7B%22display%22%3A%22true%22%2C%22text%22%3A%22Runtime+Comparison+%28seconds%29%22%7D%7D%7D)
#### Validated/Verified Findings Chart
*Validated/Verified counts are reported where available*
![Findings Comparison](https://quickchart.io/chart?c=%7B%22type%22%3A%22bar%22%2C%22data%22%3A%7B%22labels%22%3A%5B%22croc%22%2C%22rails%22%2C%22ruby%22%2C%22gitlab%22%2C%22django%22%2C%22lucene%22%2C%22mongodb%22%2C%22linux%22%2C%22typescript%22%5D%2C%22datasets%22%3A%5B%7B%22label%22%3A%22detect-secrets%22%2C%22data%22%3A%5B0%2C0%2C0%2C0%2C0%2C0%2C0%2C0%2C0%5D%7D%2C%7B%22label%22%3A%22Kingfisher%22%2C%22data%22%3A%5B0%2C0%2C0%2C6%2C0%2C0%2C0%2C0%2C0%5D%7D%2C%7B%22label%22%3A%22TruffleHog%22%2C%22data%22%3A%5B0%2C0%2C0%2C6%2C0%2C0%2C0%2C0%2C0%5D%7D%2C%7B%22label%22%3A%22GitLeaks%22%2C%22data%22%3A%5B0%2C0%2C0%2C0%2C0%2C0%2C0%2C0%2C0%5D%7D%5D%7D%2C%22options%22%3A%7B%22scales%22%3A%7B%22yAxes%22%3A%5B%7B%22ticks%22%3A%7B%22beginAtZero%22%3Atrue%7D%7D%5D%7D%2C%22title%22%3A%7B%22display%22%3A%22true%22%2C%22text%22%3A%22Validated%2FVerified+Findings%22%7D%7D%7D)
*Lower runtimes are better. Validated/Verified counts are reported where available. 'Network Requests' indicates the number of HTTP requests made during scanning.*
OS: darwin
Architecture: arm64
CPU Cores: 16
RAM: 48.00 GB

74
docs/FINGERPRINT.md Normal file
View file

@ -0,0 +1,74 @@
## Finding Fingerprints
Every reported finding carries a **64-bit fingerprint** that acts as a stable, privacy-safe ID.
It lets the scanner **deduplicate** repeated hits of the *same logical issue* while still treating different locations as distinct.
```bash
🔓 AWS SECRET ACCESS KEY => [KINGFISHER.AWS.2]
|Finding.......: 4HKmwiS1GzI[...]2TF6zYz7
|Fingerprint...: 14085685380484734428
|Confidence....: medium
|Entropy.......: 5.12
[...]
```
---
### How the *reported* fingerprint is calculated
1. **Finding Bytes** the matched finding pattern
2. **Origin label** one of
*`"git"`*, *`"file"`*, *`"ext"`*, identifying whether the hit came from a Git
history, a plain on-disk file, or an extended source.
3. **Byte offsets** `offset_start` and `offset_end`, the exact byte range of
the match inside the blob/file (little-endian `u64` each).
Those four fields are concatenated:
```bash
< finding_bytes> + <origin_label> + <offset_start> + <offset_end>
```
The resulting buffer is hashed with **XXH3-64**, producing a single unsigned-64 value:
```bash
finding-bytes + origin + start-offset + end-offset -> XXH3-64 -> finding_fingerprint
```
This fingerprint is what you see reported in the finding output.
---
### Why the rules SHA-1 is used (and not the secret)
The fingerprint is a [XXH3-64](https://github.com/Cyan4973/xxHash) hash of the following components concatenated together:
* The content of the matched secret.
* A coarse-grained origin label (`git`, `file`, or `ext`).
* The start and end byte-offsets of the match.
This content-aware approach provides several benefits:
| Reason | Benefit |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Accurate Secret Tracking** | If a key is rotated (e.g., from `AKIA…AAA` to `AKIA…BBB`), the new key correctly receives a new fingerprint. This allows for precise tracking of a secret's lifecycle. |
| **Location Uniqueness** | Because byte offsets are part of the hash, two identical secrets found on different lines will have separate fingerprints. |
| **Privacy-Safe by Design** | The fingerprint is a one-way hash, not the raw secret itself. This prevents sensitive credential data from being exposed in reports and logs. |
| **Light-weight Origin** | Using a coarse origin label (`git`, `file`, etc.) avoids fingerprint churn across commits while still separating findings from different types of scans. |
This method ensures that every unique secret is tracked precisely, providing a clear and accurate picture of sensitive data exposure.
---
### Controlling deduplication
By default the CLI **deduplicates** findings that share the same fingerprint, so you see only one entry even if the secret appears in multiple commits.
If you want to see **every individual occurrence**, run with `--no-dedup`:
```bash
kingfisher scan /path/to/repo --no-dedup
```

35
docs/PARSING.md Normal file
View file

@ -0,0 +1,35 @@
# Kingfisher Source Code Parsing
Kingfisher leverages treesitter as an extra layer of analysis when scanning source files written in supported programming languages. In practice, after its initial regexbased scan (powered by Vectorscan), Kingfisher checks if the files language is known.
If so, it creates a Checker (see below) that uses treesitter to parse the file and run languagespecific queries. This additional pass refines the detection by capturing more structured patterns—such as secret-like tokens—that might be obscured or spread over code constructs.
### How Its Called
In the scanning phase (in the Matchers implementation), Kingfisher does the following:
- **Language Detection:** When processing a blob, if a language string is provided (e.g. inferred from file metadata or extension), the code calls a helper (via a function like `get_language_and_queries`) to retrieve the corresponding treesitter language and a set of queries.
- **Checker Creation:** With these values, a `Checker` struct is instantiated. This struct holds both the target language (as defined in its `Language` enum) and a map of treesitter queries to run.
- **Parsing and Querying:** The Checkers key method (e.g. `check` or indirectly via `modify_regex`) retrieves a threadlocal treesitter parser (to avoid recreating the parser on every call), sets the appropriate language, and parses the source code into a syntax tree. It then executes the queries over that tree, extracting ranges and texts of interest that might represent secrets.
*(See the implementation details in the parser module for example, the `modify_regex` function in the Checker, and the conditional treesitter call in Matcher::scan_blob)*
### Supported Languages
The design supports many common source code languages. The Language enum (defined in the parser module) includes variants for:
- **Scripting:** Bash, Python, Ruby, PHP
- **Compiled languages:** C, C++, C#, Rust, Java
- **Web-related languages:** CSS, HTML, JavaScript, TypeScript, YAML, Toml
- **Others:** Go, and even a generic “Regex” mode
Each variant maps to its corresponding treesitter language through the `get_ts_language()` method.
### When Treesitter Is Not Called
Treesitter wont be invoked in certain cases:
- **No Language Identified:** If the file isnt recognized as belonging to one of the supported languages or no language hint is provided, the Checker isnt even constructed.
- **Non-source Files:** Binary files or files that arent expected to contain code (or arent extracted from archives) bypass treesitter parsing.
- **Fallback on Errors:** If treesitter parsing fails (e.g. due to malformed code or other errors), Kingfisher will fall back on its regex/Vectorscan matches without the additional treesitter insights.
### Summary
In essence, Kingfishers use of treesitter is conditional and complementary. It is called only when the scanned file is a source code file written in a supported language, and its role is to enrich the scanning results by leveraging the syntax tree and language-specific queries. When files are non-source, binary, or if no language is provided, treesitter is not invoked, and Kingfisher relies solely on its regex-based detection.
This layered approach helps improve the accuracy of secret detection while maintaining high performance.

309
docs/RULES.md Normal file
View file

@ -0,0 +1,309 @@
# Writing Custom Rules for Kingfisher
A _rule_ in Kingfisher is a YAML document that describes how to detect and (optionally) validate secrets in your codebase. With custom rules you can:
- **Extend** Kingfisher without touching Rust code
- **Tune** sensitivity via entropy and confidence
- **Plug in** live checks against external services
This document explains how to write custom rules for Kingfisher using a YAML-based rule system. The rules define regular expressions to detect secrets in source code and other textual data, and they can include validation steps to confirm the secret's authenticity. By using a rules-based system, Kingfisher is highly extensible—new rules can be added or existing ones modified without changing the core code.
## 1. Rule Schema
Each rule file defines one or more entries under a toplevel `rules:` list. Every entry supports the following fields:
```yaml
rules:
- name: # (string) Human-friendly rule name
id: # (string) Unique identifier (e.g. kingfisher.aws.1)
pattern: | # (multi-line regex) Detection pattern
(?x)(?i)
aws
(?:.|[\n\r]){0,32}?
\b([A-Za-z0-9/+=]{40})\b
min_entropy: 3.5 # (float) Minimum Shannon entropy
confidence: medium # (enum: low | medium | high)
examples: # (list) strings that must match
- AWS_SECRET="AKIA…"
references: # (optional list) context URLs
- https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html
visible: true # (bool) hide helper matches when false
depends_on_rule: # (optional) capture chaining
- rule_id: kingfisher.aws.id
variable: AKID # referenced as {{ AKID }}
validation: # (optional) live validation
type: Http
content:
request:
method: GET
url: https://api.example.com/v1/check
headers:
X-Secret: "{{ TOKEN }}"
X-Id: "{{ AKID }}"
response_is_html: true # by default, validation responses containing HTML or considered invalid. Set to `true` if you expect HTML returned from a validation response
response_matcher:
- report_response: true # always include raw payload
- type: StatusMatch
status: [200] # positive check
- type: StatusMatch
status: [401,403]
negative: true # negative check → must NOT match
- type: HeaderMatch
header: content-type
expected: ["application/json"]
- type: JsonValid
```
| Field | What it does |
| ----------------- | -------------------------------------------------------------------- |
| name | Friendly name shown in reports |
| id | Unique text ID (namespace.v#) used internally |
| pattern | Regex used to spot secrets (freespacing & flags allowed) |
| min_entropy | Threshold to guard against lowcomplexity false positives |
| confidence | Suggests severity: low → high |
| examples | Good matches; used for testing |
| visible | false to hide nonsecret captures (e.g. IDs) |
| depends_on_rule | Chain rules: use captures from one rule in anothers validation |
| validation | Configure HTTP, AWS, GCP, etc. checks to verify live validity |
*responser_matcher* variants. Multiple can be used
| Variant | Required keys | Behavior |
|-----------------|-------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------|
| **StatusMatch** | `status` (list\<int>)<br>`negative` (bool, default `false`) | Pass when codes match (or dont match if `negative`). |
| **WordMatch** | `words` (list\<string>)<br>`match_all_words` (bool)<br>`negative` (bool) | Word/substring checks in body. |
| **HeaderMatch** | `header` (string)<br>`expected` (list\<string>)<br>`match_all_values` (bool) | Header value assertions. |
| **JsonValid** | | Pass only if body parses as JSON. Use when response is expected as JSON data |
| **XmlValid** | | Pass only if body parses as well-formed XML. Use when response is expected as XML data |
| **ReportResponse** | `report_response` (bool) | Include raw payload in finding for debugging. |
### How depends_on_rule Works
- **Dependency Declaration:**
In your YAML rule definition, you add a `depends_on_rule` section. Here you specify:
- **rule_id:** The identifier of the rule whose output is required.
- **variable:** The name (typically in uppercase) that will be used to reference the captured value from the dependency rule.
- **Chaining Captures:**
When Kingfisher scans a file, it processes rules in a specific order. If a rule has a dependency, the engine first checks whether the dependent rule has already matched on the same input (or blob). If it did, the captured value (for example, an access key ID) is made available to the dependent rule.
- **Using the Captured Value:**
This captured value can then be used during the validation phase. For instance, if you have a rule for an Algolia Admin API Key that depends on an Algolia Application ID (captured as `APPID`), the validation logic can incorporate the `APPID` value to confirm that the secret matches the expected pattern or format for that specific account.
### Use depends_on_rule to require one rule before another runs:
```yaml
depends_on_rule:
- rule_id: kingfisher.algolia.app_id # must match first
variable: APPID # captured as {{ APPID }}
```
- **Capture flow**: First rule captures `APPID` → second rule injects `{{ APPID }}` into validation HTTP request or pattern
- **Visible control:** set `visible: false` on the supporting rule so it doesnt clutter your report for non-secret matches
## Algolia Example
Consider this example rule for an Algolia Application ID and Admin Key combination. To validate that this is an active credential, both must be matched:
```yaml
rules:
- name: Algolia Admin API Key
id: kingfisher.algolia.1
pattern: |
(?x)
(?i)
algolia
(?:.|[\n\r]){0,32}?
\b
(
[a-z0-9]{32}
)
\b
min_entropy: 3.5
confidence: medium
examples:
- algolia_api_key = "ij1mut5oe606wlrf5z4u8u31264z3gag"
validation:
type: Http
content:
request:
headers:
X-Algolia-API-Key: '{{ TOKEN }}'
X-Algolia-Application-Id: '{{ APPID }}'
method: GET
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
url: https://{{ APPID }}-dsn.algolia.net/1/keys
depends_on_rule:
- rule_id: "kingfisher.algolia.2"
variable: APPID
- name: Algolia Application ID
id: kingfisher.algolia.2
pattern: |
(?x)
(?i)
algolia
(?:.|[\n\r]){0,16}?
\b
(
[A-Z0-9]{10}
)
\b
min_entropy: 3.5
visible: false
confidence: medium
examples:
- algolia_app_id = "WRB8YLFW7Y"
```
### How It Works:
* Algolia Application ID Rule (kingfisher.algolia.2):
This rule scans for an Algolia Application ID—a 10-character alphanumeric string. It is marked with visible: false so that even if it matches, the finding is not directly reported. Its primary role is to provide a supporting value for other rules rather than to be flagged as a secret by itself.
* Algolia Admin API Key Rule (kingfisher.algolia.1):
This rule detects the Algolia Admin API Key using a regex pattern. It includes a depends_on_rule property that specifies a dependency on the Algolia Application ID rule.
* The dependency declares that the rule requires the output of the Algolia Application ID rule, and the captured value is assigned to the variable APPID.
* In the validation section, this captured `APPID` is used dynamically in the HTTP request (for example, in the header `X-Algolia-Application-Id` and in the URL).
The dependency mechanism (depends_on_rule) ensures that:
* Non-secret data (like an application ID) is captured without cluttering the scan report (thanks to visible: false).
* The secret (the API key) is validated in context, with the necessary supporting information automatically injected.
* Rules remain modular and extensible; you can update the dependent rule or its pattern independently, and the change will automatically be reflected where the value is used.
## The `visible: false` Property
The `visible: false` property tells Kingfisher to hide the finding from the final scan report. This is particularly useful for rules that capture data not meant to be reported as a secret, but rather to serve as supporting context for another rule.
For example, a rule might match a username, an email address, an AWS Access Key ID, or an Application ID. While these pieces of information are captured during scanning, they are not secrets on their own. Instead, they are used by other rules—via the `depends_on_rule` mechanism—to validate an associated secret. By marking such rules as `visible: false`, you prevent these non-secret findings from cluttering your report, yet their values remain available for dependent rules.
`visible: false` helps keep the scan output focused on actual secrets while still capturing important contextual data needed for comprehensive validation.
## Writing Custom Rules
When writing custom rules, consider the following best practices:
1. **Multi-line Regex:** Write your regex patterns over multiple lines for clarity. Use the `(?x)` flag to enable free-spacing mode.
2. **Optimize for Performance:** Structure your regex to minimize backtracking. Use non-capturing groups where possible and keep the pattern as concise as possible.
3. **Validation Integration:** Define a `validation` section if you want to verify the detected secret. You can use Liquid templating to insert dynamic values—use the unnamed capture as `TOKEN` and any named captures in uppercase.
4. **Test with Examples:** Always include examples that should match and, optionally, negative examples to ensure your rule behaves as expected.
## Examples
Below are some examples to guide you in writing custom rules
### Anthropic API Key
```yaml
rules:
- name: Anthropic API Key
id: kingfisher.anthropic.1
pattern: |
(?x)
(?i)
\b
(
sk-ant-api
\d{2,4}
-
[\w\-]{93}
AA
)
\b
min_entropy: 3.3
confidence: medium
examples:
- sk-ant-api668-Clm512odot9WDD7itfUU9R880nefA1EtYZDbpE-C9b0XQEWpqFKf9DQUo03vOfXl16oSmyar1CLF1SzV3YzpZJ6bahcpLAA
categories:
- api
- secret
references:
- https://docs.anthropic.com/claude/reference/authentication
validation:
type: Http
content:
request:
body: |
{
"model": "claude-3-haiku-20240307",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "respond only with 'success'"}
]
}
headers:
Content-Type: application/json
anthropic-version: "2023-06-01"
x-api-key: '{{ TOKEN }}'
method: POST
response_matcher:
- report_response: true
- status:
- 200
type: StatusMatch
- report_response: true
- type: WordMatch
words:
- '"type":"invalid_request_error"'
url: https://api.anthropic.com/v1/messages
```
### FileIO Secret Key
```yaml
rules:
- name: FileIO Secret Key
id: kingfisher.fileio.1
pattern: |
(?xi)
\b
fileio
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,16}?
\b
(
[A-Z0-9]{16}
(?:\.[A-Z0-9]{7}){2}
\.[A-Z0-9]{8}
)
\b
min_entropy: 3.3
confidence: medium
examples:
- fileio SECRETKEY = Z9Y8X7W6V5U4T3S2R1Q0.P9O8N7M6L5K4J3H2G1F
- fileio.PRIVATE.TOKEN = F0E1D2C3B4A596877869.5E4D3C2B1A0Z9Y8X7W6V
- fileio_key = M8N6B4V2C0X9Z7L5K3J1.H2G4F6D8S0A9P7O5I3U1
validation:
type: Http
content:
request:
method: GET
url: https://file.io/api/v2/account
headers:
Authorization: "Bearer {{ TOKEN }}"
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: HeaderMatch
header: content-type
expected: ["application/json"]
- type: JsonValid
```

462
docs/benchmark/.gitignore vendored Normal file
View file

@ -0,0 +1,462 @@
# Created by https://www.toptal.com/developers/gitignore/api/visualstudio,macos,go
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio,macos,go
*.html
### Go ###
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
### VisualStudio Patch ###
# Additional files built by Visual Studio
# End of https://www.toptal.com/developers/gitignore/api/visualstudio,macos,go

35
docs/benchmark/README.md Normal file
View file

@ -0,0 +1,35 @@
# Benchmark
A Go-based benchmarking tool that clones a set of repositories, runs security scanning tools (Kingfisher, Trufflehog, Gitleaks, and Detect-Secrets), and reports execution times, scan results, and network request counts. An intercepting HTTP proxy (without TLS decryption) counts network requests made by each tool.
## Features
- **Repository Cloning:** Clones a predefined list of repositories.
- **Tool Execution:** Runs external scanning tools and collects timing and result metrics.
- **Network Request Counting:** Uses an HTTP proxy (listening on 127.0.0.1:9191) to count each network request made.
- **Multi-Format Reporting:** Outputs results as a plain text table (default), Markdown table (`-markdown`), or interactive HTML report (`-html`).
- **Custom Base Directory:** Use a custom directory via the `-basedir` flag; defaults to `os.TempDir()/benchmark` if not specified.
- **HTML Report:** When using `-html`, writes to a timestamped HTML file, uses DataTables for sorting/searching, and automatically opens it in your default browser.
## Prerequisites
- **Go:** [Install Go](https://golang.org/doc/install)
- **Git:** Ensure `git` is installed and in your PATH.
- **Scanning Tools:** The following tools must be installed and available in your PATH:
- `kingfisher`
- `trufflehog`
- `gitleaks`
- `detect-secrets`
## Installation
Clone the repository and build the program:
```bash
git clone https://github.com/yourusername/benchmark-proxy-scanner.git
cd benchmark-proxy-scanner
go build -o benchmark
## Usage
./

3
docs/benchmark/go.mod Normal file
View file

@ -0,0 +1,3 @@
module kfbmark
go 1.24.0

536
docs/benchmark/main.go Normal file
View file

@ -0,0 +1,536 @@
// main.go
package main
import (
"bufio"
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync/atomic"
"text/tabwriter"
"time"
)
var (
// Global counter for intercepted HTTP requests.
netReqCount int64
// BASE_DIR will be set from a flag or default to os.TempDir()/benchmark.
BASE_DIR string
)
// Repo list: name and git URL.
var repos = []struct {
name string
url string
}{
{"croc", "https://github.com/schollz/croc.git"},
{"rails", "https://github.com/rails/rails.git"},
{"ruby", "https://github.com/ruby/ruby.git"},
{"gitlab", "https://gitlab.com/gitlab-org/gitlab.git"},
{"django", "https://github.com/django/django.git"},
{"lucene", "https://github.com/apache/lucene.git"},
{"mongodb", "https://github.com/mongodb/mongo.git"},
{"linux", "https://github.com/torvalds/linux.git"},
{"typescript", "https://github.com/microsoft/TypeScript.git"},
}
// RepoResult holds timing, tool outputs, and network request counts.
type RepoResult struct {
Repo string
// Runtimes in seconds.
KFTime time.Duration
KFFindings int
KFValidated int
KFNetReq int64
THTime time.Duration
THFindings int
THVerified int
THNetReq int64
GLTime time.Duration
GLFindings int
GLNetReq int64
DSTime time.Duration
DSFindings int
DSNetReq int64
}
// --- Proxy Implementation ---
// proxyHandler counts each request then forwards it.
type proxyHandler struct{}
func (p *proxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
atomic.AddInt64(&netReqCount, 1)
if req.Method == "CONNECT" {
handleTunneling(w, req)
} else {
handleHTTP(w, req)
}
}
func handleHTTP(w http.ResponseWriter, req *http.Request) {
proxy := httputil.ReverseProxy{
Director: func(r *http.Request) {},
}
proxy.ServeHTTP(w, req)
}
func handleTunneling(w http.ResponseWriter, req *http.Request) {
destConn, err := net.Dial("tcp", req.Host)
if err != nil {
http.Error(w, err.Error(), 503)
return
}
hijacker, ok := w.(interface {
Hijack() (net.Conn, *bufio.ReadWriter, error)
})
if !ok {
http.Error(w, "Hijacking not supported", 500)
return
}
clientConn, _, err := hijacker.Hijack()
if err != nil {
http.Error(w, err.Error(), 503)
return
}
_, err = clientConn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n"))
if err != nil {
clientConn.Close()
destConn.Close()
return
}
go transfer(destConn, clientConn)
go transfer(clientConn, destConn)
}
func transfer(dst io.WriteCloser, src io.ReadCloser) {
defer dst.Close()
defer src.Close()
io.Copy(dst, src)
}
func runProxy() {
server := &http.Server{
Addr: "127.0.0.1:9191",
Handler: &proxyHandler{},
}
log.Printf("Starting proxy on %s", server.Addr)
if err := server.ListenAndServe(); err != nil {
log.Fatalf("Proxy error: %v", err)
}
}
// --- Helper Functions ---
func getSystemInfo() string {
osInfo := runtime.GOOS
arch := runtime.GOARCH
cpuCount := runtime.NumCPU()
memInfo := "N/A"
if osInfo == "darwin" {
out, err := exec.Command("sysctl", "-n", "hw.memsize").Output()
if err == nil {
if memBytes, err := strconv.ParseInt(strings.TrimSpace(string(out)), 10, 64); err == nil {
memInfo = fmt.Sprintf("%.2f GB", float64(memBytes)/(1024*1024*1024))
}
}
} else if osInfo == "linux" {
data, err := ioutil.ReadFile("/proc/meminfo")
if err == nil {
scanner := bufio.NewScanner(bytes.NewReader(data))
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "MemTotal:") {
parts := strings.Fields(line)
if len(parts) >= 2 {
if memKb, err := strconv.ParseInt(parts[1], 10, 64); err == nil {
memInfo = fmt.Sprintf("%.2f GB", float64(memKb)/(1024*1024))
}
}
break
}
}
}
}
return fmt.Sprintf("OS: %s\nArchitecture: %s\nCPU Cores: %d\nRAM: %s\n", osInfo, arch, cpuCount, memInfo)
}
func cloneRepo(repoName, repoURL string) error {
dest := filepath.Join(BASE_DIR, repoName)
if _, err := os.Stat(dest); os.IsNotExist(err) {
fmt.Printf("Cloning %s...\n", repoName)
cmd := exec.Command("git", "clone", repoURL, dest)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
fmt.Printf("Repo '%s' exists, skipping clone.\n", repoName)
return nil
}
func runCommand(cmdArgs []string, cwd string, ignoreErrors bool, combineStderr bool) (time.Duration, int, string, error) {
atomic.StoreInt64(&netReqCount, 0)
start := time.Now()
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
cmd.Dir = cwd
cmd.Env = append(os.Environ(), "HTTP_PROXY=127.0.0.1:9191", "HTTPS_PROXY=127.0.0.1:9191")
var output []byte
var err error
if combineStderr {
output, err = cmd.CombinedOutput()
} else {
output, err = cmd.Output()
}
elapsed := time.Since(start)
exitCode := 0
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
exitCode = exitError.ExitCode()
} else {
if !ignoreErrors {
return elapsed, exitCode, "", err
}
}
}
fmt.Fprintf(os.Stderr, "[TIME] Command: %s took %.2fs\n", strings.Join(cmdArgs, " "), elapsed.Seconds())
return elapsed, exitCode, string(output), nil
}
func parseKingfisherOutput(output string) (int, int) {
total, validated := 0, 0
scanner := bufio.NewScanner(strings.NewReader(output))
for scanner.Scan() {
var data map[string]interface{}
if err := json.Unmarshal(scanner.Bytes(), &data); err == nil {
if v, ok := data["findings"].(float64); ok {
total = int(v)
}
if v, ok := data["successful_validations"].(float64); ok {
validated = int(v)
}
}
}
return total, validated
}
func parseTrufflehogOutput(output string) (int, int) {
total, verified := 0, 0
scanner := bufio.NewScanner(strings.NewReader(output))
for scanner.Scan() {
var data map[string]interface{}
if err := json.Unmarshal(scanner.Bytes(), &data); err == nil {
if u, ok := data["unverified_secrets"].(float64); ok {
total += int(u)
}
if v, ok := data["verified_secrets"].(float64); ok {
verified = int(v)
total += int(v)
}
}
}
return total, verified
}
func parseGitleaksOutput(reportPath string) int {
data, err := ioutil.ReadFile(reportPath)
if err != nil {
return 0
}
var arr []interface{}
if err := json.Unmarshal(data, &arr); err == nil {
return len(arr)
}
return 0
}
func parseDetectSecretsOutput(output string) int {
var data map[string]interface{}
if err := json.Unmarshal([]byte(output), &data); err != nil {
return 0
}
results := data["results"]
sum := 0
if m, ok := results.(map[string]interface{}); ok {
for _, v := range m {
if arr, ok := v.([]interface{}); ok {
sum += len(arr)
}
}
}
return sum
}
func formatDuration(d time.Duration) string {
secs := int(d.Seconds() + 0.5)
m := secs / 60
s := secs % 60
if m > 0 {
return fmt.Sprintf("%.2f (%dm %ds)", d.Seconds(), m, s)
}
return fmt.Sprintf("%.2f (%ds)", d.Seconds(), s)
}
// --- Output Functions for Markdown Tables ---
func printRuntimeTable(results []RepoResult) {
fmt.Println("### Runtime Comparison (seconds)")
fmt.Println()
fmt.Println("| Repository | Kingfisher Runtime | TruffleHog Runtime | GitLeaks Runtime | detect-secrets Runtime |")
fmt.Println("|------------|--------------------|--------------------|------------------|------------------------|")
for _, r := range results {
fmt.Printf("| %s | %.2f | %.2f | %.2f | %.2f |\n",
r.Repo,
r.KFTime.Seconds(),
r.THTime.Seconds(),
r.GLTime.Seconds(),
r.DSTime.Seconds())
}
fmt.Println()
}
func printFindingsTable(results []RepoResult) {
fmt.Println("### Validated/Verified Findings Comparison")
fmt.Println()
fmt.Println("Note: For GitLeaks and detect-secrets, validated/verified counts are not available.")
fmt.Println()
fmt.Println("| Repository | Kingfisher Validated | TruffleHog Verified | GitLeaks Verified | detect-secrets Verified |")
fmt.Println("|------------|----------------------|---------------------|-------------------|-------------------------|")
for _, r := range results {
fmt.Printf("| %s | %d | %d | %d | %d |\n",
r.Repo,
r.KFValidated,
r.THVerified,
0,
0)
}
fmt.Println()
}
func printNetworkTable(results []RepoResult) {
fmt.Println("### Network Requests Comparison")
fmt.Println()
fmt.Println("| Repository | Kingfisher Network Requests | TruffleHog Network Requests | GitLeaks Network Requests | detect-secrets Network Requests |")
fmt.Println("|------------|-----------------------------|-----------------------------|---------------------------|----------------------------------|")
for _, r := range results {
fmt.Printf("| %s | %d | %d | %d | %d |\n",
r.Repo,
r.KFNetReq,
r.THNetReq,
r.GLNetReq,
r.DSNetReq)
}
fmt.Println()
}
// --- QuickChart.io Integration ---
// This function builds chart URLs using QuickChart.io and prints Markdown image links.
func printQuickChartLinks(results []RepoResult) {
labels := []string{}
for _, r := range results {
labels = append(labels, r.Repo)
}
chartURL := func(title string, datasetMap map[string][]float64) string {
type dataset struct {
Label string `json:"label"`
Data []float64 `json:"data"`
}
data := struct {
Type string `json:"type"`
Data struct {
Labels []string `json:"labels"`
Datasets []dataset `json:"datasets"`
} `json:"data"`
Options map[string]interface{} `json:"options"`
}{
Type: "bar",
}
data.Data.Labels = labels
for label, vals := range datasetMap {
data.Data.Datasets = append(data.Data.Datasets, dataset{Label: label, Data: vals})
}
data.Options = map[string]interface{}{
"title": map[string]string{"display": "true", "text": title},
"scales": map[string]interface{}{
"yAxes": []map[string]interface{}{
{"ticks": map[string]interface{}{"beginAtZero": true}},
},
},
}
jsonBytes, err := json.Marshal(data)
if err != nil {
log.Printf("Error marshaling JSON: %v", err)
}
// URL-encode the JSON configuration.
return "https://quickchart.io/chart?c=" + url.QueryEscape(string(jsonBytes))
}
// Build datasets.
var (
kfTimes, thTimes, glTimes, dsTimes []float64
kfValids, thVerifs, glZero, dsZero []float64
kfReqs, thReqs, glReqs, dsReqs []float64
)
for _, r := range results {
kfTimes = append(kfTimes, r.KFTime.Seconds())
thTimes = append(thTimes, r.THTime.Seconds())
glTimes = append(glTimes, r.GLTime.Seconds())
dsTimes = append(dsTimes, r.DSTime.Seconds())
kfValids = append(kfValids, float64(r.KFValidated))
thVerifs = append(thVerifs, float64(r.THVerified))
glZero = append(glZero, 0)
dsZero = append(dsZero, 0)
kfReqs = append(kfReqs, float64(r.KFNetReq))
thReqs = append(thReqs, float64(r.THNetReq))
glReqs = append(glReqs, float64(r.GLNetReq))
dsReqs = append(dsReqs, float64(r.DSNetReq))
}
fmt.Println("### QuickChart.io Visualizations")
fmt.Println()
fmt.Println("#### Runtime Chart")
fmt.Println("![Runtime Comparison](" + chartURL("Runtime Comparison (seconds)", map[string][]float64{
"Kingfisher": kfTimes,
"TruffleHog": thTimes,
"GitLeaks": glTimes,
"detect-secrets": dsTimes,
}) + ")")
fmt.Println()
fmt.Println("#### Validated/Verified Findings Chart")
fmt.Println("![Findings Comparison](" + chartURL("Validated/Verified Findings", map[string][]float64{
"Kingfisher": kfValids,
"TruffleHog": thVerifs,
"GitLeaks": glZero,
"detect-secrets": dsZero,
}) + ")")
fmt.Println()
fmt.Println("#### Network Requests Chart")
fmt.Println("![Network Requests](" + chartURL("Network Requests Made", map[string][]float64{
"Kingfisher": kfReqs,
"TruffleHog": thReqs,
"GitLeaks": glReqs,
"detect-secrets": dsReqs,
}) + ")")
fmt.Println()
}
func main() {
// Parse command-line flags.
baseDirFlag := flag.String("basedir", "", "Directory to clone repos (default: os.TempDir()/benchmark)")
markdownFlag := flag.Bool("markdown", true, "Output in Markdown format")
flag.Parse()
// Set BASE_DIR.
if *baseDirFlag == "" {
BASE_DIR = filepath.Join(os.TempDir(), "benchmark")
} else {
BASE_DIR = *baseDirFlag
}
// Ensure BASE_DIR exists.
os.MkdirAll(BASE_DIR, 0755)
// Start the proxy in a goroutine.
go runProxy()
time.Sleep(500 * time.Millisecond)
fmt.Println(getSystemInfo())
var results []RepoResult
// Process each repository.
for _, r := range repos {
if err := cloneRepo(r.name, r.url); err != nil {
log.Fatalf("Error cloning %s: %v", r.name, err)
}
repoPath := filepath.Join(BASE_DIR, r.name)
var res RepoResult
res.Repo = r.name
// Kingfisher.
fmt.Printf("[Kingfisher] Scanning %s...\n", repoPath)
kfArgs := []string{"kingfisher", "scan", repoPath, "--no-dedup", "--format", "json"}
kfTime, _, kfOut, err := runCommand(kfArgs, ".", false, false)
if err != nil {
log.Printf("Error running kingfisher: %v", err)
}
res.KFTime = kfTime
res.KFFindings, res.KFValidated = parseKingfisherOutput(kfOut)
res.KFNetReq = atomic.LoadInt64(&netReqCount)
// TruffleHog.
fmt.Printf("\n[TruffleHog] Scanning %s...\n", repoPath)
fileURI := "file://" + repoPath
thArgs := []string{"trufflehog", "git", fileURI, "--json"}
thTime, _, thOut, err := runCommand(thArgs, ".", false, true)
if err != nil {
log.Printf("Error running trufflehog: %v", err)
}
res.THTime = thTime
res.THFindings, res.THVerified = parseTrufflehogOutput(thOut)
res.THNetReq = atomic.LoadInt64(&netReqCount)
// GitLeaks.
fmt.Printf("\n[GitLeaks] Scanning %s...\n", repoPath)
glReport := filepath.Join(BASE_DIR, fmt.Sprintf("gl-%s.json", r.name))
glArgs := []string{"gitleaks", "git", "-v", repoPath, "--report-path", glReport}
glTime, _, _, _ := runCommand(glArgs, ".", true, false)
res.GLTime = glTime
res.GLFindings = parseGitleaksOutput(glReport)
res.GLNetReq = atomic.LoadInt64(&netReqCount)
// detect-secrets.
fmt.Printf("\n[detect-secrets] Scanning %s...\n", repoPath)
dsArgs := []string{"detect-secrets", "scan", repoPath}
dsTime, _, dsOut, err := runCommand(dsArgs, ".", false, false)
if err != nil {
log.Printf("Error running detect-secrets: %v", err)
}
res.DSTime = dsTime
res.DSFindings = parseDetectSecretsOutput(dsOut)
res.DSNetReq = atomic.LoadInt64(&netReqCount)
results = append(results, res)
}
// --- Output Report in Markdown ---
if *markdownFlag {
// Print separate summary tables.
printRuntimeTable(results)
printFindingsTable(results)
printNetworkTable(results)
// Print QuickChart.io image links.
printQuickChartLinks(results)
} else {
// Fallback to a text table.
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "Repository\tKingfisher Runtime\tKingfisher Findings\tKingfisher Validated\tKingfisher Network Requests\tTruffleHog Runtime\tTruffleHog Findings\tTruffleHog Verified\tTruffleHog Network Requests\tGitLeaks Runtime\tGitLeaks Findings\tGitLeaks Network Requests\tdetect-secrets Runtime\tdetect-secrets Findings\tdetect-secrets Network Requests")
fmt.Fprintln(w, "----------\t------------------\t---------------------\t----------------------\t-------------------------\t------------------\t---------------------\t---------------------\t--------------------------\t----------------\t-------------------\t---------------------------\t---------------------\t-------------------------\t--------------------------")
for _, r := range results {
fmt.Fprintf(w, "%s\t%s\t%d\t%d\t%d\t%s\t%d\t%d\t%d\t%s\t%d\t%d\t%s\t%d\t%d\n",
r.Repo,
formatDuration(r.KFTime), r.KFFindings, r.KFValidated, r.KFNetReq,
formatDuration(r.THTime), r.THFindings, r.THVerified, r.THNetReq,
formatDuration(r.GLTime), r.GLFindings, r.GLNetReq,
formatDuration(r.DSTime), r.DSFindings, r.DSNetReq)
}
w.Flush()
}
fmt.Println("\n*Lower runtimes are better. Validated/Verified counts are reported where available. 'Network Requests' indicates the number of HTTP requests made during scanning.*")
fmt.Println(getSystemInfo())
}

Some files were not shown because too many files have changed in this diff Show more