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
98 changes: 0 additions & 98 deletions .github/workflows/publish.yml

This file was deleted.

38 changes: 34 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#
# Secrets via env: GITHUB_TOKEN, SLACK_WEBHOOK_URL or SLACK_BOT_TOKEN+SLACK_CHANNEL_ID

FROM python:3.11-slim AS base
FROM python:3.12-slim AS base

# Pin scanner versions so "new vs resolved" diffs aren't polluted by upstream churn.
ARG OSV_SCANNER_VERSION=1.9.2
Expand All @@ -23,9 +23,14 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
PIP_NO_CACHE_DIR=1

# Refresh OS packages with the latest security patches before installing.
# `apt-get upgrade -y` is what closes Docker Scout's "fixable critical or high
# vulnerabilities" findings against the base image's OS layer.
RUN apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y --no-install-recommends \
git ca-certificates curl tar xz-utils \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

# --- osv-scanner ----------------------------------------------------------
Expand Down Expand Up @@ -57,7 +62,11 @@ RUN set -eux; \
gitleaks version

# --- semgrep (pip — official channel) -------------------------------------
RUN pip install --no-cache-dir "semgrep==${SEMGREP_VERSION}" \
# python:3.12-slim no longer ships setuptools, and semgrep's transitive
# opentelemetry-instrumentation dep imports `pkg_resources` (provided by
# setuptools). Pin setuptools < 80 because newer setuptools dropped the
# bundled `pkg_resources` module.
RUN pip install --no-cache-dir "setuptools>=70,<80" "semgrep==${SEMGREP_VERSION}" \
&& semgrep --version

# --- trivy (Aqua) — vuln + secret + iac + license, all in one ------------
Expand All @@ -76,8 +85,13 @@ RUN set -eux; \
trivy --version

# Pre-cache the trivy DBs at build time so first-run is fast and offline-OK.
# (The runner passes --skip-db-update.)
RUN trivy image --download-db-only && trivy image --download-java-db-only
# (The runner passes --skip-db-update.) Cache lives in a world-readable
# location so the non-root scanner user (added below) can read it.
ENV TRIVY_CACHE_DIR=/var/cache/trivy
RUN mkdir -p $TRIVY_CACHE_DIR \
&& trivy --cache-dir $TRIVY_CACHE_DIR image --download-db-only \
&& trivy --cache-dir $TRIVY_CACHE_DIR image --download-java-db-only \
&& chmod -R a+rX $TRIVY_CACHE_DIR

# --- trufflehog — verified secret detection ------------------------------
RUN set -eux; \
Expand Down Expand Up @@ -124,6 +138,22 @@ RUN pip install --no-cache-dir /app
# leaving anonymous volumes behind on each run).
RUN mkdir -p /config /rules /work

# --- non-root user -------------------------------------------------------
# Run the scanner as an unprivileged user. /work is the only path the
# container itself writes to (clones, SBOM output, gitleaks temp report);
# /config is bind-mounted read-only and /rules is read-only. The trivy DB
# cache (set above) is already world-readable.
#
# uid/gid 1000 matches the typical first non-root user on Linux hosts, so
# files written under a host-mounted /work end up with a recognizable owner.
RUN groupadd --system --gid 1000 scanner \
&& useradd --system --uid 1000 --gid 1000 \
--home-dir /home/scanner --create-home --shell /sbin/nologin \
scanner \
&& chown -R scanner:scanner /work /home/scanner

USER scanner

# Default entrypoint runs the scanner against /config/config.yaml.
ENTRYPOINT ["python", "-m", "security_scan", "--config", "/config/config.yaml", "--work-dir", "/work"]
CMD []
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,23 @@ real binaries, run via `./security-scan.sh run`.

---

## Publish a new image

The image is published from your local machine — no CI secrets needed. Make
sure you're logged in to Docker Hub first:

```bash
docker login # uses your Docker Hub credentials
./security-scan.sh publish # builds multi-arch, prompts, pushes
```

`publish` reads the version from `SECURITY-SCAN-MANIFEST.yaml`, verifies
`pyproject.toml` matches, derives the tag (`v<version>`), and pushes
`leverj/security-scan:v<version>` + `:latest` for amd64 + arm64. Pass an
explicit tag to override (`./security-scan.sh publish v0.3.0-rc1`), or
`--no-push` to do a release dry-run that builds locally without pushing.
Run `./security-scan.sh publish --help` for the full set of flags.
Comment on lines +195 to +198

## Use as a Claude Code skill

The companion bundle at [`leverj/ai-skills`](https://github.com/leverj/ai-skills)
Expand Down
7 changes: 4 additions & 3 deletions SECURITY-SCAN-MANIFEST.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,16 @@
# Adding to this file in a new release is non-breaking. Removing fields is
# breaking — skills must tolerate older manifests missing keys.

version: "0.2.0"
version: "0.2.1"
config_schema_version: 2
docker_image: "leverj/security-scan"
released: "2026-06-02"

# One-liners for the upgrade prompt the skill shows users.
changelog:
- "BREAKING: config moved from a single file to a directory (config/config.yaml). Bind-mount config/ at /config:ro."
- "BREAKING: replaced parent_issue (int) with project.{owner,number} — findings file into a GitHub Projects v2 board, not as sub-issues."
- "0.2.1: hardened image — base bumped to python:3.12-slim with apt-get upgrade for current security patches; container now runs as non-root user 'scanner' (uid 1000); SBOM + SLSA provenance attestations now attached on publish."
- "BREAKING (0.2.0): config moved from a single file to a directory (config/config.yaml). Bind-mount config/ at /config:ro."
- "BREAKING (0.2.0): replaced parent_issue (int) with project.{owner,number} — findings file into a GitHub Projects v2 board, not as sub-issues."
- "PAT now needs `project` scope in addition to `repo`."
- "Added Codex + Gemma LLM SAST scanners (off by default) with bidirectional cross-validation."
- "Added bundled Semgrep rules: XSS, SQLi, Supabase migration patterns."
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "security-scan"
version = "0.2.0"
version = "0.2.1"
description = "Stateless single-repo security scanner; files findings into a GitHub Projects v2 board"
requires-python = ">=3.11"
dependencies = [
Expand Down
Loading
Loading