diff --git a/.gitignore b/.gitignore index d6f49ca..ec3fdbb 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ logs/* # Icon must end with two \r Icon +node_modules/ # Thumbnails ._* diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index ae2d00c..91f652c 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,7 +1,7 @@ - id: kingfisher-docker name: kingfisher (docker) description: Run Kingfisher in Docker against staged changes at the repository root. No local install required. - entry: ghcr.io/kingfisher-sec/kingfisher:latest + entry: ghcr.io/mongodb/kingfisher:latest language: docker args: ["scan", ".", "--staged", "--quiet", "--no-update-check"] pass_filenames: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 27d0b11..faa3f72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. +## [v1.73.0] +- Will now prefer git history findings when identical secrets appear in both current files and git history (dedup only). +- Fixed report viewer to add support for opening JSONL. +- Add opt-in contributor repository enumeration for GitHub/GitLab `--git-url` scans with `--include-contributors`, plus `--repo-clone-limit` to cap repo cloning. +- Add `--git-clone-dir` to set the parent clone directory and `--keep-clones` to preserve cloned repos after scans. +- Added several new rules. +- Added configurable validation timeout and retry settings for `kingfisher scan`. + ## [v1.72.0] - Fixed deduplication for dependency-provider rules so dependent validations run per blob - Updated Artifactory rule entropy and added new artifactory rule diff --git a/Cargo.toml b/Cargo.toml index c84c645..f4c1cd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ publish = false [package] name = "kingfisher" -version = "1.72.0" +version = "1.73.0" description = "MongoDB's blazingly fast and accurate secret scanning and validation tool" edition.workspace = true rust-version.workspace = true @@ -74,6 +74,7 @@ url = "2.5.7" include_dir = { version = "0.7", features = ["glob"] } strum = { version = "0.26", features = ["derive"] } sysinfo = "0.31.4" +webbrowser = "1.0.5" reqwest = { version = "0.12", default-features = false, features = [ "json", "gzip", diff --git a/README.md b/README.md index be895a2..f04d285 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Kingfisher is a blazingly fast secret-scanning and **live validation** tool buil It combines Intel’s SIMD-accelerated regex engine (Hyperscan) with language-aware parsing to achieve high accuracy at massive scale, and **ships with hundreds of built-in rules** to detect, **validate**, and triage secrets before they ever reach production. +Designed for offensive security engineers and blue-teamers alike, Kingfisher helps you pivot across repo ecosystems, validate exposure paths, and hunt for developer-owned leaks that spill beyond the primary codebase. + For a look at how Kingfisher has grown from its early foundations into today's full-featured scanner, see [Lineage and Evolution](#lineage-and-evolution).

@@ -33,7 +35,7 @@ For a look at how Kingfisher has grown from its early foundations into today's f ### Performance, Accuracy, and Hundreds of Rules - **Performance**: multithreaded, Hyperscan‑powered scanning built for huge codebases - **Extensible rules**: hundreds of built-in detectors plus YAML-defined custom rules ([docs/RULES.md](/docs/RULES.md)) -- **Blast Radius Mapping**: instantly map leaked keys to their effective cloud identities and exposed resources with `--access-map` +- **Blast Radius Mapping**: instantly map leaked keys to their effective cloud identities and exposed resources with `--access-map`. Supports AWS, GCP, Azure, GitHub, Gitlab, and more token support coming. - **Broad AI SaaS coverage**: finds and validates tokens for OpenAI, Anthropic, Google Gemini, Cohere, AWS Bedrock, Voyage AI, Mistral, Stability AI, Replicate, xAI (Grok), Ollama, Langchain, Perplexity, Weights & Biases, Cerebras, Friendli, Fireworks.ai, NVIDIA NIM, Together.ai, Zhipu, and many more - **Compressed Files**: Supports extracting and scanning compressed files for secrets - **Baseline management**: generate and track baselines to suppress known secrets ([docs/BASELINE.md](/docs/BASELINE.md)) @@ -51,10 +53,18 @@ See ([docs/COMPARISON.md](docs/COMPARISON.md))

## Basic Usage Demo +```bash +kingfisher scan /path/to/scan --view-report +``` +NOTE: Replay has been slowed down for demo ![alt text](docs/kingfisher-usage-01.gif) ## Report Viewer Demo -Explore Kingfisher’s built-in report viewer and its `--access-map` feature for visualizing access relationships: [Access map outputs and viewer](#access-map-outputs-and-viewer) +Explore Kingfisher’s built-in report viewer and its `--access-map`, which can show what the token (AWS, GCP, Azure, GitHub, and GitLab...more coming) can actually access : [Access map outputs and viewer](#access-map-outputs-and-viewer) + +```bash +kingfisher scan /path/to/scan --access-map --view-report +``` ![alt text](docs/kingfisher-usage-access-map.gif) @@ -159,6 +169,7 @@ Explore Kingfisher’s built-in report viewer and its `--access-map` feature for - [To add your rules alongside the built‑ins:](#to-add-your-rules-alongside-the-builtins) - [Other Examples](#other-examples) - [Customize the HTTP User-Agent](#customize-the-http-user-agent) + - [Validation tuning flags](#validation-tuning-flags) - [Notable Scan Options](#notable-scan-options) - [Understanding `--confidence`](#understanding---confidence) - [Ignore known false positives](#ignore-known-false-positives) @@ -579,15 +590,16 @@ kingfisher scan /path/to/repo --format sarif --output findings.sarif Finding a leaked credential is only the first step. The critical question isn’t just “Is this a secret?”—it’s “What can an attacker do with it?” -Kingfisher's `--access-map` feature transforms secret detection from a simple alert into a comprehensive threat assessment. Instead of leaving you with a cryptic API key, Kingfisher actively authenticates against your cloud provider (AWS or GCP) to map the full extent of the credential's power. +Kingfisher's `--access-map` feature transforms secret detection from a simple alert into a comprehensive threat assessment. Instead of leaving you with a cryptic API key, Kingfisher actively authenticates against your cloud provider (AWS, GCP, Azure Storage, Azure DevOps, GitHub, or GitLab) to map the full extent of the credential's power. * Instant Identity Resolution: Immediately identify who the key belongs to—whether it's a specific IAM user, an assumed role, or a service account. -* Visualize the Blast Radius: See exactly which resources (S3 buckets, EC2 instances, projects) are exposed and at risk. - +* Visualize the Blast Radius: See exactly which resources (S3 buckets, EC2 instances, projects, storage containers) are exposed and at risk. + Add `--access-map` to enrich JSON, JSONL, BSON, pretty, and SARIF reports with an `access_map` containing the resources and the permissions that the key can access - for each resource (grouped when identical). - If you validated cloud credentials without `--access-map`, Kingfisher will remind you on stderr to rerun with the flag so the access map appears in the output. -- Run `kingfisher view ./kingfisher.json` to explore a report locally in a local web UI +- Run `kingfisher view ./kingfisher.json` to explore a report locally in a local web UI (opens your browser automatically when a report is provided). +- Or use `kingfisher scan --view-report ...` to generate a JSON report, start the viewer at `http://127.0.0.1:7890`, and open it in your browser. > **Use the access map functionality only when you are authorized to inspect the target account, as Kingfisher will issue additional network requests to determine what access the secret grants** @@ -599,7 +611,7 @@ Add `--access-map` to enrich JSON, JSONL, BSON, pretty, and SARIF reports with a kingfisher view kingfisher.json ``` -The `view` subcommand starts a local-only server (default port `7890`) that bundles the HTML, CSS, and JavaScript for the access-map viewer directly into the Kingfisher binary. Provide a JSON or JSONL report to load it automatically, or open the page and upload a report in the browser. If port 7890 is already in use, Kingfisher will exit and tell you to re-run with `--port `. +The `view` subcommand starts a local-only server (default port `7890`) that bundles the HTML, CSS, and JavaScript for the access-map viewer directly into the Kingfisher binary. Provide a JSON or JSONL report to load it automatically and Kingfisher will open your browser, or open the page and upload a report in the browser. If port 7890 is already in use, Kingfisher will exit and tell you to re-run with `--port `. ### Pipe any text directly into Kingfisher by passing `-` @@ -867,6 +879,7 @@ kingfisher scan docker private.registry.example.com/my-image:tag ```bash kingfisher scan github --organization my-org +kingfisher scan github --organization my-org --repo-clone-limit 500 ``` ### Skip specific GitHub repositories during enumeration @@ -884,11 +897,24 @@ kingfisher scan github --organization my-org \ ### Scan remote GitHub repository -`--git-url` clones the repository and scans its files and history. To also inspect -related server-side data, supply `--repo-artifacts`. This flag pulls down the -repository's issues (including pull requests), wiki, and any public gists owned by -the repository owner and scans them for secrets. Fetching these extras counts -against API rate limits and private artifacts require a `KF_GITHUB_TOKEN`. +`--git-url` clones the repository and scans its files and history. When the URL +targets GitHub and you pass `--include-contributors`, Kingfisher enumerates +repository contributors and attempts to clone **all public repos owned by those +contributors**—a common offensive and blue-team pivot when developers leak +secrets in personal or side projects. Use `--repo-clone-limit` to cap how many +repositories are cloned during this enumeration. + +**NOTE**: This may cause you to be temporarily rate-limited by GitHub. +Providing a token (`KF_GITHUB_TOKEN`) will provide a higher rate limit. + +To inspect related server-side data, supply `--repo-artifacts`. This flag pulls +down the repository's issues (including pull requests), wiki, and any public +gists owned by the repository owner and scans them for secrets. Fetching these +extras counts against API rate limits and private artifacts require a +`KF_GITHUB_TOKEN`. + +Use `--git-clone-dir` to choose where cloned repositories land and +`--keep-clones` to preserve them for follow-on analysis. > **Why does `--git-url` sometimes report fewer findings than scanning a local checkout?**. > @@ -905,6 +931,16 @@ against API rate limits and private artifacts require a `KF_GITHUB_TOKEN`. # Scan the repository only kingfisher scan --git-url https://github.com/org/repo.git +# Scan the repository plus contributor repos, but cap the crawl +kingfisher scan --git-url https://github.com/org/repo.git \ + --include-contributors \ + --repo-clone-limit 250 + +# Keep clones for later manual inspection +kingfisher scan --git-url https://github.com/org/repo.git \ + --git-clone-dir ./kingfisher-clones \ + --keep-clones + # Include issues, wiki, and owner gists kingfisher scan --git-url https://github.com/org/repo.git --repo-artifacts @@ -922,6 +958,7 @@ KF_GITHUB_TOKEN="ghp_…" kingfisher scan --git-url https://github.com/org/priva kingfisher scan gitlab --group my-group # include repositories from all nested subgroups kingfisher scan gitlab --group my-group --include-subgroups +kingfisher scan gitlab --group my-group --repo-clone-limit 500 ``` ### Scan GitLab user @@ -945,15 +982,37 @@ kingfisher scan gitlab --group my-group \ ### Scan remote GitLab repository by URL -`--git-url` by itself clones the project repository. To include server-side -artifacts owned by the project, add `--repo-artifacts`. Kingfisher will retrieve -the project's issues, wiki, and snippets and scan them for secrets. These extra -requests may take longer and require a `KF_GITLAB_TOKEN` for private projects. +`--git-url` by itself clones the project repository. When the URL targets +GitLab and you pass `--include-contributors`, Kingfisher enumerates contributors +and tries to clone **their other public projects** to catch secrets that escape +the main repo. Apply `--repo-clone-limit` to cap the total repos cloned during +this pivot. + +**NOTE**: This may cause you to be temporarily rate-limited by GitLab. +Providing a token (`KF_GITLAB_TOKEN`) will provide a higher rate limit. + +To include server-side artifacts owned by the project, add `--repo-artifacts`. +Kingfisher will retrieve the project's issues, wiki, and snippets and scan them +for secrets. These extra requests may take longer and require a +`KF_GITLAB_TOKEN` for private projects. + +Use `--git-clone-dir` to choose where cloned projects land and `--keep-clones` +to preserve them for later review. ```bash # Scan the repository only kingfisher scan --git-url https://gitlab.com/group/project.git +# Scan the repository plus contributor projects, but cap the crawl +kingfisher scan --git-url https://gitlab.com/group/project.git \ + --include-contributors \ + --repo-clone-limit 250 + +# Keep clones for later manual inspection +kingfisher scan --git-url https://gitlab.com/group/project.git \ + --git-clone-dir ./kingfisher-clones \ + --keep-clones + # Include issues, wiki, and snippets kingfisher scan --git-url https://gitlab.com/group/project.git --repo-artifacts @@ -1377,14 +1436,24 @@ kingfisher --user-agent-suffix "Sept 2025 testing" scan github --user my-user -- ``` When omitted, Kingfisher defaults to `kingfisher/ Mozilla/5.0 ...`. The suffix is trimmed; passing an empty string -leaves the default unchanged. +{"$id":"1","innerException":null,"message":"VS403403: Cannot find any branches for the test-project repository.","typeName":"Microsoft.TeamFoundation.Git.Server.GitItemNotFoundException, Microsoft.TeamFoundation.Git.Server","typeKey":"GitItemNotFoundException","errorCode":0,"eventId":3000} +## Validation tuning flags + +Use these options with `kingfisher scan` to customize live validation behavior: + +- `--validation-timeout SECONDS`: per-request and per-match timeout for validation (default: 10, range: 1-60). +- `--validation-retries N`: number of retry attempts for validation requests (default: 1, range: 0-5). ## Notable Scan Options - `--no-dedup`: Report every occurrence of a finding (disable the default de-duplicate behavior) - `--no-base64`: By default, Kingfisher finds and decodes base64 blobs and scans them for secrets. This adds a slight performance overhead; use this flag to disable - `--confidence `: (low|medium|high) - `--min-entropy `: Override default threshold +- `--include-contributors`: When using `--git-url` for GitHub or GitLab, include contributor-owned repos in the scan +- `--git-clone-dir `: Choose the parent directory for cloned repos and scan artifacts (use with `--git-url`) +- `--keep-clones`: Preserve cloned repositories on disk after a scan completes +- `--repo-clone-limit `: Cap the number of GitHub/GitLab repositories cloned when enumerating orgs/groups or contributor repos - `--no-binary`: Skip binary files - `--no-extract-archives`: Do not scan inside archives - `--extraction-depth `: Specifies how deep nested archives should be extracted and scanned (default: 2) @@ -1399,6 +1468,8 @@ leaves the default unchanged. - `--ignore-comment `: Honor additional inline directives from other scanners (repeatable; e.g. `--ignore-comment "gitleaks:allow"`) - `--no-ignore`: Disable inline directives entirely so every match is reported - `--no-ignore-if-contains`: Ignore the `ignore_if_contains` filter in rules so placeholder words still produce findings +- `--validation-timeout SECONDS`: per-request and per-match timeout for validation (default: 10, range: 1-60). +- `--validation-retries N`: number of retry attempts for validation requests (default: 1, range: 0-5). ## Understanding `--confidence` diff --git a/data/rules/airtable.yml b/data/rules/airtable.yml index abb100d..c20c68c 100644 --- a/data/rules/airtable.yml +++ b/data/rules/airtable.yml @@ -8,7 +8,7 @@ rules: pat [a-z0-9]{14} \. - [a-z0-9]{62,66} + [a-z0-9]{64} ) \b pattern_requirements: diff --git a/data/rules/alchemy.yml b/data/rules/alchemy.yml new file mode 100644 index 0000000..75d72e9 --- /dev/null +++ b/data/rules/alchemy.yml @@ -0,0 +1,46 @@ +rules: + - name: Alchemy API Key + id: kingfisher.alchemy.1 + pattern: | + (?xi) + \balchemy + (?:.|[\n\r]){0,96}? + (?: + /v2/ + | + api[_-]?key|key|token|secret|url|endpoint|rpc + ) + (?:.|[\n\r]){0,96}? + \b + ( + [A-Za-z0-9_-]{24,64} + ) + \b + pattern_requirements: + min_digits: 4 + min_entropy: 3.5 + confidence: medium + examples: + - alchemy_key="PajdHzB75s1V_7aldcQ6XbodqDCWMC7m" + - https://eth-mainnet.alchemyapi.io/v2/PajdHzB75s1V_7aldcQ6XbodqDCWMC7m + - https://eth-goerli.alchemyapi.io/v2/AGtF3w2AsccY_bfsdDleaVRehW2xGS7W + references: + - https://www.alchemy.com/rpc/ethereum + - https://www.alchemy.com/docs/reference/nft-api-endpoints/nft-api-endpoints/nft-ownership-endpoints/get-nf-ts-for-owner-v-3 + validation: + type: Http + content: + request: + method: GET + url: "https://eth-mainnet.g.alchemy.com/nft/v3/{{ TOKEN }}/getNFTsForOwner?owner=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" + headers: + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"ownedNfts"'] + - type: WordMatch + negative: true + words: ['"error"'] diff --git a/data/rules/azure.yml b/data/rules/azure.yml index 1a1d650..6348610 100644 --- a/data/rules/azure.yml +++ b/data/rules/azure.yml @@ -8,6 +8,8 @@ rules: (?: AccountKey | SharedAccessKey | SharedSecretValue) \s*=\s* ([^;]{1,100}) (?: ;|$ ) min_entropy: 3.3 + pattern_requirements: + min_digits: 2 confidence: medium examples: - | @@ -78,6 +80,7 @@ rules: [a-z0-9][a-z0-9-]{1,100}[a-z0-9] )\.azurecr\.io confidence: medium + visible: false min_entropy: 2.0 examples: - "myregistry.azurecr.io" diff --git a/data/rules/azurestorage.yml b/data/rules/azurestorage.yml index 1fe3c44..d4ae1ef 100644 --- a/data/rules/azurestorage.yml +++ b/data/rules/azurestorage.yml @@ -36,11 +36,11 @@ rules: ["':\s=}\]\)] ( (?: - [A-Z0-9+\-]{86,88}={1,2} + [A-Z0-9+\\/-]{86,88}={1,2} ) | (?: - [A-Z0-9+\-]{86,88}\b + [A-Z0-9+\\/-]{86,88}\b ) ) pattern_requirements: diff --git a/data/rules/coveralls.yml b/data/rules/coveralls.yml new file mode 100644 index 0000000..77e8f74 --- /dev/null +++ b/data/rules/coveralls.yml @@ -0,0 +1,81 @@ +rules: + - name: Coveralls Repo Identifier + id: kingfisher.coveralls.1 + visible: false + confidence: medium + min_entropy: 2.0 + pattern: | + (?xi) + (?: + coveralls\.io/ + (?: + (?: + github|bitbucket|gitlab + ) + / + ( + [A-Z0-9_.-]+ + ) + / + ( + [A-Z0-9_.-]+ + ) + ) + | + api/v1/repos/ + ( + github|bitbucket|gitlab + ) + / + ( + [A-Z0-9_.-]+ + ) + ) + examples: + - https://coveralls.io/github/lemurheavy/coveralls-public + - https://coveralls.io/gitlab/group/project + - https://coveralls.io/api/v1/repos/github/octocat/hello-world + + - name: Coveralls Personal API Token + id: kingfisher.coveralls.2 + pattern: | + (?xi) + \b + coveralls + (?:.|[\n\r]){0,1}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) + (?:.|[\n\r]){0,32}? + \b + ( + [A-Z0-9-]{37} + ) + \b + pattern_requirements: + min_digits: 3 + min_entropy: 3.3 + confidence: medium + examples: + - coveralls_SECRETTOKEN abcdefghijklmnopqrstuvwxyzab12345cdef + - coveralls-SECRET-KEY mnopqrstuvwxyzabcdefghi12345678901234 + - coveralls_PRIVATEKEY-1234567890abcdefghijklmnopqrstuvwxyza + references: + - https://docs.coveralls.io/api-repos-endpoint + - https://docs.coveralls.io/api-introduction + depends_on_rule: + - rule_id: kingfisher.coveralls.1 + variable: COVERALLS_REPO_ID + validation: + type: Http + content: + request: + method: GET + url: "https://coveralls.io/api/v1/repos/{{ COVERALLS_REPO_ID }}" + headers: + Authorization: "token {{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"service"', '"name"', '"id"'] diff --git a/data/rules/datadog.yml b/data/rules/datadog.yml index 8607d94..c2dd478 100644 --- a/data/rules/datadog.yml +++ b/data/rules/datadog.yml @@ -1,20 +1,52 @@ rules: - - name: Datadog API Key - id: kingfisher.datadog.3 + # Helper: extract the Datadog site domain from common config/env/URLs. + # We capture the "site parameter" (domain), then validation uses https://api.. + - name: Datadog Site Domain + id: kingfisher.datadog.1 + visible: false + confidence: medium + min_entropy: 2.0 pattern: | (?xi) + (?: + # env/config patterns + \b(?:DD_SITE|DATADOG_SITE|DATADOG_HOST)\b\s*[:=]\s*["']? + (?:https?://)? + (?:api\.|app\.)? + | + # raw URLs in code/docs + \bhttps?://(?:api\.|app\.)? + )? + ( + datadoghq\.com + | us3\.datadoghq\.com + | us5\.datadoghq\.com + | datadoghq\.eu + | ap1\.datadoghq\.com + | ap2\.datadoghq\.com + | ddog-gov\.com + ) \b - (?:datadog|dd) + examples: + - DD_SITE=datadoghq.eu + - DATADOG_HOST=https://api.us3.datadoghq.com + - https://app.datadoghq.com + - https://api.ddog-gov.com + + - name: Datadog API Key + id: kingfisher.datadog.2 + pattern: | + (?xi) + \b(?:datadog|dd) (?:.|[\n\r]){0,64}? - (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) + (?:api[_-]?key|dd[_-]?api[_-]?key|secret|private|access|token) (?:.|[\n\r]){0,32}? \b ( - [A-Za-z0-9]{32} + [A-Z0-9]{32} ) - \b pattern_requirements: - min_digits: 2 + min_digits: 3 min_entropy: 3.3 confidence: medium examples: @@ -30,84 +62,55 @@ rules: headers: Accept: application/json DD-API-KEY: "{{ TOKEN }}" + response_matcher: + - report_response: true + - status: + - 200 + type: StatusMatch + - type: WordMatch + words: + - '"Forbidden"' + negative: true + + - name: Datadog Application Key + id: kingfisher.datadog.3 + pattern: | + (?xi) + \b(?:datadog|dd) + (?:.|[\n\r]){0,64}? + (?:app(?:lication)?[_-]?key|dd[_-]?application[_-]?key|secret|private|access|token) + (?:.|[\n\r]){0,32}? + \b + ( + [A-Za-z0-9-]{40} + ) + \b + pattern_requirements: + min_digits: 3 + min_entropy: 3.5 + confidence: medium + examples: + - DD_APPLICATION_KEY=abcDEF0123456789abcDEF0123456789abcDEF01 + references: + - https://docs.datadoghq.com/account_management/api-app-keys/ + - https://docs.datadoghq.com/getting_started/site/ + depends_on_rule: + - rule_id: kingfisher.datadog.2 + variable: DD_API_KEY + - rule_id: kingfisher.datadog.1 + variable: DD_SITE_DOMAIN + validation: + type: Http + content: + request: + method: GET + # Datadog recommends /api/v2/validate_keys to verify app keys with the key pair + url: "https://api.{{ DD_SITE_DOMAIN }}/api/v2/validate_keys" + headers: + Accept: application/json + DD-API-KEY: "{{ DD_API_KEY }}" + DD-APPLICATION-KEY: "{{ TOKEN }}" response_matcher: - report_response: true - type: StatusMatch status: [200] - - # - name: Datadog API Key - # id: kingfisher.datadog.1 - # pattern: | - # (?xi) - # \b - # datadog - # (?:.|[\n\r]){0,64}? - # (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) - # (?:.|[\n\r]){0,32}? - # \b - # ( - # [a-z0-9]{32} - # ) - # \b - # pattern_requirements: - # min_digits: 2 - # min_entropy: 3.3 - # confidence: medium - # examples: - # - 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 API Key (API-only validation) - # id: kingfisher.datadog.3 - # pattern: | - # (?xi) - # \b - # (?:datadog|dd) - # (?:.|[\n\r]){0,64}? - # (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)? - # (?:.|[\n\r]){0,32}? - # \b - # ( - # [A-Za-z0-9]{32} - # ) - # \b - # pattern_requirements: - # min_digits: 2 - # min_entropy: 3.3 - # confidence: medium - # examples: - # - DD_API_KEY=0024a29224affe29d173c0bf99e5a89d - # references: - # - https://docs.datadoghq.com/account_management/api-app-keys/ - # validation: - # type: Http - # content: - # request: - # method: GET - # url: https://api.datadoghq.com/api/v1/validate - # headers: - # Accept: application/json - # DD-API-KEY: "{{ TOKEN }}" - # response_matcher: - # - report_response: true - # - type: StatusMatch - # status: [200] diff --git a/data/rules/datagov.yml b/data/rules/datagov.yml new file mode 100644 index 0000000..8e10481 --- /dev/null +++ b/data/rules/datagov.yml @@ -0,0 +1,40 @@ +rules: + - name: Data.gov API Key + id: kingfisher.datagov.1 + pattern: | + (?xi) + \b + data\.gov + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) + (?:.|[\n\r]){0,32}? + \b + ( + [a-zA-Z0-9]{40} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - data.gov_api_key=pBZm2kXbuPdRfzYyarRT0bvcWAisnJg98YJzBJyJ + - data.gov_token=plZJDnKs4OrPeV8wgBr2fYO6VnXb1YPEcVaZbnYI + references: + - https://api.data.gov/docs/developer-manual/ + - https://developer.nrel.gov/docs/api-key/ + - https://developer.nrel.gov/docs/errors/ + validation: + type: Http + content: + request: + method: GET + # NREL (developer.nrel.gov) uses api.data.gov-managed keys and accepts api_key as a query param. + url: "https://developer.nrel.gov/api/alt-fuel-stations/v1.json?limit=1&api_key={{ TOKEN }}" + headers: + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + diff --git a/data/rules/disqus.yml b/data/rules/disqus.yml new file mode 100644 index 0000000..8ca8fa8 --- /dev/null +++ b/data/rules/disqus.yml @@ -0,0 +1,39 @@ +rules: + - name: Disqus API Key + id: kingfisher.disqus.1 + pattern: | + (?xi) + \b + disqus + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) + (?:.|[\n\r]){0,32}? + \b + ( + [a-zA-Z0-9]{64} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.3 + confidence: medium + examples: + - disqus_secret_key = jK5HbxY2QrPn7vMNL8tADcF3mWg4kXqR9sBdZyE1hVuT6fGwJpC0nI9vUxY2aM3K + - DISQUS_PRIVATE_TOKEN = Nh7vRf3mKp9wXc5tJq2YbL8sAg4dB6TzWeUx1nGQjCkPyDHVME0aI1FSx2Z5vY3n + references: + - https://disqus.com/api/docs/requests/ + - https://disqus.com/api/docs/threads/list/ + validation: + type: Http + content: + request: + method: GET + url: "https://disqus.com/api/3.0/threads/list.json?limit=1&api_secret={{ TOKEN }}" + headers: + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"code":0', '"response"'] diff --git a/data/rules/endorlabs.yml b/data/rules/endorlabs.yml new file mode 100644 index 0000000..3be6b1c --- /dev/null +++ b/data/rules/endorlabs.yml @@ -0,0 +1,61 @@ +rules: + - name: Endor Labs API Key + id: kingfisher.endorlabs.1 + visible: false + confidence: medium + min_entropy: 3.0 + pattern: | + (?xi) + \b + ENDOR_API_CREDENTIALS_KEY + (?:.|[\n\r]){0,32}? + ( + endr\+[A-Za-z0-9-]{16} + ) + \b + examples: + - ENDOR_API_CREDENTIALS_KEY=endr+foo1234567890abc + pattern_requirements: + min_digits: 2 + + - name: Endor Labs API Secret + id: kingfisher.endorlabs.2 + pattern: | + (?xi) + \b + ENDOR_API_CREDENTIALS_SECRET + (?:.|[\n\r]){0,32}? + ( + endr\+[A-Za-z0-9-]{16} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - ENDOR_API_CREDENTIALS_SECRET=endr+bar1234567890abc + references: + - https://docs.endorlabs.com/rest-api/authentication/ + depends_on_rule: + - rule_id: kingfisher.endorlabs.1 + variable: ENDOR_API_KEY + validation: + type: Http + content: + request: + method: POST + # Endor Labs exchanges key+secret for an ENDOR_TOKEN via this endpoint + url: https://api.endorlabs.com/v1/auth/api-key + headers: + Content-Type: application/json + Accept: application/json + body: | + {"key":"{{ ENDOR_API_KEY }}","secret":"{{ TOKEN }}"} + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + words: ['"token"'] diff --git a/data/rules/eventbrite.yml b/data/rules/eventbrite.yml new file mode 100644 index 0000000..c0856df --- /dev/null +++ b/data/rules/eventbrite.yml @@ -0,0 +1,42 @@ +rules: + - name: Eventbrite API Key + id: kingfisher.eventbrite.1 + pattern: | + (?xi) + \b + eventbrite + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) + (?:.|[\n\r]){0,32}? + \b + ( + [0-9A-Z]{20} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - eventbrite secretkey X7W8HTTHLVYXPPVRZJZS + - eventbrite privatekey YTR4GR5T89WQP8HJKLDF + - '"eventbrite private access key ZXC2JK3HV4TY5UIO6PLK"' + - eventbrite token ABCDEF1234567890QRST + references: + - https://www.eventbrite.com/platform/docs/authentication + - https://www.eventbrite.com/platform/docs/organizations + validation: + type: Http + content: + request: + method: GET + url: https://www.eventbriteapi.com/v3/users/me/organizations/ + headers: + Authorization: "Bearer {{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"organizations"'] diff --git a/data/rules/exaai.yml b/data/rules/exaai.yml new file mode 100644 index 0000000..b0934ca --- /dev/null +++ b/data/rules/exaai.yml @@ -0,0 +1,47 @@ +rules: + - name: Exa AI API Key + id: kingfisher.exa.1 + pattern: | + (?xi) + (?: + \b(?:exa|exa[_-]?api|exa[_-]?key|exa[_-]?api[_-]?key)\b + (?:.|[\n\r]){0,96}? + | + \bx-api-key\b + (?:\s*[:=]\s*|(?:.|[\n\r]){0,16}?) + ) + \b + ( + [a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12} + ) + \b + pattern_requirements: + min_digits: 4 + min_entropy: 3.0 + confidence: medium + examples: + - EXA_API_KEY=3f5a9c1e-2b4d-4a6f-8c10-1d2e3f4a5b6c + - 'exa_api_key: "3f5a9c1e-2b4d-4a6f-8c10-1d2e3f4a5b6c"' + - 'x-api-key: 3f5a9c1e-2b4d-4a6f-8c10-1d2e3f4a5b6c' + references: + - https://docs.exa.ai/reference/answer + - https://docs.exa.ai/reference/getting-started + validation: + type: Http + content: + request: + method: POST + url: https://api.exa.ai/answer + headers: + x-api-key: "{{ TOKEN }}" + Content-Type: application/json + Accept: application/json + body: | + {"query":"ping","text":false} + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + words: ['"answer"'] diff --git a/data/rules/fleetbase.yml b/data/rules/fleetbase.yml new file mode 100644 index 0000000..e4d05c3 --- /dev/null +++ b/data/rules/fleetbase.yml @@ -0,0 +1,36 @@ +rules: + - name: Fleetbase API Key + id: kingfisher.fleetbase.1 + pattern: | + (?xi) + \b + ( + flb_(?:live|test)_[0-9a-zA-Z]{20,64} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - flb_live_1234567890abcdefGHIJ + - flb_test_1234567890abcdefGHIJ + - 'Authorization: Bearer flb_live_1234567890abcdefGHIJ' + categories: + - api + - secret + references: + - https://docs.fleetbase.io/developers/api/ + validation: + type: Http + content: + request: + method: GET + url: "https://api.fleetbase.io/v1" + headers: + Authorization: "Bearer {{ TOKEN }}" + Accept: "application/json" + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] diff --git a/data/rules/foursquare.yml b/data/rules/foursquare.yml new file mode 100644 index 0000000..e2c76b9 --- /dev/null +++ b/data/rules/foursquare.yml @@ -0,0 +1,69 @@ +rules: + - name: Foursquare Client ID + id: kingfisher.foursquare.client_id.1 + visible: false + confidence: low + min_entropy: 0.0 + pattern: | + (?xi) + (?: + \bclient_id\b\s*[:=]\s*["']? + | + \bclient_id= + ) + ( + [0-9A-Z]{48} + ) + \b + examples: + - client_id=0F12A345BB67C8D901EFG23H45IJKL67MNO89PQ12RST34UV + - 'client_id: "0F12A345BB67C8D901EFG23H45IJKL67MNO89PQ12RST34UV"' + + - name: Foursquare Client Secret + id: kingfisher.foursquare.1 + pattern: | + (?xi) + (?: + \bfoursquare\b + (?:.|[\n\r]){0,32}? + )? + (?: + \bclient_secret\b\s*[:=]\s*["']? + | + \bclient_secret= + | + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) + ) + (?:.|[\n\r]){0,32}? + \b + ( + [0-9A-Z]{48} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - 'client_secret=0F12A345BB67C8D901EFG23H45IJKL67MNO89PQ12RST34UV' + - 'foursquare client_secret: "0F12A345BB67C8D901EFG23H45IJKL67MNO89PQ12RST34UV"' + references: + - https://docs.foursquare.com/developer/reference/v2-authentication + - https://docs.foursquare.com/developer/reference/upcoming-changes + depends_on_rule: + - rule_id: kingfisher.foursquare.client_id.1 + variable: FOURSQUARE_CLIENT_ID + validation: + type: Http + content: + request: + method: GET + url: "https://api.foursquare.com/v2/venues/search?ll=34.0522,-118.2437&query=coffee&client_id={{ FOURSQUARE_CLIENT_ID }}&client_secret={{ TOKEN }}&v=20211019&limit=1" + headers: + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"response"', '"venues"'] diff --git a/data/rules/freshdesk.yml b/data/rules/freshdesk.yml new file mode 100644 index 0000000..8cd3f50 --- /dev/null +++ b/data/rules/freshdesk.yml @@ -0,0 +1,62 @@ +rules: + - name: Freshdesk Domain + id: kingfisher.freshdesk.1 + visible: false + confidence: low + min_entropy: 0.0 + pattern: | + (?xi) + \b + ( + [0-9a-z-]{1,63}\.freshdesk\.com + ) + \b + examples: + - acme-support.freshdesk.com + - mycompany-helpdesk.freshdesk.com + + - name: Freshdesk API Key + id: kingfisher.freshdesk.2 + pattern: | + (?xi) + \b + freshdesk + (?:.|[\n\r]){0,64}? + (?:api[_-]?key|secret|private|access|key|token) + (?:.|[\n\r]){0,32}? + \b + ( + [0-9A-Z]{20} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.3 + confidence: medium + examples: + - 'FRESHDESK_API_KEY=abcdefghij1234567890' + - 'freshdesk token: ABCDEFGHIJ1234567890' + references: + - https://developers.freshdesk.com/api/#authentication + - https://developers.freshworks.com/docs/app-sdk/v3.0/support_agent/rest-apis/ + depends_on_rule: + - rule_id: kingfisher.freshdesk.1 + variable: FRESHDESK_DOMAIN + validation: + type: Http + content: + request: + method: GET + url: "https://{{ FRESHDESK_DOMAIN }}/api/v2/agents/me" + headers: + Accept: application/json + # Freshdesk API key auth is HTTP Basic where username=apikey and password can be any dummy value (commonly "X"). + # Docs note you can use a dummy password and (when using Authorization header) base64("apikey:X") + Authorization: "Basic {{ TOKEN | append: ':X' | b64enc }}" + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + words: ['"id"'] diff --git a/data/rules/github.yml b/data/rules/github.yml index 96ebdd8..9c04297 100644 --- a/data/rules/github.yml +++ b/data/rules/github.yml @@ -20,22 +20,18 @@ rules: type: Http content: request: - method: POST - url: https://api.github.com/graphql + method: GET + url: https://api.github.com/user 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"' + - '"id"' - name: GitHub Personal Access Token id: kingfisher.github.2 pattern: | @@ -65,22 +61,18 @@ rules: type: Http content: request: - method: POST - url: https://api.github.com/graphql + method: GET + url: https://api.github.com/user 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"' + - '"id"' - name: GitHub OAuth Access Token id: kingfisher.github.3 pattern: | @@ -107,22 +99,18 @@ rules: type: Http content: request: - method: POST - url: https://api.github.com/graphql + method: GET + url: https://api.github.com/user 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"' + - '"id"' - name: GitHub App User-to-Server Token id: kingfisher.github.4 pattern: | @@ -141,22 +129,18 @@ rules: type: Http content: request: - method: POST - url: https://api.github.com/graphql + method: GET + url: https://api.github.com/user 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"' + - '"id"' - name: GitHub App Server-to-Server Token id: kingfisher.github.5 pattern: | @@ -175,22 +159,18 @@ rules: type: Http content: request: - method: POST - url: https://api.github.com/graphql + method: GET + url: https://api.github.com/user 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"' + - '"id"' - name: GitHub Refresh Token id: kingfisher.github.6 pattern: | @@ -206,22 +186,18 @@ rules: type: Http content: request: - method: POST - url: https://api.github.com/graphql + method: GET + url: https://api.github.com/user 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"' + - '"id"' - name: GitHub Client ID id: kingfisher.github.7 pattern: | diff --git a/data/rules/grafana.yml b/data/rules/grafana.yml index d339ac3..ecbfe45 100644 --- a/data/rules/grafana.yml +++ b/data/rules/grafana.yml @@ -2,10 +2,12 @@ rules: - name: Grafana API Token id: kingfisher.grafana.1 pattern: | - (?xi) + (?x) \b ( - eyJrIjoi[a-z0-9]{60,100} + eyJrIjoi + [A-Za-z0-9+/]{40,380} + ={0,2} ) \b pattern_requirements: @@ -13,21 +15,42 @@ rules: min_entropy: 3.3 confidence: medium examples: - - 'Authorization: Bearer eyJrIjoiWHZiSWd5NzdCYUZnNUtibE8obUpESmE2bzJYNDRIc1UiLCJuIjoibXlrZXkiLCJpZCI7MX1' + - 'Authorization: Bearer eyJrIjoiWHZiSWd5NzdCYUZnNUtibE8obUpESmE2bzJYNDRIc1UiLCJuIjoibXlrZXkiLCJpZCI6MX0=' - 'admin_client = GrafanaClient("eyJrIjoiY21sM1JRYjB6RnVYSTNLenRWQkFEaWN2bXI2V202U2IiLCJuIjoiYWRtaW5rZXkiLCJpZCI6MX0=", host=grafana_host, port=3000, protocol="http")' references: - - https://grafana.com/docs/grafana/latest/developers/http_api/auth/ - + - https://grafana.com/docs/grafana/latest/developer-resources/api-reference/http-api/authentication/ + - https://grafana.com/docs/grafana/latest/developer-resources/api-reference/http-api/org/ + depends_on_rule: + - rule_id: kingfisher.grafana.4 + variable: GRAFANADOMAIN + validation: + type: Http + content: + request: + method: GET + url: "https://{{ GRAFANADOMAIN }}/api/org" + headers: + Authorization: "Bearer {{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + words: ['"id"', '"name"'] + - name: Grafana Cloud API Token id: kingfisher.grafana.2 pattern: | - (?xi) - \b + (?xi) + \b ( - glc_ + glc_ [a-z0-9+/]{40,150} ={0,2} ) + \b pattern_requirements: min_digits: 2 min_lowercase: 2 @@ -37,20 +60,21 @@ rules: - ' "token": "glc_eyJrIjoiZjI0YzZkNGEwZDBmZmZjMmUzNTU3ODcxMmY0ZWZlNTQ1NTljMDFjOCIsIm6iOiJteXRva3VuIiwiaWQiOjF8"' - 'grafana = glc_etLvNLoNMLt7MTczNNwNbN6Nm1ldGEtbW9paxRvcmlpZt14ZXN4NNwNatN6NLCxdKeH7KTUvWpNqCrHlMKE9EhLcZH7to' references: - - https://grafana.com/docs/grafana-cloud/developer-resources/api-reference/cloud-api/#regions + - https://grafana.com/docs/grafana/latest/developer-resources/api-reference/cloud-api/ validation: type: Http content: request: - headers: - Authorization: Bearer {{ TOKEN }} method: GET + url: https://grafana.com/api/stack-regions + headers: + Authorization: "Bearer {{ TOKEN }}" + Accept: application/json response_matcher: - report_response: true - - status: - - 200 - type: StatusMatch - url: https://grafana.com/api/stack-regions + - type: StatusMatch + status: [200] + - type: JsonValid - name: Grafana Service Account Token id: kingfisher.grafana.3 @@ -67,50 +91,50 @@ rules: confidence: medium examples: - | - curl -H "Authorization: Bearer glsa_HOruNAb7SOiCdshU7algkrq7FDsNSLAa_55e2f8be" -X GET '/api/access-control/user/permissions' | jq + curl -H "Authorization: Bearer glsa_HOruNAb7SOiCdshU7algkrq7FDsNSLAa_55e2f8be" -X GET '/api/org' | jq - | - // getData() - // { - // let url="http://localhost:4200/api/search" - // const headers = new HttpHeaders({ - // 'Content-Type': 'application/json', - // 'Authorization': `Bearer glsa_Sof0HKi3agxrQP9qm5r2G98VacBNwV5P_9b638c45` - // }) - // return this.http.get(url, {headers: headers}); - // } + // headers: { Authorization: `Bearer glsa_Sof0HKi3agxrQP9qm5r2G98VacBNwV5P_9b638c45` } references: + - https://grafana.com/blog/new-in-grafana-9-1-service-accounts-are-now-ga/ - https://grafana.com/docs/grafana/latest/administration/service-accounts/ + - https://grafana.com/docs/grafana/latest/developer-resources/api-reference/http-api/org/ + depends_on_rule: + - rule_id: kingfisher.grafana.4 + variable: GRAFANADOMAIN validation: type: Http content: request: method: GET + url: "https://{{ GRAFANADOMAIN }}/api/org" headers: - Authorization: Bearer {{ TOKEN }} + Authorization: "Bearer {{ TOKEN }}" + Accept: application/json response_matcher: - - report_response: true - - status: - - 200 - type: StatusMatch - url: "{{ GRAFANADOMAIN }}/api/access-control/me" - depends_on_rule: - - rule_id: kingfisher.grafana.4 - variable: GRAFANADOMAIN + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + words: ['"id"', '"name"'] - name: Grafana Domain id: kingfisher.grafana.4 pattern: | (?xi) (?:https?://)? - (?:[A-Z0-9-]+\.){0,32} - grafana\.[A-Z0-9.-]{3,32} - (?::\d{2,5})? - (?:[/?\#]\S*)? - min_entropy: 3.0 + \b + ( + (?:[a-z0-9-]+\.){0,16} + grafana\.[a-z0-9.-]{2,64} + (?::\d{2,5})? + ) + \b + min_entropy: 3.0 visible: false confidence: medium examples: - - https://grafana.example.com - - http://grafana.prod.eu-west.mycorp.internal:3000/login - - https://api.team1.grafana.services.cluster.local/health + - grafana.example.com + - grafana.prod.eu-west.mycorp.internal:3000 + - api.team1.grafana.services.cluster.local - grafana.dev.foo-bar.co.uk diff --git a/data/rules/guardian.yml b/data/rules/guardian.yml new file mode 100644 index 0000000..5344a09 --- /dev/null +++ b/data/rules/guardian.yml @@ -0,0 +1,39 @@ +rules: + - name: Guardian API Key + id: kingfisher.guardian.1 + pattern: | + (?xi) + \b + guardian + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN|API) + (?:.|[\n\r]){0,32}? + \b + ( + [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - guardian SECRET=abcdef12-1234-abcd-5678-abcdef123456 + - guardianPRIVATEKEY=abcdef12-1234-abcd-5678-abcdef123456 + references: + - https://open-platform.theguardian.com/documentation/ + - https://open-platform.theguardian.com/documentation/section + validation: + type: Http + content: + request: + method: GET + url: "https://content.guardianapis.com/sections?api-key={{ TOKEN }}" + headers: + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"status":"ok"'] diff --git a/data/rules/gumroad.yml b/data/rules/gumroad.yml new file mode 100644 index 0000000..ee5ab92 --- /dev/null +++ b/data/rules/gumroad.yml @@ -0,0 +1,42 @@ +rules: + - name: Gumroad Access Token + id: kingfisher.gumroad.1 + pattern: | + (?xi) + \b + gumroad + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN|ACCESS_TOKEN|OAUTH) + (?:.|[\n\r]){0,48}? + \b + ( + [a-f0-9]{64} + | + [A-Z0-9-]{43} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - gumroad_access_token=abf11e4ab2850ffd50ef690257f7a1c998a443059513d1a4826f2b3159620505 + - gumroadSECRET = abf11e4ab2850ffd50ef690257f7a1c998a443059513d1a4826f2b3159620505 + - gumroadPRIVATE-abf11e4ab2850ffd50ef690257f7a1c998a443059513d1a4826f2b3159620505 + references: + - https://gumroad.com/api + - https://gumroad.com/help/article/280-create-application-api + validation: + type: Http + content: + request: + method: GET + url: "https://api.gumroad.com/v2/user?access_token={{ TOKEN }}" + headers: + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"success":true', '"user"'] diff --git a/data/rules/hereapi.yml b/data/rules/hereapi.yml new file mode 100644 index 0000000..c6974ba --- /dev/null +++ b/data/rules/hereapi.yml @@ -0,0 +1,40 @@ +rules: + - name: HERE API Key + id: kingfisher.hereapi.1 + pattern: | + (?xi) + \b + hereapi + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN|APIKEY) + (?:.|[\n\r]){0,32}? + \b + ( + [A-Z0-9_-]{43} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - "hereapi_key=XxK6G3m_pQ8nR2vT4wY9jL5bN7cA1dF3hJ0iM4eP9su" + - "HEREAPI_SECRET=ZzY8xW6vU4tS2qP0nM5kJ9hF7dC1bA3gL8iK4eR9wQm" + references: + - https://stackoverflow.com/questions/65610274/here-geocoding-api-not-working-inside-my-react-app + - https://github.com/spara/geocoding_tutorial + validation: + type: Http + content: + request: + method: GET + url: "https://geocode.search.hereapi.com/v1/geocode?q=Berlin&apiKey={{ TOKEN }}" + headers: + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + # Successful geocode responses include an "items" array. + - type: WordMatch + words: ['"items"'] diff --git a/data/rules/honeycomb.yml b/data/rules/honeycomb.yml new file mode 100644 index 0000000..278d92b --- /dev/null +++ b/data/rules/honeycomb.yml @@ -0,0 +1,41 @@ +rules: + - name: Honeycomb API Key + id: kingfisher.honeycomb.1 + pattern: | + (?xi) + \b + honeycomb + (?:.|[\n\r]){0,16}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) + (?:.|[\n\r]){0,32}? + \b + ( + [0-9a-f]{32}| + [0-9a-zA-Z]{22} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - honeycomb_secret_key=8f14e45fceea167a5a36dedd4bea2543 + - honeycomb_token=z0d1f2bcaloumn3456789P + references: + - https://api-docs.honeycomb.io/api/auth + - https://docs.honeycomb.io/api/ + validation: + type: Http + content: + request: + method: GET + url: https://api.honeycomb.io/1/auth + headers: + X-Honeycomb-Team: "{{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"id"', '"type"', '"team"', '"environment"'] diff --git a/data/rules/imagekit.yml b/data/rules/imagekit.yml new file mode 100644 index 0000000..050168b --- /dev/null +++ b/data/rules/imagekit.yml @@ -0,0 +1,40 @@ +rules: + - name: ImageKit Private API Key + id: kingfisher.imagekit.1 + pattern: | + (?xi) + \b + imagekit + (?:.|[\n\r]){0,64}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN|PRIVATE_KEY) + (?:.|[\n\r]){0,64}? + \b + ( + private_[A-Z0-9_-]{8,128} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.2 + confidence: medium + examples: + - IMAGEKIT_PRIVATE_KEY=private_rGAPQJbhBx + - imagekit token private_AbCdEf0123456789GhIjKlMn + references: + - https://imagekit.io/docs/api-keys + - https://imagekit.io/docs/api-reference/account-management-api/url-endpoints/list-url-endpoints + validation: + type: Http + content: + request: + method: GET + url: "https://api.imagekit.io/v1/accounts/url-endpoints" + headers: + Authorization: "Basic {{ TOKEN | append: ':' | b64enc }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"urlEndpoint"'] diff --git a/data/rules/infura.yml b/data/rules/infura.yml new file mode 100644 index 0000000..0f999de --- /dev/null +++ b/data/rules/infura.yml @@ -0,0 +1,43 @@ +rules: + - name: Infura API Key + id: kingfisher.infura.1 + pattern: | + (?xi) + \b + infura + (?:.|[\n\r]){0,32}? + \b + ( + [0-9a-z]{32} + ) + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - https://mainnet.infura.io/v3/7238211010344719ad14a89db874158c + - infuraKEYwithspecial-abcdef1234567890abcdef1234567890 + references: + - https://www.infura.io/docs + - https://docs.metamask.io/services/reference/ethereum/json-rpc-methods/ + validation: + type: Http + content: + request: + method: POST + url: "https://mainnet.infura.io/v3/{{ TOKEN }}" + headers: + Content-Type: application/json + Accept: application/json + body: | + {"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]} + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + words: ['"result"'] + - type: WordMatch + negative: true + words: ["invalid project id", "project id required in the URL", "invalid project id or project secret"] diff --git a/data/rules/jdbc.yml b/data/rules/jdbc.yml index c0136f7..00fffd5 100644 --- a/data/rules/jdbc.yml +++ b/data/rules/jdbc.yml @@ -16,6 +16,7 @@ rules: ignore_if_contains: - "****" - "xxxx" + - "example" min_entropy: 3.3 confidence: medium validation: diff --git a/data/rules/jotform.yml b/data/rules/jotform.yml new file mode 100644 index 0000000..4b43953 --- /dev/null +++ b/data/rules/jotform.yml @@ -0,0 +1,35 @@ +rules: + - name: Jotform API Key + id: kingfisher.jotform.1 + pattern: | + (?xi) + \b + jotform + (?:.|[\n\r]){0,64}? + (?:api[_-]?key|apikey|token|secret|key) + (?:.|[\n\r]){0,32}? + \b + ( + [0-9A-Z]{32} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - jotform apikey=abcde12345abcde67890abcde12345fg + references: + - https://api.jotform.com/docs/ + validation: + type: Http + content: + request: + method: GET + url: "https://api.jotform.com/user/usage?apiKey={{ TOKEN }}" + headers: + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] diff --git a/data/rules/jumpcloud.yml b/data/rules/jumpcloud.yml new file mode 100644 index 0000000..0cab6eb --- /dev/null +++ b/data/rules/jumpcloud.yml @@ -0,0 +1,40 @@ +rules: + - name: Jumpcloud API Key + id: kingfisher.jumpcloud.1 + pattern: | + (?xi) + \b + jumpcloud + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) + (?:.|[\n\r]){0,32}? + \b + ( + [a-zA-Z0-9]{40} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - jumpcloud_api_key=1a2b3c4d5e6f7g8h9i0j1a2b3c4d5e6f7g8h9i0j + - JUMPCLOUD_SECRET=k9l8m7n6o5p4q3r2s1t0k9l8m7n6o5p4q3r2s1t0 + references: + - https://docs.jumpcloud.com/api/ + - https://jumpcloud.com/support/retrieve-object-ids-from-the-api + validation: + type: Http + content: + request: + method: GET + url: "https://console.jumpcloud.com/api/systemusers?limit=1&skip=0" + headers: + x-api-key: "{{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"results"', '"totalCount"'] diff --git a/data/rules/klaviyo.yml b/data/rules/klaviyo.yml new file mode 100644 index 0000000..c09cb21 --- /dev/null +++ b/data/rules/klaviyo.yml @@ -0,0 +1,33 @@ +rules: + - name: Klaviyo API Key + id: kingfisher.klaviyo.1 + pattern: | + (?xi) + \b + klaviyo + (?:.|[\n\r]){0,16}? + \b + ( + pk_[A-Z0-9]{34} + ) + \b + min_entropy: 3.3 + confidence: medium + examples: + - klaviyo_key = pk_abcd1234fghij5678klmn9012opqr3456s + validation: + type: Http + content: + request: + method: GET + url: https://a.klaviyo.com/api/accounts + headers: + Revision: "2023-02-22" + Authorization: "Klaviyo-API-Key {{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"data"'] diff --git a/data/rules/looker.yml b/data/rules/looker.yml new file mode 100644 index 0000000..70621e0 --- /dev/null +++ b/data/rules/looker.yml @@ -0,0 +1,85 @@ +rules: + - name: Looker Base URL + id: kingfisher.looker.1 + visible: false + confidence: low + min_entropy: 2.0 + pattern: | + (?xi) + \b + ( + https?://[a-z0-9.-]+(?::\d{2,5})? + ) + (?:/api/(?:4\.0|3\.1))? + \b + examples: + - https://example.cloud.looker.com + - https://example.cloud.looker.com:19999 + - https://example.cloud.looker.com:19999/api/4.0 + + - name: Looker Client ID + id: kingfisher.looker.2 + confidence: medium + min_entropy: 3.0 + pattern: | + (?xi) + \blooker + (?:.|[\n\r]){0,64}? + \b + ( + [a-z0-9]{20} + ) + \b + pattern_requirements: + min_digits: 2 + examples: + - LOOKER_CLIENT_ID=1a2b3c4d5e6f7g8h9i0j + - 'looker client_id: "0a1b2c3d4e5f6g7h8i9j"' + references: + - https://docs.cloud.google.com/looker/docs/api-auth + + - name: Looker Client Secret + id: kingfisher.looker.3 + confidence: medium + min_entropy: 3.5 + pattern: | + (?xi) + \b + looker + (?:.|[\n\r]){0,64}? + \b + ( + [a-z0-9]{24} + ) + \b + pattern_requirements: + min_digits: 2 + examples: + - LOOKER_CLIENT_SECRET=1a2b3c4d5e6f7g8h9i0j1k2l + - 'looker client_secret: "0a1b2c3d4e5f6g7h8i9j0k1l"' + references: + - https://docs.cloud.google.com/looker/docs/api-auth + - https://docs.cloud.google.com/looker/docs/reference/looker-api/latest/methods/ApiAuth/login + depends_on_rule: + - rule_id: kingfisher.looker.1 + variable: LOOKER_BASE_URL + - rule_id: kingfisher.looker.2 + variable: LOOKER_CLIENT_ID + validation: + type: Http + content: + request: + method: POST + url: "{{ LOOKER_BASE_URL }}/api/4.0/login" + headers: + Content-Type: application/x-www-form-urlencoded + Accept: application/json + body: | + client_id={{ LOOKER_CLIENT_ID }}&client_secret={{ TOKEN }} + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + words: ['"access_token"'] diff --git a/data/rules/mailjet.yml b/data/rules/mailjet.yml new file mode 100644 index 0000000..69161b4 --- /dev/null +++ b/data/rules/mailjet.yml @@ -0,0 +1,75 @@ +rules: + - name: MailJetSMS API Key + id: kingfisher.mailjet.1 + pattern: | + (?xi) + \b + mailjet + (?:.|[\n\r]){0,32}? + \b + ( + [A-Z0-9]{32} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - mailjet ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 + - mailjet-token 9A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P + references: + - https://dev.mailjet.com/sms/reference/overview/authentication/ + - https://www.postman.com/mailjet-api/mailjet-s-public-workspace/request/velnqvd/retrieve-a-count-of-all-sms-messages + validation: + type: Http + content: + request: + method: GET + url: "https://api.mailjet.com/v4/sms/count" + headers: + Accept: "application/vnd.mailjetsms+json; version=3" + Authorization: "Bearer {{ TOKEN }}" + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"Count"'] + - name: MailJet Basic Auth + id: kingfisher.mailjet.2 + pattern: | + (?xi) + \b + mailjet + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) + (?:.|[\n\r]){0,32}? + \b + ( + [A-Z0-9]{87}= + ) + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - mailjet_token = neno01fy530zukbtvq8xunwec74b7m7lsmzha8su93zdvy4mp4dc5gctfa2rcwetllcjzncirjv58se7iwkehhh= + references: + - https://dev.mailjet.com/email/reference/overview/authentication/ + - https://www.postman.com/mailjet-api/mailjet-s-public-workspace/request/5pnoxig/retrieve-all-api-keys + validation: + type: Http + content: + request: + method: GET + url: "https://api.mailjet.com/v3/REST/apikey?Limit=1" + headers: + Authorization: "Basic {{ TOKEN }}" + Accept: "application/json" + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"Data"', '"Count"'] diff --git a/data/rules/mongodb.yml b/data/rules/mongodb.yml index f9da274..bdca97f 100644 --- a/data/rules/mongodb.yml +++ b/data/rules/mongodb.yml @@ -85,6 +85,7 @@ rules: ignore_if_contains: - "****" - "xxxx" + - "example" min_entropy: 3 examples: - client = mongoc_client_new ("mongodb+srv://someuser:hunter2@my-atlas-rd941.mongodb.net/test?retryWrites=true&w=majority"); diff --git a/data/rules/mysql.yml b/data/rules/mysql.yml index d9b16b3..88e3154 100644 --- a/data/rules/mysql.yml +++ b/data/rules/mysql.yml @@ -36,6 +36,7 @@ rules: ignore_if_contains: - "****" - "xxxx" + - "example" min_entropy: 3.3 confidence: medium examples: diff --git a/data/rules/notion.yml b/data/rules/notion.yml index 2682c8c..8b3f798 100644 --- a/data/rules/notion.yml +++ b/data/rules/notion.yml @@ -38,10 +38,9 @@ rules: id: kingfisher.notion.2 pattern: | (?xi) - notion - (?:.|[\\n\r]){0,32}? + \b ( - ntn_[A-Z0-9]{40,55} + ntn_[0-9]{11}[A-Za-z0-9]{35} ) min_entropy: 4.0 confidence: medium diff --git a/data/rules/nylas.yml b/data/rules/nylas.yml new file mode 100644 index 0000000..5acc8d8 --- /dev/null +++ b/data/rules/nylas.yml @@ -0,0 +1,62 @@ +rules: + # Helper: capture the Nylas API base URI (data residency) from config/env so validation hits the right region. + - name: Nylas API URI + id: kingfisher.nylas.api_uri.1 + visible: false + confidence: medium + min_entropy: 2.0 + pattern: | + (?xi) + \b + ( + https://api\.(?:us|eu)\.nylas\.com + ) + \b + examples: + - https://api.us.nylas.com + - https://api.eu.nylas.com + + - name: Nylas API Key + id: kingfisher.nylas.1 + pattern: | + (?xi) + \b + nylas + (?:.|[\n\r]){0,64}? + (?:api[_-]?key|apikey|secret|private|access|token) + (?:.|[\n\r]){0,64}? + \b + ( + nyk_[A-Z0-9]{67} # common v3 API key format (71 chars total) + | + [0-9A-Z]{30} # legacy/older patterns seen in repos + ) + \b + pattern_requirements: + min_digits: 4 + min_entropy: 3.3 + confidence: medium + examples: + - NYLAS_API_KEY=nyk_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234 + - nylas_token = 2temab2qpfioneggb01j2dhfllqgiu + references: + - https://developer.nylas.com/docs/v3/auth/hosted-oauth-apikey/ + - https://developer.nylas.com/docs/v3/notifications/ + depends_on_rule: + - rule_id: kingfisher.nylas.api_uri.1 + variable: NYLAS_API_URI + validation: + type: Http + content: + request: + method: GET + url: "{{ NYLAS_API_URI }}/v3/webhooks" + headers: + Authorization: "Bearer {{ TOKEN }}" + Accept: "application/json" + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"request_id"', '"data"'] diff --git a/data/rules/openrouter.yml b/data/rules/openrouter.yml new file mode 100644 index 0000000..ff7a469 --- /dev/null +++ b/data/rules/openrouter.yml @@ -0,0 +1,36 @@ +rules: + - name: OpenRouter API Key + id: kingfisher.openrouter.1 + pattern: | + (?xi) + \b + ( + sk-or-v1-[0-9a-f]{64} + ) + \b + pattern_requirements: + min_digits: 4 + min_entropy: 4.0 + confidence: high + examples: + - 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 + - 'Authorization: Bearer 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' + references: + - https://openrouter.ai/docs/api/reference/authentication + - https://openrouter.ai/docs/api/api-reference/credits/get-credits + validation: + type: Http + content: + request: + method: GET + url: https://openrouter.ai/api/v1/credits + headers: + Authorization: "Bearer {{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + words: ['"data"', '"total_credits"', '"total_usage"'] diff --git a/data/rules/optimizely.yml b/data/rules/optimizely.yml new file mode 100644 index 0000000..46f3016 --- /dev/null +++ b/data/rules/optimizely.yml @@ -0,0 +1,38 @@ +rules: + - name: Optimizely Personal Access Token + id: kingfisher.optimizely.1 + pattern: | + (?xi) + \b + optimizely + (?:.|[\n\r]){0,64}? + \b + ( + [0-9A-Z-:]{54} + ) + \b + pattern_requirements: + min_digits: 4 + min_entropy: 3.6 + confidence: medium + examples: + - OPTIMIZELY_TOKEN=AbCDefGhijKlmnOpqrStuvWxYz01-23:45AbCDefGhijKlmnOpqrSt + - 'Optimizely Authorization: Bearer AbCDefGhijKlmnOpqrStuvWxYz01-23:45AbCDefGhijKlmnOpqrSt' + references: + - https://docs.developers.optimizely.com/web-experimentation/docs/rest-api-getting-started + - https://docs.developers.optimizely.com/feature-experimentation/reference/get_me + - https://docs.developers.optimizely.com/web-experimentation/docs/api-conventions + validation: + type: Http + content: + request: + method: GET + url: https://api.optimizely.com/v2/me + headers: + Authorization: "Bearer {{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid diff --git a/data/rules/owlbot.yml b/data/rules/owlbot.yml new file mode 100644 index 0000000..1f1d4a0 --- /dev/null +++ b/data/rules/owlbot.yml @@ -0,0 +1,39 @@ +rules: + - name: Owlbot API Key + id: kingfisher.owlbot.1 + pattern: | + (?xi) + \b + owlbot + (?:.|[\n\r]){0,64}? + (?:api[_-]?key|secret|private|access|token|key) + (?:.|[\n\r]){0,64}? + \b + ( + [a-f0-9]{40} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - "owlbot SECRET b7d21c0e88e9a3c5938fb045b2b6a5e693eaf9d1" + - "owlbot TOKEN 8a5de3a89b7e4f29bf728b45adcdea6ea3410c78" + references: + - https://owlbot.info/ + validation: + type: Http + content: + request: + method: GET + url: "https://owlbot.info/api/v4/dictionary/owl?format=json" + headers: + Authorization: "Token {{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"word"', '"definitions"'] diff --git a/data/rules/packagecloud.yml b/data/rules/packagecloud.yml new file mode 100644 index 0000000..6dd636f --- /dev/null +++ b/data/rules/packagecloud.yml @@ -0,0 +1,44 @@ +rules: + - name: PackageCloud API Key + id: kingfisher.packagecloud.1 + pattern: | + (?xi) + \b + packagecloud + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN|API[_-]?TOKEN|AUTH) + (?:.|[\n\r]){0,32}? + \b + ( + [0-9a-f]{48} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - packagecloud accessKEY 1234567890abcdef1234567890abcdef1234567890abcdef + - "packagecloud:token=1234567890abcdef1234567890abcdef1234567890abcdef" + - | + "config": { + "packagecloud_secret": "1234567890abcdef1234567890abcdef1234567890abcdef" + } + - packagecloudPRIVATEkey 1234567890abcdef1234567890abcdef1234567890abcdef + references: + - https://packagecloud.io/docs/api + validation: + type: Http + content: + request: + method: GET + url: "https://packagecloud.io/api/v1/distributions.json" + headers: + Authorization: "Basic {{ TOKEN | append: ':' | b64enc }}" + Accept: "application/json" + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"deb"', '"rpm"'] diff --git a/data/rules/pagerdutyapikey.yml b/data/rules/pagerdutyapikey.yml index 7bdf1f0..3c65ca9 100644 --- a/data/rules/pagerdutyapikey.yml +++ b/data/rules/pagerdutyapikey.yml @@ -39,7 +39,6 @@ rules: Accept: application/json response_matcher: - report_response: true - - type: JsonValid - type: WordMatch words: - '"users":' diff --git a/data/rules/paystack.yml b/data/rules/paystack.yml new file mode 100644 index 0000000..3841a8f --- /dev/null +++ b/data/rules/paystack.yml @@ -0,0 +1,40 @@ +rules: + - name: Paystack API Key + id: kingfisher.paystack.1 + pattern: | + (?xi) + \b + ( + sk_ + [a-z]{1,} + _ + [A-Z0-9]{40} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.3 + confidence: medium + examples: + - sk_test_abcdef1234567890abcdef1234567890abcdef12 + - sk_live_gwjaoi1234567890abcdef1234567890abcdef12 + references: + - https://paystack.com/docs/api/authentication/ + - https://paystack.com/docs/api/transfer-control/ + validation: + type: Http + content: + request: + method: GET + # Different endpoint than /customer: Check Balance + url: https://api.paystack.co/balance + headers: + Authorization: "Bearer {{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + words: ['"message":"Balances retrieved"', '"data"'] diff --git a/data/rules/pdflayer.yml b/data/rules/pdflayer.yml new file mode 100644 index 0000000..3d3c830 --- /dev/null +++ b/data/rules/pdflayer.yml @@ -0,0 +1,47 @@ +rules: + - name: PdfLayer API Key + id: kingfisher.pdflayer.1 + pattern: | + (?xi) + (?: + \b + pdflayer + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN|ACCESS_KEY) + (?:.|[\n\r]){0,32}? + | + \bapi\.pdflayer\.com/api/convert\?[^ \t\r\n"'<>]*\baccess_key\s*=\s* + ) + \b + ( + [a-z0-9]{32} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - pdflayer_key=1234567890abcdef1234567890abcdef + - PDFLAYER_ACCESS_TOKEN=abcdef1234567890abcdef1234567890 + - pdflayer_secret=0123456789abcdef0123456789abcdef + references: + - https://pdflayer.com/documentation + validation: + type: Http + content: + request: + method: GET + # Use Sandbox Mode (test=1) and intentionally omit document_url/document_html. + # This yields a JSON error response (instead of generating a PDF) and should not count + # toward monthly API volume per docs. + url: "https://api.pdflayer.com/api/convert?access_key={{ TOKEN }}&test=1" + headers: + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + negative: true + words: ['"invalid_access_key"', '"missing_access_key"'] diff --git a/data/rules/perplexity.yml b/data/rules/perplexity.yml index c9ba476..7942b3e 100644 --- a/data/rules/perplexity.yml +++ b/data/rules/perplexity.yml @@ -2,7 +2,7 @@ rules: - name: Perplexity AI API Key id: kingfisher.perplexity.1 pattern: | - (?xi) + (?x) \b ( pplx-[A-Za-z0-9]{48} diff --git a/data/rules/postgres.yml b/data/rules/postgres.yml index 8cc51a7..1102636 100644 --- a/data/rules/postgres.yml +++ b/data/rules/postgres.yml @@ -28,6 +28,7 @@ rules: ignore_if_contains: - "****" - "xxxx" + - "example" min_entropy: 3.3 confidence: medium examples: diff --git a/data/rules/rapidapi.yml b/data/rules/rapidapi.yml new file mode 100644 index 0000000..b80d1e7 --- /dev/null +++ b/data/rules/rapidapi.yml @@ -0,0 +1,41 @@ +rules: + - name: RapidAPI Key + id: kingfisher.rapidapi.1 + pattern: | + (?xi) + \b + rapidapi + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) + (?:.|[\n\r]){0,32}? + \b + ( + [A-Za-z0-9_-]{50} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 4.0 + confidence: medium + examples: + - rapidapi_key=abcdefghij1234567890ABCDEFGHIJ1234567890abcdefghij + - '"rapidapiKey":"ABCDEFGHIJ1234567890abcdefghij1234567890ABCDEFGHIJ"' + references: + - https://docs.rapidapi.com/docs/configuring-api-security + - https://docs.rapidapi.com/docs/keys-and-key-rotation + validation: + type: Http + content: + request: + method: GET + url: "https://weatherapi-com.p.rapidapi.com/current.json?q=London" + headers: + x-rapidapi-key: "{{ TOKEN }}" + x-rapidapi-host: "weatherapi-com.p.rapidapi.com" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"country"'] diff --git a/data/rules/riot.yml b/data/rules/riot.yml new file mode 100644 index 0000000..7ecec86 --- /dev/null +++ b/data/rules/riot.yml @@ -0,0 +1,55 @@ +rules: + - name: Riot Platform Host + id: kingfisher.riot.1 + visible: false + confidence: medium + min_entropy: 1.0 + pattern: | + (?xi) + \b + ( + (?:br1|eun1|euw1|jp1|kr|la1|la2|na1|oc1|ru|tr1 + |ph2|sg2|th2|tw2|vn2 + |americas|europe|asia) + \.api\.riotgames\.com + ) + \b + examples: + - na1.api.riotgames.com + - euw1.api.riotgames.com + - americas.api.riotgames.com + + - name: Riot Games API Key + id: kingfisher.riot.2 + pattern: | + (?xi) + \b + ( + RGAPI-[a-z0-9_-]{36} + ) + \b + pattern_requirements: + min_digits: 4 + min_entropy: 3.0 + confidence: medium + examples: + - RGAPI-4sb3f6a1-2941-5a81-9c23-4bf3a83c14f3 + references: + - https://developer.riotgames.com/docs/lol + - https://developer.riotgames.com/apis + depends_on_rule: + - rule_id: kingfisher.riot.1 + variable: RIOT_PLATFORM_HOST + validation: + type: Http + content: + request: + method: GET + url: "https://{{ RIOT_PLATFORM_HOST }}/lol/status/v4/platform-data" + headers: + X-Riot-Token: "{{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] diff --git a/data/rules/sendgrid.yml b/data/rules/sendgrid.yml index 96c5bff..122c222 100644 --- a/data/rules/sendgrid.yml +++ b/data/rules/sendgrid.yml @@ -11,6 +11,7 @@ rules: \. [0-9A-Z_-]{39,47} ) + \b pattern_requirements: min_digits: 2 min_entropy: 3.5 @@ -18,7 +19,7 @@ rules: examples: - " 'SENDGRID_API_KEYSID': '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'" - "var sendgrid = require('sendgrid')('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');" - - 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 + - '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' references: - https://docs.sendgrid.com/ui/account-and-settings/api-keys validation: diff --git a/data/rules/sentry.yml b/data/rules/sentry.yml index a76f51a..d0f4685 100644 --- a/data/rules/sentry.yml +++ b/data/rules/sentry.yml @@ -3,18 +3,19 @@ rules: id: kingfisher.sentry.1 pattern: | (?xi) + \b sentry (?:.|[\n\r]){0,32}? (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) (?:.|[\n\r]){0,32}? \b ( - [a-f0-9]{64} + [a-f0-9]{64} ) \b pattern_requirements: min_digits: 2 - min_entropy: 3.5 + min_entropy: 3.0 confidence: medium examples: - SENTRY_TOKEN=cbadefcbadefcbadefcbadefcbadefcbadefcbadefcbadefcbadefcbadefcbad @@ -39,19 +40,32 @@ rules: - name: Sentry Organization Token id: kingfisher.sentry.2 pattern: | - (?xi) + (?x) + \b ( - sntrys_eyJpYXQiO[a-zA-Z0-9+/]{10,200}(?:LCJyZWdpb25fdXJs|InJlZ2lvbl91cmwi|cmVnaW9uX3VybCI6)[a-zA-Z0-9+/]{10,200}={0,2}_[a-zA-Z0-9+/]{43} + sntrys_eyJpYXQiO + [a-zA-Z0-9+/]{10,192} + (?: + LCJyZWdpb25fdXJs + | InJlZ2lvbl91cmwi + | cmVnaW9uX3VybCI6 + ) + [a-zA-Z0-9+/]{10,192} + ={0,2} + _ + [a-zA-Z0-9+/]{43} ) + \b pattern_requirements: min_digits: 2 - min_entropy: 4.2 + min_entropy: 4.5 confidence: medium examples: - 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 - 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 references: - https://docs.sentry.io/api/auth/ + - https://github.com/getsentry/rfcs/blob/main/text/0091-ci-upload-tokens.md validation: type: Http content: @@ -71,9 +85,11 @@ rules: id: kingfisher.sentry.3 pattern: | (?xi) + \b ( sntryu_[a-f0-9]{64} ) + \b pattern_requirements: min_digits: 2 min_entropy: 3.5 diff --git a/data/rules/shopify.yml b/data/rules/shopify.yml index bb92ea4..7f70dd4 100644 --- a/data/rules/shopify.yml +++ b/data/rules/shopify.yml @@ -5,7 +5,7 @@ rules: (?xi) \b ( - (?:shpat|shpca|shppa|shpss)_[a-f0-9]{30,34} + (?:shpat|shpca|shppa|shpss)_[a-f0-9]{32} ) \b pattern_requirements: diff --git a/data/rules/sourcegraph.yml b/data/rules/sourcegraph.yml index cbda95d..a8838de 100644 --- a/data/rules/sourcegraph.yml +++ b/data/rules/sourcegraph.yml @@ -5,7 +5,7 @@ rules: (?xi) \b ( - sgp_(?:[a-f0-9]{16}_local_)?[a-f0-9]{40} + sgp_(?:[A-F0-9]{16}|local)_[A-F0-9]{40}|sgp_[A-F0-9]{40} ) \b pattern_requirements: diff --git a/data/rules/sslmate.yml b/data/rules/sslmate.yml new file mode 100644 index 0000000..937c5de --- /dev/null +++ b/data/rules/sslmate.yml @@ -0,0 +1,39 @@ +rules: + - name: SslMate API Key + id: kingfisher.sslmate.1 + pattern: | + (?xi) + \b + sslmate + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) + (?:.|[\n\r]){0,32}? + \b + ( + [A-Z0-9]{36} + ) + \b + pattern_requirements: + min_digits: 4 + min_entropy: 3.5 + confidence: medium + examples: + - sslmate_key="10000r90kriAAAAAseZJwsawemws03jdlmZY" + - SSLMATE_SECRET_KEY=ABCDEFGHIJ1234567890ABCDEFGHIJ123456 + references: + - https://sslmate.com/help/reference/apiv2 + validation: + type: Http + content: + request: + method: GET + url: "https://sslmate.com/api/v2/certs/example.com" + headers: + Authorization: "Basic {{ TOKEN | append: ':' | b64enc }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"cn"', '"exists"'] diff --git a/data/rules/statuspage.yml b/data/rules/statuspage.yml new file mode 100644 index 0000000..16294a1 --- /dev/null +++ b/data/rules/statuspage.yml @@ -0,0 +1,42 @@ +rules: + - name: Statuspage API Key + id: kingfisher.statuspage.1 + pattern: | + (?xi) + \b + statuspage + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN|API) + (?:.|[\n\r]){0,48}? + \b + ( + [0-9a-f]{64} + | + # Legacy UUID-ish keys (seen in older configs) + [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.5 + confidence: medium + examples: + - 'statuspage_api: OAuth 89a229ce1a8dbcf9ff30430fbe35eb4c0426574bca932061892cefd2138aa4b1' + - statuspage_key = 123e4567-e89b-12d3-a456-426614174000 + references: + - https://developer.statuspage.io/ + validation: + type: Http + content: + request: + method: GET + url: "https://api.statuspage.io/v1/pages" + headers: + Authorization: "OAuth {{ TOKEN }}" + Accept: "application/json" + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"id"', '"name"'] diff --git a/data/rules/yandex.yml b/data/rules/yandex.yml new file mode 100644 index 0000000..60ebeb5 --- /dev/null +++ b/data/rules/yandex.yml @@ -0,0 +1,39 @@ +rules: + - name: Yandex API Key + id: kingfisher.yandex.1 + pattern: | + (?xi) + \b + yandex + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) + (?:.|[\n\r]){0,32}? + \b + ( + [A-Z0-9.]{83} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.3 + confidence: medium + examples: + - "yandex_api_key= 'pdct.1.1.20218925T124723Z.07193b9c567c0c90.ebba3042fcf1acfc4d682db12c01a5289f9769c0'" + - "yandex_secret=1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU" + references: + - https://yandex.com/dev/dictionary + - https://pkg.go.dev/github.com/unitrans/unitrans/src/translator/backend_particular + validation: + type: Http + content: + request: + method: GET + url: "https://dictionary.yandex.net/api/v1/dicservice.json/lookup?key={{ TOKEN }}&lang=en-ru&text=time&ui=en" + headers: + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"def"'] diff --git a/data/rules/yelp.yml b/data/rules/yelp.yml new file mode 100644 index 0000000..6e4e9d3 --- /dev/null +++ b/data/rules/yelp.yml @@ -0,0 +1,39 @@ +rules: + - name: Yelp API Key + id: kingfisher.yelp.1 + pattern: | + (?xi) + \b + yelp + (?:.|[\n\r]){0,32}? + (?:SECRET|PRIVATE|ACCESS|KEY|TOKEN) + (?:.|[\n\r]){0,32}? + \b + ( + [a-zA-Z0-9_\\=.\\-]{128} + ) + \b + pattern_requirements: + min_digits: 6 + min_entropy: 3.8 + confidence: medium + examples: + - yelp_token = wiuck20l8j-oWwCd9r53FqpN6ELB7K03zGw-ccUQR7uLHc9NaWubovOMdGdyFqIGGM4aVK6nxQ1DreDZn_qBYU4jky_5kQRVkiIDPSheCPggY3WzyRzi27kxoOpoYAYx + references: + - https://docs.developer.yelp.com/docs/places-authentication + - https://docs.developer.yelp.com/reference/v3_all_categories + validation: + type: Http + content: + request: + method: GET + url: "https://api.yelp.com/v3/categories?locale=en_US" + headers: + Authorization: "Bearer {{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"categories"'] diff --git a/data/rules/zohocrm.yml b/data/rules/zohocrm.yml new file mode 100644 index 0000000..f0ab57b --- /dev/null +++ b/data/rules/zohocrm.yml @@ -0,0 +1,35 @@ +rules: + - name: Zoho CRM API Access Token + id: kingfisher.zohocrm.1 + pattern: | + (?xi) + \b + ( + 1000\.[a-f0-9]{32}\.[a-f0-9]{32} + ) + \b + pattern_requirements: + min_digits: 4 + min_entropy: 3.5 + confidence: medium + examples: + - 1000.a23f12b4c5d6e7f8901234567890abc1.23d4e5f67890abcdef1234567890abcd + - 1000.123fa4b5c678d90eabcdef1234567890.ab12c3d4e5f6a7890bcd12ef345678ab + references: + - https://www.zoho.com/crm/developer/docs/api/v8/access-refresh.html + - https://www.zoho.com/crm/developer/docs/api/v8/get-users.html + validation: + type: Http + content: + request: + method: GET + url: "https://www.zohoapis.com/crm/v7/users?type=CurrentUser" + headers: + Authorization: "Zoho-oauthtoken {{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: WordMatch + words: ['"users"'] diff --git a/docs/access-map-viewer/index.html b/docs/access-map-viewer/index.html index be621fb..3e2da62 100644 --- a/docs/access-map-viewer/index.html +++ b/docs/access-map-viewer/index.html @@ -453,6 +453,9 @@ .badge-aws { background: #fff7ed; color: #c2410c; border-color: #fed7aa; } .badge-gcp { background: #eff6ff; color: #1e40af; border-color: #bfdbfe; } + .badge-azure { background: #ecfeff; color: #0e7490; border-color: #a5f3fc; } + .badge-github { background: #f4f4f5; color: #18181b; border-color: #d4d4d8; } + .badge-gitlab { background: #fff1f2; color: #be123c; border-color: #fecdd3; } .badge-perm { background: #ecfdf5; color: #16a34a; border-color: #bbf7d0; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; } .detail-grid { @@ -842,7 +845,7 @@ Dashboard