Free Security Scanning for Any Codebase

Enterprise-grade scanning with zero cost, zero accounts

Guide — April 2026 — ← Back to Research

Most developers assume security scanning requires expensive SaaS subscriptions or enterprise CI/CD platforms. It doesn't.

Enterprise-grade SAST (Static Application Security Testing) and dependency scanning tools are available as free, public Docker images. They work on any codebase regardless of where it's hosted — GitHub, Bitbucket, GitLab, or a local directory that isn't even a git repo.

This guide covers:

Prerequisites

That's it. No accounts, tokens, or licenses required for the scanners.

Step 1: Run the Security Scan

Two scanners are available as public Docker images. No login or token needed to pull them.

SAST (Static Application Security Testing)

Scans your source code for vulnerabilities — SQL injection, XSS, insecure deserialization, hardcoded secrets, and more. Based on Semgrep with thousands of maintained rules.

docker run --rm --pull always \
  -v "$(pwd):/code" \
  registry.gitlab.com/security-products/semgrep \
  /analyzer run --target-dir /code --artifact-dir /code

Output: gl-sast-report.json in your current directory.

Dependency Scanning

Scans your lockfiles (composer.lock, package-lock.json, requirements.txt, go.sum, etc.) for known CVEs in third-party dependencies.

docker run --rm --pull always \
  -v "$(pwd):/code" \
  registry.gitlab.com/security-products/gemnasium \
  /analyzer run --target-dir /code --artifact-dir /code

Output: gl-dependency-scanning-report.json in your current directory.

Important: Mount Your Actual Source Code

The -v "$(pwd):/code" flag mounts your local directory into the Docker container as /code. This must point to where your source code actually lives — not the repo root if your code is in a subdirectory.

For example, if your repo looks like this:

my-project/
  deploy/
  infra/
  docs/
  app/              <- actual source code is here
    src/
    public_html/
    config/

Then mount app/, not the repo root:

# Wrong — scans deploy scripts, infra templates, docs
docker run --rm --pull always -v "$(pwd):/code" ...

# Right — scans your actual application code
docker run --rm --pull always -v "$(pwd)/app:/code" ...

The report JSON file will be written into whichever directory you mount, so check there for the output.

Directory Depth Limit

Both scanners default to a maximum depth of 2 directories. If your source code or lockfiles are nested more than 2 levels deep from the mounted directory, the scanner won't find them.

Increase the depth with the SEARCH_MAX_DEPTH environment variable (works for both scanners), or use DS_MAX_DEPTH for the dependency scanner specifically:

# Scan up to 5 levels deep
docker run --rm --pull always \
  -e SEARCH_MAX_DEPTH=5 \
  -v "$(pwd)/app:/code" \
  registry.gitlab.com/security-products/semgrep \
  /analyzer run --target-dir /code --artifact-dir /code

# Dependency scanner — unlimited depth
docker run --rm --pull always \
  -e DS_MAX_DEPTH=-1 \
  -v "$(pwd)/app:/code" \
  registry.gitlab.com/security-products/gemnasium \
  /analyzer run --target-dir /code --artifact-dir /code

Setting DS_MAX_DEPTH=-1 disables the limit entirely for the dependency scanner. For the SAST scanner, pick a number that covers your deepest source file.

Keeping Scanners Updated

The Docker images are tagged releases, updated roughly weekly to fortnightly with new vulnerability rules and CVE databases. The --pull always flag ensures Docker checks for newer images on each run — it only downloads changed layers, so subsequent pulls are fast.

Step 2: Analyse Results with Claude Code

The scanners output structured JSON. Feed this directly to Claude Code for analysis:

# Run the scan
docker run --rm --pull always \
  -v "$(pwd):/code" \
  registry.gitlab.com/security-products/semgrep \
  /analyzer run --target-dir /code --artifact-dir /code

Then in Claude Code:

Read gl-sast-report.json and:
1. List all Critical and High findings with plain-English explanations
2. Identify any likely false positives and explain why
3. Suggest specific code fixes for each real vulnerability
4. Prioritise by exploitability — what should I fix first?

Claude Code will:

  1. Explain each vulnerability in plain English — what it is and why it matters
  2. Prioritise by severity — Critical and High findings first, based on real exploitability
  3. Suggest specific code fixes — not generic advice, but changes to your actual code
  4. Filter false positives — identify findings that aren't exploitable in your context
  5. Fix the code — apply the changes directly if you ask it to

This replaces hours of manual triage with a focused, prioritised action list.

Understanding the Report Format

Both scanners output JSON following a standard schema. Key fields:

Step 3: Automate with Claude Code Hooks

Claude Code supports hooks — shell commands that run automatically at specific points in your workflow. Configure a hook that runs the security scanner on every commit and feeds the results directly into Claude Code for analysis.

Setup: Scan on Every Commit

Step 1: Create the hook script at .claude/hooks/security-scan.sh:

#!/bin/bash
INPUT=$(cat)

# Run SAST scanner
docker run --rm --pull always \
  -v "$(pwd):/code" \
  registry.gitlab.com/security-products/semgrep \
  /analyzer run --target-dir /code --artifact-dir /code 2>/dev/null

# Parse results and feed back to Claude Code
if [ -f gl-sast-report.json ]; then
  VULN_COUNT=$(jq '.vulnerabilities | length' gl-sast-report.json)
  CRITICAL=$(jq '[.vulnerabilities[] | select(.severity == "Critical" or .severity == "High")] | length' gl-sast-report.json)

  if [ "$VULN_COUNT" -gt 0 ]; then
    SUMMARY=$(jq -r '.vulnerabilities[] | "[\(.severity)] \(.name) in \(.location.file):\(.location.start_line // "?")"' gl-sast-report.json)
    jq -n --arg count "$VULN_COUNT" --arg critical "$CRITICAL" --arg summary "$SUMMARY" '{
      hookSpecificOutput: {
        hookEventName: "PostToolUse",
        additionalContext: "Security scan found \($count) vulnerabilities (\($critical) Critical/High):\n\($summary)"
      }
    }'
  fi
  rm -f gl-sast-report.json
fi
exit 0

Step 2: Make it executable: chmod +x .claude/hooks/security-scan.sh

Step 3: Add the hook to .claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/security-scan.sh",
            "timeout": 120,
            "if": "Bash(git commit*)"
          }
        ]
      }
    ]
  }
}

The "if": "Bash(git commit*)" filter ensures the scan only runs when Claude Code makes a git commit. Now every commit triggers automated scanning and triage.

Alternative: Block Commits with Critical Vulnerabilities

Use a PreToolUse hook instead, and return exit code 2 from the script to prevent the commit when Critical vulnerabilities are found.

Global Setup (All Projects)

Add the hook to ~/.claude/settings.json instead of the project-level file, and every project gets automatic security scanning.

Git Hooks (Without Claude Code)

For scanning in your normal git workflow, use a pre-push hook. Scanning takes 30–60 seconds, so pre-push is the sweet spot — it runs before code leaves your machine without slowing down every commit.

Create .git/hooks/pre-push with the scan commands, check for Critical/High findings, and exit with code 1 to block the push. Use git push --no-verify to override when needed.

CI/CD Integration

The same Docker images work in any CI/CD pipeline:

GitHub Actions

name: Security Scan
on: [push, pull_request]

jobs:
  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run SAST
        run: |
          docker run --rm \
            -v "${{ github.workspace }}:/code" \
            registry.gitlab.com/security-products/semgrep \
            /analyzer run --target-dir /code --artifact-dir /code
      - name: Upload SAST Report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: sast-report
          path: gl-sast-report.json

GitLab has native support via CI components. Bitbucket Pipelines works with the same Docker commands in a step with the docker service enabled.

Infrastructure as Code Scanning

If your project includes CloudFormation, Terraform, Kubernetes manifests, or Dockerfiles, the Semgrep scanner already includes IaC rules. The same SAST command flags:

Language-Specific Tips

The scanners auto-detect your stack by looking for lockfiles and source file extensions.

Language Lockfile Needed Key Vulnerabilities Detected
PHP composer.lock SQL injection, XSS, command injection, insecure deserialization, path traversal
Node.js package-lock.json / yarn.lock Prototype pollution, XSS, eval injection, ReDoS, JWT misconfiguration
Python requirements.txt / Pipfile.lock SQL injection, command injection, SSRF, unsafe deserialization, template injection
Go go.sum SQL injection, command injection, path traversal, race conditions, insecure TLS
Java pom.xml / build.gradle SQL injection, XXE, insecure deserialization, LDAP injection, SSRF
Ruby Gemfile.lock SQL injection, XSS, command injection, mass assignment, YAML deserialization
C# .csproj / packages.lock.json SQL injection, XSS, path traversal, insecure deserialization, XXE

Quick Reference

# SAST — scans source code
docker run --rm --pull always \
  -v "$(pwd):/code" \
  registry.gitlab.com/security-products/semgrep \
  /analyzer run --target-dir /code --artifact-dir /code

# Dependency Scanning — scans lockfiles
docker run --rm --pull always \
  -v "$(pwd):/code" \
  registry.gitlab.com/security-products/gemnasium \
  /analyzer run --target-dir /code --artifact-dir /code

See the companion case study for a complete walkthrough of applying this guide to a real PHP + React codebase, including all the gotchas and how we resolved them.