Added support for HTTP request bodies in rule validation. Added mistral and perplexity rule

This commit is contained in:
Mick Grove 2025-07-08 17:49:12 -07:00
commit cd4f626502
11 changed files with 160 additions and 5 deletions

View file

@ -2,6 +2,12 @@
All notable changes to this project will be documented in this file.
## [1.20.0]
- Removed confirmation prompt when user provides --self-update flag
- Added support for HTTP request bodies in rule validation
- Added rules for mistral, perplexity
## [1.19.0]
- JSON output was missing committer name and email
- Fixed Gitlab rule which was incorrectly identifying certain tokens as valid

View file

@ -10,7 +10,7 @@ publish = false
[package]
name = "kingfisher"
version = "1.19.0"
version = "1.20.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View file

@ -230,7 +230,18 @@ _If no token is provided Kingfisher still works for public repositories._
### Update Checks
Kingfisher checks for newer releases on GitHub each time it starts and exits, printing whether a new version is available. Use `--self-update` to automatically download and replace the binary when an update is found. Add `--no-update-check` to disable these checks entirely.
Kingfisher automatically queries GitHub for a newer release when it starts and tells you whether an update is available.
- **Hands-free updates** Add `--self-update` to any Kingfisher command
If a newer version exists, Kingfisher will download it, replace the running
binary, and re-launch itself with the **exact same arguments**.
If the update fails or no newer release is found, the current run
proceeds as normal
- **Disable version checks** Pass `--no-update-check` to skip both the
startup and shutdown checks entirely
---

View file

@ -19,7 +19,7 @@ rules:
(?xi)
\b
alibaba
(?:.|[\n\r]){0,16}?
(?:.|[\n\r]){0,32}?
\b
(
[a-z0-9]{30}
@ -30,4 +30,37 @@ rules:
examples:
- alibaba_secret = 7jkWdTjKLnSlGddwPR5gBn65PHcZG6
- alibaba-token = aJHKLnSlGddwPR5g7jkWdTBn65PHc5
validation:
type: Http
content:
request:
method: GET
url: >
{% assign nonce = "" | uuid | url_encode -%}
{% assign ts = "" | iso_timestamp | url_encode -%}
{% capture qs -%}
AccessKeyId={{ AKID }}&
Action=GetCallerIdentity&
Format=JSON&
SignatureMethod=HMAC-SHA1&
SignatureNonce={{ nonce }}&
SignatureVersion=1.0&
Timestamp={{ ts }}&
Version=2015-04-01
{%- endcapture %}
{% capture sts -%}GET&%2F&{{ qs | url_encode }}{%- endcapture %}
{% assign key = TOKEN | append: '&' -%}
{% assign sig = sts | hmac_sha256: key | b64enc | url_encode -%}
https://sts.aliyuncs.com/?{{ qs }}&Signature={{ sig }}
headers:
Accept: application/json
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: WordMatch
words: ['"Arn"']
match_all_words: true
depends_on_rule:
- rule_id: kingfisher.alibabacloud.1
variable: AKID

41
data/rules/mistral.yml Normal file
View file

@ -0,0 +1,41 @@
rules:
- name: Mistral AI API Key
id: kingfisher.mistral.1
pattern: |
(?xi)
\b
mistral
(?:.|[\n\r]){0,32}?
(?:SECRET|PRIVATE|ACCESS|KEY|TOKEN)
(?:.|[\n\r]){0,32}?
\b
(
[A-Z0-9]{32}
)
\b
min_entropy: 3.0
confidence: medium
examples:
- mistral_token = 47cFZMzkoEo9DBapfvhrmMst3zfV2EIh
- mistral_api_key = olI6zlb3F0jO8jjDQUi34skQLPzvox84
- 'mistral secret: kMrbwHjG78fYzneT934Pn34NQxVSxniq'
references:
- 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}
validation:
type: Http
content:
request:
method: GET
url: https://api.mistral.ai/v1/models
headers:
Authorization: "Bearer {{ TOKEN }}"
Accept: application/json
response_matcher:
- report_response: true
- type: StatusMatch
status: [200]
- type: WordMatch
words: ['"data"']

37
data/rules/perplexity.yml Normal file
View file

@ -0,0 +1,37 @@
rules:
- name: Perplexity AI API Key
id: kingfisher.perplexity.1
pattern: |
(?xi)
\b
(
pplx-[A-Za-z0-9]{48}
)
\b
min_entropy: 3.8
confidence: medium
examples:
- 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
- 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
- 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
- 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
references:
- https://docs.perplexity.ai/guides/getting-started
- https://pplx.readme.io/reference/post_chat_completions
- https://www.perplexity.ai/hub/blog/introducing-pplx-api
- https://docs.litellm.ai/docs/providers/perplexity
- https://developers.cloudflare.com/ai-gateway/providers/perplexity/
validation:
type: Http
content:
request:
method: POST
url: https://api.perplexity.ai/chat/completions
headers:
Authorization: "Bearer {{ TOKEN }}"
Content-Type: application/json
body: '{"model": "kingfisher", "messages": [{ "role": "user", "content": "." }]}'
response_matcher:
- report_response: false
- type: WordMatch
match_all_words: false

View file

@ -979,6 +979,7 @@ mod test {
method: "GET".to_string(),
url: "https://example.com".to_string(),
headers: BTreeMap::new(),
body: None,
response_matcher: Some(vec![]),
multipart: None,
response_is_html: false,

View file

@ -65,6 +65,8 @@ pub struct HttpRequest {
#[serde(default)]
pub headers: BTreeMap<String, String>,
#[serde(default)]
pub body: Option<String>,
#[serde(default)]
pub response_matcher: Option<Vec<ResponseMatcher>>,
#[serde(default)]
pub multipart: Option<MultipartConfig>,

View file

@ -69,6 +69,7 @@ pub fn check_for_update(global_args: &GlobalArgs, base_url: Option<&str>) -> Opt
.repo_name("kingfisher")
.bin_name("kingfisher")
.show_download_progress(false)
.no_confirm(true) // Don't prompt for confirmation when selfupdating
.current_version(cargo_crate_version!());
// Allow tests to point at a mock HTTP server.

View file

@ -353,6 +353,7 @@ async fn timed_validate_single_match<'a>(
&http_validation.request.method,
&url,
&http_validation.request.headers,
&http_validation.request.body,
parser,
&globals,
) {

View file

@ -63,6 +63,7 @@ pub fn build_request_builder(
method_str: &str,
url: &Url,
headers: &BTreeMap<String, String>,
body: &Option<String>,
parser: &liquid::Parser,
globals: &liquid::Object,
) -> Result<RequestBuilder, String> {
@ -99,6 +100,19 @@ pub fn build_request_builder(
}
}
request_builder = request_builder.headers(combined_headers);
// If a body template is provided, parse and render it
if let Some(body_template) = body {
let template = parser
.parse(body_template)
.map_err(|e| format!("Error parsing body template: {}", e))?;
let rendered_body = template
.render(globals)
.map_err(|e| format!("Error rendering body template: {}", e))?;
request_builder = request_builder.body(rendered_body);
}
Ok(request_builder)
}
@ -427,7 +441,15 @@ mod tests {
let headers =
BTreeMap::from([("Content-Type".to_string(), "application/json".to_string())]);
let url = Url::from_str("https://example.com").unwrap();
let result = build_request_builder(&client, "GET", &url, &headers, &parser, &globals);
let result = build_request_builder(
&client,
"GET",
&url,
&headers,
&None,
&parser,
&globals,
);
assert!(result.is_ok());
}
#[tokio::test]