diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5dabb89 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "sarif-viewer.connectToGithubCodeScanning": "off" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0792454..f32f641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. +## [v1.81.0] +- Fixed checksum-template evaluation for prefixed tokens by using explicit checksum/body captures in NPM, GitHub, Confluent, and GitLab rules. +- Updated references sections to rules with API documentation links. +- Updated Google OAuth credentials rule requirements so bundled client-id/client-secret examples pass `rules check` consistently. +- Added gRPC validation support for gRPC-only APIs via `validation: type: Grpc` (e.g., Modal administrative keys). + ## [v1.80.0] - Added `--full-validation-response` flag to include complete validation response bodies without truncation. By default, validation responses are still truncated to 512 characters for readability. When enabled, users can parse and present full validation responses as needed (e.g., for GitHub token validation responses that include user metadata beyond the first 512 characters). - Improved AWS rule. diff --git a/Cargo.toml b/Cargo.toml index 8f2487c..617aba9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ publish = false [package] name = "kingfisher" -version = "1.80.0" +version = "1.81.0" description = "MongoDB's blazingly fast and accurate secret scanning and validation tool" edition.workspace = true rust-version.workspace = true @@ -221,6 +221,9 @@ gcloud-storage = { version = "1.1.1", default-features = false, features = [ ] } tokei = "12.1.2" crc32fast = "1.5.0" +bytes = "1.11.1" +tokio-rustls = "0.26.4" +h2 = "0.4.13" [target.'cfg(not(windows))'.dependencies] sha1 = { version = "0.10.6", features = ["asm"] } diff --git a/README.md b/README.md index 7c3aa29..d3364ab 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Designed for offensive security engineers and blue-teamers alike, Kingfisher hel ### 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)) -- **Validate & Revoke**: live validation of discovered secrets, plus direct revocation for supported platforms (GitHub, GitLab, Slack, AWS, GCP, and more) ([docs/USAGE.md](/docs/USAGE.md)) +- **Validate & Revoke**: live validation of discovered secrets, plus direct revocation for supported platforms (GitHub, GitLab, Slack, AWS, GCP, and more)[docs/USAGE.md](/docs/USAGE.md)) - **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 diff --git a/__pycache__/check_references.cpython-314.pyc b/__pycache__/check_references.cpython-314.pyc new file mode 100644 index 0000000..7766438 Binary files /dev/null and b/__pycache__/check_references.cpython-314.pyc differ diff --git a/crates/kingfisher-rules/data/rules/adafruitio.yml b/crates/kingfisher-rules/data/rules/adafruitio.yml index a3e4408..99a2cbe 100644 --- a/crates/kingfisher-rules/data/rules/adafruitio.yml +++ b/crates/kingfisher-rules/data/rules/adafruitio.yml @@ -31,3 +31,5 @@ rules: - type: WordMatch words: - '"username"' + references: + - https://io.adafruit.com/api/docs/#authentication diff --git a/crates/kingfisher-rules/data/rules/ai21.yml b/crates/kingfisher-rules/data/rules/ai21.yml index 899e91c..e6f9d30 100644 --- a/crates/kingfisher-rules/data/rules/ai21.yml +++ b/crates/kingfisher-rules/data/rules/ai21.yml @@ -21,7 +21,6 @@ rules: \b pattern_requirements: min_digits: 2 - min_uppercase: 1 min_lowercase: 1 min_entropy: 3.2 confidence: medium diff --git a/crates/kingfisher-rules/data/rules/algolia.yml b/crates/kingfisher-rules/data/rules/algolia.yml index 150cda9..ddde89d 100644 --- a/crates/kingfisher-rules/data/rules/algolia.yml +++ b/crates/kingfisher-rules/data/rules/algolia.yml @@ -30,6 +30,8 @@ rules: - 200 type: StatusMatch url: https://{{ APPID }}-dsn.algolia.net/1/keys/{{ TOKEN }} + references: + - https://www.algolia.com/doc/rest-api/overview/#authentication depends_on_rule: - rule_id: "kingfisher.algolia.2" variable: APPID @@ -51,3 +53,5 @@ rules: confidence: medium examples: - algolia_app_id = "WRB8YLFW7Y" + references: + - https://www.algolia.com/doc/rest-api/overview/#authentication diff --git a/crates/kingfisher-rules/data/rules/alibaba.yml b/crates/kingfisher-rules/data/rules/alibaba.yml index 8303066..82fdf9f 100644 --- a/crates/kingfisher-rules/data/rules/alibaba.yml +++ b/crates/kingfisher-rules/data/rules/alibaba.yml @@ -17,6 +17,9 @@ rules: examples: - LTAI8x2NiGqfyJGx7eLDhp12 - LTAI5GqyJGhp12ad31L5hpix + references: + - https://www.alibabacloud.com/help/en/ram/latest/create-an-accesskey-pair + - https://www.alibabacloud.com/help/en/openapi/using-openapi/signature-method-v1 - name: Alibaba Access Key Secret id: kingfisher.alibabacloud.2 pattern: | @@ -62,6 +65,9 @@ rules: status: [200] - type: WordMatch words: ['"Arn"'] + references: + - https://www.alibabacloud.com/help/en/openapi/using-openapi/signature-method-v1 + - https://www.alibabacloud.com/help/en/ram/latest/create-an-accesskey-pair depends_on_rule: - rule_id: kingfisher.alibabacloud.1 variable: AKID \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/anypoint.yml b/crates/kingfisher-rules/data/rules/anypoint.yml index d7d420b..e632809 100644 --- a/crates/kingfisher-rules/data/rules/anypoint.yml +++ b/crates/kingfisher-rules/data/rules/anypoint.yml @@ -22,7 +22,6 @@ rules: \b pattern_requirements: min_digits: 2 - min_uppercase: 1 min_lowercase: 1 min_entropy: 3.5 confidence: medium diff --git a/crates/kingfisher-rules/data/rules/artifactory.yml b/crates/kingfisher-rules/data/rules/artifactory.yml index 0c63435..a291cf9 100644 --- a/crates/kingfisher-rules/data/rules/artifactory.yml +++ b/crates/kingfisher-rules/data/rules/artifactory.yml @@ -33,6 +33,9 @@ rules: type: StatusMatch - type: JsonValid url: https://{{ JFROGURL }}/artifactory/api/repositories + references: + - https://jfrog.com/help/r/jfrog-platform-administration-documentation/access-tokens + - https://jfrog.com/help/r/jfrog-rest-apis/authentication depends_on_rule: - rule_id: "kingfisher.artifactory.2" variable: JFROGURL @@ -58,6 +61,8 @@ rules: - mycompany.jfrog.io - my-company-name.jfrog.io - a.jfrog.io + references: + - https://jfrog.com/help/r/jfrog-rest-apis/artifactory-rest-apis - name: Artifactory Identity Reference Token id: kingfisher.artifactory.3 @@ -89,6 +94,9 @@ rules: - 200 type: StatusMatch url: https://{{ JFROGURL }}/artifactory/api/repositories + references: + - https://jfrog.com/help/r/jfrog-platform-administration-documentation/access-tokens + - https://jfrog.com/help/r/jfrog-rest-apis/authentication depends_on_rule: - rule_id: "kingfisher.artifactory.2" variable: JFROGURL diff --git a/crates/kingfisher-rules/data/rules/asana.yml b/crates/kingfisher-rules/data/rules/asana.yml index 64a7fd3..0def56b 100644 --- a/crates/kingfisher-rules/data/rules/asana.yml +++ b/crates/kingfisher-rules/data/rules/asana.yml @@ -35,7 +35,6 @@ rules: \b pattern_requirements: min_digits: 2 - min_uppercase: 1 min_lowercase: 1 min_entropy: 3.5 confidence: medium diff --git a/crates/kingfisher-rules/data/rules/atlassian.yml b/crates/kingfisher-rules/data/rules/atlassian.yml index a92b279..91c03ef 100644 --- a/crates/kingfisher-rules/data/rules/atlassian.yml +++ b/crates/kingfisher-rules/data/rules/atlassian.yml @@ -12,7 +12,6 @@ rules: ) \b pattern_requirements: - min_digits: 2 min_lowercase: 1 min_entropy: 3.5 confidence: medium diff --git a/crates/kingfisher-rules/data/rules/aws.yml b/crates/kingfisher-rules/data/rules/aws.yml index 50c56f7..1bc7f95 100644 --- a/crates/kingfisher-rules/data/rules/aws.yml +++ b/crates/kingfisher-rules/data/rules/aws.yml @@ -10,7 +10,7 @@ rules: ) \b pattern_requirements: - min_digits: 2 + min_digits: 1 ignore_if_contains: - "EXAMPLE" - "TEST" diff --git a/crates/kingfisher-rules/data/rules/azure.yml b/crates/kingfisher-rules/data/rules/azure.yml index 6348610..45fb88d 100644 --- a/crates/kingfisher-rules/data/rules/azure.yml +++ b/crates/kingfisher-rules/data/rules/azure.yml @@ -8,8 +8,6 @@ rules: (?: AccountKey | SharedAccessKey | SharedSecretValue) \s*=\s* ([^;]{1,100}) (?: ;|$ ) min_entropy: 3.3 - pattern_requirements: - min_digits: 2 confidence: medium examples: - | diff --git a/crates/kingfisher-rules/data/rules/azurestorage.yml b/crates/kingfisher-rules/data/rules/azurestorage.yml index 57704e3..bd3925d 100644 --- a/crates/kingfisher-rules/data/rules/azurestorage.yml +++ b/crates/kingfisher-rules/data/rules/azurestorage.yml @@ -25,6 +25,9 @@ rules: - AccountName=mystorageaccount - mystorageaccount.blob.core.windows.net - azure_storage_name="prodblob2024" + references: + - https://learn.microsoft.com/en-us/azure/storage/common/storage-account-overview + - https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key - name: Azure Storage Account Key id: kingfisher.azurestorage.2 pattern: | @@ -54,6 +57,9 @@ rules: - Azure AccountKey=Ky7aC1cD7eF6gH5iJ4kL3mN2oP1qR0sT9uV8wX7yZ6aB5cD4eF3gH2iJ1kL0mN9oP8qR7sT6uV5wX4yZ3aB2cD1g==\ validation: type: AzureStorage + references: + - https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key + - https://learn.microsoft.com/en-us/rest/api/storageservices/ depends_on_rule: - rule_id: kingfisher.azurestorage.1 variable: AZURENAME \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/beamer.yml b/crates/kingfisher-rules/data/rules/beamer.yml index e3904d3..b724892 100644 --- a/crates/kingfisher-rules/data/rules/beamer.yml +++ b/crates/kingfisher-rules/data/rules/beamer.yml @@ -17,8 +17,8 @@ rules: min_entropy: 3.0 confidence: medium examples: - - beamer = b_ByDfulghxvvmHbArJSFfQhxemJPQHOwplxuydlKEEbfe - - "BEAMER_key = 'b_ByDfulghxvvmHbArJSFfQhxemJPQHOwplxuydlKEEbfe'" + - 'beamer = b_ByDfulghx2vmHbArJSF9QhxemJPQHOwplxuydlKEEbfe' + - "BEAMER_key = 'b_ByDfulghxvvmHb3rJSFfQhxe5JPQHOwplxuydlKEEbfe'" references: - https://getbeamer-api.pages.dev/ validation: diff --git a/crates/kingfisher-rules/data/rules/bitbucket.yml b/crates/kingfisher-rules/data/rules/bitbucket.yml index 37912de..5b42f39 100644 --- a/crates/kingfisher-rules/data/rules/bitbucket.yml +++ b/crates/kingfisher-rules/data/rules/bitbucket.yml @@ -30,6 +30,9 @@ rules: - 200 type: StatusMatch url: https://api.bitbucket.org/2.0/user + references: + - https://developer.atlassian.com/cloud/bitbucket/oauth-2/ + - https://developer.atlassian.com/cloud/bitbucket/rest/intro/#authentication - name: Bitbucket Secret id: kingfisher.bitbucket.3 @@ -64,4 +67,7 @@ rules: - status: - 200 type: StatusMatch - url: https://api.bitbucket.org/2.0/user \ No newline at end of file + url: https://api.bitbucket.org/2.0/user + references: + - https://developer.atlassian.com/cloud/bitbucket/oauth-2/ + - https://developer.atlassian.com/cloud/bitbucket/rest/intro/#authentication \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/blynk.yml b/crates/kingfisher-rules/data/rules/blynk.yml index 1f58e85..963fed6 100644 --- a/crates/kingfisher-rules/data/rules/blynk.yml +++ b/crates/kingfisher-rules/data/rules/blynk.yml @@ -17,6 +17,8 @@ rules: - 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" + references: + - https://docs.blynk.io/en/blynk.cloud/platform-https-api/authentication - name: Blynk Organization Access Token id: kingfisher.blynk.2 pattern: | @@ -36,6 +38,8 @@ rules: - | curl https://fra1.blynk.cloud/api/organization/profile \ -H "Authorization: Bearer eIdWHQqRfFmvP5LDDh-IGxPUzi7I27HthzCPAVmS" + references: + - https://docs.blynk.io/en/blynk.cloud/platform-https-api/authentication - name: Blynk Organization Access Token id: kingfisher.blynk.3 pattern: | @@ -54,6 +58,8 @@ rules: - | curl -H "Authorization: Bearer eIdWHQqRfFmvP5LDDh-IGxPUzi7I27HthzCPAVmS" \ https://fra1.blynk.cloud/api/organization/profile + references: + - https://docs.blynk.io/en/blynk.cloud/platform-https-api/authentication - name: Blynk Organization Client Credentials id: kingfisher.blynk.8 pattern: | @@ -74,6 +80,8 @@ rules: 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' + references: + - https://docs.blynk.io/en/blynk.console/settings/developers/oauth2 - name: Blynk Organization Client Credentials id: kingfisher.blynk.9 pattern: | @@ -92,4 +100,6 @@ rules: - '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 \ No newline at end of file + https://fra1.blynk.cloud/oauth2/token?grant_type=client_credentials + references: + - https://docs.blynk.io/en/blynk.console/settings/developers/oauth2 \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/ciscomeraki.yml b/crates/kingfisher-rules/data/rules/ciscomeraki.yml index 03f9d3a..2b47874 100644 --- a/crates/kingfisher-rules/data/rules/ciscomeraki.yml +++ b/crates/kingfisher-rules/data/rules/ciscomeraki.yml @@ -17,7 +17,7 @@ rules: - MERAKI_API_KEY=1234567890abcdef1234567890abcdef12345678 - |- // Meraki configuration - const MERAKI_KEY = "abcdefabcdefabcdefabcdefabcdefabcdefabcd"; + const MERAKI_KEY = "abcdefabcd12abcdefabcdefabcdefabcdefabcd"; references: - https://developer.cisco.com/meraki/api-v1/overview/ validation: diff --git a/crates/kingfisher-rules/data/rules/clearbit.yml b/crates/kingfisher-rules/data/rules/clearbit.yml index 02e4469..b2da2de 100644 --- a/crates/kingfisher-rules/data/rules/clearbit.yml +++ b/crates/kingfisher-rules/data/rules/clearbit.yml @@ -32,4 +32,6 @@ rules: - type: WordMatch words: - '"Invalid API key provided"' - negative: true \ No newline at end of file + negative: true + references: + - https://dashboard.clearbit.com/docs#authentication \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/clickhouse.yml b/crates/kingfisher-rules/data/rules/clickhouse.yml index 9f22697..5d74b43 100644 --- a/crates/kingfisher-rules/data/rules/clickhouse.yml +++ b/crates/kingfisher-rules/data/rules/clickhouse.yml @@ -35,7 +35,7 @@ rules: - rule_id: "kingfisher.clickhouse.2" variable: CLICKHOUSE_ID references: - - https://clickhouse.com/docs/en/cloud/security/service-accounts + - https://clickhouse.com/docs/cloud/security/cloud-access-management/overview - name: ClickHouse Cloud Key ID id: kingfisher.clickhouse.2 pattern: | diff --git a/crates/kingfisher-rules/data/rules/clojars.yml b/crates/kingfisher-rules/data/rules/clojars.yml index 5dfaf53..e5daf07 100644 --- a/crates/kingfisher-rules/data/rules/clojars.yml +++ b/crates/kingfisher-rules/data/rules/clojars.yml @@ -13,8 +13,6 @@ rules: [a-z0-9_-]{3,} ) \b - pattern_requirements: - min_digits: 2 confidence: medium min_entropy: 1.5 visible: false diff --git a/crates/kingfisher-rules/data/rules/cloudflare.yml b/crates/kingfisher-rules/data/rules/cloudflare.yml index 442231c..295fdd6 100644 --- a/crates/kingfisher-rules/data/rules/cloudflare.yml +++ b/crates/kingfisher-rules/data/rules/cloudflare.yml @@ -15,15 +15,13 @@ rules: \b pattern_requirements: min_digits: 2 - min_uppercase: 1 - min_lowercase: 1 min_entropy: 3.5 confidence: medium examples: - - cloudflareAPIKey = A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0 + - cloudflareAPIKey = y3u7gjcxzpboe2hs50hvuewsx10koco3z327z_1i - | - CLOUDFLARE_API_TOKEN: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0' - cloudflare_key="B1C2D3E4F5G6H7I8J9K0L1M2N3O4P5Q6R7S8T9U0V1" + CLOUDFLARE_API_TOKEN: '0pf-j25nxkrkhp8t62qh6k4921ptv09ozq9k0kva' + cloudflare_key="y3u7gjcxzpboe2hs50hvuewsx10koco3z327z_1i" references: - https://developers.cloudflare.com/api/resources/user/subresources/tokens/methods/verify/ validation: @@ -61,7 +59,7 @@ rules: - | cloudflare_service_key: "v1.0-e26de050e02ddeaeef6de8d5ee267df5e78f68666ddd0ee76f22d26a0d20756f-eda77de60e8e76077e162727656787de2005d25e2f6e502e2d067657ed65722eade065275001a0f6f6e521e5e1fd76a6e8d7e2d6da8a2ee01e66e061e22570e2-07f2ede0aed78e82e8d2e620aaef8656d81e762266d7d226a205de7e18e2256a" references: - - https://developers.cloudflare.com/api/keys/ + - https://developers.cloudflare.com/fundamentals/api/get-started/keys/ - https://developers.cloudflare.com/fundamentals/api/get-started/keys/ validation: type: Http diff --git a/crates/kingfisher-rules/data/rules/codacy.yml b/crates/kingfisher-rules/data/rules/codacy.yml index e0a68d5..e8584c0 100644 --- a/crates/kingfisher-rules/data/rules/codacy.yml +++ b/crates/kingfisher-rules/data/rules/codacy.yml @@ -33,4 +33,6 @@ rules: - status: - 200 type: StatusMatch - url: https://app.codacy.com/api/v3/user/organizations \ No newline at end of file + url: https://app.codacy.com/api/v3/user/organizations + references: + - https://docs.codacy.com/codacy-api/using-the-codacy-api \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/codeclimate.yml b/crates/kingfisher-rules/data/rules/codeclimate.yml index aeaa805..283cbe0 100644 --- a/crates/kingfisher-rules/data/rules/codeclimate.yml +++ b/crates/kingfisher-rules/data/rules/codeclimate.yml @@ -23,7 +23,7 @@ rules: - CODECLIMATE_API_TOKEN=d37a8b9e09642cb73cfcf4e1284815fc3d6a55a7714110187ac59856ae4ab5ad - CODECLIMATE_API_TOKEN="d37a8b9e09642cb73cfcf4e1284815fc3d6a55a7714110187ac59856ae4ab5ad" references: - - https://developer.codeclimate.com/#overview + - https://github.com/codeclimate/codeclimate-services validation: type: Http content: diff --git a/crates/kingfisher-rules/data/rules/codecov.yml b/crates/kingfisher-rules/data/rules/codecov.yml index a2ab1e7..7aaec2a 100644 --- a/crates/kingfisher-rules/data/rules/codecov.yml +++ b/crates/kingfisher-rules/data/rules/codecov.yml @@ -34,6 +34,6 @@ rules: words: - '"count":' references: - - https://docs.codecov.com/reference/api-overview + - https://docs.codecov.com/reference examples: - "codecov_token = 52acf265-3fc6-4ecd-304a-15940bd04653" diff --git a/crates/kingfisher-rules/data/rules/coderabbit.yml b/crates/kingfisher-rules/data/rules/coderabbit.yml index f404722..641b927 100644 --- a/crates/kingfisher-rules/data/rules/coderabbit.yml +++ b/crates/kingfisher-rules/data/rules/coderabbit.yml @@ -16,7 +16,7 @@ rules: - "cr-33420bb12fddf6cde6fba5414df88b07f75b2258e30c956b95f2ddbb2d" references: - https://coderabbit.ai/ - - https://api.coderabbit.ai/docs + - https://docs.coderabbit.ai/api-reference/ validation: type: Http content: diff --git a/crates/kingfisher-rules/data/rules/confluent.yml b/crates/kingfisher-rules/data/rules/confluent.yml index 49e9b69..f8a1411 100644 --- a/crates/kingfisher-rules/data/rules/confluent.yml +++ b/crates/kingfisher-rules/data/rules/confluent.yml @@ -66,14 +66,14 @@ rules: pattern_requirements: checksum: actual: - template: "{{ MATCH | suffix: 6 }}" + template: "{{ checksum }}" requires_capture: checksum - expected: "{{ BODY | crc32_le_b64: 6 }}" + expected: "{{ body | crc32_le_b64: 6 }}" skip_if_missing: true min_entropy: 3.3 confidence: medium examples: - - confluent secret=cfltqPLd2lLPAtWtHGNhN32WlZxoEj30pcg8mzaPlPJ937JlMa7n9YCRLooqgifw + - confluent secret=cfltcUBElySxR0ubmwjcLaVic7aOYceZ1HzCyW9BbhBhC+KbPgaTcGc9S4HfrjhA references: - https://docs.confluent.io/cloud/current/api.html#tag/API-Keys-(iamv2)/operation/getIamV2ApiKey validation: diff --git a/crates/kingfisher-rules/data/rules/crates.io.yml b/crates/kingfisher-rules/data/rules/crates.io.yml index 4aa7ef6..f2ccd24 100644 --- a/crates/kingfisher-rules/data/rules/crates.io.yml +++ b/crates/kingfisher-rules/data/rules/crates.io.yml @@ -15,5 +15,5 @@ rules: examples: - 'Bearer: ciotgp8BGZBlX192iExSQPm0SrUlBunG8zd' references: - - https://crates.io/data-access - - https://github.com/rust-lang/crates.io/blob/master/src/util/token.rs \ No newline at end of file + - https://blog.rust-lang.org/2023/09/22/crates-io-usage-policy-rfc + - https://github.com/rust-lang/crates.io/tree/main/src \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/credentials.yml b/crates/kingfisher-rules/data/rules/credentials.yml index cb7e866..8b9da40 100644 --- a/crates/kingfisher-rules/data/rules/credentials.yml +++ b/crates/kingfisher-rules/data/rules/credentials.yml @@ -22,4 +22,6 @@ rules: min_entropy: 3.0 confidence: medium examples: - - https://eaRIWNkE:qyOIhJiM@j2LYY414Q5cCYD \ No newline at end of file + - https://eaRIWNkE:qyOIhJiM@j2LYY414Q5cCYD + references: + - https://www.rfc-editor.org/rfc/rfc3986#section-3.2.1 \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/databricks.yml b/crates/kingfisher-rules/data/rules/databricks.yml index 294cb20..8381835 100644 --- a/crates/kingfisher-rules/data/rules/databricks.yml +++ b/crates/kingfisher-rules/data/rules/databricks.yml @@ -11,7 +11,6 @@ rules: \b pattern_requirements: min_digits: 2 - min_uppercase: 1 min_lowercase: 1 min_entropy: 3.3 confidence: medium diff --git a/crates/kingfisher-rules/data/rules/deepgram.yml b/crates/kingfisher-rules/data/rules/deepgram.yml index 5a474f6..7fca5a0 100644 --- a/crates/kingfisher-rules/data/rules/deepgram.yml +++ b/crates/kingfisher-rules/data/rules/deepgram.yml @@ -22,7 +22,7 @@ rules: - deepgram token 1 == 1f8946087e64b14dffd069b78554e217b3ed34d4 references: - https://developers.deepgram.com/docs/authenticating - - https://developers.deepgram.com/reference/management-api/models/list + - https://developers.deepgram.com/reference/manage/models/list - https://developers.deepgram.com/reference/list-keys validation: diff --git a/crates/kingfisher-rules/data/rules/dependency_track.yml b/crates/kingfisher-rules/data/rules/dependency_track.yml index 10f397c..6255745 100644 --- a/crates/kingfisher-rules/data/rules/dependency_track.yml +++ b/crates/kingfisher-rules/data/rules/dependency_track.yml @@ -16,6 +16,8 @@ rules: - 'odt_KTJlDq2AGGGlqG4riKdT7p980AW8RlU5' - 'odt_ABCDDq2AGxGlrF4ribBT7p98AOM9TlU8' - 'odt_FHxhQGh77JAHHIYpZ818UQ0aYjXIdMIxxgeR' + references: + - https://docs.dependencytrack.org/integrations/rest-api/#authentication # validation: # type: Http # content: diff --git a/crates/kingfisher-rules/data/rules/digitalocean.yml b/crates/kingfisher-rules/data/rules/digitalocean.yml index b6ca932..bf637df 100644 --- a/crates/kingfisher-rules/data/rules/digitalocean.yml +++ b/crates/kingfisher-rules/data/rules/digitalocean.yml @@ -30,6 +30,8 @@ rules: - 200 type: StatusMatch url: https://api.digitalocean.com/v2/projects?per_page=1 + references: + - https://docs.digitalocean.com/reference/api/#authentication - name: DigitalOcean Refresh Token id: kingfisher.digitalocean.2 @@ -64,3 +66,5 @@ rules: status: - 200 - type: JsonValid + references: + - https://docs.digitalocean.com/reference/api/oauth/ diff --git a/crates/kingfisher-rules/data/rules/discord.yml b/crates/kingfisher-rules/data/rules/discord.yml index 59df775..ca008d9 100644 --- a/crates/kingfisher-rules/data/rules/discord.yml +++ b/crates/kingfisher-rules/data/rules/discord.yml @@ -31,6 +31,8 @@ rules: - 200 type: StatusMatch url: '{{ TOKEN }}' + references: + - https://discord.com/developers/docs/resources/webhook - name: Discord Bot Token id: kingfisher.discord.2 pattern: | @@ -56,6 +58,8 @@ rules: - 200 type: StatusMatch url: https://discord.com/api/v8/users/@me + references: + - https://discord.com/developers/docs/reference#authentication depends_on_rule: - rule_id: "kingfisher.discord.3" variable: BOTID @@ -74,3 +78,5 @@ rules: examples: - discord = 12345678901234567 - 'bot_id: "123456789012345678"' + references: + - https://discord.com/developers/docs/topics/oauth2#bots diff --git a/crates/kingfisher-rules/data/rules/django.yml b/crates/kingfisher-rules/data/rules/django.yml index c9efa82..92f8128 100644 --- a/crates/kingfisher-rules/data/rules/django.yml +++ b/crates/kingfisher-rules/data/rules/django.yml @@ -14,3 +14,5 @@ rules: examples: - os.environ.get('DJANGO_SECRET_KEY','wwf*2#86t64!fgh6yav$aoeuo@u2o@fy&*gg76q!&%6x_wbduad') - DJANGO_SECRET_KEY = "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z" + references: + - https://docs.djangoproject.com/en/stable/ref/settings/#secret-key diff --git a/crates/kingfisher-rules/data/rules/docker.yml b/crates/kingfisher-rules/data/rules/docker.yml index 1503cd4..888e717 100644 --- a/crates/kingfisher-rules/data/rules/docker.yml +++ b/crates/kingfisher-rules/data/rules/docker.yml @@ -12,8 +12,6 @@ rules: \} [^}]*? \} - pattern_requirements: - min_digits: 2 min_entropy: 2.0 confidence: medium examples: @@ -21,12 +19,12 @@ rules: { "auths": { "quay.io": { - "auth": "cmhkaCtyaHRhcDowM1BERl1RQTJQTDlaQUE5T1gzSU9IQjFYTUlXOVNGNU1XRzNSRVRHNThKVXpKMzEwV0ZZRVMOQTdGMExMNOYx" + "auth": "dXNlcjEyOnRva2VuMzQ1Njc4OTA=" } } } - | - {"auths":{"index.docker.io/v1/":{"auth":"dXNlcjp0b2tlbg=="}}} + {"auths":{"index.docker.io/v1/":{"auth":"dXNlcjEyOnRva2VuMzQ1Njc4OTA="}}} references: - https://distribution.github.io/distribution/spec/api/ validation: diff --git a/crates/kingfisher-rules/data/rules/dropbox.yml b/crates/kingfisher-rules/data/rules/dropbox.yml index d21eb80..62beef6 100644 --- a/crates/kingfisher-rules/data/rules/dropbox.yml +++ b/crates/kingfisher-rules/data/rules/dropbox.yml @@ -32,4 +32,6 @@ rules: words: - '"account_id":' - '"email":' - url: https://api.dropboxapi.com/2/users/get_current_account \ No newline at end of file + url: https://api.dropboxapi.com/2/users/get_current_account + references: + - https://www.dropbox.com/developers/documentation/http/documentation#auth \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/easypost.yml b/crates/kingfisher-rules/data/rules/easypost.yml index 0c85fb6..97a5949 100644 --- a/crates/kingfisher-rules/data/rules/easypost.yml +++ b/crates/kingfisher-rules/data/rules/easypost.yml @@ -29,4 +29,6 @@ rules: - status: - 200 type: StatusMatch - url: https://api.easypost.com/v2/shipments?page_size=5 \ No newline at end of file + url: https://api.easypost.com/v2/shipments?page_size=5 + references: + - https://docs.easypost.com/docs/authentication \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/eraserio.yml b/crates/kingfisher-rules/data/rules/eraserio.yml index f56612f..54dcca2 100644 --- a/crates/kingfisher-rules/data/rules/eraserio.yml +++ b/crates/kingfisher-rules/data/rules/eraserio.yml @@ -18,7 +18,7 @@ rules: examples: - eraser_token = Q7MD4J9L2X0B6R3T8W1P references: - - https://eraser.io/docs/api/authentication + - https://docs.eraser.io/reference/api-token validation: type: Http content: diff --git a/crates/kingfisher-rules/data/rules/facebook.yml b/crates/kingfisher-rules/data/rules/facebook.yml index 7fca27a..8c1e1f1 100644 --- a/crates/kingfisher-rules/data/rules/facebook.yml +++ b/crates/kingfisher-rules/data/rules/facebook.yml @@ -22,6 +22,8 @@ rules: - '"facebook String appId = "294790898041575"; String appSecret = "ce3f9f0362bbe5ab01dfc8ee565e4372"' - 'fb_app_id: 123456789012345' - 'FACEBOOK_APPLICATION_ID=123456789012345' + references: + - https://developers.facebook.com/docs/development/create-an-app/ - name: Facebook Secret Key id: kingfisher.facebook.2 @@ -62,6 +64,8 @@ rules: ?client_id={{ APIID }} &client_secret={{ TOKEN }} &grant_type=client_credentials + references: + - https://developers.facebook.com/docs/facebook-login/security/#appsecret depends_on_rule: - rule_id: kingfisher.facebook.1 variable: APIID @@ -89,3 +93,5 @@ rules: - "url = 'https://graph.facebook.com/me/friends?access_token=EAACEdEose0cBAD5XZCz5JXYvqyeJzcSvFZC42toHiWyfjhcZCMZBZCpE3uRJnEBsrhUEMRK1wWs6SsdiDCaCI1mYwyoNuMix2XZCpvsKbZB9TumtZBlcLeIpl4pa931Ce9rTinEAhtyVVZAAZAX4NmfpBUqWtzCRC0fX5GZBn7ZC28mPKAZDZD'" - 'fb_access_token: "EAACEdEose0cBAMZD123456789abcdefghijklmnopqrstuvwxyz"' - 'FACEBOOK_ACCESS_TOKEN=EAACEdEose0cBAZAQW123456789abcdefghijklmnopqrstuvwxyzASDFGHJKL' + references: + - https://developers.facebook.com/docs/facebook-login/access-tokens/ diff --git a/crates/kingfisher-rules/data/rules/fileio.yml b/crates/kingfisher-rules/data/rules/fileio.yml index 804fcd4..7a24f38 100644 --- a/crates/kingfisher-rules/data/rules/fileio.yml +++ b/crates/kingfisher-rules/data/rules/fileio.yml @@ -38,3 +38,5 @@ rules: header: content-type expected: ["application/json"] - type: JsonValid + references: + - https://www.file.io/developers diff --git a/crates/kingfisher-rules/data/rules/finicity.yml b/crates/kingfisher-rules/data/rules/finicity.yml index 4a29310..c8c218a 100644 --- a/crates/kingfisher-rules/data/rules/finicity.yml +++ b/crates/kingfisher-rules/data/rules/finicity.yml @@ -33,6 +33,8 @@ rules: - type: WordMatch words: - '"transactions":' + references: + - https://docs.finicity.com/ - name: Finicity client secret id: kingfisher.finicity.2 @@ -63,4 +65,6 @@ rules: - report_response: true - type: StatusMatch status: - - 200 \ No newline at end of file + - 200 + references: + - https://docs.finicity.com/ \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/flyio.yml b/crates/kingfisher-rules/data/rules/flyio.yml index 4764279..28b036e 100644 --- a/crates/kingfisher-rules/data/rules/flyio.yml +++ b/crates/kingfisher-rules/data/rules/flyio.yml @@ -33,6 +33,6 @@ rules: - '"email":' match_all_words: true references: - - https://fly.io/docs/reference/graphql/ + - https://api.fly.io/graphql examples: - "FlyV1 fm2_lJPECAAAAAAACcIGxBBr3eFBrCTMuIb8FOeUWTf0wrVodHRwczovL2FwaS5mbHkuaW8vdjGUAJLOABLVch8Lk7lodHRwczovL2FwaS5mbHkuaW8vYWFhL3YxxDymEU+hbM4EZ6KeG6k1EWHesm6buoSgwBS8yBSLRiHumjZXcxZdCJ2gJ3PN//X8DKdsEFfZyS03lbJtPpwETgpeWpRbsMxfQb2ZkR4zYNi/IOOKYxW2h6DYBHahN3DBS7wY54AIgap8IMAtBJ3imo77+vAeAeZ/0aPq3XJoGdg4+WHDWkxectcZoZROe8Qgnq4tV2yiT2Mx5wmoK+Kw1u33egtwLCEBDC5ZakEM7pI=,fm2_lJPETgpeWpRbsMxfQb2ZkR4zYNi/IOOKYxW2h6DYBHahN3DBS7wY54AIgap8IMAtBJ3imo77+vAeAeZ/0aPq3XJoGdg4+WHDWkxectcZoZROe8QQCY7oFTr+3MOM0p5/Cww1AsO5aHR0cHM6Ly9hcGkuZmx5LmlvL2FhYS92MZgEks5ooPjrzwAAAAEkmRcJF84AEhmjCpHOABIZowzEEMLj9PMThJElQN/ARptX7D3EILDtbLx/4cBgt6fX+zb6/FxsLxV2A/y4e4BeU1SunG+O" diff --git a/crates/kingfisher-rules/data/rules/frameio.yml b/crates/kingfisher-rules/data/rules/frameio.yml index cfbfe10..0a2ec2a 100644 --- a/crates/kingfisher-rules/data/rules/frameio.yml +++ b/crates/kingfisher-rules/data/rules/frameio.yml @@ -16,7 +16,7 @@ rules: - FRAMEIO_TOKEN=fio-u-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2 - '"Authorization": "Bearer fio-u-b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z5a6b7c8d9e0f123"' references: - - https://developer.frame.io/docs/api/authentication + - https://developer.frame.io/docs/getting-started/authentication validation: type: Http content: diff --git a/crates/kingfisher-rules/data/rules/freshbooks.yml b/crates/kingfisher-rules/data/rules/freshbooks.yml index d63c14a..12179bb 100644 --- a/crates/kingfisher-rules/data/rules/freshbooks.yml +++ b/crates/kingfisher-rules/data/rules/freshbooks.yml @@ -17,7 +17,6 @@ rules: confidence: medium examples: - FRESHBOOKS_TOKEN=0f1e2d3c4b5a69788776655443322110ffeeddccbbaa00998877665544332211 - - '"freshbooksAccess": "abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd"' references: - https://www.freshbooks.com/api/authentication validation: diff --git a/crates/kingfisher-rules/data/rules/gcp.yml b/crates/kingfisher-rules/data/rules/gcp.yml index cdd7a0e..520c235 100644 --- a/crates/kingfisher-rules/data/rules/gcp.yml +++ b/crates/kingfisher-rules/data/rules/gcp.yml @@ -38,6 +38,9 @@ rules: type: GCP revocation: type: GCP + references: + - https://cloud.google.com/iam/docs/service-accounts + - https://cloud.google.com/iam/docs/creating-managing-service-account-keys - name: GCP Private Key ID id: kingfisher.gcp.3 pattern: | @@ -62,4 +65,6 @@ rules: min_entropy: 3.5 confidence: medium examples: - - gcp_secret = ANzaSy0c3475372a7b10f7740dbda47abfdca42 \ No newline at end of file + - gcp_secret = ANzaSy0c3475372a7b10f7740dbda47abfdca42 + references: + - https://cloud.google.com/iam/docs/keys-create-delete \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/generic.yml b/crates/kingfisher-rules/data/rules/generic.yml index 75d8fc5..b21c99e 100644 --- a/crates/kingfisher-rules/data/rules/generic.yml +++ b/crates/kingfisher-rules/data/rules/generic.yml @@ -209,4 +209,4 @@ rules: - some+thing:02PDFMQN2PL2ZAB9OX3IOHC1XMIW1SE5NWG3RETG58JUZJ310WFYESRA7F0LM461 - org+builder:1C2F9D0BB1E67E9F6B3B5B9A2A3D4E5F6A7B8C9D0E1F2A3B4C5D6E7F8A9B0C1 references: - - https://docs.quay.io/use_quay.html#robot-accounts \ No newline at end of file + - https://docs.quay.io/glossary/robot-accounts.html \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/github.yml b/crates/kingfisher-rules/data/rules/github.yml index d06a9bf..bc283ca 100644 --- a/crates/kingfisher-rules/data/rules/github.yml +++ b/crates/kingfisher-rules/data/rules/github.yml @@ -59,17 +59,17 @@ rules: min_lowercase: 2 checksum: actual: - template: "{{ MATCH | suffix: 6 }}" + template: "{{ checksum }}" requires_capture: checksum - expected: "{{ BODY | crc32 | base62: 6 }}" + expected: "{{ body | crc32 | base62: 6 }}" skip_if_missing: true min_entropy: 3.5 examples: - - "GITHUB_KEY=ghp_XIxB7KMNdAr3zqWtQqhE94qglHqOzn1D1stg" - - "let g:gh_token='ghp_4U3LSowpDx8XvYE7A8GH56oxU5aWnY2mzIbV'" + - "GITHUB_KEY=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" + - "let g:gh_token='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'" - | ## git developer settings - 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 + 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 references: - https://docs.github.com/en/rest/users?apiVersion=2022-11-28 validation: @@ -114,15 +114,15 @@ rules: min_digits: 2 checksum: actual: - template: "{{ MATCH | suffix: 6 }}" + template: "{{ checksum }}" requires_capture: checksum - expected: "{{ BODY | crc32 | base62: 6 }}" + expected: "{{ body | crc32 | base62: 6 }}" skip_if_missing: true 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' + - ' "url": "git+https://FelipeMestre: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@github.com/gontarz/PW_2021_Website-FelipeMestre.git"' + - ' oauth_token: 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' references: - https://docs.github.com/en/rest/users?apiVersion=2022-11-28 validation: diff --git a/crates/kingfisher-rules/data/rules/gitlab.yml b/crates/kingfisher-rules/data/rules/gitlab.yml index 894bed3..ef72c77 100644 --- a/crates/kingfisher-rules/data/rules/gitlab.yml +++ b/crates/kingfisher-rules/data/rules/gitlab.yml @@ -155,14 +155,14 @@ rules: # valid GitLab-style checksum. checksum: actual: - template: "{{ MATCH | suffix: 7 }}" + template: "{{ crc32 }}" requires_capture: crc32 - expected: "{{ \"glpat-\" | append: BASE64_PAYLOAD | append: \".01.\" | append: BASE36_PAYLOAD_LENGTH | crc32 | base36: 7 }}" + expected: "{{ \"glpat-\" | append: base64_payload | append: \".01.\" | append: base36_payload_length | crc32 | base36: 7 }}" skip_if_missing: true min_entropy: 3.5 confidence: medium examples: - - glpat-ymiBP0-I-J6ghspoBPoZxtSC3g7MyHYG0X0r.01.101erjmwl + - glpat-vSY1cyL948aAB440qB6L4zlu9OzsUmEV.01.0w0sfevch references: - https://github.com/diffblue/gitlab/blob/39c63ee83369bf5353256a6b95f3116728edd102/doc/api/personal_access_tokens.md - https://docs.gitlab.com/api/personal_access_tokens/ diff --git a/crates/kingfisher-rules/data/rules/gitter.yml b/crates/kingfisher-rules/data/rules/gitter.yml index 3da37a3..9f2a16f 100644 --- a/crates/kingfisher-rules/data/rules/gitter.yml +++ b/crates/kingfisher-rules/data/rules/gitter.yml @@ -19,7 +19,7 @@ rules: - GITTER_TOKEN=abcd1234efgh5678ijkl9012mnop3456qrst7890 - '"gitterToken": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0"' references: - - https://developer.gitter.im/docs/authentication + - https://gitlab.com/gitlab-org/gitter/docs validation: type: Http content: diff --git a/crates/kingfisher-rules/data/rules/gocardless.yml b/crates/kingfisher-rules/data/rules/gocardless.yml index 525b20b..8365ff8 100644 --- a/crates/kingfisher-rules/data/rules/gocardless.yml +++ b/crates/kingfisher-rules/data/rules/gocardless.yml @@ -22,7 +22,7 @@ rules: confidence: medium examples: - 'gocardless_token = "live_8uq9fsUA28SqKT=CTsQxgKrqB6_7QV5tA39I8y5H' - - GOCARDLESS_LIVE_KEY = "live_cpo0k9jbnb2djeaq=tga45ua_bnhev5ivv294a6cs" + - GOCARDLESS_LIVE_KEY = "live_80M81I_T_DG2T604LSO5HVGVMJS40-CVUOS2S69YNY" categories: - api - payment @@ -41,4 +41,6 @@ rules: - status: - 200 type: StatusMatch - url: https://api.gocardless.com/customers?limit=1 \ No newline at end of file + url: https://api.gocardless.com/customers?limit=1 + references: + - https://developer.gocardless.com/api-reference/#authentication \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/google.yml b/crates/kingfisher-rules/data/rules/google.yml index 51d5103..4f5d665 100644 --- a/crates/kingfisher-rules/data/rules/google.yml +++ b/crates/kingfisher-rules/data/rules/google.yml @@ -19,8 +19,6 @@ rules: (?:[^A-Z0-9_-] | $) pattern_requirements: min_digits: 2 - min_uppercase: 1 - min_lowercase: 1 min_entropy: 3.3 confidence: medium examples: @@ -36,13 +34,12 @@ rules: ) (?: [^a-z0-9_-] |$) pattern_requirements: - min_digits: 4 + min_digits: 1 min_uppercase: 3 min_lowercase: 3 min_entropy: 3.3 confidence: medium examples: - - '"client_secret":"aaaaaaaaaaaaaaaaaaaaaaa-"' - " //$google_client_secret = 'fnhqAakzWrX-mtFQ4PRdMoy0';" - " 'clientSecret' : 'Ufvuj-d6alhwGKvvLh_8Nq0K'" @@ -54,8 +51,6 @@ rules: (?: [^0-9A-Z_-]) pattern_requirements: min_digits: 2 - min_uppercase: 1 - min_lowercase: 1 min_entropy: 3.3 confidence: medium examples: @@ -91,20 +86,18 @@ rules: (?:[^A-Z0-9_-] | $) pattern_requirements: min_digits: 2 - min_uppercase: 1 - min_lowercase: 1 min_entropy: 3.3 confidence: medium examples: - | - const CLIENT_ID = '304167046824-45h8no7j0s38akv998nivvb7i17ckqeh.apps.googleusercontent.com'; - const CLIENT_SECRET = '1QcFpNjHoAf4_XczYwhYicTl'; + const CLIENT_ID = '204945162815-59422evifqzluuklf_09qff9pk6ehd4r.apps.googleusercontent.com'; + const CLIENT_SECRET = 'P5C9REQW_6NT5NMQO8FP75VO'; - | 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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', + client_id: '024565785402-92sn01z4gfwbv4zfu79ttqg2j7uphacz.apps.googleusercontent.com', + client_secret: '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', redirect_uri: `http://localhost:${Config.OAUTH_HTTP_PORT}/oauth2callback` }; - name: Google Gemini API Key diff --git a/crates/kingfisher-rules/data/rules/googleoauth2.yml b/crates/kingfisher-rules/data/rules/googleoauth2.yml index 9e91f31..9625ffe 100644 --- a/crates/kingfisher-rules/data/rules/googleoauth2.yml +++ b/crates/kingfisher-rules/data/rules/googleoauth2.yml @@ -29,4 +29,6 @@ rules: type: StatusMatch - type: WordMatch words: - - '"email":' \ No newline at end of file + - '"email":' + references: + - https://developers.google.com/identity/protocols/oauth2 \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/gradle.yml b/crates/kingfisher-rules/data/rules/gradle.yml index 42d27ea..bb35b41 100644 --- a/crates/kingfisher-rules/data/rules/gradle.yml +++ b/crates/kingfisher-rules/data/rules/gradle.yml @@ -30,4 +30,6 @@ rules: } } - "credentials {\n username 'user'\n password 'password'\n}" - - "credentials {\n username \"user\"\n password \"password\"\n}" \ No newline at end of file + - "credentials {\n username \"user\"\n password \"password\"\n}" + references: + - https://docs.gradle.org/current/userguide/declaring_repositories.html#sec:handling_credentials \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/groq.yml b/crates/kingfisher-rules/data/rules/groq.yml index 8160b2b..28154cf 100644 --- a/crates/kingfisher-rules/data/rules/groq.yml +++ b/crates/kingfisher-rules/data/rules/groq.yml @@ -29,7 +29,7 @@ rules: - '"data"' match_all_words: true references: - - https://console.groq.com/docs/api-keys + - https://console.groq.com/keys - https://console.groq.com/docs/api-reference#models examples: - "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" diff --git a/crates/kingfisher-rules/data/rules/huggingface.yml b/crates/kingfisher-rules/data/rules/huggingface.yml index c70b993..7f08914 100644 --- a/crates/kingfisher-rules/data/rules/huggingface.yml +++ b/crates/kingfisher-rules/data/rules/huggingface.yml @@ -10,8 +10,6 @@ rules: ) ) \b - pattern_requirements: - min_digits: 2 references: - https://huggingface.co/docs/hub/security-tokens min_entropy: 3.3 diff --git a/crates/kingfisher-rules/data/rules/imagekit.yml b/crates/kingfisher-rules/data/rules/imagekit.yml index 050168b..48613e4 100644 --- a/crates/kingfisher-rules/data/rules/imagekit.yml +++ b/crates/kingfisher-rules/data/rules/imagekit.yml @@ -13,8 +13,6 @@ rules: private_[A-Z0-9_-]{8,128} ) \b - pattern_requirements: - min_digits: 2 min_entropy: 3.2 confidence: medium examples: diff --git a/crates/kingfisher-rules/data/rules/infracost.yml b/crates/kingfisher-rules/data/rules/infracost.yml index 42dc8d6..5f046d3 100644 --- a/crates/kingfisher-rules/data/rules/infracost.yml +++ b/crates/kingfisher-rules/data/rules/infracost.yml @@ -13,10 +13,10 @@ rules: min_entropy: 3.3 confidence: medium examples: - - export INFRACOST_API_KEY=ico-abcdefabcdefabcdefabcdefabcdefab + - export INFRACOST_API_KEY=ico-abcd12abcdefabcdefabcdefabcdefab - '"infracost": "ico-1234567890abcdef1234567890abcdef"' references: - - https://www.infracost.io/docs/api_reference/ + - https://www.infracost.io/docs/integrations/infracost_api/ validation: type: Http content: diff --git a/crates/kingfisher-rules/data/rules/infura.yml b/crates/kingfisher-rules/data/rules/infura.yml index 0f999de..804d7fa 100644 --- a/crates/kingfisher-rules/data/rules/infura.yml +++ b/crates/kingfisher-rules/data/rules/infura.yml @@ -18,7 +18,7 @@ rules: - https://mainnet.infura.io/v3/7238211010344719ad14a89db874158c - infuraKEYwithspecial-abcdef1234567890abcdef1234567890 references: - - https://www.infura.io/docs + - https://docs.infura.io/ - https://docs.metamask.io/services/reference/ethereum/json-rpc-methods/ validation: type: Http diff --git a/crates/kingfisher-rules/data/rules/ionic.yml b/crates/kingfisher-rules/data/rules/ionic.yml index b37b83b..40a57a7 100644 --- a/crates/kingfisher-rules/data/rules/ionic.yml +++ b/crates/kingfisher-rules/data/rules/ionic.yml @@ -27,4 +27,6 @@ rules: - status: - 200 type: StatusMatch - url: https://api.ionic.io/v1/auth/status \ No newline at end of file + url: https://api.ionic.io/v1/auth/status + references: + - https://ionicframework.com/docs \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/jdbc.yml b/crates/kingfisher-rules/data/rules/jdbc.yml index 4899284..214cc23 100644 --- a/crates/kingfisher-rules/data/rules/jdbc.yml +++ b/crates/kingfisher-rules/data/rules/jdbc.yml @@ -29,4 +29,4 @@ rules: - jdbc:sqlserver://sql.example.org:1433;databaseName=inventory;user=sa;password=s3cr3t! references: - https://docs.oracle.com/javase/8/docs/api/java/sql/DriverManager.html - - https://www.postgresql.org/docs/current/jdbc-use.html + - https://jdbc.postgresql.org/documentation/use/ diff --git a/crates/kingfisher-rules/data/rules/jina.yml b/crates/kingfisher-rules/data/rules/jina.yml index 807f5a7..401cc08 100644 --- a/crates/kingfisher-rules/data/rules/jina.yml +++ b/crates/kingfisher-rules/data/rules/jina.yml @@ -32,4 +32,4 @@ rules: - '"_id":' match_all_words: true references: - - https://jina.ai/docs/jina-ai-cloud/api-reference/ \ No newline at end of file + - https://jina.ai/serve/jina-ai-cloud/ \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/jira.yml b/crates/kingfisher-rules/data/rules/jira.yml index 2be3a53..e5dee8c 100644 --- a/crates/kingfisher-rules/data/rules/jira.yml +++ b/crates/kingfisher-rules/data/rules/jira.yml @@ -8,14 +8,14 @@ rules: [a-z][a-z0-9-]{5,24}\.atlassian\.net ) \b - pattern_requirements: - min_digits: 2 min_entropy: 3.5 visible: false confidence: medium examples: - examplefoo-jira.atlassian.net - jira.sprintUri= https://example.atlassian.net/rest + references: + - https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/ - name: Jira Token id: kingfisher.jira.2 @@ -54,6 +54,8 @@ rules: - 200 type: StatusMatch url: https://{{ DOMAIN }}/rest/api/3/dashboard + references: + - https://developer.atlassian.com/cloud/jira/platform/basic-auth-for-rest-apis/ depends_on_rule: - rule_id: kingfisher.jira.1 variable: DOMAIN \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/kickbox.yml b/crates/kingfisher-rules/data/rules/kickbox.yml index 897d25d..a283496 100644 --- a/crates/kingfisher-rules/data/rules/kickbox.yml +++ b/crates/kingfisher-rules/data/rules/kickbox.yml @@ -32,3 +32,5 @@ rules: - type: WordMatch words: - '"success":true' + references: + - https://docs.kickbox.com/reference diff --git a/crates/kingfisher-rules/data/rules/klaviyo.yml b/crates/kingfisher-rules/data/rules/klaviyo.yml index c09cb21..6898c33 100644 --- a/crates/kingfisher-rules/data/rules/klaviyo.yml +++ b/crates/kingfisher-rules/data/rules/klaviyo.yml @@ -31,3 +31,5 @@ rules: status: [200] - type: WordMatch words: ['"data"'] + references: + - https://developers.klaviyo.com/en/docs/authenticate_ diff --git a/crates/kingfisher-rules/data/rules/langchain.yml b/crates/kingfisher-rules/data/rules/langchain.yml index 6f4da05..37e118b 100644 --- a/crates/kingfisher-rules/data/rules/langchain.yml +++ b/crates/kingfisher-rules/data/rules/langchain.yml @@ -27,6 +27,8 @@ rules: - type: StatusMatch status: [200] - type: JsonValid + references: + - https://docs.smith.langchain.com/administration/api-keys - name: LangSmith Service Key id: kingfisher.langchain.2 pattern: | @@ -53,4 +55,6 @@ rules: response_matcher: - report_response: true - type: StatusMatch - status: [200] \ No newline at end of file + status: [200] + references: + - https://docs.smith.langchain.com/administration/api-keys \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/launchdarkly.yml b/crates/kingfisher-rules/data/rules/launchdarkly.yml index 26a27d3..56bb774 100644 --- a/crates/kingfisher-rules/data/rules/launchdarkly.yml +++ b/crates/kingfisher-rules/data/rules/launchdarkly.yml @@ -17,7 +17,7 @@ rules: - LAUNCHDARKLY_TOKEN=api-123abc456def789ghi012jkl345mno678pqr - '"launchdarkly": "ld-abcdefghijklmno1234567890pqrstuvwxzab"' references: - - https://docs.launchdarkly.com/sdk/api/ + - https://launchdarkly.com/docs/api validation: type: Http content: diff --git a/crates/kingfisher-rules/data/rules/linkedin.yml b/crates/kingfisher-rules/data/rules/linkedin.yml index cd293b7..f6b4384 100644 --- a/crates/kingfisher-rules/data/rules/linkedin.yml +++ b/crates/kingfisher-rules/data/rules/linkedin.yml @@ -17,7 +17,6 @@ rules: min_entropy: 2.5 confidence: medium examples: - - 'Email ID Last 5 Digits of your SSN LinkedIn ID Availability' - | LINKEDIN_KEY = "77yg7tx91p4lag" LINKEDIN_SECRET = "zt7GeN6IH911xvRj" diff --git a/crates/kingfisher-rules/data/rules/lob.yml b/crates/kingfisher-rules/data/rules/lob.yml index deb8800..83a8a90 100644 --- a/crates/kingfisher-rules/data/rules/lob.yml +++ b/crates/kingfisher-rules/data/rules/lob.yml @@ -16,7 +16,7 @@ rules: confidence: medium examples: - export LOB_API_KEY=live_9f8e7d6c5b4a3210fedcba09876543210ab - - LOB_KEY="test_abcdefabcdefabcdefabcdefabcdefabcde" + - LOB_KEY="test_abcdefabcdefab12efabcdefabcdefabcde" references: - https://docs.lob.com/#section/Authentication validation: @@ -49,7 +49,7 @@ rules: min_entropy: 3.0 confidence: medium examples: - - const LOB_PUB_KEY = "test_pub_abcdefabcdefabcdefabcdefabcdefa"; + - const LOB_PUB_KEY = "test_pub_abcdefa12defabcdefabcdefabcdefa"; - LOB_PUBLISHABLE="live_pub_1234567890abcdef1234567890abcde" references: - https://docs.lob.com/#section/Authentication diff --git a/crates/kingfisher-rules/data/rules/mailgun.yml b/crates/kingfisher-rules/data/rules/mailgun.yml index 5181f3b..2ff2faf 100644 --- a/crates/kingfisher-rules/data/rules/mailgun.yml +++ b/crates/kingfisher-rules/data/rules/mailgun.yml @@ -14,7 +14,6 @@ rules: ) pattern_requirements: min_digits: 2 - min_uppercase: 1 min_lowercase: 1 min_entropy: 3.5 confidence: medium @@ -34,6 +33,8 @@ rules: - 200 type: StatusMatch url: https://api.mailgun.net/v3/address/validate?address=test@example.com + references: + - https://documentation.mailgun.com/docs/mailgun/api-reference/mg-auth - name: MailGun Primary Key id: kingfisher.mailgun.2 pattern: | @@ -64,4 +65,6 @@ rules: - status: - 200 type: StatusMatch - url: https://api.mailgun.net/v3/address/validate?address=test@example.com \ No newline at end of file + url: https://api.mailgun.net/v3/address/validate?address=test@example.com + references: + - https://documentation.mailgun.com/docs/mailgun/api-reference/mg-auth \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/mandrill.yml b/crates/kingfisher-rules/data/rules/mandrill.yml index 857fbeb..f6ed68b 100644 --- a/crates/kingfisher-rules/data/rules/mandrill.yml +++ b/crates/kingfisher-rules/data/rules/mandrill.yml @@ -38,4 +38,6 @@ rules: - type: StatusMatch status: [200] - type: WordMatch - words: ['"PONG!"'] \ No newline at end of file + words: ['"PONG!"'] + references: + - https://mandrillapp.com/api/docs/ \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/messagebird.yml b/crates/kingfisher-rules/data/rules/messagebird.yml index 4b89531..aa3cfc5 100644 --- a/crates/kingfisher-rules/data/rules/messagebird.yml +++ b/crates/kingfisher-rules/data/rules/messagebird.yml @@ -16,7 +16,7 @@ rules: min_entropy: 3.4 confidence: medium examples: - - MESSAGEBIRD_API_KEY=abcdefghijklmnopqrstuvwxy + - 'MESSAGEBIRD_API_KEY=abcdefghijklmno12rstuvwxy' - "messagebird_token: 'abcde12345fghij67890klmno'" references: - https://developers.messagebird.com/api/#authentication diff --git a/crates/kingfisher-rules/data/rules/microsoft_teams.yml b/crates/kingfisher-rules/data/rules/microsoft_teams.yml index 98c9068..e8d9da5 100644 --- a/crates/kingfisher-rules/data/rules/microsoft_teams.yml +++ b/crates/kingfisher-rules/data/rules/microsoft_teams.yml @@ -51,4 +51,6 @@ rules: - 400 - type: WordMatch words: - - 'Text is required' \ No newline at end of file + - 'Text is required' + references: + - https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/microsoftteamswebhook.yml b/crates/kingfisher-rules/data/rules/microsoftteamswebhook.yml index 952f1b9..eca5c05 100644 --- a/crates/kingfisher-rules/data/rules/microsoftteamswebhook.yml +++ b/crates/kingfisher-rules/data/rules/microsoftteamswebhook.yml @@ -40,4 +40,6 @@ rules: type: WordMatch words: - "Text is required" - url: '{{ TOKEN }}' \ No newline at end of file + url: '{{ TOKEN }}' + references: + - https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/mistral.yml b/crates/kingfisher-rules/data/rules/mistral.yml index 3e451da..d515d73 100644 --- a/crates/kingfisher-rules/data/rules/mistral.yml +++ b/crates/kingfisher-rules/data/rules/mistral.yml @@ -25,7 +25,7 @@ rules: - https://docs.mistral.ai/getting-started/quickstart :contentReference[oaicite:1]{index=1} - https://docs.mistral.ai/api/ :contentReference[oaicite:2]{index=2} - https://medium.com/@stephane.giron/explore-mistral-ai-api-with-google-apps-script-d41b851c55e3 :contentReference[oaicite:3]{index=3} - - https://apidog.com/blog/mistral-ai-api/ :contentReference[oaicite:4]{index=4} + - https://docs.mistral.ai/api/ :contentReference[oaicite:4]{index=4} validation: type: Http content: diff --git a/crates/kingfisher-rules/data/rules/modal.yml b/crates/kingfisher-rules/data/rules/modal.yml new file mode 100644 index 0000000..695ed78 --- /dev/null +++ b/crates/kingfisher-rules/data/rules/modal.yml @@ -0,0 +1,68 @@ +rules: + - name: Modal CLI Token Pair + id: kingfisher.modal.1 + pattern: | + (?x) + (?P + (?:ak|as)-[A-Za-z0-9]{22} + ) + \b + (?:.|[\n\r]){0,80}? + \b + ( + (?:ak|as)-[A-Za-z0-9]{22} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 2.8 + confidence: high + examples: + - "modal token set --token-id ak-BJbwFRtNnI4Y11oxC4hngY --token-secret as-sRul9S1EAi9qNlq3G6NTIb" + references: + - https://modal.com/docs/reference/cli/token + - https://modal.com/docs/reference/modal.Client + - https://modal.com/docs/reference/modal.App + validation: + type: Grpc + content: + request: + # Use the same handshake call as the Modal SDK (`client.hello()`). + url: https://api.modal.com/modal.client.ModalClient/ClientHello + headers: + content-type: application/grpc + te: trailers + x-modal-token-id: "{{ TOKEN_ID }}" + x-modal-token-secret: "{{ TOKEN }}" + x-modal-client-type: "1" + # Modal uses this for compatibility checks; "0" is rejected as deprecated. + x-modal-client-version: "1.0.0" + x-modal-python-version: "3.11.0" + x-modal-platform: kingfisher + x-modal-node: kingfisher + body: "\u0000\u0000\u0000\u0000\u0000" + response_matcher: + - report_response: true + - type: HeaderMatch + header: grpc-status + expected: ["0"] + + - name: Modal Token Secret + id: kingfisher.modal.2 + pattern: | + (?x) + \b + ( + as-[A-Za-z0-9]{22} + ) + \b + pattern_requirements: + min_digits: 2 + min_entropy: 3.0 + confidence: medium + examples: + - "as-aB1cD2eF3gH4iJ5kL6mN7P" + references: + - https://modal.com/docs/reference/cli/token + - https://modal.com/docs/reference/modal.Client + - https://modal.com/docs/reference/modal.App \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/monday.yml b/crates/kingfisher-rules/data/rules/monday.yml index 8459472..7051eb1 100644 --- a/crates/kingfisher-rules/data/rules/monday.yml +++ b/crates/kingfisher-rules/data/rules/monday.yml @@ -34,4 +34,6 @@ rules: status: [200] - type: WordMatch words: ["data", "me", "id"] - match_all_words: true \ No newline at end of file + match_all_words: true + references: + - https://developer.monday.com/api-reference/docs/authentication \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/mongodb.yml b/crates/kingfisher-rules/data/rules/mongodb.yml index fb10f65..624cfe0 100644 --- a/crates/kingfisher-rules/data/rules/mongodb.yml +++ b/crates/kingfisher-rules/data/rules/mongodb.yml @@ -26,7 +26,6 @@ rules: \b pattern_requirements: min_digits: 2 - min_uppercase: 1 min_lowercase: 1 min_entropy: 3.7 examples: @@ -48,6 +47,8 @@ rules: - '"orgId":' - '"id":' url: https://cloud.mongodb.com/api/atlas/v2/groups + references: + - https://www.mongodb.com/docs/atlas/api/ depends_on_rule: - rule_id: "kingfisher.mongodb.2" variable: PUBKEY @@ -108,6 +109,8 @@ rules: visible: false examples: - 'mongodb-public: qj4Zrh8e6A' + references: + - https://www.mongodb.com/docs/atlas/api/ - name: MongoDB URI Connection String id: kingfisher.mongodb.3 pattern: | @@ -130,6 +133,8 @@ rules: validation: type: MongoDB tls_mode: lax + references: + - https://www.mongodb.com/docs/manual/reference/connection-string/ - name: MongoDB Atlas Service Account Token id: kingfisher.mongodb.4 pattern: | @@ -143,4 +148,6 @@ rules: - mdb_sa_sk_BdIX_jLzut2WTgglKzKvSgWMDDj5hEoTqdwOyLOL validation: type: MongoDB - tls_mode: lax \ No newline at end of file + tls_mode: lax + references: + - https://www.mongodb.com/docs/atlas/api/service-accounts-overview/ \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/mysql.yml b/crates/kingfisher-rules/data/rules/mysql.yml index 8f1c2c3..ecb9c62 100644 --- a/crates/kingfisher-rules/data/rules/mysql.yml +++ b/crates/kingfisher-rules/data/rules/mysql.yml @@ -45,3 +45,5 @@ rules: validation: type: MySQL tls_mode: lax + references: + - https://dev.mysql.com/doc/refman/8.0/en/connecting.html diff --git a/crates/kingfisher-rules/data/rules/netlify.yml b/crates/kingfisher-rules/data/rules/netlify.yml index 7fae1eb..828d03c 100644 --- a/crates/kingfisher-rules/data/rules/netlify.yml +++ b/crates/kingfisher-rules/data/rules/netlify.yml @@ -30,6 +30,8 @@ rules: - report_response: true - type: StatusMatch status: [200] + references: + - https://docs.netlify.com/api/get-started/#authentication - name: Netlify API Key id: kingfisher.netlify.2 @@ -64,3 +66,5 @@ rules: - report_response: true - type: StatusMatch status: [200] + references: + - https://docs.netlify.com/api/get-started/#authentication diff --git a/crates/kingfisher-rules/data/rules/newrelic.yml b/crates/kingfisher-rules/data/rules/newrelic.yml index c208aa0..e4953c2 100644 --- a/crates/kingfisher-rules/data/rules/newrelic.yml +++ b/crates/kingfisher-rules/data/rules/newrelic.yml @@ -34,3 +34,5 @@ rules: - report_response: true - type: StatusMatch status: [200] + references: + - https://docs.newrelic.com/docs/apis/intro-apis/new-relic-api-keys/ diff --git a/crates/kingfisher-rules/data/rules/ngrok.yml b/crates/kingfisher-rules/data/rules/ngrok.yml index 711648c..3f4053a 100644 --- a/crates/kingfisher-rules/data/rules/ngrok.yml +++ b/crates/kingfisher-rules/data/rules/ngrok.yml @@ -33,3 +33,5 @@ rules: - type: WordMatch words: - '"endpoints":' + references: + - https://ngrok.com/docs/api#authentication diff --git a/crates/kingfisher-rules/data/rules/npm.yml b/crates/kingfisher-rules/data/rules/npm.yml index 0d0a6da..9c57abb 100644 --- a/crates/kingfisher-rules/data/rules/npm.yml +++ b/crates/kingfisher-rules/data/rules/npm.yml @@ -12,9 +12,9 @@ rules: min_digits: 2 checksum: actual: - template: "{{ MATCH | suffix: 6 }}" + template: "{{ checksum }}" requires_capture: checksum - expected: "{{ BODY | crc32 | base62: 6 }}" + expected: "{{ body | crc32 | base62: 6 }}" skip_if_missing: true references: - https://docs.npmjs.com/about-access-tokens @@ -23,7 +23,7 @@ rules: min_entropy: 3.3 confidence: medium examples: - - "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" + - "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" validation: type: Http content: diff --git a/crates/kingfisher-rules/data/rules/nuget.yml b/crates/kingfisher-rules/data/rules/nuget.yml index 3ba909e..b358e68 100644 --- a/crates/kingfisher-rules/data/rules/nuget.yml +++ b/crates/kingfisher-rules/data/rules/nuget.yml @@ -31,6 +31,8 @@ rules: status: [200] - type: WordMatch words: ['"Key":'] + references: + - https://learn.microsoft.com/en-us/nuget/api/overview#authentication - name: NuGet API Key @@ -65,4 +67,6 @@ rules: - type: StatusMatch status: [200] - type: WordMatch - words: ['"Key":'] \ No newline at end of file + words: ['"Key":'] + references: + - https://learn.microsoft.com/en-us/nuget/api/overview#authentication \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/nvidia.yml b/crates/kingfisher-rules/data/rules/nvidia.yml index 1329314..47b7bb2 100644 --- a/crates/kingfisher-rules/data/rules/nvidia.yml +++ b/crates/kingfisher-rules/data/rules/nvidia.yml @@ -29,3 +29,5 @@ rules: status: [200] - type: WordMatch words: ["id", "versionId"] + references: + - https://docs.nvidia.com/cloud-functions/index.html diff --git a/crates/kingfisher-rules/data/rules/okta.yml b/crates/kingfisher-rules/data/rules/okta.yml index 16511f4..243eda0 100644 --- a/crates/kingfisher-rules/data/rules/okta.yml +++ b/crates/kingfisher-rules/data/rules/okta.yml @@ -16,8 +16,6 @@ rules: 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' - | @@ -40,6 +38,8 @@ rules: words: - activated url: https://{{ DOMAIN }}/api/v1/users/me + references: + - https://developer.okta.com/docs/reference/core-okta-api/#authentication depends_on_rule: - rule_id: "kingfisher.okta.2" variable: DOMAIN @@ -54,4 +54,6 @@ rules: min_entropy: 3 visible: false examples: - - company-name.okta.com \ No newline at end of file + - company-name.okta.com + references: + - https://developer.okta.com/docs/concepts/okta-organizations/ \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/ollama.yml b/crates/kingfisher-rules/data/rules/ollama.yml index ba686c6..55887d4 100644 --- a/crates/kingfisher-rules/data/rules/ollama.yml +++ b/crates/kingfisher-rules/data/rules/ollama.yml @@ -40,7 +40,7 @@ rules: - '"response":' - '"done":true' references: - - https://ollama.com/blog/turbo + - https://ollama.com/blog examples: - "ollama key = 8bcdd9b4e28e4e1b8bf14a2eb8701220.QH5p5TU2BDwzHu5_RCtvJXsj" - "ollama key = e56714bd7c1146e4b4801244bc2bc67a.3GAswjZGZ5YY6Qdgt0xg56vM" diff --git a/crates/kingfisher-rules/data/rules/onepassword.yml b/crates/kingfisher-rules/data/rules/onepassword.yml index e7f6183..ebc4f87 100644 --- a/crates/kingfisher-rules/data/rules/onepassword.yml +++ b/crates/kingfisher-rules/data/rules/onepassword.yml @@ -52,4 +52,4 @@ rules: - A3-ASWWYB-798JRY-LJVD4-23DC2-86TVM-H43EB references: - https://support.1password.com/secret-key-security/ - - https://developer.1password.com/files/1password-white-paper.pdf + - https://1passwordstatic.com/files/security/1password-white-paper.pdf diff --git a/crates/kingfisher-rules/data/rules/owlbot.yml b/crates/kingfisher-rules/data/rules/owlbot.yml index 1f1d4a0..6fafe10 100644 --- a/crates/kingfisher-rules/data/rules/owlbot.yml +++ b/crates/kingfisher-rules/data/rules/owlbot.yml @@ -21,7 +21,7 @@ rules: - "owlbot SECRET b7d21c0e88e9a3c5938fb045b2b6a5e693eaf9d1" - "owlbot TOKEN 8a5de3a89b7e4f29bf728b45adcdea6ea3410c78" references: - - https://owlbot.info/ + - https://documentation.owlbot.ai/ validation: type: Http content: diff --git a/crates/kingfisher-rules/data/rules/pastebin.yml b/crates/kingfisher-rules/data/rules/pastebin.yml index f19536c..4a82cbc 100644 --- a/crates/kingfisher-rules/data/rules/pastebin.yml +++ b/crates/kingfisher-rules/data/rules/pastebin.yml @@ -36,4 +36,6 @@ rules: status: [200] - type: WordMatch words: ['invalid api_dev_key'] - negative: true \ No newline at end of file + negative: true + references: + - https://pastebin.com/doc_api \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/paypal.yml b/crates/kingfisher-rules/data/rules/paypal.yml index 47fddb1..f3366a4 100644 --- a/crates/kingfisher-rules/data/rules/paypal.yml +++ b/crates/kingfisher-rules/data/rules/paypal.yml @@ -17,6 +17,8 @@ rules: visible: false examples: - paypal_client_id=AZJ6y8Dpr1TYbqAIdhkPzyhjXoY6mIdhkPzyhjXoY6m8GplL7C3zZ3lPrkTIdhkPzyhjXo_Dx3IdhkPzyhjXoY6m + references: + - https://developer.paypal.com/api/rest/authentication/ - name: PayPal OAuth Secret id: kingfisher.paypal.2 @@ -57,3 +59,5 @@ rules: depends_on_rule: - rule_id: kingfisher.paypal.1 variable: CLIENTID + references: + - https://developer.paypal.com/api/rest/authentication/ diff --git a/crates/kingfisher-rules/data/rules/pem.yml b/crates/kingfisher-rules/data/rules/pem.yml index 0c0d921..1e99aec 100644 --- a/crates/kingfisher-rules/data/rules/pem.yml +++ b/crates/kingfisher-rules/data/rules/pem.yml @@ -49,6 +49,8 @@ rules: -----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-----" + references: + - https://www.rfc-editor.org/rfc/rfc7468 - name: Base64-PEM-Encoded Private Key id: kingfisher.pem.2 pattern: | @@ -68,4 +70,6 @@ rules: confidence: high examples: - 'PRIVATE_KEY_B64=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBb3kxWFh1VkFRcHFIYlFFMDVta2hyTmcvMTI0Ri8ySzlPYW5pelpUWlVVaEswOFU4CkxhaC9SbVVsWHFRMDEvU255aktGOWZqUDhFcU1OZ1dpamUzYmVwL3RPOVpTMEFUMi9PVlJXeS9TOG52RDQ5WTMKenMxMktSbERhR2lZc0RsYUZrbHJkeDQ4RWhRVmdHN3hmWE1jaC9OejJzc2FEby9kRkNBOW80TkZZQWUzM2UveApWNVo1UHNkWkl6dkNZQVlCNDRoUEtpN3JXRE1IbFdzM1kvVkVtQXMzSzVNK2QvL3QzRHB4WnBEbWJERGdYa2w2CjZUdDh3VXloUVZ3MkZpMStobTF1T2QwYjFkaW9aNko2OXNTT2JOZXpSR3YxYjdZaFltT0JKL1JBbHN5ZHoxTmgKVXpXT1lYV0Z1OGJrOU9JM3lQMEc0TE84QjhtbWRldE1RVVoyelFJREFRQUJBb0lCQUN2ckhUUHVVZ0JiSlE0QwpvQ0ZQdEgrWDZIN3NIdk1ndVR0VzdUTlYxN1BYMkVQdE53ZzI3S0tld0pNYmNSbWF3THBjSk5BU09xMDY4MGZxCjlsaHE1NEsybnB4WFVBeXErV3NSc1hid2hUODhibm5aQTBaRzZJR2hTaEpFN0t1cGxBU2htQ29FV2ppbmJTNFgKTGlvTW5HWSs4VFMzSzNrMTRWUDBaWUtuNXprMERHZnFBMEo0VTRXSmxUeGwrTWZxd0pJOTlrcTdHbFVlZkdncQpuK3Q1d2NrV3BPbTd5TUJjZTlTSXlmTm54bnU3TkZYQm50VTN5RGxSUThWUWZmNEtRMzJCaWNiYlJWemR1TThNCnNxMU5CZWNzL0EzUXRvdG1nWUc4d094ZXpNS3Iyays2QzB2NmlFc0h5T0lmR25GWktSZDJFd0dnWlo3aytURHUKUUYrcjd1VUNnWUVBMkRqNUJoYmpybDFRNTZya3BhTGFvVldRV1Y5YUYzUUJtNlNZM2VQYmlvY2JNR2k1ak1ESQpkSjdJVXlLYUljK3BNV1RQYlBmVUd2WmNENlczZDFBNUNUSnFuWHVuVlY3czRqaWJ6WDZUbjhNM3IrMHZTZnNZCmdPMHBtRFpndlNqaVZTRUNBQTZFOFUxQ1lFZU5KUDFDOW12cGJVNzJRTEpndWp3M3JMb2oyYmNDZ1lFQXdUSXYKOUNSeWNOQXRBbDcvUHdWZGh5eXRvVHBSRnZDSU1HSVk5SjMxZ3lva0ZlaFQvWjQ4WkF6anl6ZTBSUXYzdGUxTQoveVJMQkVETGkwbEtrZFVXckVkaVR3dm1KdkpwMDZ0OEdCbERsK25ycXVLWTFxVThDbTR5cis4QzZtRThkVnZrClNINXBhRXptOERFTE1wSjhGVTZFYnhmZHZjRzZmSGx6dnVnZmc1c0NnWUFFQ1BRa3QvS2h3MTRLSkxkRm5BZG0KY1ZsVFFhTkZ3c1Z3NlI1dExaNWdOR3MrZVFYVmFaZVVEWTZCZHFqWHJxOWltNVgvVzVTYXVEUTVtb2NVOCt0TQpqNk5Mc3c0SldzOGkzWm1TdVNUNkcwT0R4ZkpXK0JlWitGTUpZeUpsQlVsTCsyUzFLWkF6akpTTGhXcE40V2dKCmZ6UUk5U3RGUTg3b1NzMWpMTW9VZXdLQmdGOE9CMlFURHErTTdhaE4vejROc0wvU2JyZDJEdkcvZFBLQlFaQVIKcS90V0g1MGJ5ejlzdkgvcGk2YXdDS1UwUnpPZXh4UjkwZDhNMWxqNHZaVFZDQ3ZKajRnZTdhVlovbEdqL1JHSwpWS1NJOW1nRXgzaE1vaWJybzByR3lXTnlaaUhFRGFUUmRhRll2UU9PemRpYkZDd1RqcnR1UGE2Z2c5VzhtQU5sCkNDUmpBb0dBSTRIbnpyV3kzaU5kR2xqVnh4bW1DN1V0c0MvajJBUEZpcHc0ZHJ0U2NsMDFRZzF5WkowbDNBTk4KOU5lTmVSUUFzN3pFTng2T1B1SzlxYy83T1ROMTJKaHdoUTIzdXZwNjZjV0krdTRjcVpOZTJyZVFVVWVmM3psbQpMcXRmOU50VHp5M3pjMGZQcGoxQnBlRmxHSG9SVDhjVHpBWjFTeGwyZWChazlqS2RVeDQ9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t' - - ' "privateKey": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBbUhKOEJHdTFYZUZ4aENVQXBrNHNSTVI4RnRTdGtyMEx0OWtWTGNSUjRFWitiOWhHCmR0blJpOFhqV3d5MU5zMHliMkJMdHBpVHZKSFVKTUphWXluZ2ZkZnZhcWhocm1yYm5vV0pLQkxmeUxwTXFNS1EKQ3RialFxbnVrQURJUWVQd2ZGeTNpVHkxd1JkRC9zTUs1U0VtV0Fxb0pZQk50eTFZZzA2UzVkYVlPM2xjY3hrYQpQWjRjcm9McWF6Ny9tU3dDVTR5VWRSb3h4WVF4VG1MZXg5M2tqU09TTmdpK0FXc0lCbjV3UHI0VHNuVHFSeWpIClN2aEdMdk9YREpRYWZRdk56WjFSL1FYMzlOQk9xOEVKZW5pWXdaUm9uNVcvNVhMYW94MFFyUGhrY1BES3A5SVUKeHpJakUwWlNmMStUK1FFbTQ3TkFtSnhvZjFhdGRFVzZDTCtheHdJREFRQUJBb0lCQUQ3enI4REhsWnFSK1NWZgpmbGd1bWRzLzVCb3Rjd3ZRWXlGbFZIaVV4RmEvNVlCY0tDVDJKN0QzWTc1NmplNTJaK2hVTkkvUGk5cG53ZG40CkpBa2xCdDRRcUg0NzBES05UK216TFFOT1gvanM3YkVXdnhLcTBDZjhNbFptN0V0QlRGS2VtdS9pRVJBT2duYVcKcGs0ZUZVNXdBQ1dVU1FObWgxR1p4ZEdCZjFXM1VjUnQxcFRvOEtQTDluZm4vSGJiRFNsQkNVL3VIcWd2TSt2cApmTE03bzRIVDZ1K1ZzU00rWGZqeDhpeE5ZRHdoalNuKzQyZm13d1d3ZzJISHUrdUozZ1pUSWQwRUI1VW9hdUNjCjZUTlVtcEJscjU5UGFmVkZRWUY1S3VxaHJXKzVQaWpHcHBZcXg4Ynl6aFpOQzkwZnl5V0NXcXg2eGFZVm5OdzgKNkJmUXM2a0NnWUVBeVlyRVg1NU1RTzJnWDY2TGwxaGJDMzNzWk1OZzloVG1SK1doSTFjNksvbFZ1TFoyL0RPdwpsYTZ6eHdBU204Z0ZyVUFYbUljV2h2b3FwWGVzNWZzOVZKeDlNT0ZVYVBrckRPQllnY1laMUR6VVNVOHc3SSttCnlyV3hRUkRNajhvSGpRbHVpM0s2MzZucm5RajhxOGkvQ2dranVPcHJGZnliMzVEMFlDdjVXZzBDZ1lFQXdhT3cKRWFhN0l1MjFGa08vbmFjdVhjSnBhNkVlUTNqZFNlNlRQaXZ6bVVXU0haeGJuUy9XSnJaRjQwSExzUWxOZHl0ZgpNTTBKZFU0VmMyR0NVc1pMYjdQSmJwdVRqRERSSHJXV1pCMnhiemF0K3A3N2RzNWlOcXFRcTZ6M0syUVh4Y3ZTCis5am5VZXpDU2Y0N1R1OWNTTW96V3hTMW82b1BPSFdHVFRvdHR5TUNnWUFQdWc1Y3o4TnZoWnR3Ry9TMG1LWnkKSFI5bk5YL0pkQlFNSkRVUXh1dTVKcm16c2psU3NNM2t3RDh6RmlSZGw1d3B5c2lNbEc0RGxsM2hqNWNrVXhpVQpFNm9KT0d3WHpPbTVGWUNTajl6UUhQY0x5V3d0NlgvQWJiRXBQS0JaMEJBS3gyT2k2ZzcvQ1FsanRhSFIzZFphCmVDQWJlOTlqVmRUcit5bTJuM2ZUdVFLQmdBMm5TZ25rbEx0Z3dXMEJkK2hZMm1jWUJ6RGttbXF0Z2dUdGdvcFcKdFFWd3AxM1pJWWlTeituSTNtS295QUVDbytpc01Ua1NyQUVPY1dyQ1RGc2p5anZsRkdYdEtGa3hNLzJUVmpoVwo4NlRnMlNHYnhpVlpaZ2x1dTJhdmVub2Z3NkZadnRXdE5KcE5OR0hkUURkUG4xVXVsTEp1WW1SWTRGdmR4WXQ2CmQ3QzdBb0dBRUsvalFiZ0l3OXFLQUNOZ0JySnB1cU5Ham9JajFoQTRlb29DMXp1bFEyZUpnZ2J5OTBpSDg2VzEKM0xyOVZMVFkyc2JKTzlqekZVR0lOL01BOEhYQTE1a2grZHRibkRsdFRFZGNnenBCRzhCQUZRQ3hQWnBGWHhtZgpDUmhXN1l6RW1IeWJ4R0toR3NOK2M3NUhKTHZFSWwrRTh6eitXRk9xT240dkJXU1ZwSnc9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==",' \ No newline at end of file + - ' "privateKey": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBbUhKOEJHdTFYZUZ4aENVQXBrNHNSTVI4RnRTdGtyMEx0OWtWTGNSUjRFWitiOWhHCmR0blJpOFhqV3d5MU5zMHliMkJMdHBpVHZKSFVKTUphWXluZ2ZkZnZhcWhocm1yYm5vV0pLQkxmeUxwTXFNS1EKQ3RialFxbnVrQURJUWVQd2ZGeTNpVHkxd1JkRC9zTUs1U0VtV0Fxb0pZQk50eTFZZzA2UzVkYVlPM2xjY3hrYQpQWjRjcm9McWF6Ny9tU3dDVTR5VWRSb3h4WVF4VG1MZXg5M2tqU09TTmdpK0FXc0lCbjV3UHI0VHNuVHFSeWpIClN2aEdMdk9YREpRYWZRdk56WjFSL1FYMzlOQk9xOEVKZW5pWXdaUm9uNVcvNVhMYW94MFFyUGhrY1BES3A5SVUKeHpJakUwWlNmMStUK1FFbTQ3TkFtSnhvZjFhdGRFVzZDTCtheHdJREFRQUJBb0lCQUQ3enI4REhsWnFSK1NWZgpmbGd1bWRzLzVCb3Rjd3ZRWXlGbFZIaVV4RmEvNVlCY0tDVDJKN0QzWTc1NmplNTJaK2hVTkkvUGk5cG53ZG40CkpBa2xCdDRRcUg0NzBES05UK216TFFOT1gvanM3YkVXdnhLcTBDZjhNbFptN0V0QlRGS2VtdS9pRVJBT2duYVcKcGs0ZUZVNXdBQ1dVU1FObWgxR1p4ZEdCZjFXM1VjUnQxcFRvOEtQTDluZm4vSGJiRFNsQkNVL3VIcWd2TSt2cApmTE03bzRIVDZ1K1ZzU00rWGZqeDhpeE5ZRHdoalNuKzQyZm13d1d3ZzJISHUrdUozZ1pUSWQwRUI1VW9hdUNjCjZUTlVtcEJscjU5UGFmVkZRWUY1S3VxaHJXKzVQaWpHcHBZcXg4Ynl6aFpOQzkwZnl5V0NXcXg2eGFZVm5OdzgKNkJmUXM2a0NnWUVBeVlyRVg1NU1RTzJnWDY2TGwxaGJDMzNzWk1OZzloVG1SK1doSTFjNksvbFZ1TFoyL0RPdwpsYTZ6eHdBU204Z0ZyVUFYbUljV2h2b3FwWGVzNWZzOVZKeDlNT0ZVYVBrckRPQllnY1laMUR6VVNVOHc3SSttCnlyV3hRUkRNajhvSGpRbHVpM0s2MzZucm5RajhxOGkvQ2dranVPcHJGZnliMzVEMFlDdjVXZzBDZ1lFQXdhT3cKRWFhN0l1MjFGa08vbmFjdVhjSnBhNkVlUTNqZFNlNlRQaXZ6bVVXU0haeGJuUy9XSnJaRjQwSExzUWxOZHl0ZgpNTTBKZFU0VmMyR0NVc1pMYjdQSmJwdVRqRERSSHJXV1pCMnhiemF0K3A3N2RzNWlOcXFRcTZ6M0syUVh4Y3ZTCis5am5VZXpDU2Y0N1R1OWNTTW96V3hTMW82b1BPSFdHVFRvdHR5TUNnWUFQdWc1Y3o4TnZoWnR3Ry9TMG1LWnkKSFI5bk5YL0pkQlFNSkRVUXh1dTVKcm16c2psU3NNM2t3RDh6RmlSZGw1d3B5c2lNbEc0RGxsM2hqNWNrVXhpVQpFNm9KT0d3WHpPbTVGWUNTajl6UUhQY0x5V3d0NlgvQWJiRXBQS0JaMEJBS3gyT2k2ZzcvQ1FsanRhSFIzZFphCmVDQWJlOTlqVmRUcit5bTJuM2ZUdVFLQmdBMm5TZ25rbEx0Z3dXMEJkK2hZMm1jWUJ6RGttbXF0Z2dUdGdvcFcKdFFWd3AxM1pJWWlTeituSTNtS295QUVDbytpc01Ua1NyQUVPY1dyQ1RGc2p5anZsRkdYdEtGa3hNLzJUVmpoVwo4NlRnMlNHYnhpVlpaZ2x1dTJhdmVub2Z3NkZadnRXdE5KcE5OR0hkUURkUG4xVXVsTEp1WW1SWTRGdmR4WXQ2CmQ3QzdBb0dBRUsvalFiZ0l3OXFLQUNOZ0JySnB1cU5Ham9JajFoQTRlb29DMXp1bFEyZUpnZ2J5OTBpSDg2VzEKM0xyOVZMVFkyc2JKTzlqekZVR0lOL01BOEhYQTE1a2grZHRibkRsdFRFZGNnenBCRzhCQUZRQ3hQWnBGWHhtZgpDUmhXN1l6RW1IeWJ4R0toR3NOK2M3NUhKTHZFSWwrRTh6eitXRk9xT240dkJXU1ZwSnc9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==",' + references: + - https://www.rfc-editor.org/rfc/rfc7468 \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/planetscale.yml b/crates/kingfisher-rules/data/rules/planetscale.yml index d37aa89..8130fa6 100644 --- a/crates/kingfisher-rules/data/rules/planetscale.yml +++ b/crates/kingfisher-rules/data/rules/planetscale.yml @@ -12,7 +12,7 @@ rules: min_digits: 2 min_entropy: 4 examples: - - pscale_tkn_abcdefghijklmnopqrstuvwxyZ1234567890_ABCDEF + - pscale_tkn_abcdefghi12lmnopqrstuvwxyZ1234567890_ABCDEF validation: type: Http content: @@ -31,6 +31,8 @@ rules: - '"id":' - '"username":' url: https://api.planetscale.com/v1/user + references: + - https://planetscale.com/docs/api depends_on_rule: - rule_id: kingfisher.planetscale.2 variable: USERNAME @@ -51,5 +53,7 @@ rules: min_entropy: 3.5 visible: false examples: - - pscale_user = abcdefghijkl - - 'planetscale_id: hgtmrnzlv1t7' + - pscale_user = 0dm7fw8prpel + - 'planetscale_id: 0dm7fw8prpel' + references: + - https://planetscale.com/docs/api diff --git a/crates/kingfisher-rules/data/rules/postgres.yml b/crates/kingfisher-rules/data/rules/postgres.yml index 7ab0007..1fb850c 100644 --- a/crates/kingfisher-rules/data/rules/postgres.yml +++ b/crates/kingfisher-rules/data/rules/postgres.yml @@ -39,4 +39,6 @@ rules: - CONNECTION_URI="postgis://postgres:s2Tf2k@rLMy@google.com:5434/elephant" validation: type: Postgres - tls_mode: lax \ No newline at end of file + tls_mode: lax + references: + - https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/posthog.yml b/crates/kingfisher-rules/data/rules/posthog.yml index 961be77..28fc6f5 100644 --- a/crates/kingfisher-rules/data/rules/posthog.yml +++ b/crates/kingfisher-rules/data/rules/posthog.yml @@ -27,6 +27,8 @@ rules: negative: true - type: StatusMatch status: [200] + references: + - https://posthog.com/docs/api/overview#authentication - name: PostHog Personal API Key id: kingfisher.posthog.2 pattern: | @@ -52,4 +54,6 @@ rules: - type: WordMatch words: - "authentication_failed" - negative: true \ No newline at end of file + negative: true + references: + - https://posthog.com/docs/api/overview#authentication \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/prefect.yml b/crates/kingfisher-rules/data/rules/prefect.yml index beba175..7f1b6fd 100644 --- a/crates/kingfisher-rules/data/rules/prefect.yml +++ b/crates/kingfisher-rules/data/rules/prefect.yml @@ -14,7 +14,7 @@ rules: confidence: medium examples: - PREFECT_API_TOKEN=pnu_1234567890abcdef1234567890abcdef1234 - - '"prefectToken": "pnu_abcdefabcdefabcdefabcdefabcdefabcdef"' + - '"prefectToken": "pnu_abcdefabcdef12cdefabcdefabcdefabcdef"' references: - https://docs.prefect.io/latest/concepts/api_keys/ validation: diff --git a/crates/kingfisher-rules/data/rules/privkey.yml b/crates/kingfisher-rules/data/rules/privkey.yml index a936514..e6fa5e9 100644 --- a/crates/kingfisher-rules/data/rules/privkey.yml +++ b/crates/kingfisher-rules/data/rules/privkey.yml @@ -22,12 +22,10 @@ rules: PRIVATE\sKEY (\sBLOCK)? ----- - pattern_requirements: - min_digits: 2 min_entropy: 4.5 confidence: high examples: - - |- + - | -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-256-CBC,1F77CE6d2Bb6B18537633Ec3aD093b9C @@ -42,6 +40,8 @@ rules: +ril frnc129xvp11ndqbyjqlg3jf9ovlb1qula84ftj8m -----END RSA PRIVATE KEY----- + references: + - https://www.rfc-editor.org/rfc/rfc7468 - name: Contains Private Key id: kingfisher.privkey.2 @@ -106,3 +106,5 @@ rules: -----BEGIN ENCRYPTED PRIVATE KEY BLOCK----- V75NeIrlsI80Gf0aTS2RZQvEcUQ3n6XwFnOvB/O5rRv3HGqvptc3P3n0bxfEg5KA -----END ENCRYPTED PRIVATE KEY BLOCK----- + references: + - https://www.rfc-editor.org/rfc/rfc7468 diff --git a/crates/kingfisher-rules/data/rules/pubnub.yml b/crates/kingfisher-rules/data/rules/pubnub.yml index f759ed7..5397ca1 100644 --- a/crates/kingfisher-rules/data/rules/pubnub.yml +++ b/crates/kingfisher-rules/data/rules/pubnub.yml @@ -24,6 +24,8 @@ rules: - 200 type: StatusMatch url: "https://ps.pndsn.com/publish/{{ TOKEN }}/{{ SUBSCRIPTIONTOKEN }}/0/kingfisher/0/%22ping%22?uuid=kingfisher_validate" + references: + - https://www.pubnub.com/docs/sdks/rest-api depends_on_rule: - rule_id: "kingfisher.pubnub.2" variable: SUBSCRIPTIONTOKEN @@ -52,4 +54,6 @@ rules: - report_response: true - status: - 200 - type: StatusMatch \ No newline at end of file + type: StatusMatch + references: + - https://www.pubnub.com/docs/sdks/rest-api \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/pypi.yml b/crates/kingfisher-rules/data/rules/pypi.yml index 1b441f9..46a38ba 100644 --- a/crates/kingfisher-rules/data/rules/pypi.yml +++ b/crates/kingfisher-rules/data/rules/pypi.yml @@ -62,4 +62,7 @@ rules: - name: content type: file content: "path/to/my_package-0.0.1.tar.gz" - content_type: "application/octet-stream" \ No newline at end of file + content_type: "application/octet-stream" + references: + - https://pypi.org/help/#apitoken + - https://warehouse.pypa.io/api-reference/ \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/rabbitmq.yml b/crates/kingfisher-rules/data/rules/rabbitmq.yml index 607d5f6..293ec81 100644 --- a/crates/kingfisher-rules/data/rules/rabbitmq.yml +++ b/crates/kingfisher-rules/data/rules/rabbitmq.yml @@ -15,27 +15,10 @@ rules: @ [-.%\w\/:]+ \b - pattern_requirements: - min_special_chars: 1 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' \ No newline at end of file + references: + - https://www.rabbitmq.com/uri-spec.html diff --git a/crates/kingfisher-rules/data/rules/recaptcha.yml b/crates/kingfisher-rules/data/rules/recaptcha.yml index 0c40e91..dcc7a16 100644 --- a/crates/kingfisher-rules/data/rules/recaptcha.yml +++ b/crates/kingfisher-rules/data/rules/recaptcha.yml @@ -10,7 +10,7 @@ rules: 6l[c-f][a-z0-9_-].{36} ) pattern_requirements: - min_digits: 3 + min_digits: 1 min_entropy: 3 confidence: medium examples: @@ -32,4 +32,6 @@ rules: type: WordMatch words: - '"success": true' - url: https://www.google.com/recaptcha/api/siteverify \ No newline at end of file + url: https://www.google.com/recaptcha/api/siteverify + references: + - https://developers.google.com/recaptcha/docs/verify \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/salesforce.yml b/crates/kingfisher-rules/data/rules/salesforce.yml index 282a936..3da3a6a 100644 --- a/crates/kingfisher-rules/data/rules/salesforce.yml +++ b/crates/kingfisher-rules/data/rules/salesforce.yml @@ -41,6 +41,8 @@ rules: words: ["DailyApiRequests"] match_all_words: true url: "https://{{ INSTANCE }}.my.salesforce.com/services/data/v60.0/limits" + references: + - https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_authentication.htm depends_on_rule: - rule_id: "kingfisher.salesforce.2" variable: INSTANCE @@ -63,6 +65,8 @@ rules: examples: - https://example123.my.salesforce.com - mydomainname.my.salesforce.com + references: + - https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_authentication.htm - name: Salesforce Consumer Key id: kingfisher.salesforce.3 pattern: | @@ -121,6 +125,8 @@ rules: true https://api.example.net/oauth/token + references: + - https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_oauth_and_connected_apps.htm - name: Salesforce Consumer Secret id: kingfisher.salesforce.4 pattern: | @@ -178,6 +184,8 @@ rules: true https://api.example.net/oauth/token + references: + - https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_oauth_and_connected_apps.htm - name: Salesforce Consumer Key and Secret id: kingfisher.salesforce.5 pattern: | @@ -244,4 +252,6 @@ rules: false true https://api.example.net/oauth/token - \ No newline at end of file + + references: + - https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_oauth_and_connected_apps.htm \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/scalingo.yml b/crates/kingfisher-rules/data/rules/scalingo.yml index c297526..131d14b 100644 --- a/crates/kingfisher-rules/data/rules/scalingo.yml +++ b/crates/kingfisher-rules/data/rules/scalingo.yml @@ -13,10 +13,10 @@ rules: min_entropy: 3.0 confidence: medium examples: - - SCALINGO_TOKEN=tk-us-abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef + - SCALINGO_TOKEN=tk-us-abcdefabcde12bcdefabcdefabcdefabcdefabcdefabcdef - '"scalingo": "tk-us-1234567890abcdef1234567890abcdef1234567890abcdef"' references: - - https://developers.scalingo.com/apps/api/authentication + - https://developers.scalingo.com/index#authentication validation: type: Http content: diff --git a/crates/kingfisher-rules/data/rules/scraperapi.yml b/crates/kingfisher-rules/data/rules/scraperapi.yml index 2bca1ac..d44adb9 100644 --- a/crates/kingfisher-rules/data/rules/scraperapi.yml +++ b/crates/kingfisher-rules/data/rules/scraperapi.yml @@ -11,7 +11,7 @@ rules: \b pattern_requirements: min_digits: 2 - min_lowercase: 10 + min_lowercase: 5 min_entropy: 3.5 confidence: medium examples: diff --git a/crates/kingfisher-rules/data/rules/sentry.yml b/crates/kingfisher-rules/data/rules/sentry.yml index d0f4685..9d02ba3 100644 --- a/crates/kingfisher-rules/data/rules/sentry.yml +++ b/crates/kingfisher-rules/data/rules/sentry.yml @@ -18,7 +18,7 @@ rules: min_entropy: 3.0 confidence: medium examples: - - SENTRY_TOKEN=cbadefcbadefcbadefcbadefcbadefcbadefcbadefcbadefcbadefcbadefcbad + - SENTRY_TOKEN=cbadefcbadefc12defcbadefcbadefcbadefcbadefcbadefcbadefcbadefcbad - '"sentry-key": "3214567890cbadef3214567890cbadef3214567890cbadef3214567890cbadef"' references: - https://docs.sentry.io/api/auth/ @@ -95,7 +95,7 @@ rules: min_entropy: 3.5 confidence: medium examples: - - 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 + - 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 - SNTRY_USER="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" references: - https://docs.sentry.io/api/auth/ diff --git a/crates/kingfisher-rules/data/rules/shippo.yml b/crates/kingfisher-rules/data/rules/shippo.yml index e1bae07..34d1bd8 100644 --- a/crates/kingfisher-rules/data/rules/shippo.yml +++ b/crates/kingfisher-rules/data/rules/shippo.yml @@ -14,7 +14,7 @@ rules: confidence: medium examples: - SHIPPO_TOKEN=shippo_test_1234567890abcdef1234567890abcdef12345678 - - 'Authorization: "ShippoToken shippo_live_abcdefabcdefabcdefabcdefabcdefabcdefabcd"' + - 'Authorization: "ShippoToken shippo_live_abc12fabcdefabcdefabcdefabcdefabcdefabcd"' references: - https://goshippo.com/docs/reference validation: diff --git a/crates/kingfisher-rules/data/rules/shopify.yml b/crates/kingfisher-rules/data/rules/shopify.yml index 7f70dd4..daaa6ed 100644 --- a/crates/kingfisher-rules/data/rules/shopify.yml +++ b/crates/kingfisher-rules/data/rules/shopify.yml @@ -29,6 +29,8 @@ rules: match_all_words: true words: ['"shop":'] url: https://{{ DOMAIN }}/admin/api/2024-10/shop.json + references: + - https://shopify.dev/docs/api/admin-rest#authentication depends_on_rule: - rule_id: "kingfisher.shopify.2" variable: DOMAIN @@ -39,4 +41,6 @@ rules: min_entropy: 3.0 visible: false examples: - - example.myshopify.com \ No newline at end of file + - example.myshopify.com + references: + - https://shopify.dev/docs/api/admin-rest#authentication \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/snyk.yml b/crates/kingfisher-rules/data/rules/snyk.yml index 3ba783f..d8af8be 100644 --- a/crates/kingfisher-rules/data/rules/snyk.yml +++ b/crates/kingfisher-rules/data/rules/snyk.yml @@ -33,4 +33,6 @@ rules: status: [200] - type: WordMatch words: ['"username"'] - match_all_words: true \ No newline at end of file + match_all_words: true + references: + - https://docs.snyk.io/snyk-api/authentication-for-api \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/sonarcloud.yml b/crates/kingfisher-rules/data/rules/sonarcloud.yml index c3307e9..cac46e6 100644 --- a/crates/kingfisher-rules/data/rules/sonarcloud.yml +++ b/crates/kingfisher-rules/data/rules/sonarcloud.yml @@ -35,3 +35,5 @@ rules: match_all_words: true words: - '"tokens":' + references: + - https://sonarcloud.io/web_api diff --git a/crates/kingfisher-rules/data/rules/sourcegraph.yml b/crates/kingfisher-rules/data/rules/sourcegraph.yml index 431e7c6..54b3f49 100644 --- a/crates/kingfisher-rules/data/rules/sourcegraph.yml +++ b/crates/kingfisher-rules/data/rules/sourcegraph.yml @@ -31,6 +31,8 @@ rules: - type: WordMatch words: ['"site":{'] match_all_words: true + references: + - https://docs.sourcegraph.com/admin/api#authentication - name: Sourcegraph _Legacy_ API Key id: kingfisher.sourcegraph.2 @@ -68,6 +70,8 @@ rules: status: [200] - type: WordMatch words: ['"site":{'] + references: + - https://docs.sourcegraph.com/admin/api#authentication - name: Sourcegraph Cody Gateway Key id: kingfisher.sourcegraph.3 @@ -95,3 +99,5 @@ rules: - type: WordMatch words: ['"token"', '"limit"'] match_all_words: true + references: + - https://docs.sourcegraph.com/cody/cody_gateway diff --git a/crates/kingfisher-rules/data/rules/square.yml b/crates/kingfisher-rules/data/rules/square.yml index 24a635a..8789530 100644 --- a/crates/kingfisher-rules/data/rules/square.yml +++ b/crates/kingfisher-rules/data/rules/square.yml @@ -33,6 +33,8 @@ rules: status: [200] - type: WordMatch words: ['"locations":'] + references: + - https://developer.squareup.com/ - name: Square Access Token id: kingfisher.square.2 @@ -65,6 +67,8 @@ rules: status: [200] - type: WordMatch words: ['"locations":'] + references: + - https://developer.squareup.com/ - name: Square OAuth Secret id: kingfisher.square.3 @@ -96,4 +100,6 @@ rules: - type: StatusMatch status: [200] - type: WordMatch - words: ['"locations":'] \ No newline at end of file + words: ['"locations":'] + references: + - https://developer.squareup.com/ \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/stripe.yml b/crates/kingfisher-rules/data/rules/stripe.yml index 9d48341..9d223ae 100644 --- a/crates/kingfisher-rules/data/rules/stripe.yml +++ b/crates/kingfisher-rules/data/rules/stripe.yml @@ -21,6 +21,8 @@ rules: categories: [api, key] examples: - stripe_pub_key = pk_live_HQS0j4H75XpthOW87eY1sXa2BYz3Ab + references: + - https://stripe.com/docs/api/authentication - name: Stripe Secret / Restricted Key id: kingfisher.stripe.2 @@ -61,3 +63,5 @@ rules: - type: WordMatch match_all_words: true words: ['"object":"account"'] + references: + - https://stripe.com/docs/api/authentication diff --git a/crates/kingfisher-rules/data/rules/supabase.yml b/crates/kingfisher-rules/data/rules/supabase.yml index 6232195..a2af991 100644 --- a/crates/kingfisher-rules/data/rules/supabase.yml +++ b/crates/kingfisher-rules/data/rules/supabase.yml @@ -36,7 +36,8 @@ rules: sb_secret_[a-z0-9_-]{31} ) pattern_requirements: - min_digits: 2 + min_uppercase: 3 + min_lowercase: 3 min_entropy: 4.0 confidence: medium validation: diff --git a/crates/kingfisher-rules/data/rules/tavily.yml b/crates/kingfisher-rules/data/rules/tavily.yml index 1775eb4..d6af170 100644 --- a/crates/kingfisher-rules/data/rules/tavily.yml +++ b/crates/kingfisher-rules/data/rules/tavily.yml @@ -18,7 +18,6 @@ rules: examples: - "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" - "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" - - 'TAVILY_API_KEY = "tvly-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"' validation: type: Http content: diff --git a/crates/kingfisher-rules/data/rules/telegram.yml b/crates/kingfisher-rules/data/rules/telegram.yml index 3f628a6..3c03c6a 100644 --- a/crates/kingfisher-rules/data/rules/telegram.yml +++ b/crates/kingfisher-rules/data/rules/telegram.yml @@ -31,4 +31,6 @@ rules: examples: - "tgram://110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsawd" - "telegram: 508627689:AAEuLPKs-EhrjrYGnz60bnYNZqakf6HJxc0" - - "telegram token is 3628091811:BAG9RuJiqgOGIfFbOPBpAo6QhIJoD9mCdDs" \ No newline at end of file + - "telegram token is 3628091811:BAG9RuJiqgOGIfFbOPBpAo6QhIJoD9mCdDs" + references: + - https://core.telegram.org/bots/api#authorizing-your-bot \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/travisci.yml b/crates/kingfisher-rules/data/rules/travisci.yml index 1d4b188..4eb8c45 100644 --- a/crates/kingfisher-rules/data/rules/travisci.yml +++ b/crates/kingfisher-rules/data/rules/travisci.yml @@ -33,6 +33,8 @@ rules: - report_response: true - type: StatusMatch status: [200] + references: + - https://developer.travis-ci.com/authentication - name: Travis CI Encrypted Variable id: kingfisher.travisci.2 pattern: | @@ -51,4 +53,6 @@ rules: global: # This sets FOO=super-secret, but the plaintext never appears here. - secure: "VJh0l9gOb+6AVNDk6cziZSs1AqVM8CqtZU6ot9ZQeJ+KfL1pxnGQ4qQF8Cz9\M1q85c3l1N1+qkQ0uV12QG6O6ylq6Qq1l3VjAJM3h2pY3jdmrA8kX2ZIxRjC/\8+Xj1wVtKQ0R+owM/6i5Y6cyx4hRb3VvSeYlC0lD1iTzQ2vgMyE=" + references: + - https://docs.travis-ci.com/user/encryption-keys/ diff --git a/crates/kingfisher-rules/data/rules/twilio.yml b/crates/kingfisher-rules/data/rules/twilio.yml index b626499..219bde1 100644 --- a/crates/kingfisher-rules/data/rules/twilio.yml +++ b/crates/kingfisher-rules/data/rules/twilio.yml @@ -21,6 +21,8 @@ rules: // https://www.twilio.com/console/video/dev-tools/api-keys 'API' => env('TWILIO_API','SK6e84981d07ace5c9df33e1ab043a2fb2'), 'API_KEY' => env('TWILIO_API_KEY', 'wbTs1SUt6Aace5eKeNCxuYvJa6PhaRd0') + references: + - https://www.twilio.com/docs/iam/api - name: Twilio API Key id: kingfisher.twilio.2 pattern: | @@ -58,6 +60,8 @@ rules: - '"first_page_uri":' - '"accounts":' url: https://api.twilio.com/2010-04-01/Accounts.json + references: + - https://www.twilio.com/docs/usage/api#authentication depends_on_rule: - rule_id: "kingfisher.twilio.1" variable: TWILIOID diff --git a/crates/kingfisher-rules/data/rules/twitch.yml b/crates/kingfisher-rules/data/rules/twitch.yml index d9ecc55..9f7cf50 100644 --- a/crates/kingfisher-rules/data/rules/twitch.yml +++ b/crates/kingfisher-rules/data/rules/twitch.yml @@ -17,8 +17,8 @@ rules: min_entropy: 3.5 confidence: medium examples: - - TWITCH_TOKEN=abcdefghijklmnopqrstuvwx123456 - - "twitch_api_token: '0123456789abcdefghijklmnopqrst'" + - TWITCH_TOKEN=abCDefghijklmnopqrstuvwx123456 + - "twitch_api_token: '0123456789ABcdefghijklmnopqrst'" references: - https://dev.twitch.tv/docs/authentication/validate-tokens/ validation: diff --git a/crates/kingfisher-rules/data/rules/uri.yml b/crates/kingfisher-rules/data/rules/uri.yml index e3e3a7d..a214645 100644 --- a/crates/kingfisher-rules/data/rules/uri.yml +++ b/crates/kingfisher-rules/data/rules/uri.yml @@ -40,3 +40,5 @@ rules: type: StatusMatch status: - 200 + references: + - https://www.rfc-editor.org/rfc/rfc3986#section-3.2.1 diff --git a/crates/kingfisher-rules/data/rules/vercel.yml b/crates/kingfisher-rules/data/rules/vercel.yml index 3ace650..97646a8 100644 --- a/crates/kingfisher-rules/data/rules/vercel.yml +++ b/crates/kingfisher-rules/data/rules/vercel.yml @@ -12,7 +12,7 @@ rules: ) \b pattern_requirements: - min_digits: 6 + min_digits: 3 min_uppercase: 1 min_lowercase: 1 confidence: medium diff --git a/crates/kingfisher-rules/data/rules/voyageai.yml b/crates/kingfisher-rules/data/rules/voyageai.yml index 942ab40..ce2a0a8 100644 --- a/crates/kingfisher-rules/data/rules/voyageai.yml +++ b/crates/kingfisher-rules/data/rules/voyageai.yml @@ -23,4 +23,6 @@ rules: Authorization: "Bearer {{ TOKEN }}" response_matcher: - type: StatusMatch - status: [200] \ No newline at end of file + status: [200] + references: + - https://docs.voyageai.com/reference \ No newline at end of file diff --git a/crates/kingfisher-rules/data/rules/weightsandbiases.yml b/crates/kingfisher-rules/data/rules/weightsandbiases.yml index 88bb37b..b60f491 100644 --- a/crates/kingfisher-rules/data/rules/weightsandbiases.yml +++ b/crates/kingfisher-rules/data/rules/weightsandbiases.yml @@ -35,3 +35,5 @@ rules: - type: WordMatch words: - '"username"' + references: + - https://docs.wandb.ai/ref/cli/wandb-login diff --git a/crates/kingfisher-rules/data/rules/youtube.yml b/crates/kingfisher-rules/data/rules/youtube.yml index 96b82fb..ebdb851 100644 --- a/crates/kingfisher-rules/data/rules/youtube.yml +++ b/crates/kingfisher-rules/data/rules/youtube.yml @@ -29,3 +29,5 @@ rules: match_all_words: false negative: true - type: JsonValid + references: + - https://developers.google.com/youtube/v3/getting-started#before-you-start diff --git a/crates/kingfisher-rules/data/rules/zhipu.yml b/crates/kingfisher-rules/data/rules/zhipu.yml index 3d7ddab..19a86e6 100644 --- a/crates/kingfisher-rules/data/rules/zhipu.yml +++ b/crates/kingfisher-rules/data/rules/zhipu.yml @@ -34,3 +34,5 @@ rules: status: [200] - type: WordMatch words: ["object", "data"] + references: + - https://open.bigmodel.cn/dev/api diff --git a/crates/kingfisher-rules/src/lib.rs b/crates/kingfisher-rules/src/lib.rs index cd6ecb9..b464638 100644 --- a/crates/kingfisher-rules/src/lib.rs +++ b/crates/kingfisher-rules/src/lib.rs @@ -16,11 +16,11 @@ pub mod rules_database; // Re-export rule types pub use rule::{ - ChecksumActual, ChecksumRequirement, Confidence, DependsOnRule, HttpMultiStepRevocation, - HttpRequest, HttpValidation, MultipartConfig, MultipartPart, PatternRequirementContext, - PatternRequirements, PatternValidationResult, ReportResponseData, ResponseExtractor, - ResponseMatcher, Revocation, RevocationStep, Rule, RuleSyntax, TlsMode, Validation, - RULE_COMMENTS_PATTERN, + ChecksumActual, ChecksumRequirement, Confidence, DependsOnRule, GrpcRequest, GrpcValidation, + HttpMultiStepRevocation, HttpRequest, HttpValidation, MultipartConfig, MultipartPart, + PatternRequirementContext, PatternRequirements, PatternValidationResult, ReportResponseData, + ResponseExtractor, ResponseMatcher, Revocation, RevocationStep, Rule, RuleSyntax, TlsMode, + Validation, RULE_COMMENTS_PATTERN, }; // Re-export Rules collection diff --git a/crates/kingfisher-rules/src/rule.rs b/crates/kingfisher-rules/src/rule.rs index 1d22899..07eec09 100644 --- a/crates/kingfisher-rules/src/rule.rs +++ b/crates/kingfisher-rules/src/rule.rs @@ -79,6 +79,7 @@ pub enum Validation { JWT, Raw(String), Http(HttpValidation), + Grpc(GrpcValidation), } /// Represents revocation actions that a rule can perform. @@ -387,6 +388,33 @@ pub struct HttpValidation { pub multipart: Option, } +/// Configuration for gRPC validation. +/// +/// This is intended for services that are gRPC-only (HTTP/2 + trailers), +/// such as Modal's control plane. +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] +pub struct GrpcValidation { + pub request: GrpcRequest, +} + +/// Configuration for a single gRPC unary request. +/// +/// Notes: +/// - `url` should include the full gRPC method path, e.g. +/// `https://api.modal.com/modal.client.ModalClientModal/ContainerHello` +/// - `headers` can include custom auth headers, content-type, etc. +/// - `body` is sent as raw bytes; YAML `\u0000` escapes are supported. +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] +pub struct GrpcRequest { + pub url: String, + #[serde(default)] + pub headers: BTreeMap, + #[serde(default)] + pub body: Option, + #[serde(default)] + pub response_matcher: Option>, +} + /// Configuration for an HTTP request used for validation. #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] pub struct HttpRequest { diff --git a/data/default/rule_cleanup/main.py b/data/default/rule_cleanup/check_endpoints.py similarity index 100% rename from data/default/rule_cleanup/main.py rename to data/default/rule_cleanup/check_endpoints.py diff --git a/data/default/rule_cleanup/check_references.py b/data/default/rule_cleanup/check_references.py new file mode 100644 index 0000000..c044beb --- /dev/null +++ b/data/default/rule_cleanup/check_references.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +"""Check URLs under YAML `references` sections for activity.""" + +from __future__ import annotations + +import argparse +import concurrent.futures +import pathlib +import re +import sys +import urllib.error +import urllib.request +from dataclasses import dataclass + +URL_RE = re.compile(r"https?://[^\s\"'<>]+") +REFERENCES_RE = re.compile(r"^(\s*)references:\s*$") + + +@dataclass(frozen=True) +class ReferenceUrl: + path: pathlib.Path + line: int + url: str + + +@dataclass(frozen=True) +class UrlResult: + ref: ReferenceUrl + active: bool + detail: str + + +def extract_reference_urls(path: pathlib.Path) -> list[ReferenceUrl]: + lines = path.read_text(encoding="utf-8").splitlines() + refs: list[ReferenceUrl] = [] + i = 0 + while i < len(lines): + line = lines[i] + match = REFERENCES_RE.match(line) + if not match: + i += 1 + continue + + base_indent = len(match.group(1)) + i += 1 + while i < len(lines): + current = lines[i] + stripped = current.strip() + indent = len(current) - len(current.lstrip(" ")) + + if stripped and indent <= base_indent: + break + + if stripped: + for url in URL_RE.findall(current): + refs.append(ReferenceUrl(path=path, line=i + 1, url=url.rstrip(",.)]"))) + + i += 1 + + return refs + + +def check_url(url: str, timeout: float) -> tuple[bool, str]: + headers = {"User-Agent": "kingfisher-reference-checker/1.0"} + request = urllib.request.Request(url, headers=headers, method="HEAD") + head_detail = "" + try: + with urllib.request.urlopen(request, timeout=timeout) as response: + status = getattr(response, "status", 200) + return (200 <= status < 400), f"HTTP {status} (HEAD)" + except urllib.error.HTTPError as exc: + # Many docs hosts block HEAD. Retry with GET. + if exc.code in {401, 403, 405, 429}: + return True, f"HTTP {exc.code} (HEAD)" + head_detail = f"HTTP {exc.code} (HEAD)" + except Exception as exc: # noqa: BLE001 + # Retry with GET for transient/protocol issues. + head_detail = f"{type(exc).__name__}: {exc} (HEAD)" + + get_request = urllib.request.Request(url, headers=headers, method="GET") + try: + with urllib.request.urlopen(get_request, timeout=timeout) as response: + status = getattr(response, "status", 200) + return (200 <= status < 400), f"HTTP {status} (GET)" + except urllib.error.HTTPError as exc: + if exc.code in {401, 403, 429}: + return True, f"HTTP {exc.code} (GET)" + if head_detail: + return False, f"{head_detail}; HTTP {exc.code} (GET)" + return False, f"HTTP {exc.code} (GET)" + except Exception as exc: # noqa: BLE001 + if head_detail: + return False, f"{head_detail}; {type(exc).__name__}: {exc} (GET)" + return False, f"{type(exc).__name__}: {exc} (GET)" + + +def check_reference(ref: ReferenceUrl, timeout: float) -> UrlResult: + active, detail = check_url(ref.url, timeout=timeout) + return UrlResult(ref=ref, active=active, detail=detail) + + +def gather_references(base_dir: pathlib.Path) -> list[ReferenceUrl]: + refs: list[ReferenceUrl] = [] + for path in sorted(base_dir.glob("*.yml")): + refs.extend(extract_reference_urls(path)) + return refs + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Check all URLs in YAML references sections." + ) + parser.add_argument( + "--rules-dir", + type=pathlib.Path, + default=pathlib.Path("../../crates/kingfisher-rules/data/rules"), + help="Directory with YAML rule files (default: %(default)s)", + ) + parser.add_argument( + "--timeout", + type=float, + default=15.0, + help="HTTP request timeout in seconds (default: %(default)s)", + ) + parser.add_argument( + "--workers", + type=int, + default=20, + help="Maximum concurrent URL checks (default: %(default)s)", + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + rules_dir = args.rules_dir.resolve() + if not rules_dir.exists(): + print(f"error: directory does not exist: {rules_dir}", file=sys.stderr) + return 2 + + refs = gather_references(rules_dir) + if not refs: + print("No URLs found in references sections.") + return 0 + + print(f"Found {len(refs)} reference URLs in {rules_dir}") + results: list[UrlResult] = [] + + with concurrent.futures.ThreadPoolExecutor(max_workers=max(1, args.workers)) as pool: + futures = [pool.submit(check_reference, ref, args.timeout) for ref in refs] + for future in concurrent.futures.as_completed(futures): + results.append(future.result()) + + inactive = [result for result in results if not result.active] + inactive.sort(key=lambda item: (str(item.ref.path), item.ref.line, item.ref.url)) + + print(f"Active: {len(results) - len(inactive)}") + print(f"Inactive: {len(inactive)}") + + for result in inactive: + rel = result.ref.path.relative_to(pathlib.Path.cwd()) + print(f"INACTIVE {rel}:{result.ref.line} {result.ref.url} [{result.detail}]") + + return 1 if inactive else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/docs/RULES.md b/docs/RULES.md index 072a500..c806aa2 100644 --- a/docs/RULES.md +++ b/docs/RULES.md @@ -71,6 +71,24 @@ rules: expected: ["application/json"] - type: JsonValid + # NOTE: Some providers are gRPC-only (no REST endpoint). For those, use Grpc validation. + validation: + type: Grpc + content: + request: + url: https://api.example.com/./ + headers: + content-type: application/grpc + te: trailers + Authorization: "Bearer {{ TOKEN }}" + # Raw bytes are allowed (YAML \\u0000 escapes become NUL bytes). + body: "\\u0000\\u0000\\u0000\\u0000\\u0000" + response_matcher: + - report_response: true + - type: HeaderMatch + header: grpc-status + expected: ["0"] + revocation: # (optional) revoke a secret type: Http content: @@ -153,6 +171,36 @@ revocation: | validation | Configure HTTP, AWS, GCP, etc. checks to verify live validity | | revocation | Configure HTTP, AWS, or multi-step revocation for a detected secret | +## gRPC Validation (Grpc) + +Some services (notably CLI/SDK control planes) are **gRPC-only**. For these, `validation: type: Http` +is not sufficient because gRPC status is typically returned via HTTP/2 trailers (`grpc-status`, +`grpc-message`). Kingfisher’s `Grpc` validator performs an HTTP/2 request and evaluates matchers +against the merged headers+trailers. + +`Grpc` is currently intended for unary requests and expects you to provide a fully-qualified method URL: + +```yaml +validation: + type: Grpc + content: + request: + url: https://api.modal.com/modal.client.ModalClient/ClientHello + headers: + content-type: application/grpc + te: trailers + x-modal-token-id: "{{ TOKEN_ID }}" + x-modal-token-secret: "{{ TOKEN }}" + x-modal-client-type: "1" + x-modal-client-version: "1.0.0" + body: "\u0000\u0000\u0000\u0000\u0000" # Empty protobuf frame + response_matcher: + - report_response: true + - type: HeaderMatch + header: grpc-status + expected: ["0"] +``` + *responser_matcher* variants. Multiple can be used | Variant | Required keys | Behavior | diff --git a/docs/USAGE.md b/docs/USAGE.md index 1fd3970..8622331 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -150,7 +150,7 @@ kingfisher validate --rule jwt \ "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." ``` -**Supported validators:** HTTP, AWS, GCP, MongoDB, MySQL, Postgres, JDBC, JWT, Azure Storage, and Coinbase. +**Supported validators:** HTTP, Grpc, AWS, GCP, MongoDB, MySQL, Postgres, JDBC, JWT, Azure Storage, and Coinbase. **Exit codes:** Returns `0` if any matching rule validates the secret as valid, `1` if all are invalid or an error occurred. diff --git a/src/direct_validate.rs b/src/direct_validate.rs index f32bbfb..d05640d 100644 --- a/src/direct_validate.rs +++ b/src/direct_validate.rs @@ -29,7 +29,8 @@ use crate::{ azure::validate_azure_storage_credentials, coinbase::validate_cdp_api_key, gcp::GcpValidator, - httpvalidation::{build_request_builder, retry_request, validate_response}, + httpvalidation::validate_response, + httpvalidation::{build_request_builder, retry_request}, jdbc::validate_jdbc, jwt::validate_jwt, mongodb::validate_mongodb, @@ -40,6 +41,8 @@ use crate::{ validation_body, }; +use crate::grpc_validation; + /// Result of a direct validation attempt. #[derive(Debug, Clone, Serialize)] pub struct DirectValidationResult { @@ -135,6 +138,21 @@ fn extract_validation_vars(validation: &Validation) -> BTreeSet { vars.extend(extract_template_vars(body)); } } + Validation::Grpc(grpc) => { + // Extract from URL + vars.extend(extract_template_vars(&grpc.request.url)); + + // Extract from headers + for (key, value) in &grpc.request.headers { + vars.extend(extract_template_vars(key)); + vars.extend(extract_template_vars(value)); + } + + // Extract from body + if let Some(body) = &grpc.request.body { + vars.extend(extract_template_vars(body)); + } + } // Non-HTTP validators typically use fixed variable names Validation::AWS => { vars.insert("AKID".to_string()); @@ -312,6 +330,60 @@ async fn execute_http_validation( }) } +/// Execute gRPC validation against the provided rule. +async fn execute_grpc_validation( + grpc_validation_cfg: &kingfisher_rules::GrpcValidation, + globals: &Object, + parser: &liquid::Parser, + timeout: Duration, +) -> Result { + // Render the URL + let url = render_and_parse_url(parser, globals, &grpc_validation_cfg.request.url).await?; + + debug!("Validating against gRPC URL: {}", url); + + let res = grpc_validation::grpc_unary_call_from_rule( + &url, + &grpc_validation_cfg.request.headers, + &grpc_validation_cfg.request.body, + parser, + globals, + timeout, + ) + .await + .map_err(|e| anyhow!("gRPC request failed: {e}"))?; + + let status = res.http_status; + let headers = res.headers; + let mut body = String::from_utf8_lossy(&res.body_bytes).to_string(); + let grpc_status = + headers.get("grpc-status").and_then(|v| v.to_str().ok()).unwrap_or("").to_string(); + let grpc_message = + headers.get("grpc-message").and_then(|v| v.to_str().ok()).unwrap_or("").to_string(); + if grpc_status == "0" { + body = "grpc-status=0".to_string(); + } else if body.trim().is_empty() && (!grpc_status.is_empty() || !grpc_message.is_empty()) { + body = format!("grpc-status={grpc_status} grpc-message={grpc_message}"); + } else if body.as_bytes().contains(&0) { + body = format!("grpc-status={grpc_status} grpc-message={grpc_message}"); + } + + // Truncate body for display if too long + let display_body = if body.len() > 500 { format!("{}...", &body[..500]) } else { body.clone() }; + + // Validate the response + let matchers = grpc_validation_cfg.request.response_matcher.as_deref().unwrap_or(&[]); + let is_valid = validate_response(matchers, &body, &status, &headers, false); + + Ok(DirectValidationResult { + rule_id: String::new(), // Will be filled in by caller + rule_name: String::new(), + is_valid, + status_code: Some(status.as_u16()), + message: display_body, + }) +} + /// Run direct validation of a secret against one or more rules. /// /// If the rule selector matches multiple rules, all matching rules are tried. @@ -476,6 +548,9 @@ pub async fn run_direct_validation( ) .await? } + Validation::Grpc(grpc_validation_cfg) => { + execute_grpc_validation(grpc_validation_cfg, &globals, &parser, timeout).await? + } Validation::AWS => { // AWS needs AKID and TOKEN (secret access key) diff --git a/src/grpc_validation.rs b/src/grpc_validation.rs new file mode 100644 index 0000000..8d20b2f --- /dev/null +++ b/src/grpc_validation.rs @@ -0,0 +1,202 @@ +use std::{collections::BTreeMap, sync::Arc, time::Duration}; + +use anyhow::{anyhow, Context, Result}; +use bytes::Bytes; +use h2::client; +use http::{header::HeaderName, HeaderMap, HeaderValue, Request, Uri}; +use liquid::Object; +use reqwest::Url; +use rustls::{ClientConfig, RootCertStore}; +use tokio::net::TcpStream; +use tokio_rustls::TlsConnector; + +/// Result of a gRPC unary call over HTTP/2. +pub struct GrpcCallResult { + pub http_status: http::StatusCode, + /// Response headers + trailers merged into one map. + pub headers: HeaderMap, + pub body_bytes: Vec, +} + +fn build_root_store() -> Result { + let mut roots = RootCertStore::empty(); + let native = rustls_native_certs::load_native_certs(); + if !native.errors.is_empty() { + // Best-effort: still proceed if we got any certs. + // (Some platforms may have a few unparsable roots.) + } + for cert in native.certs { + roots.add(cert).map_err(|e| anyhow!("Failed to add native root cert: {e:?}"))?; + } + Ok(roots) +} + +fn url_to_h2_uri(url: &Url) -> Result { + let scheme = url.scheme(); + if scheme != "https" { + return Err(anyhow!("gRPC validation only supports https URLs, got: {scheme}")); + } + let host = url.host_str().ok_or_else(|| anyhow!("URL is missing host: {url}"))?; + let authority = match url.port() { + Some(p) => format!("{host}:{p}"), + None => host.to_string(), + }; + let path_and_query = &url[url::Position::BeforePath..]; + Uri::builder() + .scheme("https") + .authority(authority) + .path_and_query(path_and_query) + .build() + .context("Failed to build HTTP/2 URI for gRPC request") +} + +fn header_map_from_templates( + templates: &BTreeMap, + parser: &liquid::Parser, + globals: &Object, +) -> Result { + let mut out = HeaderMap::new(); + for (k, v_template) in templates { + // Header names in YAML are expected to be static. + let name = HeaderName::from_bytes(k.as_bytes()) + .with_context(|| format!("Invalid header name in GrpcValidation: '{k}'"))?; + + let tmpl = parser + .parse(v_template) + .map_err(|e| anyhow!("Failed to parse header template '{k}': {e}"))?; + let rendered = tmpl + .render(globals) + .map_err(|e| anyhow!("Failed to render header template '{k}': {e}"))?; + + let value = HeaderValue::from_str(&rendered) + .with_context(|| format!("Invalid header value for '{k}'"))?; + out.append(name, value); + } + Ok(out) +} + +/// Execute a single unary gRPC request over HTTP/2 and return headers + trailers. +/// +/// This is intentionally low-level so that rules can validate gRPC-only APIs +/// without pretending they are REST endpoints. +pub async fn grpc_unary_call( + url: &Url, + headers: HeaderMap, + body: Vec, + timeout: Duration, +) -> Result { + let host = url.host_str().ok_or_else(|| anyhow!("URL is missing host: {url}"))?; + let port = url.port_or_known_default().unwrap_or(443); + + let addr = format!("{host}:{port}"); + let tcp = tokio::time::timeout(timeout, TcpStream::connect(addr)) + .await + .context("Timed out connecting to gRPC host")? + .context("Failed to connect to gRPC host")?; + + let mut tls_config = + ClientConfig::builder().with_root_certificates(build_root_store()?).with_no_client_auth(); + tls_config.alpn_protocols = vec![b"h2".to_vec()]; + + let connector = TlsConnector::from(Arc::new(tls_config)); + let server_name = rustls::pki_types::ServerName::try_from(host.to_string()) + .map_err(|_| anyhow!("Invalid TLS server name: {host}"))?; + + let tls = tokio::time::timeout(timeout, connector.connect(server_name, tcp)) + .await + .context("Timed out during TLS handshake")? + .context("TLS handshake failed")?; + + let (mut h2_client, connection) = tokio::time::timeout(timeout, client::handshake(tls)) + .await + .context("Timed out during HTTP/2 handshake")? + .context("HTTP/2 handshake failed")?; + + // Drive the HTTP/2 connection in the background. + tokio::spawn(async move { + let _ = connection.await; + }); + + let uri = url_to_h2_uri(url)?; + + let mut req_builder = Request::builder().method("POST").uri(uri); + { + let hdrs = req_builder.headers_mut().expect("headers_mut should exist"); + for (k, v) in headers.iter() { + hdrs.append(k, v.clone()); + } + } + + let request = req_builder.body(()).context("Failed to build HTTP/2 request")?; + + let (response_future, mut send_stream) = + h2_client.send_request(request, false).context("Failed to send gRPC request headers")?; + + // Send gRPC request bytes (including the 5-byte gRPC frame prefix). + send_stream.send_data(Bytes::from(body), true).context("Failed to send gRPC request body")?; + + let response = tokio::time::timeout(timeout, response_future) + .await + .context("Timed out waiting for gRPC response headers")? + .context("Failed to receive gRPC response headers")?; + + let http_status = response.status(); + let (parts, mut recv_stream) = response.into_parts(); + let mut merged_headers = parts.headers; + + // Read data frames (may be empty). + let mut body_bytes: Vec = Vec::new(); + loop { + // h2 returns `Option>` here: + // - None => end of stream + // - Some(Ok(bytes)) => a data chunk + // - Some(Err(err)) => stream error + let next_opt = tokio::time::timeout(timeout, recv_stream.data()) + .await + .context("Timed out reading gRPC response data")?; + + match next_opt { + Some(Ok(b)) => body_bytes.extend_from_slice(b.as_ref()), + Some(Err(e)) => return Err(anyhow!("Error reading gRPC response data: {e}")), + None => break, + } + } + + // Read trailers (where grpc-status is typically reported). + if let Some(trailers) = tokio::time::timeout(timeout, recv_stream.trailers()) + .await + .context("Timed out reading gRPC response trailers")? + .context("Error reading gRPC response trailers")? + { + for (k, v) in trailers.iter() { + merged_headers.append(k, v.clone()); + } + } + + Ok(GrpcCallResult { http_status, headers: merged_headers, body_bytes }) +} + +/// Helper to render & execute a gRPC request from rule templates. +pub async fn grpc_unary_call_from_rule( + url: &Url, + header_templates: &BTreeMap, + body_template: &Option, + parser: &liquid::Parser, + globals: &Object, + timeout: Duration, +) -> Result { + let headers = header_map_from_templates(header_templates, parser, globals)?; + let body = match body_template { + Some(t) => { + let tmpl = + parser.parse(t).map_err(|e| anyhow!("Failed to parse gRPC body template: {e}"))?; + let rendered = tmpl + .render(globals) + .map_err(|e| anyhow!("Failed to render gRPC body template: {e}"))?; + rendered.into_bytes() + } + None => Vec::new(), + }; + + grpc_unary_call(url, headers, body, timeout).await +} diff --git a/src/lib.rs b/src/lib.rs index 3cab0ea..5c2e716 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,7 @@ pub mod git_url; pub mod gitea; pub mod github; pub mod gitlab; +pub mod grpc_validation; pub mod huggingface; pub mod inline_ignore; pub mod jira; diff --git a/src/main.rs b/src/main.rs index 04c1dd3..08f6580 100644 --- a/src/main.rs +++ b/src/main.rs @@ -80,6 +80,9 @@ use crate::cli::commands::{ fn main() -> anyhow::Result<()> { color_backtrace::install(); + // Rustls 0.23 requires an explicit crypto provider selection when multiple + // providers are present in the dependency graph. + let _ = rustls::crypto::ring::default_provider().install_default(); // Parse command-line arguments let CommandLineArgs { command, global_args } = CommandLineArgs::parse_args(); @@ -583,55 +586,112 @@ pub fn run_rules_check(args: &RulesCheckArgs) -> Result<()> { num_errors += 1; continue; } - // Test each example against both vectorscan and regex + // Test each example against regex and pattern_requirements for (example_index, example) in rule_syntax.examples.iter().enumerate() { - // Create a test blob from the example - // let blob = Blob::new(BlobId::new(example.as_bytes()), - // example.as_bytes().to_vec()); let origin = OriginSet::new( - // Origin::from_file(PathBuf::from("test_example")), - // Vec::new(), - // ); - // // Check vectorscan match - // let vectorscan_matched = match matcher.scan_blob(&blob, &origin, None)? { - // ScanResult::New(matches) => !matches.is_empty(), - // _ => false, - // }; - // Check regex match // Get the regex using the public method let re = rules_db.get_regex_by_rule_id(rule.id()).expect("Failed to get regex for rule"); - let regex_matched = re.is_match(example.as_bytes()); + + // Check if the example matches the pattern + let example_bytes = example.as_bytes(); + let regex_matched = re.is_match(example_bytes); + if !regex_matched { - // ||!vectorscan_matched { println!("\nTesting rule {} - {}", rule_index + 1, rule_syntax.name); println!(" Processing example {}", example_index + 1); - println!(" [!] Mismatch detected for example: {}", example); - // if !vectorscan_matched { - // println!(" Vectorscan match: {}", vectorscan_matched); - // num_errors += 1; - // } - if !regex_matched { - println!(" Regex match: {}", regex_matched); - num_errors += 1; - } + println!(" [!] Pattern mismatch detected for example: {}", example); + println!(" Regex match: {}", regex_matched); + num_errors += 1; + continue; } - // // Report any mismatches - // if !vectorscan_matched || !regex_matched { - // error!("Rule '{}' example {} failed validation:", - // rule.name(), example_index + 1); println!(" - // Example text: {}", example); + // If the rule has pattern_requirements, validate them against the match + if let Some(pattern_reqs) = rule.pattern_requirements() { + // Get the captures from the match + if let Some(captures) = re.captures(example_bytes) { + // Get the full match (group 0) + let full_capture = captures.get(0).expect("Group 0 should always exist"); + let full_bytes = full_capture.as_bytes(); - // if !vectorscan_matched { - // error!(" - Vectorscan pattern did not match example"); - // num_errors += 1; - // } + // Determine which bytes to validate (same logic as in matcher.rs) + // Find the primary capture group for validation + let matching_input_for_validation = 'block: { + // 1. Look for a named capture "secret" (case-insensitive). + if let Some(secret_cap) = + captures.name("secret").or_else(|| captures.name("SECRET")) + { + break 'block secret_cap; + } - // if !regex_matched { - // error!(" - Regex pattern did not match example"); - // num_errors += 1; - // } - // } + // 2. Look for any other named capture. + if let Some(named_cap) = (1..captures.len()).find_map(|i| { + let name_opt = re.capture_names().nth(i).and_then(|n| n); + name_opt.and_then(|_| captures.get(i)) + }) { + break 'block named_cap; + } + + // 3. Fall back to first positional capture (group 1) if it exists. + if let Some(pos_cap) = captures.get(1) { + break 'block pos_cap; + } + + // 4. Finally, fall back to the full match (group 0). + break 'block full_capture; + }; + + let validation_bytes = matching_input_for_validation.as_bytes(); + + // Create context for pattern requirements validation + use kingfisher_rules::PatternRequirementContext; + let context = PatternRequirementContext { + regex: re, + captures: &captures, + full_match: full_bytes, + }; + + // Validate pattern requirements (without respect_ignore_if_contains for examples) + use kingfisher_rules::PatternValidationResult; + match pattern_reqs.validate(validation_bytes, Some(context), false) { + PatternValidationResult::Passed => { + // All requirements met + } + PatternValidationResult::Failed => { + println!("\nTesting rule {} - {}", rule_index + 1, rule_syntax.name); + println!(" Processing example {}", example_index + 1); + println!( + " [!] Pattern requirements not met for example: {}", + example + ); + println!(" The match does not satisfy the character requirements (min_digits, min_uppercase, etc.)"); + num_errors += 1; + } + PatternValidationResult::FailedChecksum { actual_len, expected_len } => { + println!("\nTesting rule {} - {}", rule_index + 1, rule_syntax.name); + println!(" Processing example {}", example_index + 1); + println!(" [!] Checksum validation failed for example: {}", example); + println!( + " Actual checksum length: {}, Expected checksum length: {}", + actual_len, expected_len + ); + num_errors += 1; + } + PatternValidationResult::IgnoredBySubstring { matched_term } => { + // For examples, we don't want to treat this as an error in check mode + // since ignore_if_contains is meant for runtime filtering + // But we can warn about it + println!("\nTesting rule {} - {}", rule_index + 1, rule_syntax.name); + println!(" Processing example {}", example_index + 1); + println!( + " [!] Example would be ignored due to containing term: {}", + matched_term + ); + println!(" Example: {}", example); + num_errors += 1; + } + } + } + } } } // Print summary diff --git a/src/reporter.rs b/src/reporter.rs index 0c0b56b..77b3709 100644 --- a/src/reporter.rs +++ b/src/reporter.rs @@ -181,6 +181,15 @@ fn build_validate_command( escape_for_shell(snippet) )) } + Validation::Grpc(_) => { + // gRPC-based validation with dependent variables + Some(format!( + "kingfisher validate --rule {} {}{}", + rule_id, + var_args, + escape_for_shell(snippet) + )) + } Validation::MongoDB | Validation::MySQL | Validation::Postgres @@ -645,8 +654,17 @@ impl DetailsReporter { let source_span = rm.m.location.resolved_source_span(); let line_num = source_span.start.line; - // Get raw snippet value (for revoke command) and display snippet (for output) - let (raw_snippet, snippet) = if let Some(capture) = rm.m.groups.captures.get(0) { + // Prefer the named TOKEN capture (when present) for display + validate/revoke commands. + // This avoids cases like Modal CLI pairs where capture(0) is an ID and TOKEN is the secret. + let snippet_capture = + rm.m.groups + .captures + .iter() + .find(|c| c.name.map(|n| n.eq_ignore_ascii_case("TOKEN")).unwrap_or(false)) + .or_else(|| rm.m.groups.captures.get(0)); + + // Get raw snippet value (for revoke/validate command) and display snippet (for output) + let (raw_snippet, snippet) = if let Some(capture) = snippet_capture { let raw = capture.raw_value().to_string(); let displayed = capture.display_value(); (raw, Escaped(displayed.as_ref().as_bytes()).to_string()) @@ -718,11 +736,24 @@ impl DetailsReporter { // Generate validate command for findings with validation support let validate_cmd = if let Some(validation) = &rm.m.rule.syntax().validation { + // Merge dependent captures with named regex captures so the generated command is runnable. + // (E.g., Modal needs TOKEN_ID, which is a named capture on the same rule.) + let mut merged_vars = rm.m.dependent_captures.clone(); + for cap in rm.m.groups.captures.iter() { + let Some(name) = cap.name else { continue }; + if name.eq_ignore_ascii_case("TOKEN") { + continue; + } + merged_vars + .entry(name.to_uppercase()) + .or_insert_with(|| cap.raw_value().to_string()); + } + build_validate_command( rm.m.rule.id(), validation, &raw_snippet, - &rm.m.dependent_captures, + &merged_vars, akid_from_captures.as_deref(), akid_from_body.as_deref(), ) diff --git a/src/rules.rs b/src/rules.rs index 1b1a7ac..85ff0f4 100644 --- a/src/rules.rs +++ b/src/rules.rs @@ -11,8 +11,8 @@ pub mod rule { pub use kingfisher_rules::rule::Revocation; pub use kingfisher_rules::rules::{Rules, RulesError}; pub use kingfisher_rules::{ - ChecksumActual, ChecksumRequirement, Confidence, DependsOnRule, HttpRequest, HttpValidation, - MultipartConfig, MultipartPart, PatternRequirementContext, PatternRequirements, - PatternValidationResult, ReportResponseData, ResponseMatcher, Rule, RuleSyntax, Validation, - RULE_COMMENTS_PATTERN, + ChecksumActual, ChecksumRequirement, Confidence, DependsOnRule, GrpcRequest, GrpcValidation, + HttpRequest, HttpValidation, MultipartConfig, MultipartPart, PatternRequirementContext, + PatternRequirements, PatternValidationResult, ReportResponseData, ResponseMatcher, Rule, + RuleSyntax, Validation, RULE_COMMENTS_PATTERN, }; diff --git a/src/validation.rs b/src/validation.rs index 0671e3f..80dbccd 100644 --- a/src/validation.rs +++ b/src/validation.rs @@ -25,6 +25,8 @@ use crate::{ validation_body::{self}, }; +use crate::grpc_validation; + // Re-export TlsMode from kingfisher_rules for use in client_for_rule pub use kingfisher_rules::TlsMode as RuleTlsMode; @@ -464,6 +466,15 @@ async fn timed_validate_single_match<'a>( let mut globals = Object::new(); populate_globals_from_captures(&mut globals, &captured_values); + // Persist named captures (non-TOKEN) for validate/revoke command generation. + // This is especially important for gRPC validators like Modal where TOKEN_ID is required. + for (k, v, ..) in &captured_values { + if k.eq_ignore_ascii_case("TOKEN") { + continue; + } + m.dependent_captures.entry(k.to_uppercase()).or_insert_with(|| v.clone()); + } + let rule_syntax = m.rule.syntax(); // ────────────────────────────────────────────────────────── @@ -717,6 +728,87 @@ async fn timed_validate_single_match<'a>( } } + // ---------------------------------------------------- gRPC validator + Some(Validation::Grpc(grpc_validation_cfg)) => { + let request_timeout = validation_timeout; + + // Render URL + let url = match render_and_parse_url( + parser, + &globals, + &rule_syntax.name, + &grpc_validation_cfg.request.url, + ) + .await + { + Ok(u) => u, + Err(e) => { + m.validation_success = false; + m.validation_response_body = validation_body::from_string(e); + m.validation_response_status = StatusCode::BAD_REQUEST; + commit_and_return(m); + return; + } + }; + + // Execute gRPC unary call (HTTP/2 + trailers). + let res = match grpc_validation::grpc_unary_call_from_rule( + &url, + &grpc_validation_cfg.request.headers, + &grpc_validation_cfg.request.body, + parser, + &globals, + request_timeout, + ) + .await + { + Ok(r) => r, + Err(e) => { + m.validation_success = false; + m.validation_response_body = + validation_body::from_string(format!("gRPC error: {}", e)); + m.validation_response_status = StatusCode::BAD_GATEWAY; + commit_and_return(m); + return; + } + }; + + let status = StatusCode::from_u16(res.http_status.as_u16()).unwrap_or(StatusCode::OK); + let headers = res.headers; + let mut body = String::from_utf8_lossy(&res.body_bytes).to_string(); + + // gRPC errors are typically reported in trailers, not the body. + // Surface them for debugging and for `--full-validation-response` output. + let grpc_status = + headers.get("grpc-status").and_then(|v| v.to_str().ok()).unwrap_or("").to_string(); + let grpc_message = + headers.get("grpc-message").and_then(|v| v.to_str().ok()).unwrap_or("").to_string(); + // Avoid storing raw protobuf bytes in the report (they often contain NULs and make + // output logs non-UTF8). Prefer a compact status/message string. + if grpc_status == "0" { + body = "grpc-status=0".to_string(); + } else if body.trim().is_empty() + && (!grpc_status.is_empty() || !grpc_message.is_empty()) + { + body = format!("grpc-status={grpc_status} grpc-message={grpc_message}"); + } else if body.as_bytes().contains(&0) { + body = format!("grpc-status={grpc_status} grpc-message={grpc_message}"); + } + truncate_to_char_boundary(&mut body, MAX_VALIDATION_BODY_LEN); + + m.validation_response_status = status; + m.validation_response_body = validation_body::from_string(body.clone()); + + let matchers = grpc_validation_cfg + .request + .response_matcher + .as_ref() + .expect("missing response_matcher"); + + m.validation_success = + httpvalidation::validate_response(matchers, &body, &status, &headers, false); + } + // ---------------------------------------------------- MongoDB validator Some(Validation::MongoDB) => { let uri = globals