forked from mirrors/kingfisher
v1.81.0
This commit is contained in:
parent
e9fa5911a2
commit
4a74e95756
10 changed files with 103 additions and 53 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -10,7 +10,7 @@ env:
|
|||
VCPKG_DOWNLOADS: C:\vcpkg\downloads
|
||||
VCPKG_FEATURE_FLAGS: binarycaching
|
||||
VCPKG_BINARY_SOURCES: clear;x-gha,readwrite
|
||||
RUST_TOOLCHAIN: "1.90"
|
||||
RUST_TOOLCHAIN: "1.92"
|
||||
|
||||
jobs:
|
||||
linux-arm64:
|
||||
|
|
|
|||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -17,7 +17,7 @@ env:
|
|||
VCPKG_DOWNLOADS: C:\vcpkg\downloads
|
||||
VCPKG_FEATURE_FLAGS: binarycaching
|
||||
VCPKG_BINARY_SOURCES: clear;x-gha,readwrite
|
||||
RUST_TOOLCHAIN: "1.90"
|
||||
RUST_TOOLCHAIN: "1.92"
|
||||
|
||||
jobs:
|
||||
# ──────────────── Linux (via Makefile) ────────────────
|
||||
|
|
|
|||
18
Makefile
18
Makefile
|
|
@ -110,11 +110,11 @@ setup-zig:
|
|||
ubuntu-x64: setup-zig # ensures Zig & cargo-zigbuild exist
|
||||
@echo "Checking Rust toolchain…"
|
||||
@$(MAKE) check-rust || { \
|
||||
echo "🦀 Installing Rust 1.90.0 …"; \
|
||||
echo "🦀 Installing Rust 1.92.0 …"; \
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y; \
|
||||
. $$HOME/.cargo/env; \
|
||||
rustup toolchain install 1.90.0; \
|
||||
rustup default 1.90.0; \
|
||||
rustup toolchain install 1.92.0; \
|
||||
rustup default 1.92.0; \
|
||||
}
|
||||
|
||||
@echo "📦 Installing build dependencies (musl, cmake, etc.)…"
|
||||
|
|
@ -150,11 +150,11 @@ ubuntu-x64: setup-zig # ensures Zig & cargo-zigbuild exist
|
|||
ubuntu-arm64: setup-zig # ensures Zig & cargo-zigbuild exist
|
||||
@echo "Checking Rust toolchain…"
|
||||
@$(MAKE) check-rust || { \
|
||||
echo "🦀 Installing Rust 1.90.0 …"; \
|
||||
echo "🦀 Installing Rust 1.92.0 …"; \
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y; \
|
||||
. $$HOME/.cargo/env; \
|
||||
rustup toolchain install 1.90.0; \
|
||||
rustup default 1.90.0; \
|
||||
rustup toolchain install 1.92.0; \
|
||||
rustup default 1.92.0; \
|
||||
}
|
||||
|
||||
@echo "📦 Installing build dependencies (musl, cmake, etc.)…"
|
||||
|
|
@ -248,7 +248,7 @@ endif
|
|||
linux-x64: check-docker create-dockerignore
|
||||
@mkdir -p target/release
|
||||
docker run --platform linux/amd64 --rm \
|
||||
-v "$$(pwd):/src" -w /src rust:1.90-alpine sh -eu -c '\
|
||||
-v "$$(pwd):/src" -w /src rust:1.92-alpine sh -eu -c '\
|
||||
apk add --no-cache \
|
||||
musl-dev \
|
||||
gcc g++ make cmake pkgconfig \
|
||||
|
|
@ -277,7 +277,7 @@ linux-x64: check-docker create-dockerignore
|
|||
linux-arm64: check-docker create-dockerignore
|
||||
@mkdir -p target/release
|
||||
docker run --platform linux/arm64 --rm \
|
||||
-v "$$(pwd):/src" -w /src rust:1.90-alpine sh -eu -c '\
|
||||
-v "$$(pwd):/src" -w /src rust:1.92-alpine sh -eu -c '\
|
||||
apk add --no-cache \
|
||||
musl-dev \
|
||||
gcc g++ make cmake pkgconfig \
|
||||
|
|
@ -388,7 +388,7 @@ check-rust:
|
|||
echo "Rust not found."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
required=1.90.0; \
|
||||
required=1.92.0; \
|
||||
if [ $$(printf '%s\n' "$$required" "$$version" | sort -V | head -n1) != "$$required" ]; then \
|
||||
echo "Rust version $$version is older than required $$required."; \
|
||||
exit 1; \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -47,22 +47,22 @@ rules:
|
|||
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
|
||||
# - 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
|
||||
|
|
@ -27,13 +27,14 @@ rules:
|
|||
content:
|
||||
request:
|
||||
method: GET
|
||||
url: "https://owlbot.info/api/v4/dictionary/owl?format=json"
|
||||
url: "https://www.owlbot.ai/api/login/checkToken"
|
||||
headers:
|
||||
Authorization: "Token {{ TOKEN }}"
|
||||
# Owlbot expects the API key directly in `Authorization`.
|
||||
Authorization: "{{ TOKEN }}"
|
||||
Accept: application/json
|
||||
response_matcher:
|
||||
- report_response: true
|
||||
- type: StatusMatch
|
||||
status: [200]
|
||||
- type: WordMatch
|
||||
words: ['"word"', '"definitions"']
|
||||
words: ['"user"', '"chatbot"']
|
||||
|
|
|
|||
|
|
@ -43,6 +43,20 @@ use crate::{
|
|||
|
||||
use crate::grpc_validation;
|
||||
|
||||
fn preview_body_for_display(body: &str, max_bytes: usize) -> String {
|
||||
if body.len() <= max_bytes {
|
||||
return body.to_string();
|
||||
}
|
||||
|
||||
// `String` slicing must be on a UTF-8 char boundary to avoid panics.
|
||||
let mut end = max_bytes.min(body.len());
|
||||
while end > 0 && !body.is_char_boundary(end) {
|
||||
end -= 1;
|
||||
}
|
||||
|
||||
format!("{}...", &body[..end])
|
||||
}
|
||||
|
||||
/// Result of a direct validation attempt.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct DirectValidationResult {
|
||||
|
|
@ -314,7 +328,7 @@ async fn execute_http_validation(
|
|||
response.text().await.unwrap_or_else(|e| format!("Failed to read response body: {}", e));
|
||||
|
||||
// Truncate body for display if too long
|
||||
let display_body = if body.len() > 500 { format!("{}...", &body[..500]) } else { body.clone() };
|
||||
let display_body = preview_body_for_display(&body, 500);
|
||||
|
||||
// Validate the response
|
||||
let matchers = http_validation.request.response_matcher.as_deref().unwrap_or(&[]);
|
||||
|
|
@ -369,7 +383,7 @@ async fn execute_grpc_validation(
|
|||
}
|
||||
|
||||
// Truncate body for display if too long
|
||||
let display_body = if body.len() > 500 { format!("{}...", &body[..500]) } else { body.clone() };
|
||||
let display_body = preview_body_for_display(&body, 500);
|
||||
|
||||
// Validate the response
|
||||
let matchers = grpc_validation_cfg.request.response_matcher.as_deref().unwrap_or(&[]);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use bytes::Bytes;
|
|||
use h2::client;
|
||||
use http::{header::HeaderName, HeaderMap, HeaderValue, Request, Uri};
|
||||
use liquid::Object;
|
||||
use once_cell::sync::OnceCell;
|
||||
use reqwest::Url;
|
||||
use rustls::{ClientConfig, RootCertStore};
|
||||
use tokio::net::TcpStream;
|
||||
|
|
@ -31,6 +32,21 @@ fn build_root_store() -> Result<RootCertStore> {
|
|||
Ok(roots)
|
||||
}
|
||||
|
||||
fn cached_h2_tls_config() -> Result<Arc<ClientConfig>> {
|
||||
static TLS_CONFIG: OnceCell<Arc<ClientConfig>> = OnceCell::new();
|
||||
|
||||
let cfg = TLS_CONFIG.get_or_try_init(|| -> Result<Arc<ClientConfig>> {
|
||||
// Loading native roots can be relatively expensive; do it once and reuse.
|
||||
let mut cfg = ClientConfig::builder()
|
||||
.with_root_certificates(build_root_store()?)
|
||||
.with_no_client_auth();
|
||||
cfg.alpn_protocols = vec![b"h2".to_vec()];
|
||||
Ok(Arc::new(cfg))
|
||||
})?;
|
||||
|
||||
Ok(Arc::clone(cfg))
|
||||
}
|
||||
|
||||
fn url_to_h2_uri(url: &Url) -> Result<Uri> {
|
||||
let scheme = url.scheme();
|
||||
if scheme != "https" {
|
||||
|
|
@ -94,11 +110,7 @@ pub async fn grpc_unary_call(
|
|||
.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 connector = TlsConnector::from(cached_h2_tls_config()?);
|
||||
let server_name = rustls::pki_types::ServerName::try_from(host.to_string())
|
||||
.map_err(|_| anyhow!("Invalid TLS server name: {host}"))?;
|
||||
|
||||
|
|
|
|||
11
src/main.rs
11
src/main.rs
|
|
@ -82,7 +82,14 @@ 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();
|
||||
match rustls::crypto::ring::default_provider().install_default() {
|
||||
Ok(()) => {}
|
||||
Err(_already_installed) => {
|
||||
// Another crate already installed a provider. This is unusual for a CLI, but
|
||||
// surfacing it makes later TLS issues much easier to diagnose.
|
||||
warn!("rustls crypto provider was already installed; keeping existing provider");
|
||||
}
|
||||
}
|
||||
// Parse command-line arguments
|
||||
let CommandLineArgs { command, global_args } = CommandLineArgs::parse_args();
|
||||
|
||||
|
|
@ -687,7 +694,7 @@ pub fn run_rules_check(args: &RulesCheckArgs) -> Result<()> {
|
|||
matched_term
|
||||
);
|
||||
println!(" Example: {}", example);
|
||||
num_errors += 1;
|
||||
num_warnings += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -693,11 +693,19 @@ async fn timed_validate_single_match<'a>(
|
|||
m.validation_response_status = status;
|
||||
let body_opt = validation_body::from_string(body.clone());
|
||||
m.validation_response_body = body_opt.clone();
|
||||
let matchers = http_validation
|
||||
.request
|
||||
.response_matcher
|
||||
.as_ref()
|
||||
.expect("missing response_matcher");
|
||||
let matchers = match http_validation.request.response_matcher.as_ref() {
|
||||
Some(m) => m,
|
||||
None => {
|
||||
m.validation_success = false;
|
||||
m.validation_response_body = validation_body::from_string(format!(
|
||||
"HTTP validation for rule '{}' is missing `response_matcher`",
|
||||
rule_syntax.name
|
||||
));
|
||||
m.validation_response_status = StatusCode::BAD_REQUEST;
|
||||
commit_and_return(m);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
m.validation_success = httpvalidation::validate_response(
|
||||
matchers,
|
||||
|
|
@ -799,11 +807,19 @@ async fn timed_validate_single_match<'a>(
|
|||
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");
|
||||
let matchers = match grpc_validation_cfg.request.response_matcher.as_ref() {
|
||||
Some(m) => m,
|
||||
None => {
|
||||
m.validation_success = false;
|
||||
m.validation_response_body = validation_body::from_string(format!(
|
||||
"gRPC validation for rule '{}' is missing `response_matcher`",
|
||||
rule_syntax.name
|
||||
));
|
||||
m.validation_response_status = StatusCode::BAD_REQUEST;
|
||||
commit_and_return(m);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
m.validation_success =
|
||||
httpvalidation::validate_response(matchers, &body, &status, &headers, false);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue