Sign inSign up

stden/code-quality

By stden

•Updated 1 day ago

Run gitleaks, hadolint, shellcheck, actionlint, typos, semgrep, and trivy from one image.

Image
Security
Languages & frameworks
Developer tools
0

420

stden/code-quality repository overview

⁠stden/code-quality

Run gitleaks, hadolint, shellcheck, actionlint, typos, semgrep, and trivy from one image.

⁠Overview

This image has the static checks that many repositories run in CI:

ToolIt checksIt exits with an error when
gitleaks⁠Secrets in files and in the git historyit finds a secret
hadolint⁠Dockerfilesit finds a problem
shellcheck⁠Shell scriptsit finds a problem
actionlint⁠GitHub Actions workflows, with shellcheck for run scriptsit finds a problem
typos⁠Spelling in code and documentsit finds a spelling mistake
semgrep⁠Code patterns from your rulesit finds a match and you set --error
trivy⁠Known CVEs in a file tree or a container imageit finds a CRITICAL CVE that has a fix and you set --exit-code 1

Each build installs the newest stable release of each tool. The build downloads gitleaks, hadolint, shellcheck, actionlint, typos, and trivy from their GitHub releases. It checks each file with the SHA-256 digest in the release metadata. The build installs Semgrep with pip in its own environment on the Debian Python. The build also stores the trivy vulnerability database. This database is large, so the image is much larger than the tool files alone.

The base image is debian:stable-slim. The image also has git, so gitleaks can read the commit history. The image trusts all git directories (safe.directory). Without this setting, git does not accept a mounted repository with a different owner, and gitleaks scans 0 commits and passes. The working directory is /workspace. The default command starts Bash.

⁠Tags

TagMeaningPlatform
latestLatest successful build from main with the newest stable tools.linux/amd64

View all tags on Docker Hub⁠. The latest tag changes when a new build passes its tests.

⁠Pull the image

docker pull stden/code-quality:latest

⁠Tutorial

Run the commands from the root of your project. The commands mount the project at /workspace. The container runs as your user, so the files that a tool writes belong to you. Set a short alias:

alias cq='docker run --rm --user "$(id -u):$(id -g)" -v "$PWD":/workspace stden/code-quality:latest'

Each tool exits with a non-zero status when it finds a problem. A script or a CI job then stops.

⁠Step 1: Find secrets with gitleaks
cq gitleaks git --redact --no-banner --verbose .
cq gitleaks dir --redact --no-banner .

gitleaks git scans each commit. gitleaks dir scans the files, also the files that you did not commit. --redact replaces the secret value with REDACTED in the output. The log shows the number of scanned commits. If it shows 0 commits, the scan did not read the history.

A secret that you delete in a new commit stays in the history. Revoke the secret. Then make a new secret. To accept a known finding, add its Fingerprint line to .gitleaksignore. To accept all current findings of an old repository, save a baseline once. Then compare each scan with it:

cq gitleaks git --redact --no-banner --report-path gitleaks-baseline.json .
cq gitleaks git --redact --no-banner --baseline-path gitleaks-baseline.json .
⁠Step 2: Check Dockerfiles with hadolint
cq hadolint Dockerfile

To ignore a rule in the project, write .hadolint.yaml:

ignored:
  - DL3008

To ignore a rule for one instruction, write # hadolint ignore=DL3008 on the line before it.

⁠Step 3: Check shell scripts with shellcheck
cq bash -c 'git ls-files -z "*.sh" | xargs -0 --no-run-if-empty shellcheck'

To ignore a rule for one command, write # shellcheck disable=SC2086 on the line before it. Put project settings in .shellcheckrc.

⁠Step 4: Check GitHub workflows with actionlint
cq actionlint

actionlint reads all files in .github/workflows. It finds incorrect keys, incorrect expressions, and jobs that do not exist. It also runs shellcheck on each run script.

⁠Step 5: Find spelling mistakes with typos
cq typos
cq typos --write-changes

The second command corrects the files. Review the change with git diff. To skip files or accept project words, write _typos.toml:

[files]
extend-exclude = ["*.min.js", "testdata/"]

[default.extend-words]
# Add a project word here as word = "word".
⁠Step 6: Find code patterns with semgrep

Write a rule file semgrep.yml:

rules:
  - id: no-eval
    languages: [python]
    severity: ERROR
    message: Do not pass data to eval().
    pattern: eval(...)
cq semgrep scan --config semgrep.yml --metrics=off --error

--error makes semgrep exit with status 1 when it finds a match. Without it, semgrep exits with status 0. With --metrics=off, semgrep does not send usage data. A local rule file does not use the network. A registry rule set, for example --config p/default, downloads its rules from semgrep.dev.

To skip a file, add it to .semgrepignore. To accept one line, write # nosemgrep: no-eval on it.

⁠Step 7: Find known CVEs with trivy

Do not use Mend for this check. Mend must have a license, MEND_EMAIL, and MEND_USER_KEY. Mend uploads the scan. trivy runs inside this image. The image disables telemetry and the version check.

Scan a file tree. --exit-code 1 makes trivy exit with status 1 when it finds a match. --ignore-unfixed skips a CVE that has no fix yet. --skip-db-update uses the database from this image and does not use the network.

cq trivy fs --scanners vuln --severity CRITICAL --ignore-unfixed --exit-code 1 \
  --skip-db-update --offline-scan .

Scan a local image. Save it first. trivy reads the tar. The Docker socket is not necessary.

docker save my-app:test -o /tmp/my-app.tar
docker run --rm -v /tmp/my-app.tar:/image.tar:ro stden/code-quality \
  trivy image --input /image.tar --scanners vuln --severity CRITICAL \
  --ignore-unfixed --exit-code 1 --skip-db-update --offline-scan

To accept one CVE, add its id to .trivyignore, one id on each line. Review that file as you review code.

⁠Use in CI

⁠GitHub Actions

The job runs in the image. The image has git, so actions/checkout makes a real clone. fetch-depth: 0 gets the full history for gitleaks.

name: Code quality
on:
  push:
  pull_request:
permissions:
  contents: read
jobs:
  checks:
    runs-on: ubuntu-latest
    container: stden/code-quality:latest
    defaults:
      run:
        shell: bash
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0
      - run: gitleaks git --redact --no-banner .
      - run: hadolint Dockerfile
      - run: git ls-files -z '*.sh' | xargs -0 --no-run-if-empty shellcheck
      - run: actionlint
      - run: typos
      - run: semgrep scan --config semgrep.yml --metrics=off --error
      - run: trivy fs --scanners vuln --severity CRITICAL --ignore-unfixed --exit-code 1 --skip-db-update --offline-scan .
⁠GitLab CI

GIT_DEPTH: 0 gets the full history for gitleaks.

code-quality:
  image: stden/code-quality:latest
  variables:
    GIT_DEPTH: 0
  script:
    - gitleaks git --redact --no-banner .
    - hadolint Dockerfile
    - git ls-files -z '*.sh' | xargs -0 --no-run-if-empty shellcheck
    - typos
    - semgrep scan --config semgrep.yml --metrics=off --error
    - trivy fs --scanners vuln --severity CRITICAL --ignore-unfixed --exit-code 1 --skip-db-update --offline-scan .

⁠Best practices

  • Make the pipeline fail on findings. Do not add || true, continue-on-error, or allow_failure. Always give semgrep --error. Always give trivy --exit-code 1.
  • Keep the baseline and the ignore files in git. Commit .gitleaksignore or the gitleaks baseline, .hadolint.yaml, .shellcheckrc, _typos.toml, .semgrepignore, and .trivyignore. Review each change to them as you review code. Write the reason next to each inline ignore comment.
  • Never print found secrets in logs. Always use gitleaks --redact. CI logs stay for a long time, and many people can read them. Do not upload a report without --redact as a CI artifact.
  • Revoke a leaked secret first. Each person who can read the repository can see a secret in a pushed commit. A new commit that deletes it does not remove it from the history.
  • Scan the full history once, then each change. In a merge request, scan only the new commits, for example gitleaks git --redact --log-opts="origin/main..HEAD" ..
  • Use local rules for repeatable semgrep scans. Keep the rule file in git. Use --metrics=off. Registry rule sets change without a change in your repository.
  • Control tool updates. The latest tag gets new tool versions, and a new version can find new problems. To choose when this happens, pin the image digest. Update the digest with Dependabot or Renovate.
  • Run the same checks before you push. The same image gives the same results on your computer and in CI.

⁠Source and checks

The smoke test works offline. It makes small sample files. It checks that each tool finds a known problem and accepts the clean file. The known problems are:

  • A fake AWS key in a file and in the git history of a repository with a different owner.
  • A Dockerfile with ADD.
  • A shell script and a workflow run script with an unquoted variable.
  • A workflow job with a needs entry for a job that does not exist.
  • A spelling mistake.
  • Code that matches a local semgrep rule.
  • An old lock file with CVE-2019-10744. An empty tree has no CVE. The trivy database is in the image, so this check does not download a database.

The test also checks that gitleaks does not print the secret, that a gitleaks baseline accepts a known finding, and that typos corrects a file.

Tag summary

Content type

Image

Digest

sha256:aa9b99fb5…

Size

384.2 MB

Last updated

1 day ago

docker pull stden/code-quality