- Fixed header precedence so custom HTTP validation headers like "Accept" are preserved

- Added new Heroku rule
This commit is contained in:
Mick Grove 2025-08-04 19:32:19 -07:00
commit 28fd24c9b4
5 changed files with 56 additions and 7 deletions

View file

@ -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`

View file

@ -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

View file

@ -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"

View file

@ -333,7 +333,6 @@ pub async fn fetch_s3_objects(
ProgressBar::hidden()
};
let bucket_name = bucket.to_string();
let pb = progress.clone();

View file

@ -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() {