openssf scorecard suggested improvements

Made-with: Cursor
This commit is contained in:
Mick Grove 2026-03-19 23:38:17 -07:00
commit 5fa4ce59b7
16 changed files with 204 additions and 39 deletions

View file

@ -0,0 +1,5 @@
FROM gcr.io/oss-fuzz-base/base-builder-rust
COPY . $SRC/kingfisher
COPY .clusterfuzzlite/build.sh $SRC/build.sh
WORKDIR $SRC/kingfisher

20
.clusterfuzzlite/build.sh Executable file
View file

@ -0,0 +1,20 @@
#!/bin/bash -eu
# Install build dependencies required by vendored vectorscan (C/C++)
apt-get update -qq
apt-get install -y --no-install-recommends \
cmake pkg-config libboost-dev patch ragel
cd "$SRC/kingfisher"
# Build all fuzz targets in release mode with debug assertions
cargo fuzz build -O --debug-assertions
# Copy built fuzz binaries to the output directory
FUZZ_TARGET_OUTPUT_DIR=fuzz/target/x86_64-unknown-linux-gnu/release
for f in fuzz/fuzz_targets/*.rs; do
FUZZ_TARGET_NAME=$(basename "${f%.*}")
if [ -f "$FUZZ_TARGET_OUTPUT_DIR/$FUZZ_TARGET_NAME" ]; then
cp "$FUZZ_TARGET_OUTPUT_DIR/$FUZZ_TARGET_NAME" "$OUT/"
fi
done

View file

@ -0,0 +1 @@
language: rust

View file

@ -11,3 +11,16 @@ updates:
schedule:
interval: "weekly"
open-pull-requests-limit: 10
ignore:
- dependency-name: "actions/checkout"
update-types: ["version-update:semver-major"]
- dependency-name: "actions/upload-artifact"
update-types: ["version-update:semver-major"]
- dependency-name: "actions/download-artifact"
update-types: ["version-update:semver-major"]
- dependency-name: "docker/login-action"
update-types: ["version-update:semver-major"]
- dependency-name: "docker/setup-buildx-action"
update-types: ["version-update:semver-major"]
- dependency-name: "docker/build-push-action"
update-types: ["version-update:semver-major"]

33
.github/workflows/cflite_batch.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: ClusterFuzzLite batch fuzzing
on:
schedule:
- cron: '0 3 * * 1' # Weekly on Monday at 03:00 UTC
permissions: read-all
jobs:
BatchFuzzing:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
sanitizer:
- address
steps:
- name: Build Fuzzers (${{ matrix.sanitizer }})
id: build
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
with:
language: rust
sanitizer: ${{ matrix.sanitizer }}
- name: Run Fuzzers (${{ matrix.sanitizer }})
id: run
uses: google/clusterfuzzlite/actions/run_fuzzers@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
fuzz-seconds: 3600
mode: 'batch'
sanitizer: ${{ matrix.sanitizer }}
output-sarif: true

38
.github/workflows/cflite_pr.yml vendored Normal file
View file

@ -0,0 +1,38 @@
name: ClusterFuzzLite PR fuzzing
on:
pull_request:
branches:
- main
permissions: read-all
jobs:
PR:
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-${{ matrix.sanitizer }}-${{ github.ref }}
cancel-in-progress: true
strategy:
fail-fast: false
matrix:
sanitizer:
- address
steps:
- name: Build Fuzzers (${{ matrix.sanitizer }})
id: build
uses: google/clusterfuzzlite/actions/build_fuzzers@v1
with:
language: rust
github-token: ${{ secrets.GITHUB_TOKEN }}
sanitizer: ${{ matrix.sanitizer }}
- name: Run Fuzzers (${{ matrix.sanitizer }})
id: run
uses: google/clusterfuzzlite/actions/run_fuzzers@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
fuzz-seconds: 300
mode: 'code-change'
sanitizer: ${{ matrix.sanitizer }}
output-sarif: true

View file

@ -36,7 +36,7 @@ jobs:
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: kingfisher-${{ runner.os }}-${{ runner.arch }}
cache-on-failure: true
@ -72,7 +72,7 @@ jobs:
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: kingfisher-${{ runner.os }}-${{ runner.arch }}
cache-on-failure: true
@ -96,7 +96,7 @@ jobs:
- uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 # master
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: kingfisher-${{ runner.os }}-${{ runner.arch }}
cache-on-failure: true
@ -132,7 +132,7 @@ jobs:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Set up MSYS2
uses: msys2/setup-msys2@61f9e5e925871ba6c9e3e8da24ede83ea27fa91f # v2.27.0
uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2.30.0
with:
msystem: ${{ matrix.msystem }}
update: true
@ -140,7 +140,7 @@ jobs:
make
git
- uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: kingfisher-${{ runner.os }}-${{ runner.arch }}
cache-on-failure: true

View file

@ -45,7 +45,7 @@ jobs:
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: kingfisher-${{ runner.os }}-${{ runner.arch }}
cache-on-failure: true
@ -120,7 +120,7 @@ jobs:
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: kingfisher-${{ runner.os }}-${{ runner.arch }}
cache-on-failure: true
@ -185,7 +185,7 @@ jobs:
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: kingfisher-${{ runner.os }}-${{ runner.arch }}
cache-on-failure: true
@ -224,7 +224,7 @@ jobs:
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: kingfisher-${{ runner.os }}-${{ runner.arch }}
cache-on-failure: true
@ -274,7 +274,7 @@ jobs:
toolchain: ${{ env.RUST_TOOLCHAIN }}
- name: Set up MSYS2
uses: msys2/setup-msys2@61f9e5e925871ba6c9e3e8da24ede83ea27fa91f # v2.27.0
uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2.30.0
with:
msystem: ${{ matrix.msystem }}
update: true
@ -282,7 +282,7 @@ jobs:
make
git
- uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: kingfisher-${{ runner.os }}-${{ runner.arch }}
cache-on-failure: true
@ -390,7 +390,7 @@ jobs:
# ── create the release using just that snippet ─────────────────────
- name: Create release & upload assets
uses: ncipollo/release-action@440c8c1cb0ed28b9f43e4d1d670870f059653174 # v1.16.0
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
with:
tag: ${{ steps.version.outputs.tag }}
name: "Kingfisher ${{ steps.version.outputs.tag }}"

View file

@ -2,6 +2,13 @@
All notable changes to this project will be documented in this file.
## [v1.91.0]
- Remediated current RustSec vulnerability findings by upgrading core dependencies including `gix`, `mysql_async`, `axum`, `indicatif`, `quick-xml`, and `console`.
- Added `make audit-deps` to run `cargo audit` locally and report vulnerable dependencies.
- Refreshed pinned GitHub Actions for `swatinem/rust-cache`, `msys2/setup-msys2`, and `ncipollo/release-action`, and configured Dependabot to ignore selected GitHub Action major-version bumps.
- OpenSSF Scorecard hardening: added `SECURITY.md`, `.github/dependabot.yml`, pinned all GitHub Actions by SHA, fixed dangerous workflow expression injection patterns, added top-level `permissions: {}` to `pypi.yml`, and added SLSA provenance generation for releases.
- Added ClusterFuzzLite integration with four fuzz targets (entropy, location mapping, base64 decoding, span deduplication) and a `make fuzz` target for local fuzzing.
## [v1.90.0]
- Added `--max-validation-response-length <BYTES>` for `scan` to control validation response storage truncation (default: `2048`, `0` disables truncation).
- Updated `--full-validation-response` to bypass both validation storage truncation and reporter truncation, preserving complete response bodies end-to-end for parsing/reporting workflows.

View file

@ -48,7 +48,7 @@ http = "1.4"
[package]
name = "kingfisher"
version = "1.90.0"
version = "1.91.0"
description = "MongoDB's blazingly fast and accurate secret scanning and validation tool"
edition.workspace = true
rust-version.workspace = true
@ -94,7 +94,7 @@ clap = { version = "4.5", features = [
anyhow = "1.0"
bstr = { version = "1.12", features = ["serde"] }
fixedbitset = "0.5"
gix = { version = "0.73", features = ["max-performance-safe", "serde", "blocking-network-client"] }
gix = { version = "0.80", features = ["max-performance-safe", "serde", "blocking-network-client"] }
ignore = "0.4"
petgraph = "0.8"
roaring = "0.10"
@ -106,7 +106,7 @@ smallvec = { version = "1", features = [
"union",
] }
tracing = "0.1.43"
indicatif = { version = "0.17", features = ["improved_unicode"] }
indicatif = { version = "0.18", features = ["improved_unicode"] }
rayon = "1.11"
hex = "0.4.3"
vectorscan-rs = "0.0.5"
@ -130,7 +130,7 @@ reqwest = { version = "0.12", default-features = false, features = [
"blocking",
"multipart",
] }
axum = { version = "0.7", default-features = false, features = ["tokio", "http1"] }
axum = { version = "0.8", default-features = false, features = ["tokio", "http1"] }
chrono = "0.4.42"
@ -140,7 +140,7 @@ base64 = "0.22.1"
crossbeam-channel = "0.5.15"
indenter = "0.3.4"
serde-sarif = "0.4"
console = "0.15.11"
console = "0.16.3"
time = "0.3.44"
tempfile = "3.23.0"
num_cpus = "1.17.0"
@ -154,7 +154,7 @@ base32 = "0.5.1"
crossbeam-skiplist = "0.1.3"
tokio-postgres = { version = "0.7", default-features = false, features = ["runtime"] }
mongodb = { version = "3.4", default-features = false, features = ["rustls-tls", "aws-auth", "compat-3-0-0", "dns-resolver"] }
mysql_async = { version = "0.34.2", default-features = false, features = ["default-rustls"] }
mysql_async = { version = "0.36.1", default-features = false, features = ["default-rustls"] }
bson = "2.15.0"
ring = "0.17.14"
pem = "3.0.6"
@ -193,7 +193,6 @@ tree-sitter-regex = "0.25.0"
tree_magic_mini = "3.2"
content_inspector = "0.2.4"
rustc-hash = "2.1.1"
term_size = "0.3.2"
bzip2-rs = "0.1.2"
zip = { version = "2.4.2", default-features = false, features = ["deflate", "deflate64", "time"] }
tar = "0.4.44"
@ -212,7 +211,7 @@ sha2 = "0.10.9"
strum_macros = "0.27.2"
humantime = "2.3.0"
path-dedot = "3.1.1"
quick-xml = {version = "0.38.4", features = ["serde","serialize"] }
quick-xml = { version = "0.39.2", features = ["serde", "serialize"] }
rustls = "0.23.35"
tokio-postgres-rustls = "0.13.0"
rustls-native-certs = "0.8.2"
@ -227,7 +226,6 @@ bloomfilter = "3.0.1"
uuid = "1.19.0"
rand = "0.9.2"
percent-encoding = "2.3.2"
atty = "0.2.14"
self_update = { version = "0.42.0", default-features = false, features = ["rustls", "archive-tar", "archive-zip", "compression-flate2"] }
semver = "1.0.27"
globset = "0.4.18"

View file

@ -52,7 +52,7 @@ ARCHIVE_CMD = $(TAR_CMD) $(TAR_OPTS)
SUDO_CMD := $(shell command -v sudo 2>/dev/null)
.PHONY: default help create-dockerignore ubuntu-x64 ubuntu-arm64 linux-x64 linux-arm64 darwin-arm64 darwin-x64 windows-x64 windows-arm64 windows \
linux darwin all list-archives check-docker check-rust clean tests
linux darwin all list-archives check-docker check-rust clean tests audit-deps fuzz
default: help
@ -71,6 +71,8 @@ help:
@echo " all"
@echo " list-archives"
@echo " tests"
@echo " audit-deps Run cargo-audit to report vulnerable dependencies"
@echo " fuzz Run fuzz targets (FUZZ_SECONDS=N to control duration, default 60s)"
create-dockerignore:
@echo "target/" > .dockerignore
@ -691,6 +693,44 @@ tests:
cargo test --workspace --all-targets; \
fi
audit-deps:
@echo "🔍 checking for cargo-audit …"
@if command -v cargo-audit >/dev/null 2>&1; then \
echo "✅ cargo-audit already present"; \
else \
echo "📦 installing cargo-audit …"; \
cargo install --locked cargo-audit; \
fi
@echo "▶ auditing dependency vulnerabilities …"
@cargo audit
fuzz:
@echo "🐛 Running fuzz targets (cargo-fuzz required, nightly Rust required)…"
@command -v cargo-fuzz >/dev/null 2>&1 || { \
echo "📦 installing cargo-fuzz …"; \
cargo install cargo-fuzz; \
}
@rustup toolchain list | grep -q nightly || { \
echo "📦 installing nightly toolchain …"; \
rustup toolchain install nightly; \
}
@fuzz_seconds=$${FUZZ_SECONDS:-60}; \
NIGHTLY_PATH="$$HOME/.rustup/toolchains/nightly-$$(rustc -vV | awk '/^host:/{print $$2}')/bin"; \
if [ ! -d "$$NIGHTLY_PATH" ]; then \
echo "❌ Nightly toolchain not found at $$NIGHTLY_PATH"; \
exit 1; \
fi; \
export PATH="$$NIGHTLY_PATH:$$PATH"; \
echo "Using rustc: $$(which rustc) ($$(rustc --version))"; \
for target in fuzz_entropy fuzz_location fuzz_base64 fuzz_span; do \
echo "▶ fuzzing $$target for $${fuzz_seconds}s …"; \
cargo fuzz run $$target -- \
-max_total_time=$${fuzz_seconds} \
-max_len=4096 || { echo "$$target found a crash"; exit 1; }; \
echo "$$target passed"; \
done
@echo "🎉 All fuzz targets passed"
clean:
@echo "Cleaning build artifacts..."
cargo clean

View file

@ -39,10 +39,10 @@ bstr.workspace = true
memchr = "2.7"
# Git types (minimal, for ObjectId and Time)
gix = { version = "0.73", default-features = false, features = ["serde"] }
gix = { version = "0.80", default-features = false, features = ["serde"] }
# Console formatting
console = "0.15"
console = "0.16"
# Language detection for content types
tokei = "14.0.0"

View file

@ -158,7 +158,7 @@ reqwest = { version = "0.12", default-features = false, features = [
tokio = { version = "1.48", features = ["net", "time", "sync"], optional = true }
liquid = { version = "0.26", optional = true }
liquid-core = { version = "0.26", optional = true }
quick-xml = { version = "0.38", features = ["serde", "serialize"], optional = true }
quick-xml = { version = "0.39", features = ["serde", "serialize"], optional = true }
sha1 = { workspace = true, optional = true }
chrono = { version = "0.4.42", optional = true }
hmac = { workspace = true, optional = true }
@ -174,7 +174,7 @@ hex = { workspace = true, optional = true }
url = { version = "2.5.7", optional = true }
bson = { version = "2.15.0", optional = true }
mongodb = { version = "3.4", default-features = false, features = ["rustls-tls", "aws-auth", "compat-3-0-0", "dns-resolver"], optional = true }
mysql_async = { version = "0.34.2", default-features = false, features = ["default-rustls"], optional = true }
mysql_async = { version = "0.36.1", default-features = false, features = ["default-rustls"], optional = true }
tokio-postgres = { version = "0.7", default-features = false, features = ["runtime"], optional = true }
tokio-postgres-rustls = { version = "0.13.0", optional = true }
rustls = { version = "0.23.35", optional = true }

View file

@ -28,11 +28,8 @@ pub const MIN_SCANNABLE_BLOB_SIZE: u64 = 20;
// Convert "<seconds> <offset>" -- Time; fallback to the Unix-epoch on parse error
#[inline]
fn parse_sig_time<T: AsRef<[u8]>>(raw: T) -> Time {
match std::str::from_utf8(raw.as_ref()) {
Ok(s) => parse_time(s, None).unwrap_or_else(|_| Time::new(0, 0)),
Err(_) => Time::new(0, 0),
}
fn parse_sig_time(raw: &str) -> Time {
parse_time(raw, None).unwrap_or_else(|_| Time::new(0, 0))
}
/// How blobs are provided to the scanning pipeline.
@ -157,11 +154,22 @@ impl<'a> GitRepoWithMetadataEnumerator<'a> {
continue;
}
};
let committer = &commit.committer;
let committer = match commit.committer() {
Ok(committer) => committer,
Err(err) => {
debug!(
"Failed to decode committer metadata for {}: {err}",
e.commit_oid
);
continue;
}
};
let parsed = Arc::new(CommitMetadata {
commit_id: e.commit_oid,
committer_name: String::from_utf8_lossy(&committer.name).into_owned(),
committer_email: String::from_utf8_lossy(&committer.email).into_owned(),
committer_name: String::from_utf8_lossy(committer.name.as_ref())
.into_owned(),
committer_email: String::from_utf8_lossy(committer.email.as_ref())
.into_owned(),
committer_timestamp: parse_sig_time(committer.time),
});
commit_metadata.insert(e.commit_oid, Arc::clone(&parsed));

View file

@ -33,6 +33,7 @@ use std::{
};
use anyhow::{Context, Result};
use console::Term;
use kingfisher::{
access_map, azure, bitbucket,
cli::{
@ -61,7 +62,6 @@ use kingfisher::{
};
use serde_json::json;
use tempfile::TempDir;
use term_size;
use tokio::runtime::Builder;
use tracing::{error, info, warn};
use tracing_core::metadata::LevelFilter;
@ -268,7 +268,7 @@ async fn async_main(args: CommandLineArgs) -> Result<()> {
);
let paths = &scan_args.input_specifier_args.path_inputs;
let is_dash = paths.iter().any(|p| p.as_os_str() == "-");
if (paths.is_empty() || is_dash) && !atty::is(atty::Stream::Stdin) {
if (paths.is_empty() || is_dash) && !std::io::stdin().is_terminal() {
let mut buf = Vec::new();
std::io::stdin().read_to_end(&mut buf)?;
let stdin_file = temp_dir_path.join("stdin_input");
@ -772,7 +772,7 @@ pub fn run_rules_list(args: &RulesListArgs) -> Result<()> {
match args.output_args.format {
RulesListOutputFormat::Pretty => {
// Determine terminal width if possible, otherwise use default
let term_width = term_size::dimensions().map(|(w, _)| w).unwrap_or(120);
let term_width = usize::from(Term::stdout().size().1);
// First pass: calculate column widths
let max_name_width = resolved.iter().map(|r| r.name().len()).max().unwrap_or(0).max(4); // "Rule" header
let max_id_width = resolved.iter().map(|r| r.id().len()).max().unwrap_or(0).max(2); // "ID" header

View file

@ -598,8 +598,10 @@ impl DetailsReporter {
// String::from_utf8_lossy(cmd.message.lines().next().unwrap_or(&[],),).
// into_owned();
let atime =
cmd.committer_timestamp.format(gix::date::time::format::SHORT.clone()).to_string();
let atime = cmd
.committer_timestamp
.format(gix::date::time::format::SHORT.clone())
.unwrap_or_else(|_| cmd.committer_timestamp.seconds.to_string());
let git_metadata = serde_json::json!({
"repository_url": repository_url,