diff --git a/.gitignore b/.gitignore index 1ccd1e3..c3ae723 100644 --- a/.gitignore +++ b/.gitignore @@ -239,5 +239,6 @@ rust-project.json # MkDocs build output docs-site/site/ +.codegraph/ # Created by https://www.toptal.com/developers/gitignore/api/python # Edit at https://www.toptal.com/developers/gitignore?templates=python diff --git a/CHANGELOG.md b/CHANGELOG.md index c71f32a..1d2024e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## [v1.102.0] +- Added 3 detection and validation rules for Cognition Devin API credentials: `kingfisher.devin.1` (legacy personal keys, `apk_user_` prefix), `kingfisher.devin.2` (legacy service keys, `apk_` prefix), and `kingfisher.devin.3` (v3 service-user tokens, `cog_` prefix / RFC 4648 base32). Live validation uses `GET /v1/sessions` for `apk_*` keys and `GET /v3/self` for `cog_` tokens. + ## [v1.101.0] - Fixed asymmetric JWT validation panics by using a single `jsonwebtoken` crypto backend and adding RS256 regression coverage. Thanks @AgentEnder. [#386](https://github.com/mongodb/kingfisher/pull/386) - Validator panics now fail that validation result instead of crashing the scan, with panic payloads kept out of cached and user-visible validation responses. Thanks @AgentEnder. [#387](https://github.com/mongodb/kingfisher/pull/387) diff --git a/Cargo.lock b/Cargo.lock index c8962ee..25f6b6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4958,7 +4958,7 @@ dependencies = [ [[package]] name = "kingfisher" -version = "1.101.0" +version = "1.102.0" dependencies = [ "anyhow", "asar", diff --git a/Cargo.toml b/Cargo.toml index 483c034..bd3af6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ http = "1.4" [package] name = "kingfisher" -version = "1.101.0" +version = "1.102.0" description = "MongoDB's blazingly fast and accurate secret scanning and validation tool" edition.workspace = true rust-version.workspace = true diff --git a/crates/kingfisher-rules/data/rules/devin.yml b/crates/kingfisher-rules/data/rules/devin.yml new file mode 100644 index 0000000..92415d1 --- /dev/null +++ b/crates/kingfisher-rules/data/rules/devin.yml @@ -0,0 +1,153 @@ +rules: + - name: Cognition Devin Personal API Key + id: kingfisher.devin.1 + # Personal/user API keys are emitted as: + # apk_user__org-<32hex>:<32hex>" )> + # The encoded body is ~144 base64 chars and may end with `=` padding. + pattern: | + (?x) + \b + ( + apk_user_[A-Za-z0-9+/]{120,180}={0,2} + ) + pattern_requirements: + min_digits: 2 + min_lowercase: 1 + min_uppercase: 1 + ignore_if_contains: + - "YOUR_" + - "REPLACE_" + - "EXAMPLE" + min_entropy: 4.0 + confidence: medium + examples: + - apk_user_dXNlci0yMDc5ZjllYTUyZDA0OWE0OTVlOWUwNDc2OTJiNWZhYl9vcmctZmE4NzllMzdjYWRmNGI2YmJmMmE3YWYzMTgxZGVjMTM6MjUwZjRhNzc2ZDEyNGVlMTk0NDk5OGNhNmRmNjBiY2I= + - "DEVIN_API_KEY=apk_user_dXNlci0yMDc5ZjllYTUyZDA0OWE0OTVlOWUwNDc2OTJiNWZhYl9vcmctZmE4NzllMzdjYWRmNGI2YmJmMmE3YWYzMTgxZGVjMTM6YTYzNWU0MTA3M2VkNDU3OGFmZDFhMjAxZDhkMjNkODg=" + - "Authorization: Bearer apk_user_dXNlci0yMDc5ZjllYTUyZDA0OWE0OTVlOWUwNDc2OTJiNWZhYl9vcmctZmE4NzllMzdjYWRmNGI2YmJmMmE3YWYzMTgxZGVjMTM6NDMxYWFhYjBmN2VmNGRmMTlmZjkwNTBiNDhlYmE3NjM=" + references: + - https://docs.devin.ai/api-reference/overview + - https://docs.devin.ai/api-reference/v1/sessions + validation: + type: Http + content: + request: + method: GET + url: https://api.devin.ai/v1/sessions?limit=1 + headers: + Authorization: "Bearer {{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + words: + - '"sessions"' + match_all_words: true + # Revocation: no documented self-service revocation endpoint exists for + # legacy apk_user_* keys; users must rotate them via the Devin web UI. + + - name: Cognition Devin Service API Key + id: kingfisher.devin.2 + # Legacy service API keys are emitted as: + # apk_:<32hex>" )> + # The encoded body is ~92 base64 chars. The base64 charset contains no + # underscore, so apk_user_* tokens (which contain an underscore after + # "user") cannot be matched by this rule. + pattern: | + (?x) + \b + ( + apk_[A-Za-z0-9+/]{80,100}={0,2} + ) + \b + pattern_requirements: + min_digits: 2 + min_lowercase: 1 + min_uppercase: 1 + ignore_if_contains: + - "apk_user_" + - "YOUR_" + - "REPLACE_" + - "EXAMPLE" + min_entropy: 4.0 + confidence: medium + examples: + - apk_b3JnLWZhODc5ZTM3Y2FkZjRiNmJiZjJhN2FmMzE4MWRlYzEzOjM0MTU3ZWU4NTZiMjRkMjI5MDYwNzAxOGJmMGEyYzU0 + - "DEVIN_API_KEY=apk_b3JnLWZhODc5ZTM3Y2FkZjRiNmJiZjJhN2FmMzE4MWRlYzEzOmFjMWE2YWEwZjhjYzQ0OGNiY2Q5ZDJlOTI5MGEyN2Jh" + - "Authorization: Bearer apk_b3JnLWZhODc5ZTM3Y2FkZjRiNmJiZjJhN2FmMzE4MWRlYzEzOjU2NTNiMWJhNTMyMDRmMmFhNjg5Y2E5OGE2OTM4Yzc2" + references: + - https://docs.devin.ai/api-reference/overview + - https://docs.devin.ai/api-reference/v1/sessions + validation: + type: Http + content: + request: + method: GET + url: https://api.devin.ai/v1/sessions?limit=1 + headers: + Authorization: "Bearer {{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + words: + - '"sessions"' + match_all_words: true + # Revocation: no documented self-service revocation endpoint exists for + # legacy apk_* service keys; users must rotate them via the Devin web UI. + + - name: Cognition Devin Service User Token + id: kingfisher.devin.3 + # v3 service-user credentials issued via the Devin UI. Format: + # cog_<52 chars from RFC 4648 base32 lowercase alphabet [a-z2-7]> + pattern: | + (?x) + \b + ( + cog_[a-z2-7]{52} + ) + \b + pattern_requirements: + min_lowercase: 10 + ignore_if_contains: + - "YOUR_" + - "REPLACE_" + - "EXAMPLE" + min_entropy: 4.0 + confidence: medium + examples: + - cog_l5osrifmypvazi4j3yko52gj6jfj7qprsmy4lrcf27jas4szffha + - "DEVIN_API_KEY=cog_uv23fh6fc5kpaxdqif7hyvmzslnbmwriqita7cqkbb4rpaixnleq" + - "Authorization: Bearer cog_nxcgv6nuzdvwpgla5wcqsf6dhqrvjmi63j6f6say2au72ihjlxua" + references: + - https://docs.devin.ai/api-reference/overview + - https://docs.devin.ai/api-reference/v3/self + validation: + type: Http + content: + request: + method: GET + url: https://api.devin.ai/v3/self + headers: + Authorization: "Bearer {{ TOKEN }}" + Accept: application/json + response_matcher: + - report_response: true + - type: StatusMatch + status: [200] + - type: JsonValid + - type: WordMatch + words: + - '"principal_type"' + - '"service_user_id"' + match_all_words: false + # Revocation: DELETE /v3beta1/enterprise/service-users/{service_user_id}/api-keys/{api_key_id} + # is not implemented here because (1) it requires a separate admin service user with the + # `ManageAccountServiceUsers` permission (no self-revoke) and (2) the corresponding + # list-api-keys endpoint returns only metadata (no key value/hash), so a leaked token + # cannot be matched to its api_key_id from the token alone. Rotate via the Devin web UI. diff --git a/docs-site/docs/changelog.md b/docs-site/docs/changelog.md index 4d8af32..06e3fcd 100644 --- a/docs-site/docs/changelog.md +++ b/docs-site/docs/changelog.md @@ -6,6 +6,10 @@ description: "Kingfisher release history: new features, rules, bug fixes, and im # Changelog All notable changes to this project will be documented in this file. + +## [v1.102.0] +- Added 3 detection and validation rules for Cognition Devin API credentials: `kingfisher.devin.1` (legacy personal keys, `apk_user_` prefix), `kingfisher.devin.2` (legacy service keys, `apk_` prefix), and `kingfisher.devin.3` (v3 service-user tokens, `cog_` prefix / RFC 4648 base32). Live validation uses `GET /v1/sessions` for `apk_*` keys and `GET /v3/self` for `cog_` tokens. + ## [v1.101.0] - Fixed asymmetric JWT validation panics by using a single `jsonwebtoken` crypto backend and adding RS256 regression coverage. Thanks @AgentEnder. [#386](https://github.com/mongodb/kingfisher/pull/386) - Validator panics now fail that validation result instead of crashing the scan, with panic payloads kept out of cached and user-visible validation responses. Thanks @AgentEnder. [#387](https://github.com/mongodb/kingfisher/pull/387) diff --git a/docs-site/docs/usage/configuration.md b/docs-site/docs/usage/configuration.md index 1e18e48..c9ea0d6 100644 --- a/docs-site/docs/usage/configuration.md +++ b/docs-site/docs/usage/configuration.md @@ -97,6 +97,57 @@ kingfisher scan . --config ./kingfisher.yaml --confidence low # scan.confidence: high in YAML → CLI flag wins, runs at low confidence ``` +## Provider endpoint overrides + +For self-hosted or private instances of GitHub, GitLab, Gitea, Jira, Confluence, +or Artifactory you can redirect validation and revocation requests away from the +public cloud endpoints. + +**Per-run (CLI):** + +```bash +# Single provider +kingfisher scan ./repo \ + --endpoint github=https://ghe.corp.example.com \ + --allow-internal-ips + +# Multiple providers at once via a YAML file +kingfisher scan ./repo \ + --endpoint-config ./kingfisher-endpoints.yml \ + --allow-internal-ips +``` + +**Persisted in `kingfisher.yaml`** (under `global:`): + +```yaml +global: + endpoints: + - github=https://ghe.corp.example.com + - gitlab=https://gitlab.corp.example.com + endpoint_config: ./kingfisher-endpoints.yml # merged with `endpoints` list + allow_internal_ips: true # required for private/localhost URLs +``` + +`--endpoint-config` / `endpoint_config` points to a YAML file that maps provider +keys to base URLs: + +```yaml +endpoints: + github: https://ghe.corp.example.com + gitlab: https://gitlab.corp.example.com + jira: https://jira.corp.example.com + confluence: https://confluence.corp.example.com + artifactory: http://localhost:8071 +``` + +Supported provider keys: `github`, `gitlab`, `gitea`, `jira`, `jira-cloud`, +`confluence`, `artifactory`. For endpoints on private IPs or `localhost`, +always combine with `--allow-internal-ips` (or `global.allow_internal_ips: true` +in `kingfisher.yaml`) — Kingfisher blocks SSRF by default. + +See the [GitHub Enterprise section](basic-scanning.md#scan-a-github-enterprise-self-hosted-github-instance) +in the scanning guide for end-to-end examples. + ## Webhook URL policy `alerts.webhooks[].url` (and `--alert-webhook URL`) **must use `https://`**.