Most deployment-security checklists are warmed-over web application security advice. They tell you to validate input, hash passwords, and rotate keys — all true, all already covered better by OWASP's own pages. None of them address the part where the code actually moves from a developer's laptop to production: the build runner, the deploy key, the environment variable, the SSH session, the rollback path. That gap is where breaches happen.

This is an OWASP-aligned checklist written for the deployment pipeline specifically. It maps each item to the current OWASP project that owns it (Top 10 2021, ASVS 4.0.3, CI/CD Top 10, SCVS 1.0, LLM Top 10) so you can audit a real CI/CD setup against authoritative references — not a vague be more secure essay.

If you ship code through any kind of pipeline — [DeployHQ](https://www.deployhq.com/signup), GitHub Actions, GitLab CI, Jenkins, Argo, Spinnaker, anything — every checklist item below applies to you.

### Which OWASP projects actually apply to deployments

OWASP has hundreds of projects. Most don't apply to deployment. These five do, and the rest of this checklist references them by name:

- **[OWASP Top 10 (2021)](https://owasp.org/www-project-top-ten/)** — the canonical web application risk list. Cited as `A01:2021` … `A10:2021`. The 2025 refresh is in draft as of this writing; we cite 2021 because it's still the authoritative version.
- **[OWASP Top 10 CI/CD Security Risks (2022)](https://owasp.org/www-project-top-10-ci-cd-security-risks/)** — the one most deployment teams have never read. Cited as `CICD-SEC-1` … `CICD-SEC-10`. This is the _deployment-specific_ OWASP list and the most important reference for anyone running a pipeline.
- **[OWASP ASVS 4.0.3](https://owasp.org/www-project-application-security-verification-standard/)** — Application Security Verification Standard. Concrete, numbered, testable requirements at three levels (L1, L2, L3). Use ASVS L2 as the baseline for production deployments.
- **[OWASP SCVS 1.0](https://owasp.org/www-project-software-component-verification-standard/)** — Software Component Verification Standard. The SBOM and supply-chain side. If you ship containers or third-party packages, this owns your dependency hygiene.
- **[OWASP Top 10 for LLM Applications (2025)](https://owasp.org/www-project-top-10-for-large-language-model-applications/)** — covers prompt injection, training-data poisoning, and supply chain risks specific to AI-augmented systems. Relevant if your pipeline includes [AI coding agents](https://www.deployhq.com/blog/ai-agents-cicd-pipelines-github-issue-to-production-deploy) or your application runs LLM inference.

Most OWASP checklist articles cite only the Top 10. The deployment-specific risks live in the CI/CD list — that's what we'll spend most of this post on.

* * *

### Phase 1: Source control and access (CICD-SEC-1, CICD-SEC-6)

#### Insufficient flow control: who can push what, where

`CICD-SEC-1` is the boring one nobody fixes: a developer with `write` access to `main` can push a malicious commit that triggers a deploy. No code review, no CI gate, straight to production. This is the single most common breach pattern in startup CI/CD setups.

Concrete checklist for source control:

- **Require pull requests for the default branch.** No direct pushes to `main`, `master`, or `production` branches. Enforced at the repository level, not the team norm level.
- **Require at least one review from someone other than the author.** GitHub: branch protection rule with `Required approving reviews: 1` and `Dismiss stale pull request approvals when new commits are pushed`. GitLab: `Merge requests approvals` with `Prevent approval by author`.
- **Require status checks to pass before merge.** Tests, linting, [security scans](https://www.deployhq.com/blog/protect-your-environments-practical-security-tips-for-smarter-deployments) — all green before the merge button is enabled.
- **Disallow force-push to protected branches.** A force-push to `main` rewrites history and can erase the audit trail of an attacker's changes.
- **Sign your commits.** Require GPG- or SSH-signed commits for merges to protected branches. Unsigned commits should fail status checks. This blocks attacker-controlled commits forged through stolen tokens.

#### Identity and access (CICD-SEC-2)

`CICD-SEC-2: Inadequate Identity and Access Management` covers what most teams call we'll clean up permissions later. Specifics:

- **Enforce two-factor authentication on every Git provider account.** GitHub: organization-wide 2FA requirement. GitLab: instance enforcement. See [how to enable 2FA on DeployHQ](https://www.deployhq.com/blog/increase-your-deployhq-account-security-using-two-factor-authentication) for the deployment side. SMS 2FA is acceptable but not preferred — use TOTP (Authenticator app) or a hardware key (YubiKey).
- **Use short-lived tokens, not personal access tokens.** GitHub fine-grained PATs with explicit repo scope and 30-day expiry. Avoid classic PATs with `repo` scope — they grant access to every repo the user can read.
- **Revoke offboarded users within 24 hours.** Not at the next audit. Build a script. Tie it to your HR system.
- **Audit deploy keys quarterly.** This is the one almost everyone fails. See the section on deploy-key sprawl below.

* * *

### Phase 2: Secrets management (CICD-SEC-6, ASVS V2)

#### Secrets in environment variables: the most overrated security pattern

The conventional wisdom is put secrets in environment variables, not in code. This is correct but incomplete. Environment variables get logged. They show up in error dumps, in `ps auxe`, in process inspection, in third-party crash reporters (Sentry, Bugsnag, Rollbar). Once a secret enters the environment, you've lost control of its blast radius.

The checklist:

- **Never `console.log(process.env)` or equivalent.** Filter your logger. Use an allowlist of keys it's allowed to print, not a denylist.
- **Sanitize crash reporter payloads.** Sentry's `denyUrls`, `beforeSend`, and `ignoreErrors` are not enough — the request body and headers go up by default. Configure `beforeSend` to scrub `Authorization`, `Cookie`, and any header matching `/[Tt]oken|[Ss]ecret|[Kk]ey/`.
- **Use a secrets manager, not `.env` files in production.** AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager, or 1Password Connect. `.env` files are fine in development; in production they get baked into container images and leak. For the boundary between the two, see [the guide on encrypted environment variables with Dotenvx](https://www.deployhq.com/blog/how-to-deploy-php-applications-with-encrypted-environment-variables-using-dotenvx-and-deployhq).
- **Rotate secrets on a schedule, not on suspicion.** ASVS L2 requires rotation; the practical schedule is 90 days for API keys, 30 days for database credentials, immediately on offboarding. Tie rotation to the same script that revokes Git provider access.
- **Mask secrets in CI/CD logs.** GitHub Actions: `add-mask::$SECRET`. GitLab: `masked: true` on the variable. Verify by running a job that intentionally echoes the masked secret — if you see it in the log, the mask isn't working.

For [DeployHQ](https://www.deployhq.com) users specifically, configuration files containing secrets should use the encrypted config files feature rather than being stored as raw text. The `.env` patterns in [how to keep API keys out of your Git repository](https://www.deployhq.com/blog/protecting-your-api-keys-a-quick-guide) cover the source-control side; the deploy-target side belongs in a secrets manager.

#### Deploy-key sprawl: the silent failure mode

Every CI/CD system, every staging server, every monitoring agent, every backup script, every developer's laptop — each gets a deploy key. After two years of growth, you have 40 keys in your GitHub deploy-key list and nobody can identify what 30 of them are for.

The fix is mechanical:

- **One deploy key per system, named with the system it belongs to.** `deploy-prod-web-01-2026-q2`, not `deploy-key-3`.
- **Read-only by default.** A web server doesn't need write access to your repo. Only your CI system does, and even there it can usually use a fine-grained PAT instead.
- **Quarterly audit: delete any key whose owner can't identify it in writing.** If nobody on the team can explain what `deploy-key-12` is for, it's an attacker's path or a forgotten developer laptop — either way, delete it.
- **Use machine users instead of deploy keys for CI.** A GitHub machine user with fine-grained PATs scoped to specific repos is auditable, revocable, and centralized. [Deploy](https://www.deployhq.com) keys are not.

* * *

### Phase 3: Build pipeline security (CICD-SEC-3, CICD-SEC-4, CICD-SEC-7)

#### Poisoned pipeline execution (CICD-SEC-4)

`CICD-SEC-4` is the most under-reported deployment vulnerability of the last three years. The pattern: an attacker submits a pull request from a fork. The CI configuration runs on that PR. Because the CI configuration _itself is part of the PR_, the attacker controls what runs. Result: arbitrary code execution on your build runner, with your secrets in the environment.

The GitHub Actions variant has its own name: **`pull_request_target` injection** , sometimes called pwn-request. If your workflow uses `pull_request_target` (which has access to secrets) and then checks out `github.event.pull_request.head.sha` (the attacker's code), the attacker has root on your runner. CVE-2023-49291 is one of many publicly disclosed cases.

The checklist:

- **Never run third-party code with access to your secrets.** Workflows triggered by external PRs must run without access to repository secrets. Use `pull_request` (read-only token, no secrets) for forks, not `pull_request_target`.
- **Pin third-party actions by SHA, not by tag.** `uses: tj-actions/changed-files@v44` is a tag — the maintainer can move it to point at malicious code. `uses: tj-actions/changed-files@a29e8b565651ce417abb5db7164b4a2ad8b6155c` is a commit SHA — immutable. The March 2025 `tj-actions/changed-files` compromise affected ~23,000 repos that pinned by tag. Dependabot will help you keep SHA pins up to date.
- **Use the principle of least privilege for the `GITHUB_TOKEN`.** `permissions: contents: read` at the workflow level. Add specific write permissions only on the jobs that need them. The default `write-all` is almost always wrong.
- **Treat build runners as ephemeral and untrusted.** Self-hosted runners running on persistent VMs are a footgun — one malicious build can leave behind a backdoor for the next build. Use ephemeral runners (fresh VM or container per job) or cloud-hosted runners.

#### Insecure system configuration (CICD-SEC-7)

Build agents and deploy targets need the same hardening you'd apply to a production server. For the deploy-target side specifically, see [how to secure a Linux server for deployments](https://www.deployhq.com/blog/secure-linux-server-for-deployments) — that post covers SSH hardening, firewall configuration, unattended upgrades, and audit logging in depth. For the build-runner side:

- **Patch the base image weekly, not when something breaks.** A Jenkins controller pinned to an LTS from 2022 is a public archive of unpatched CVEs. CVE-2024-23897 (Jenkins arbitrary file read via CLI) was exploited in the wild within days of disclosure; tens of thousands of internet-facing Jenkins controllers were vulnerable for months.
- **Don't expose the build controller to the public internet.** Jenkins, TeamCity, Octopus — these should sit behind a VPN or zero-trust proxy. The we only expose port 443 with auth defense fails the moment a CVE drops authentication entirely.
- **Disable unused build features.** Jenkins Script Console, Groovy sandbox bypass, etc. If you don't use it, uninstall it.

#### Ungoverned third-party dependencies (CICD-SEC-3)

This is the SCVS overlap area. The supply chain attack surface for a typical Node.js or PHP application is in the thousands of transitive dependencies — and every one of them is code you ship.

- **Generate an SBOM on every build.** CycloneDX (`cyclonedx-npm`, `cyclonedx-php-composer`) or SPDX. Store it alongside the build artifact, not just in CI logs.
- **Scan for known CVEs at build time, fail the build on critical findings.** Trivy, Grype, Snyk, GitHub Advanced Security — pick one and wire it into your [build pipeline](https://www.deployhq.com/features/build-pipelines). A `critical` finding on a transitive dependency should block the deploy.
- **Lockfile integrity is non-negotiable.** `package-lock.json`, `composer.lock`, `Gemfile.lock`, `poetry.lock` — committed and reviewed. `npm install --frozen-lockfile` (or `npm ci`) in CI, not `npm install`.
- **Treat post-install scripts as hostile.** `npm` `postinstall` hooks have been the vector for multiple supply-chain attacks (the `event-stream` incident, `ua-parser-js`, the recent `lottie-player` typosquats). Use `--ignore-scripts` in CI where you can, and audit the ones you can't disable.

* * *

### Phase 4: Deployment and runtime (CICD-SEC-8, CICD-SEC-9, ASVS V14)

#### Insufficient logging and visibility (CICD-SEC-9)

You can't respond to what you can't see. The checklist:

- **Log every deploy: who triggered it, from what commit SHA, to what target, at what time.** This is the deployment audit trail. [DeployHQ](https://www.deployhq.com) ships this by default; if you're rolling your own, you need to.
- **Forward deploy events to your SIEM or centralized log store.** Datadog, Splunk, Elastic, Grafana Loki — wherever your security team looks. A deploy at 3 AM from an unfamiliar IP should generate an alert.
- **Retain deploy logs for at least 12 months.** SOC 2 wants this. Most breach investigations need 90+ days of history. See [SOC 2 compliance for deployment workflows](https://www.deployhq.com/blog/soc-2-compliance-deployment-workflows) for the specific control mappings.
- **Sign your build artifacts.** Sigstore + cosign for containers; in-toto attestations for build provenance. The deploy target should verify the signature before running the artifact.

#### Improper artifact integrity validation (CICD-SEC-8)

If your deploy script runs `curl … | bash` or pulls the latest image without verification, an attacker who compromises your artifact storage has compromised your production environment. The defenses:

- **Pin artifact versions by digest, not by tag.** `myapp:latest` is dangerous; `myapp@sha256:a1b2c3…` is safe. Same principle as pinning actions by SHA.
- **Verify signatures before running.** `cosign verify` in your deploy step. If the signature check fails, the deploy fails.
- **Use immutable artifact storage.** A tagged version should never be overwritten. Container registries support this via tag immutability rules — turn it on.

#### Rollback path: the under-tested safety net

The OWASP CI/CD list doesn't have a rollback item, but it should. A working rollback is what makes the difference between we shipped a security regression and we shipped a security regression for 6 hours. The checklist:

- **Every deploy must be reversible to the previous known-good state in under 5 minutes.** Practiced, not theoretical. [One-click rollback](https://www.deployhq.com/features/one-click-rollback) is a [DeployHQ](https://www.deployhq.com) feature for the same reason it's an ASVS V14 control: you need it before you need it.
- **[Zero-downtime deployments](https://www.deployhq.com/features/zero-downtime-deployments) make rollback safer.** Blue-green or canary deploys mean the previous version is still running when the new one ships; rolling back is a load-balancer config change, not a redeploy.
- **Test the rollback in staging quarterly.** A rollback you've never tested doesn't exist.

* * *

### Phase 5: Application-layer security (Top 10 2021, ASVS V5/V7/V10)

The application-layer items everyone talks about. Briefer here because the OWASP Top 10 itself is a better reference than any one blog post.

- **`A01:2021` Broken Access Control** — server-side authorization checks on every request. Don't trust the client. ASVS V4 has the testable requirements.
- **`A02:2021` Cryptographic Failures** — TLS 1.2+ everywhere, including internal service-to-service. Modern hashing (Argon2id, bcrypt, scrypt) for passwords. Never roll your own crypto.
- **`A03:2021` Injection** — parameterized queries for SQL, structured logging for log injection, content security policy headers for XSS. ASVS V5 has the test cases.
- **`A04:2021` Insecure Design** — threat-model your architecture before you build, not after the breach.
- **`A05:2021` Security Misconfiguration** — disable unused features, change default credentials, harden TLS configuration. The same hardening that applies to your build runner applies to your application server.
- **`A06:2021` Vulnerable Components** — covered by the SCVS section above.
- **`A07:2021` Authentication Failures** — MFA, account lockout, password policies. ASVS V2 is the canonical reference.
- **`A08:2021` Software and Data Integrity Failures** — covered by `CICD-SEC-8` artifact integrity above. This is the SolarWinds category.
- **`A09:2021` Security Logging and Monitoring Failures** — covered by `CICD-SEC-9` above.
- **`A10:2021` Server-Side Request Forgery (SSRF)** — allowlist outbound destinations from your application servers. Block link-local and metadata IP ranges (`169.254.169.254`) at the application layer, not just the network layer.

* * *

### Phase 6: AI and LLM-augmented pipelines (OWASP Top 10 for LLMs)

If your pipeline includes AI coding agents (Claude Code, Codex, GitHub Copilot Workspace, Cursor) or your application performs LLM inference, the OWASP LLM Top 10 applies:

- **`LLM01: Prompt Injection`** — an attacker controlling part of a prompt (via a PR description, an issue comment, a fetched web page) can hijack the agent. If your CI agents read PR bodies and act on them, treat the PR body as untrusted input.
- **`LLM05: Improper Output Handling`** — never `eval()` or `exec()` LLM output. Pipeline operators have shipped malware this way.
- **`LLM03: Supply Chain`** — model files are dependencies. Pin model versions. Verify checksums on download.

The patterns differ from human-written code; treat the agent as a privileged automated user with the same controls you'd apply to any service account. See [running AI coding agents in CI/CD](https://www.deployhq.com/blog/running-ai-coding-agents-cicd-headless-mode-claude-code-codex-gemini) for the headless-mode setup that contains the blast radius.

* * *

### How DeployHQ maps to this checklist

[DeployHQ](https://www.deployhq.com) isn't a security tool, but several of its defaults align with this checklist:

- **Server-side deploy execution** — your source code is checked out and built in DeployHQ's infrastructure, not on the deploy target. The target only sees the finished artifact. Reduces the attack surface compared to running `git pull` on production.
- **Audit trail by default** — every deploy is logged with user, commit SHA, target, and timestamp (`CICD-SEC-9`).
- **Role-based access control** — granular permissions on who can deploy which project to which environment (`CICD-SEC-2`).
- **SSH key management** — keys are stored encrypted, scoped per project, and revocable in one click.
- **[One-click rollback](https://www.deployhq.com/features/one-click-rollback)** — the recovery path described in Phase 4, built in.
- **[Build pipelines](https://www.deployhq.com/features/build-pipelines)** — your compile/test/scan steps run in a clean, ephemeral environment per deploy (`CICD-SEC-7`).

If you're evaluating deployment tooling against this checklist, see how [DeployHQ compares to traditional CI/CD setups](https://www.deployhq.com/blog/deployhq-vs-traditional-ci-cd-why-small-teams-are-making-the-switch) or [start a free trial](https://www.deployhq.com/signup).

* * *

### The audit you should actually run

Once a quarter, sit down with this checklist and a real CI/CD setup. For each item, you should be able to answer:

1. Is the control in place? (Y/N)
2. How do I know? (Show me the configuration, not the team norm.)
3. When was it last tested?

Items where the answer to (2) is I think Mike set that up last year are the ones an attacker will find. Security is not the absence of we should fix that — it's the presence of here is the configuration line that enforces it.

For questions about applying this checklist to your [DeployHQ](https://www.deployhq.com) setup, email [support@deployhq.com](mailto:support@deployhq.com) or reach us on [X](https://x.com/deployhq).

