Home Technical Support Embedding Security Scanners in CI/CD: Safety and Bandit in GitLab Pipelines

Embedding Security Scanners in CI/CD: Safety and Bandit in GitLab Pipelines

Last updated on Sep 08, 2026

Embedding Security Scanners in CI/CD: Safety and Bandit in GitLab Pipelines

The How To Embed Safety Into GitLab and How To Embed Bandit Into GitLab exercises teach how to integrate Software Component Analysis (SCA) and Static Application Security Testing (SAST) into GitLab CI/CD pipelines. These exercises appear across 3-5 courses. This article covers the most common failure modes and how to fix them.


Table of Contents

  1. Safety in CI/CD: Exit Code 64
  2. Safety Volume Mount Path
  3. Safety Policy File Required
  4. Bandit in CI/CD: User Permissions
  5. Bandit Output Format and Artifacts
  6. allow_failure for Scanner Jobs
  7. Docker-in-Docker Requirement
  8. Common Questions

Safety in CI/CD: Exit Code 64

Safety Finds Vulnerabilities by Design

When you embed Safety (SCA/OAST scanner) into the pipeline, it will typically find vulnerable dependencies and exit with code 64:

Found 26 known vulnerability alerts affecting 15 packages

Exit code 64 means vulnerabilities were found. This causes the CI/CD job to fail (red status).

Fix: Add allow_failure: true to the job so the pipeline continues despite findings:

oast:
  stage: test
  script:
    - docker run --rm -v $(pwd):/src hysnsec/safety check -r requirements.txt --json > oast-results.json
  allow_failure: true

This is expected behavior at DevSecOps Maturity Levels 1-2, where security scanning is being introduced but shouldn't block deployments yet.


Safety Volume Mount Path

-v $(pwd):/src Must Match

The Safety Docker image expects the project files at /src inside the container. The volume mount must map the runner's current directory to /src:

script:
  - docker run --rm -v $(pwd):/src hysnsec/safety check -r requirements.txt --json > oast-results.json

Common mistake: Using a different path like /app or /code:

# WRONG - Safety won't find requirements.txt:
docker run --rm -v $(pwd):/app hysnsec/safety check -r requirements.txt

# CORRECT:
docker run --rm -v $(pwd):/src hysnsec/safety check -r requirements.txt

The task verification checks for the exact pattern: docker run.*\$(pwd):/src.*hysnsec/safety check -r requirements.txt


Safety Policy File Required

.safety-policy.yml Must Exist

Without a Safety policy file, the scan may fail or produce unexpected results:

No such file or directory: .safety-policy.yml

Fix: Create the policy file before running the scan:

script:
  - |
    cat > .safety-policy.yml <<EOF
    security:
      ignore-vulnerabilities: {}
    EOF
  - docker run --rm -v $(pwd):/src hysnsec/safety check -r requirements.txt --json > oast-results.json

An empty ignore-vulnerabilities: {} means no vulnerabilities are suppressed — all findings are reported.

Artifacts Must Use when: always

Since the Safety job may fail (exit code 64), artifacts need when: always to be saved:

artifacts:
  paths: [oast-results.json]
  when: always

Without when: always, the JSON output is lost when the job fails.


Bandit in CI/CD: User Permissions

--user $(id -u):$(id -g) Is Required

The Bandit Docker image must run as the same user as the CI runner to have file permissions on the mounted volume:

sast:
  stage: build
  script:
    - docker run --user $(id -u):$(id -g) -v $(pwd):/src --rm hysnsec/bandit -r /src -f json -o /src/bandit-output.json

Common mistake: Forgetting --user $(id -u):$(id -g):

# WRONG - permission denied on mounted volume:
docker run -v $(pwd):/src --rm hysnsec/bandit -r /src

# CORRECT:
docker run --user $(id -u):$(id -g) -v $(pwd):/src --rm hysnsec/bandit -r /src -f json -o /src/bandit-output.json

Without --user, the container runs as root, which may not have write access to the runner's mounted volume.


Bandit Output Format and Artifacts

JSON Output Must Be Written to the Mounted Volume

Bandit's output file (-o /src/bandit-output.json) must be written to the mounted volume path so the CI runner can save it as an artifact:

script:
  - docker run --user $(id -u):$(id -g) -v $(pwd):/src --rm hysnsec/bandit -r /src -f json -o /src/bandit-output.json
artifacts:
  paths: [bandit-output.json]
  when: always

Key flags:

Flag Purpose
-r /src Recursive scan of /src directory
-f json JSON output format
-o /src/bandit-output.json Write output to mounted volume

allow_failure for Scanner Jobs

Both Safety and Bandit Need allow_failure

Security scanners in early DevSecOps maturity levels should not block the pipeline:

sast:
  stage: build
  script:
    - docker run --user $(id -u):$(id -g) -v $(pwd):/src --rm hysnsec/bandit -r /src -f json -o /src/bandit-output.json
  artifacts:
    paths: [bandit-output.json]
    when: always
  allow_failure: true

With allow_failure: true, the job shows an exclamation mark (!) instead of a red X, and downstream jobs continue.


Docker-in-Docker Requirement

Scanner Docker Images Require docker:dind

Both Safety and Bandit exercises run Docker images inside the CI pipeline. Without the Docker-in-Docker service, the docker run command fails:

services:
  - docker:dind

Fix: Add services: - docker:dind to the job or at the top level of .gitlab-ci.yml.


Common Questions

Question Answer
My Safety job fails with exit code 64. This means vulnerabilities were found — expected behavior. Add allow_failure: true to keep the pipeline green.
Safety can't find requirements.txt. Check that -v $(pwd):/src is used. Safety looks for files inside /src in the container.
Bandit output file is empty or missing. Ensure -o /src/bandit-output.json writes to the mounted volume, and --user $(id -u):$(id -g) is set.
docker run fails with "Cannot connect to Docker daemon." Add services: - docker:dind to your .gitlab-ci.yml.
What does allow_failure: true do? The job can fail without blocking the pipeline. Shows ! instead of red X.
Where do I find the scan results? Check the job's artifacts section in GitLab, or download the JSON file listed under artifacts: paths:.

Wrap-Up

Embedding security scanners into CI/CD pipelines is straightforward but has a few gotchas:

  • Safety and Bandit both use Docker images with -v $(pwd):/src volume mounts.
  • Bandit needs --user $(id -u):$(id -g) for file permissions.
  • Safety needs .safety-policy.yml before running the scan.
  • Both scanners will find issues and return non-zero exit codes — use allow_failure: true.
  • artifacts: when: always preserves output from failing scan jobs.
  • services: - docker:dind is required for Docker commands in CI.

Every machine resets after 2 hours. The DevSecOps Box is stateless and resets on any page refresh or tab close.

Reach out to support with your lab ID if you encounter errors not covered in this guide.