Application Security and DevSecOps

How application security controls actually work inside a delivery pipeline: SAST, DAST, SCA, secret scanning, supply-chain provenance, triage by reachability, and the failure modes that turn a security program into noise.

Application security in a DevSecOps context means embedding controls that detect, prevent, and remediate vulnerabilities at the speed of delivery. It matters whenever code, dependencies, containers, or infrastructure change before they reach production. This page covers the concrete steps a team can take to make security a pipeline concern, not a post‑deployment ticket.

Integrating Security into CI/CD Pipelines

The most immediate lever for application security is the CI/CD pipeline. The goal is to fail fast: if a vulnerability is present, the pipeline stops before the artifact is promoted. The following patterns work with standard tooling and can be adapted to any CI system.

Linting code on commit

A pre‑commit hook is the lightest way to catch obvious issues before a commit even enters the pipeline. Install the hook framework once:

pre-commit install

Add a .pre-commit-config.yaml that references language‑specific scanners. A minimal config for Python might look like this:

repos:
  - repo: https://github.com/returntocorp/semgrep
    rev: latest
    hooks:
      - id: semgrep
        args: ["--config", "p/python.security"]
  - repo: https://github.com/PyCQA/bandit
    rev: 1.7.5
    hooks:
      - id: bandit
        args: ["-r", "."]

Run all hooks across the working tree:

pre-commit run --all-files

If any scanner exits non‑zero, the commit is blocked. The output includes the file, line, rule ID, and a snippet of the offending code. This pattern—scan, report, exit code—applies equally to JavaScript (eslint-plugin-security), Go (gosec), and other runtimes.

Scanning container images

When the pipeline builds a Docker image, a scanner should run against it before the push step. trivy is a widely used open‑source scanner that supports Docker, OCI, and Kubernetes images.

trivy image --exit-code 0 --severity HIGH,CRITICAL myrepo/app:ci-1234

Trivy reads the image layers, matches them against CVE databases, and prints a table with ID, TITLE, SEVERITY, FIXED-IN, and a link to the advisory. If the image contains a patched base layer, trivy reports FIXED-IN; otherwise it lists the vulnerability as active.

You can also scan a filesystem directory that represents a built rootfs:

trivy fs --exit-code 1 --severity MEDIUM, HIGH /path/build/rootfs

Auditing package dependencies

Dependency‑level vulnerabilities are independent of the language runtime. Use the language‑native auditors:

  pip-audit --format json --output pip-audit-report.json
  npm audit --json > npm-audit-report.json
  bundler-audit --update

Each tool queries its respective registry (PyPI, npm, RubyGems) and reports known CVEs, patched versions, and—where available—remediation commands. The output is machine‑parsable JSON, suitable for post‑processing in the pipeline.

Hardening Dependencies and Supply Chain

Beyond runtime scans, supply‑chain security addresses the integrity of what goes into the build.

Verifying package signatures

Many registries support signed releases. When available, verify the signature before installation:

# Example for npm with npm signing (if enabled)
npm install --verify-registry https://registry.example.com mypkg

If your ecosystem does not provide signatures, pin exact versions and hash them in version control. A common pattern is to commit package-lock.json or requirements.txt with exact versions and run a post‑install check:

pip install -r requirements.txt
pip freeze > freeze.txt
diff freeze.txt requirements.txt.lock

Any drift signals an unexpected upgrade.

Container root user and privileged runs

A frequent sharp edge is images that run as USER root or expose --privileged. Both increase the blast radius if the image is compromised. Enforce non‑root execution in the Dockerfile:

FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
USER nobody:nobody
CMD ["python", "main.py"]

Build and verify:

docker build -t myapp:latest .
docker run --rm myapp:latest id

The id output should show nobody rather than root.

Running Dynamic and Interactive Security Tests

Static and dependency scans miss runtime behaviors. Dynamic Application Security Testing (DAST) and interactive testing fill that gap.

Baseline DAST with OWASP ZAP

OWASP ZAP can run headless against a staging endpoint. The following command starts a quick baseline scan:

zap-baseline.py -t https://staging.example.com -r zap-report.html

If the application is behind a proxy or requires authentication, pre‑configure a ZAP API key and pass it via the ZAP_API_KEY environment variable. The tool will then include login steps in the scan flow.

Interactive request inspection

Sometimes a baseline scan misses business‑logic flaws. Use curl or httpie to manually probe endpoints for unexpected behavior:

http POST https://staging.example.com/api/transfer from=alice to=bob amount=999999

Examine the HTTP status, response body, and any Set-Cookie headers. Look for over‑permissive CORS, missing anti‑CSRF tokens, or data leakage in error messages. This pattern—craft a request, inspect the response, reason about the gap—applies regardless of language or framework.

Managing Secrets and Credentials

Hardcoded passwords, API keys, and TLS private keys are a leading cause of production breaches. The goal is to never store secrets in source control or pipeline YAML.

Environment‑driven secret injection

Most CI systems provide a secure way to inject variables at runtime. In GitHub Actions, GitLab CI, or Jenkins, add the secret to the vault and reference it as an environment variable:

steps:
  - run: python main.py
    env:
      DB_PASSWORD: ${{ secrets.DB_PASSWORD }}

The variable is set in the container environment for that step only; it never appears in logs or persisted artifacts unless explicitly redirected.

Secret encryption at rest

When secrets must be checked into version control (e.g., for local developer onboarding), encrypt them with a tool like sops and age or pgp. The encrypted file is committed; decryption happens locally via a key stored in a password manager or hardware token.

# Encrypt a JSON file
sops --encrypt --age age1xxxx mysecrets.json > mysecrets.enc.json

# Decrypt locally
sops --decrypt mysecrets.enc.json > mysecrets.json

The .enc.json file is safe to git‑track. The decryption key never travels with the repo.

Sharp Edges

What silently fails, and what do you only learn after it breaks in production?

Wrapping Up

Application security and DevSecOps are not a single tool or a checklist—they are a set of repeatable practices woven into the delivery loop. By using real, off‑the‑shelf scanners, auditors, and secret‑management patterns, teams can surface vulnerabilities before they reach production, reduce firefighting, and maintain a predictable release cadence. The concrete commands and configurations above can be copied into your pipeline today; the surrounding patterns can be mapped to whatever platform your organization runs on.

This page was rewritten on 16 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.