diff --git a/CHANGELOG.md b/CHANGELOG.md index 59f655a..4f169a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## [1.33.0] +- Fixed header precedence so custom HTTP validation headers like `Accept` are preserved +- Added new Heroku rule + ## [1.32.0] - Added support for scanning AWS S3 buckets via `--s3-bucket` and optional `--s3-prefix` - Added `--role-arn` and `--aws-local-profile` flags for S3 authentication alongside `KF_AWS_KEY`/`KF_AWS_SECRET` diff --git a/README.md b/README.md index b60ae55..ef68c1f 100644 --- a/README.md +++ b/README.md @@ -444,6 +444,8 @@ KF_SLACK_TOKEN="xoxp-1234..." kingfisher scan \ | `KF_JIRA_TOKEN` | Jira API token | | `KF_SLACK_TOKEN` | Slack API token | | `KF_DOCKER_TOKEN` | Docker registry token (`user:pass` or bearer token). If unset, credentials from the Docker keychain are used | +| `KF_AWS_KEY` and `KF_AWS_SECRET` | AWS Credentials to use with S3 bucket scanning | + Set them temporarily per command: ```bash diff --git a/data/rules/heroku.yml b/data/rules/heroku.yml index 817a2a4..3686e7a 100644 --- a/data/rules/heroku.yml +++ b/data/rules/heroku.yml @@ -32,3 +32,36 @@ rules: - report_response: true - type: StatusMatch status: [200] + - name: Heroku API Key (Platform Key) + id: kingfisher.heroku.2 + pattern: | + (?xi) + \b + ( + HRKU-[A-Z0-9_]{60} + ) + confidence: medium + min_entropy: 4.0 + validation: + type: Http + content: + request: + method: GET + url: "https://api.heroku.com/apps" + headers: + Authorization: "Bearer {{TOKEN}}" + Accept: "application/vnd.heroku+json;version=3" + response_matcher: + - type: StatusMatch + status: [200] + - type: WordMatch + words: + - '"id":' + - '"name":' + match_all_words: true + references: + - https://devcenter.heroku.com/articles/platform-api-quickstart + examples: + - "HRKU-AADVTUYvfjT4nhuJ07bEfAUq9GS3PkTdyWuNBiXYmYMg_____wgAf6OTnGyh" + - "HRKU-AABW9W1iH9NHEIlAABq9nZUq9GS3PkTdyWuNBiXYmYMg_____wV2XYIXxm5p" + - "HRKU-AAWpqREEr2V1gqh6urSXWYUq9GS3PkTdyWuNBiXYmYMg_____wNI1VGijd8y" diff --git a/src/scanner/repos.rs b/src/scanner/repos.rs index ffa29ea..75fa919 100644 --- a/src/scanner/repos.rs +++ b/src/scanner/repos.rs @@ -333,7 +333,6 @@ pub async fn fetch_s3_objects( ProgressBar::hidden() }; - let bucket_name = bucket.to_string(); let pb = progress.clone(); diff --git a/src/validation/httpvalidation.rs b/src/validation/httpvalidation.rs index 1e15605..cc866b3 100644 --- a/src/validation/httpvalidation.rs +++ b/src/validation/httpvalidation.rs @@ -92,13 +92,17 @@ pub fn build_request_builder( (header::ACCEPT_ENCODING, "gzip, deflate, br"), (header::CONNECTION, "keep-alive"), ]; - // Extend custom headers with the standard ones (overwriting any duplicates). - let mut combined_headers = custom_headers; + // Start with the standard headers and then overlay any custom headers so + // caller-specified values take precedence over defaults. + let mut combined_headers = HeaderMap::new(); for (name, value) in &standard_headers { if let Ok(hv) = HeaderValue::from_str(value) { combined_headers.insert(name.clone(), hv); } } + for (name, value) in custom_headers.iter() { + combined_headers.insert(name.clone(), value.clone()); + } request_builder = request_builder.headers(combined_headers); // If a body template is provided, parse and render it @@ -437,12 +441,19 @@ mod tests { .expect("building reqwest client"); let parser = liquid::ParserBuilder::with_stdlib().build().unwrap(); let globals = liquid::Object::new(); - let headers = - BTreeMap::from([("Content-Type".to_string(), "application/json".to_string())]); + let headers = BTreeMap::from([ + ("Content-Type".to_string(), "application/json".to_string()), + ("Accept".to_string(), "application/custom".to_string()), + ]); let url = Url::from_str("https://example.com").unwrap(); let result = - build_request_builder(&client, "GET", &url, &headers, &None, &parser, &globals); - assert!(result.is_ok()); + build_request_builder(&client, "GET", &url, &headers, &None, &parser, &globals) + .expect("building request"); + let req = result.build().expect("finalizing request"); + assert_eq!( + req.headers().get(header::ACCEPT).and_then(|v| v.to_str().ok()), + Some("application/custom"), + ); } #[tokio::test] async fn test_retry_request() {