Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 5 additions & 39 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ on:
# Code Scanning, packages:write for GHCR push, attestations:write for
# actions/attest-build-provenance, contents:write for release-asset upload
# — declare them explicitly at job level. See the docker / attest /
# build-vex / semgrep-sast / iac-scan / grype / sign-artifacts /
# slsa-provenance / upload-release-assets jobs below.
# build-vex / iac-scan / grype / sign-artifacts / slsa-provenance /
# upload-release-assets jobs below. (Semgrep SAST runs from sast.yaml.)
permissions:
contents: read

Expand Down Expand Up @@ -648,43 +648,9 @@ jobs:
if: github.event_name == 'pull_request'
run: make gitleaks

# ─────────────────────────────────────────────────────────────────────────────
# SAST (Semgrep OSS) — PR + push to main.
# Runs the community Rust / security-audit / secrets / OWASP Top Ten rulesets
# and uploads SARIF to the Code Scanning dashboard. No token required — this
# is the free OSS Community Edition; findings appear under the `semgrep` tool
# category in the Security tab.
# ─────────────────────────────────────────────────────────────────────────────
semgrep-sast:
name: 🔎 SAST (Semgrep)
if: github.event_name != 'release'
runs-on: ubuntu-latest
needs: [verify-commits]
permissions:
contents: read
security-events: write
container:
image: returntocorp/semgrep
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Run Semgrep
run: |
semgrep scan \
--config=p/rust \
--config=p/security-audit \
--config=p/secrets \
--config=p/owasp-top-ten \
--sarif --output=semgrep.sarif \
--metrics=off \
--error || true

- name: Upload Semgrep SARIF to Code Scanning
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
sarif_file: semgrep.sarif
category: semgrep
# Semgrep SAST moved to .github/workflows/sast.yaml so it runs on every
# PR and push with no `paths:` filter (Scorecard's SAST check records
# skipped commits as uncovered otherwise).

# ─────────────────────────────────────────────────────────────────────────────
# IaC security scan (Trivy config) — PR + push to main.
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,11 @@ jobs:
env:
# Pinned for reproducibility; bump alongside docs/pyproject.toml.
POETRY_VERSION: "1.8.4"
# pip install (vs curl | python3) because the curl-pipe pattern trips
# Scorecard's Pinned-Dependencies downloadThenRun rule; pip uses
# PyPI's per-wheel sha256 from the Python package index.
run: |
curl -sSL https://install.python-poetry.org | python3 - --version "${POETRY_VERSION}"
python3 -m pip install --user "poetry==${POETRY_VERSION}"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"

- name: Configure Poetry
Expand Down
84 changes: 84 additions & 0 deletions .github/workflows/fuzz.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Copyright (c) 2025 Erick Bourgeois, firestoned
# SPDX-License-Identifier: Apache-2.0
#
# Short-duration fuzzing of 5-Spot's string parsers on every PR and push.
#
# Each target runs for 60 seconds against libFuzzer via cargo-fuzz. This is a
# credible smoke-fuzz, not continuous fuzzing — the Scorecard FuzzingID check
# only requires that some fuzzing infrastructure exists; follow-up work is
# tracked in ~/dev/roadmaps/5spot-code-scanning-remediation.md § Phase 8 to
# integrate with OSS-Fuzz for continuous coverage.
#
# Requires nightly Rust (libfuzzer-sys uses nightly-only build-script hooks).

name: Fuzz

on:
pull_request:
branches:
- main
paths:
- 'src/**/*.rs'
- 'Cargo.toml'
- 'Cargo.lock'
- 'fuzz/**'
- '.github/workflows/fuzz.yaml'
push:
branches:
- main
paths:
- 'src/**/*.rs'
- 'Cargo.toml'
- 'Cargo.lock'
- 'fuzz/**'
- '.github/workflows/fuzz.yaml'

permissions:
contents: read

env:
# Override the cross-compilation linkers set in .cargo/config.toml.
# cargo-fuzz builds with --target x86_64-unknown-linux-gnu, which picks up
# the macOS cross-compiler setting in .cargo/config.toml — that tool is
# not on Linux CI runners, so builds fail with
# `linker x86_64-unknown-linux-gnu-gcc not found`. `cc` is the native GCC.
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: cc
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: cc

jobs:
fuzz:
name: 🎲 Fuzz ${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target:
- parse_duration
- parse_day_ranges
- parse_hour_ranges
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Install nightly Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (moving ref — re-pin quarterly)
with:
toolchain: nightly

- name: Install cargo-fuzz
run: cargo install cargo-fuzz --locked

- name: Cache cargo dependencies
uses: firestoned/github-actions/rust/cache-cargo@53b483254bc648903c364ee3c73a546d0936a91e # v1.3.6

- name: Run fuzz target (60 s)
working-directory: fuzz
run: cargo +nightly fuzz run ${{ matrix.target }} -- -max_total_time=60

- name: Upload crash artifacts on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: fuzz-crash-${{ matrix.target }}
path: fuzz/artifacts/${{ matrix.target }}/
if-no-files-found: ignore
55 changes: 55 additions & 0 deletions .github/workflows/sast.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright (c) 2025 Erick Bourgeois, firestoned
# SPDX-License-Identifier: Apache-2.0
#
# SAST (Semgrep OSS) — always-on. Runs on every PR and every push to main
# with no `paths:` filter so Scorecard's SAST check records every commit
# as covered (the path-gated Semgrep job in build.yaml was producing
# uncovered-commit events on dependabot and docs-only PRs).
#
# Runs the community Rust / security-audit / secrets / OWASP Top Ten
# rulesets and uploads SARIF to the Code Scanning dashboard under the
# `semgrep` category. No token required — this is the free OSS Community
# Edition.

name: SAST

on:
pull_request:
branches:
- main
push:
branches:
- main

permissions:
contents: read

jobs:
semgrep:
name: 🔎 SAST (Semgrep)
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
container:
image: returntocorp/semgrep
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Run Semgrep
run: |
semgrep scan \
--config=p/rust \
--config=p/security-audit \
--config=p/secrets \
--config=p/owasp-top-ten \
--sarif --output=semgrep.sarif \
--metrics=off \
--error || true

- name: Upload Semgrep SARIF to Code Scanning
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
sarif_file: semgrep.sarif
category: semgrep
11 changes: 11 additions & 0 deletions .trivyignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,17 @@ AVD-KSV-0012
# with every other capability dropped and the filesystem read-only.
AVD-KSV-0118

# KSV-0116 — DaemonSet 5spot-reclaim-agent should set
# spec.securityContext.runAsGroup, supplementalGroups[*] and fsGroup to
# integer greater than 0. Architecturally required (same root cause as
# KSV-0012 / KSV-0021 / KSV-0105 / KSV-0118). UID 0 is required to read
# /proc/<pid>/{comm,cmdline} under hidepid=2; UID 0 implies GID 0, so
# runAsGroup > 0 cannot be satisfied without abandoning the root
# requirement already justified above. supplementalGroups / fsGroup are
# irrelevant — the agent mounts only host /proc read-only and has no
# writable volumes whose ownership fsGroup would govern.
AVD-KSV-0116

# ─────────────────────────────────────────────────────────────────────────────
# Dockerfile — Dockerfile, Dockerfile.chainguard
# ─────────────────────────────────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion .vex/CVE-2010-4756.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ cve = "CVE-2010-4756"
status = "not_affected"
justification = "vulnerable_code_not_in_execute_path"
impact_statement = "5-Spot performs no file-system glob expansion; the vulnerable glibc glob() implementation is never called from the controller."
products = ["pkg:oci/5-spot-distroless"]
products = ["pkg:oci/5-spot"]
author = "erick.bourgeois@gmail.com"
timestamp = "2026-04-19T00:00:00Z"
2 changes: 1 addition & 1 deletion .vex/CVE-2018-20796.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ cve = "CVE-2018-20796"
status = "not_affected"
justification = "vulnerable_code_not_in_execute_path"
impact_statement = "5-Spot uses the Rust regex crate, a separate implementation from glibc's posix/regexec.c; the vulnerable C function is never invoked."
products = ["pkg:oci/5-spot-distroless"]
products = ["pkg:oci/5-spot"]
author = "erick.bourgeois@gmail.com"
timestamp = "2026-04-19T00:00:00Z"
2 changes: 1 addition & 1 deletion .vex/CVE-2019-1010022.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ cve = "CVE-2019-1010022"
status = "not_affected"
justification = "vulnerable_code_cannot_be_controlled_by_adversary"
impact_statement = "The controller does not execute adversary-supplied native code; the pre-conditions required to reach the glibc stack-guard bypass do not exist in this workload."
products = ["pkg:oci/5-spot-distroless"]
products = ["pkg:oci/5-spot"]
author = "erick.bourgeois@gmail.com"
timestamp = "2026-04-19T00:00:00Z"
2 changes: 1 addition & 1 deletion .vex/CVE-2019-1010023.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ cve = "CVE-2019-1010023"
status = "not_affected"
justification = "vulnerable_code_not_in_execute_path"
impact_statement = "The Distroless image ships no ldd or shell; the vulnerable workflow (running ldd on an adversary-supplied ELF) cannot occur at runtime."
products = ["pkg:oci/5-spot-distroless"]
products = ["pkg:oci/5-spot"]
author = "erick.bourgeois@gmail.com"
timestamp = "2026-04-19T00:00:00Z"
2 changes: 1 addition & 1 deletion .vex/CVE-2019-1010024.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ cve = "CVE-2019-1010024"
status = "not_affected"
justification = "vulnerable_code_cannot_be_controlled_by_adversary"
impact_statement = "An attacker has no primitive within the 5-Spot controller to leak cached stack/heap addresses, so the ASLR-bypass precondition is absent."
products = ["pkg:oci/5-spot-distroless"]
products = ["pkg:oci/5-spot"]
author = "erick.bourgeois@gmail.com"
timestamp = "2026-04-19T00:00:00Z"
2 changes: 1 addition & 1 deletion .vex/CVE-2019-1010025.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ cve = "CVE-2019-1010025"
status = "not_affected"
justification = "vulnerable_code_cannot_be_controlled_by_adversary"
impact_statement = "5-Spot exposes no interface that leaks raw process memory; the pre-requisite local read primitive for this information-disclosure bug does not exist."
products = ["pkg:oci/5-spot-distroless"]
products = ["pkg:oci/5-spot"]
author = "erick.bourgeois@gmail.com"
timestamp = "2026-04-19T00:00:00Z"
2 changes: 1 addition & 1 deletion .vex/CVE-2019-9192.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ cve = "CVE-2019-9192"
status = "not_affected"
justification = "vulnerable_code_not_in_execute_path"
impact_statement = "5-Spot uses the Rust regex crate, not glibc's POSIX regex; the vulnerable check_dst_limits_calc_pos_1 function is never invoked."
products = ["pkg:oci/5-spot-distroless"]
products = ["pkg:oci/5-spot"]
author = "erick.bourgeois@gmail.com"
timestamp = "2026-04-19T00:00:00Z"
2 changes: 1 addition & 1 deletion .vex/CVE-2026-27171.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ cve = "CVE-2026-27171"
status = "not_affected"
justification = "vulnerable_code_not_in_execute_path"
impact_statement = "The 5-Spot controller binary has no link-time or runtime dependency on zlib; the vulnerable crc32_combine code path is never executed."
products = ["pkg:oci/5-spot-distroless"]
products = ["pkg:oci/5-spot"]
author = "erick.bourgeois@gmail.com"
timestamp = "2026-04-19T00:00:00Z"
2 changes: 1 addition & 1 deletion .vex/CVE-2026-4046.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ cve = "CVE-2026-4046"
status = "not_affected"
justification = "vulnerable_code_not_in_execute_path"
impact_statement = "5-Spot performs no character-set conversions; the glibc iconv() path exercised by this CVE is unreachable from the controller binary."
products = ["pkg:oci/5-spot-distroless"]
products = ["pkg:oci/5-spot"]
author = "erick.bourgeois@gmail.com"
timestamp = "2026-04-19T00:00:00Z"
2 changes: 1 addition & 1 deletion .vex/CVE-2026-4437.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ cve = "CVE-2026-4437"
status = "not_affected"
justification = "vulnerable_code_cannot_be_controlled_by_adversary"
impact_statement = "5-Spot only resolves names against the in-cluster DNS resolver configured by the platform operator; no adversary-controlled DNS path reaches the glibc resolver."
products = ["pkg:oci/5-spot-distroless"]
products = ["pkg:oci/5-spot"]
author = "erick.bourgeois@gmail.com"
timestamp = "2026-04-19T00:00:00Z"
2 changes: 1 addition & 1 deletion .vex/CVE-2026-4438.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ cve = "CVE-2026-4438"
status = "not_affected"
justification = "vulnerable_code_cannot_be_controlled_by_adversary"
impact_statement = "5-Spot performs no reverse DNS lookups; gethostbyaddr is never invoked and no attacker-controlled address can reach the vulnerable code."
products = ["pkg:oci/5-spot-distroless"]
products = ["pkg:oci/5-spot"]
author = "erick.bourgeois@gmail.com"
timestamp = "2026-04-19T00:00:00Z"
12 changes: 12 additions & 0 deletions .vex/CVE-2026-5358.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# glibc nis_local_principal() buffer overflow via spoofed UDP NIS response.
# NIS is a 1980s identity service deprecated in glibc 2.26. 5-Spot does not
# use NIS for anything: authentication against the Kubernetes API is TLS +
# bearer token via kube-rs; there is no nss-nis configuration, no yp.conf
# lookup, and no Cargo dependency that binds libnsl.
cve = "CVE-2026-5358"
status = "not_affected"
justification = "vulnerable_code_not_in_execute_path"
impact_statement = "5-Spot does not use NIS for identity; the vulnerable nis_local_principal() code path is never called from the controller."
products = ["pkg:oci/5-spot"]
author = "erick.bourgeois@gmail.com"
timestamp = "2026-04-22T00:00:00Z"
13 changes: 13 additions & 0 deletions .vex/CVE-2026-5450.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# glibc scanf-family heap buffer overflow when the "%mc" conversion is used
# with an explicit width > 1024. 5-Spot is Rust; no 5-Spot source file calls
# scanf(), sscanf(), fscanf() or any C stdio conversion — I/O is via
# Tokio/Hyper with Rust's typed parsers (serde_json, kube-rs decoders).
# scanf lives in the base image's libc6 but is not reached from the
# controller's execute path.
cve = "CVE-2026-5450"
status = "not_affected"
justification = "vulnerable_code_not_in_execute_path"
impact_statement = "5-Spot is Rust and never invokes the scanf family; the vulnerable %mc conversion is not reachable from the controller."
products = ["pkg:oci/5-spot"]
author = "erick.bourgeois@gmail.com"
timestamp = "2026-04-22T00:00:00Z"
13 changes: 13 additions & 0 deletions .vex/CVE-2026-5928.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# glibc ungetwc() buffer under-read via a pointer-swap bug in
# _IO_wdefault_pbackfail. Only triggers with non-standard multibyte
# encodings that have overlaps between single-byte and multibyte forms;
# NVD explicitly notes it cannot occur with standard Unicode.
# 5-Spot is Rust, uses UTF-8 everywhere, and never calls ungetwc() or the
# wide-char stdio family. Controller I/O is typed Rust, not libio.
cve = "CVE-2026-5928"
status = "not_affected"
justification = "vulnerable_code_not_in_execute_path"
impact_statement = "5-Spot uses Rust UTF-8 I/O exclusively and never calls ungetwc(); the vulnerable wide-char stream path is unreachable."
products = ["pkg:oci/5-spot"]
author = "erick.bourgeois@gmail.com"
timestamp = "2026-04-22T00:00:00Z"
4 changes: 2 additions & 2 deletions .vex/GHSA-cq8v-f236-94qc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ status = "not_affected"
justification = "vulnerable_code_not_in_execute_path"
impact_statement = "Vulnerable rand 0.8.6 is pulled transitively by tungstenite 0.21 for websocket frame masking only. The advisory requires ThreadRng to be invoked from within a custom log::Log implementation during reseed; 5-Spot's tracing-subscriber stack does not call into rand from any logger emit path, so the unsound code is not in the execute path."
products = [
"pkg:oci/5-spot-chainguard",
"pkg:oci/5-spot-distroless",
"pkg:oci/5-spot",
"pkg:oci/5-spot",
]
author = "erick.bourgeois@gmail.com"
timestamp = "2026-04-19T00:00:00Z"
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading