CircleCI provider
Parses .circleci/config.yml on disk, no CircleCI API token, no
runner install.
Producer workflow
# --circleci-path is auto-detected when .circleci/config.yml exists at cwd.
pipeline_check --pipeline circleci
# …or pass it explicitly.
pipeline_check --pipeline circleci --circleci-path .circleci/config.yml
All other flags (--output, --severity-threshold, --checks,
--standard, …) behave the same as with the other providers.
CircleCI-specific checks
Several checks target CircleCI concepts that have no direct analogue in other providers:
- CC-001, orb version pinning (
@volatile,@1→@5.1.0) - CC-009, approval gate via
type: approvalpredecessor job - CC-012, dynamic config generation via
setup: true - CC-019,
add_ssh_keysfingerprint restriction
What it covers
38 checks · 10 have an autofix patch (--fix).
| Check | Title | Severity | Fix |
|---|---|---|---|
| CC-001 | Orb not pinned to exact semver | HIGH | 🔧 fix |
| CC-002 | Script injection via untrusted environment variable | HIGH | |
| CC-003 | Docker image not pinned by digest | HIGH | |
| CC-004 | Secret-like environment variable not managed via context | MEDIUM | |
| CC-005 | AWS auth uses long-lived access keys in environment block | MEDIUM | 🔧 fix |
| CC-006 | Artifacts not signed (no cosign/sigstore step) | MEDIUM | |
| CC-007 | SBOM not produced (no CycloneDX/syft/Trivy-SBOM step) | MEDIUM | |
| CC-008 | Credential-shaped literal in config body | CRITICAL | 🔧 fix |
| CC-009 | Deploy job missing manual approval gate | MEDIUM | |
| CC-010 | Self-hosted runner without ephemeral marker | MEDIUM | |
| CC-011 | No store_test_results step (test results not archived) | LOW | |
| CC-012 | Dynamic config via setup: true enables code injection |
MEDIUM | |
| CC-013 | Deploy job in workflow has no branch filter | MEDIUM | |
| CC-014 | Job missing resource_class declaration |
MEDIUM | |
| CC-015 | No no_output_timeout configured |
MEDIUM | 🔧 fix |
| CC-016 | Remote script piped to shell interpreter | HIGH | 🔧 fix |
| CC-017 | Docker run with insecure flags (privileged/host mount) | CRITICAL | 🔧 fix |
| CC-018 | Package install from insecure source | HIGH | 🔧 fix |
| CC-019 | add_ssh_keys without fingerprint restriction |
HIGH | |
| CC-020 | No vulnerability scanning step | MEDIUM | |
| CC-021 | Package install without lockfile enforcement | MEDIUM | 🔧 fix |
| CC-022 | Dependency update command bypasses lockfile pins | MEDIUM | 🔧 fix |
| CC-023 | TLS / certificate verification bypass | HIGH | 🔧 fix |
| CC-024 | No SLSA provenance attestation produced | MEDIUM | |
| CC-025 | Cache key derives from attacker-controllable input | MEDIUM | |
| CC-026 | Config contains indicators of malicious activity | CRITICAL | |
| CC-027 | Dangerous shell idiom (eval, sh -c variable, backtick exec) | HIGH | |
| CC-028 | Package install bypasses registry integrity (git / path / tarball source) | MEDIUM | |
| CC-029 | Machine executor image not pinned | HIGH | |
| CC-030 | Workflow job uses context without branch filter or approval gate | MEDIUM | |
| CC-031 | OIDC role assumption without branch filter or approval gate | HIGH | |
| CC-032 | Secret-named variable echoed / printed in a run step | HIGH | |
| CC-033 | Job disables Go module checksum / sum-db verification | HIGH | |
| CC-034 | ML model loaded with trust_remote_code (code execution) | HIGH | |
| CC-035 | AI model pulled without a pinned revision | MEDIUM | |
| CC-036 | Unsafe deserialization of a fetched artifact (pickle RCE) | HIGH | |
| CC-037 | Untrusted PR/build context reaches an agentic AI CLI (prompt injection) | HIGH | |
| CC-038 | Agentic CLI output lands without human review | HIGH |
CC-001: Orb not pinned to exact semver
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.
Recommended action
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.
CC-002: Script injection via untrusted environment variable
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. The same applies to the native << pipeline.git.branch >> / << pipeline.git.tag >> interpolations, which CircleCI splices into the command at config-compile time straight from the (attacker-named) ref. << pipeline.parameters.* >> is the safe alternative: typed and set by the triggering workflow, not by a ref name.
Recommended action
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.
CC-003: Docker image not pinned by digest
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.
Recommended action
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.
CC-004: Secret-like environment variable not managed via context
Jobs that declare environment variables with secret-looking names (containing PASSWORD, TOKEN, SECRET, or API_KEY) in inline environment: blocks bypass CircleCI's context restrictions, security groups, OIDC claims, and audit logs are only enforced when secrets live in contexts.
Recommended action
Move secret-like variables (PASSWORD, TOKEN, SECRET, API_KEY) into a CircleCI context and reference the context in the workflow job configuration. Contexts support security groups and audit logging that inline environment: blocks lack.
CC-005: AWS auth uses long-lived access keys in environment block
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.
Recommended action
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.
CC-006: Artifacts not signed (no cosign/sigstore step)
Unsigned artifacts cannot be verified downstream, so a tampered build is indistinguishable from a legitimate one. The check recognizes cosign, sigstore, slsa-framework, and notation-sign as signing tools.
Recommended action
Add a signing step to the pipeline, e.g. install cosign and run cosign sign, or use the sigstore CLI. Publish the signature alongside the artifact and verify it at consumption time.
CC-007: SBOM not produced (no CycloneDX/syft/Trivy-SBOM step)
Without an SBOM, downstream consumers cannot audit the exact set of dependencies shipped in the artifact, delaying vulnerability response when a transitive dep is disclosed. The check recognizes CycloneDX, syft, Anchore SBOM action, spdx-sbom-generator, Microsoft sbom-tool, and Trivy in SBOM mode.
Recommended action
Add an SBOM generation step, syft . -o cyclonedx-json, Trivy with --format cyclonedx, or Microsoft's sbom-tool. Attach the SBOM to the build artifacts so consumers can ingest it into their vulnerability management pipeline.
CC-008: Credential-shaped literal in config body
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.
Known false-positive modes
- Test fixtures and documentation blobs sometimes embed credential-shaped strings (JWT samples, vendor example keys). Well-known vendor example tokens (
AKIAIOSFODNN7EXAMPLE, Stripesk_test_docs keys) are suppressed via theVENDOR_EXAMPLE_TOKENSallowlist. Defaults to LOW confidence.
Recommended action
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.
CC-009: Deploy job missing manual approval gate
In CircleCI, manual approval is implemented by adding a job with type: approval to the workflow and making the deploy job require it. Without this gate, any push to the triggering branch deploys immediately with no human review.
Recommended action
Add a type: approval job that precedes the deploy job in the workflow, and list it in the deploy job's requires:. This ensures a human must click Approve in the CircleCI UI before production changes roll out.
CC-010: Self-hosted runner without ephemeral marker
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.
Recommended action
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.
CC-011: No store_test_results step (test results not archived)
Without store_test_results, test output is only available in the raw build log. Archiving test results enables CircleCI's test insights, timing-based splitting, and provides an audit trail that links each build to its test outcomes.
Recommended action
Add a store_test_results step to jobs that run tests. This archives test results in CircleCI for traceability, trend analysis, and debugging flaky tests.
CC-012: Dynamic config via setup: true enables code injection
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.
Recommended action
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.
CC-013: Deploy job in workflow has no branch filter
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.
Recommended action
Add filters.branches.only to deploy-like workflow jobs so they only run on protected branches (e.g. main, release/*).
CC-014: Job missing resource_class declaration
Without an explicit resource_class, CircleCI assigns a default executor. Declaring the class documents the expected scope and prevents accidental use of larger (or self-hosted) executors that may have elevated privileges.
Recommended action
Add resource_class: to every job to explicitly control the executor size and capabilities. Use the smallest class that satisfies build requirements.
CC-015: No no_output_timeout configured
Without no_output_timeout, a hung step can consume executor time indefinitely. Explicit timeouts cap cost and the window during which a compromised step has access to secrets and the build environment.
Recommended action
Add no_output_timeout: to long-running run steps, or set it at the job level. A reasonable default is 10-30 minutes. CircleCI's default of 10 minutes may be too long for some pipelines and absent for others.
CC-016: Remote script piped to shell interpreter
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.
Known false-positive modes
- 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.
Recommended action
Download the script to a file, verify its checksum, then execute it. Or vendor the script into the repository.
CC-017: Docker run with insecure flags (privileged/host mount)
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.
Recommended action
Remove --privileged and --cap-add flags. Use minimal volume mounts. Prefer rootless containers.
CC-018: Package install from insecure source
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.
Recommended action
Use HTTPS registry URLs. Remove --trusted-host and --no-verify flags. Pin to a private registry with TLS.
CC-019: add_ssh_keys without fingerprint restriction
A bare - add_ssh_keys step (without fingerprints:) loads every SSH key configured on the project into the job. This violates least privilege, the job gains access to keys it does not need, increasing the blast radius if the job is compromised.
Recommended action
Always specify fingerprints: when using add_ssh_keys to restrict which SSH keys are loaded into the job. A bare add_ssh_keys step loads ALL project SSH keys.
CC-020: No vulnerability scanning step
Without a vulnerability scanning step, known-vulnerable dependencies ship to production undetected. The check recognizes trivy, grype, snyk, npm audit, yarn audit, safety check, pip-audit, osv-scanner, and govulncheck.
Recommended action
Add a vulnerability scanning step, trivy, grype, snyk test, npm audit, pip-audit, or osv-scanner. Publish results so vulnerabilities surface before deployment.
CC-021: Package install without lockfile enforcement
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.
Recommended action
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.
CC-022: Dependency update command bypasses lockfile pins
Detects pip install --upgrade, npm update, yarn upgrade, bundle update, cargo update, go get -u, and composer update. These commands bypass lockfile pins and pull whatever version is currently latest. Tooling upgrades (pip install --upgrade pip) are exempted.
Known false-positive modes
- Common build-tool bootstrapping idioms (
pip install --upgrade pip,pip install --upgrade setuptools wheel virtualenv) and security-tool installs (pip install --upgrade pip-audit / cyclonedx-bom / semgrep) are exempted by theDEP_UPDATE_REtooling allowlist. Other tooling-upgrade idioms not yet on the list can still trip the rule. Defaults to MEDIUM confidence so CI gates can require--min-confidence HIGHto ignore.
Recommended action
Remove dependency-update commands from CI. Use lockfile-pinned install commands (npm ci, pip install -r requirements.txt) and update dependencies via a dedicated PR workflow (e.g. Dependabot, Renovate).
CC-023: TLS / certificate verification bypass
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.
Recommended action
Remove TLS verification bypasses. Fix certificate issues at the source (install CA certificates, configure proper trust stores) instead of disabling verification.
CC-024: No SLSA provenance attestation produced
Signing (cosign sign) binds identity to bytes; attestation (cosign attest) binds a structured claim about how the artifact was built. SLSA verifiers check the latter so consumers can enforce builder/source/parameter policies.
Recommended action
Add a run: cosign attest command against a provenance.intoto.jsonl statement, or use the circleci/attestation orb. CC-006 covers signing; this rule covers the build-provenance step SLSA Build L3 requires.
CC-025: Cache key derives from attacker-controllable input
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.
Recommended action
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.
CC-026: Config contains indicators of malicious activity
Fires on concrete indicators only (reverse shells, base64-decoded execution, miner binaries, Discord/Telegram webhooks, webhook.site callbacks, credential-dump pipes, history-erasure).
Known false-positive modes
- 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, ortestare auto-suppressed; bare lines in a production pipeline still fire. - Defaults to LOW confidence. Filter with
--min-confidence MEDIUMto ignore all matches; the rule still surfaces the hit for teams that want to spot-check.
Recommended action
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.
CC-027: Dangerous shell idiom (eval, sh -c variable, backtick exec)
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.
Known false-positive modes
eval "$(ssh-agent -s)"and similareval "$(<literal-tool>)"bootstrap idioms are intentionally NOT flagged, the substituted command is literal, only its output is eval'd.
Recommended action
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.
CC-028: Package install bypasses registry integrity (git / path / tarball source)
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.
Recommended action
Pin git dependencies to a commit SHA. Publish private packages to an internal registry instead of installing from a filesystem path or tarball URL.
CC-029: Machine executor image not pinned
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.
Recommended action
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.
CC-030: Workflow job uses context without branch filter or approval gate
CircleCI contexts are the recommended way to store shared secrets, but binding a context to a job is only half of least-privilege, the other half is controlling when the binding activates. Unrestricted workflow entries with context: turn every branch push into a secret-read event.
Recommended action
Either add filters.branches.only: [<protected branches>] to restrict when the context-bound job runs, or require a type: approval job in requires: so a human gates the secret-carrying execution. Without either gate, every push to the project loads the context's secrets into an ephemeral runner where any compromised step can exfiltrate them.
CC-031: OIDC role assumption without branch filter or approval gate
Pairs with IAM-008. IAM-008 verifies the cloud-side trust policy pins audience + subject; this rule verifies the CircleCI-side workflow can't drive the role assumption from any branch. Distinct from CC-030 (broad context binding, MEDIUM); CC-031 narrows to OIDC role assumption and is HIGH because role-bound credentials reach further than the project-scoped secrets in a context.
Recommended action
Restrict every workflow job that passes a cloud role_arn (or equivalent OIDC parameter) to a protected branch list, or require a type: approval predecessor. Without either gate, any push triggers a cloud-role assumption with the full blast radius of the IdP-side trust policy.
CC-032: Secret-named variable echoed / printed in a run step
Scans every run: command across all jobs. Variable names matching common secret patterns (PASSWORD, TOKEN, API_KEY, SECRET, CREDENTIAL) trigger the rule when they appear as arguments to echo, printf, cat, or tee. Also fires on printenv / env (full environment dump) and set -x with secret-named variables in scope.
Recommended action
Don't print secret values in CI scripts. CircleCI masks context variables in logs, but only exact-match substrings. Encoded, truncated, or derived forms bypass the mask. Log a boolean instead ([ -n "$X" ] && echo set || echo unset). Avoid set -x when secret-bound variables are in scope.
CC-033: Job disables Go module checksum / sum-db verification
Walks each job's environment: map, every run step's environment: map, and every run command body (for inline export GOSUMDB=off assignments), and flags the Go integrity-disabling settings via the shared _primitives/go_insecure_env detector: GOFLAGS with -insecure, GOSUMDB=off, truthy GONOSUMCHECK, any GOINSECURE, and a broad GOPRIVATE / GONOSUMDB glob.
Scoped GOPRIVATE and GOPROXY=off / direct (still checksum-verified) are not flagged. The CircleCI sibling of GHA-110 / GL-037, the CI-env face of the verification-bypass surface GOMOD-001 warns about.
Known false-positive modes
- A job that builds only against an internal module proxy on a trusted network may set a scoped
GOINSECUREfor one internal host deliberately. Suppress per job with a rationale; a TLS-terminating internal proxy that preserves checksum verification is the safer path.
Seen in the wild
- Verification-bypass class: a runner told to skip the Go checksum database / sum file can be served a substituted module without
go mod verifycatching it, the same gap GOMOD-001 flags from thego.sumside.
Recommended action
Remove the Go toolchain environment settings that turn off module integrity verification so go build keeps checking every downloaded module against go.sum and the checksum transparency database. Drop GOFLAGS=-insecure (plain HTTP fetch, TLS off), GOSUMDB=off / legacy GONOSUMCHECK (checksum DB / sum check off), and any GOINSECURE; scope GOPRIVATE / GONOSUMDB to the exact internal namespace (corp.example.com/team/*) rather than a broad * or whole public host. This is the CI-env twin of GOMOD-001, a committed go.sum is moot if the runner ignores it. For private modules, prefer a trusted internal GOPROXY that still enforces checksums over disabling verification.
CC-034: ML model loaded with trust_remote_code (code execution)
Scans every run: command across all jobs for trust_remote_code=True / --trust-remote-code (the shared model_trust detector, with GHA-120 / GL-045 / BB-035 / ADO-034 / HARNESS-010 / JF-039). The transformers / huggingface_hub loader executes the model repo's own Python at load time, so an untrusted or unpinned model is arbitrary code execution on the runner with the job's context secrets and OIDC in scope. The first AI model-load rule for CircleCI.
Recommended action
Load models with trust_remote_code=False (the library default). If a model genuinely needs custom code, vet it and pin an exact revision (a commit SHA, not a tag or branch), run the load in a job with no production context bound, and prefer safetensors weights over pickle.
CC-035: AI model pulled without a pinned revision
Scans every run: command for a model fetched by a mutable registry reference with no revision pin (the shared model_ref detector, with GHA-121 / GL-046 / BB-038 / ADO-037 / HARNESS-012 / JF-040). Detected fetch forms: from_pretrained("org/model"), hf_hub_download / snapshot_download with a org/model repo id, and huggingface-cli download org/model.
Does NOT fire when a revision is pinned in the same command (revision='<sha>' / --revision <sha>), when the reference is a local path or a variable interpolation (the value can't be judged statically), or on a bare single-segment canonical hub name (bert-base-uncased) with no org/ namespace.
Known false-positive modes
- A team that re-pulls its own org's model on every run may treat the latest revision as intentional. The right fix is still to pin the revision (it makes an upstream compromise visible); if a rolling pull is genuinely wanted, suppress on the specific step with a rationale naming the model and who controls it.
Recommended action
Pin the model to an immutable revision. Pass an exact commit revision= to from_pretrained / hf_hub_download / snapshot_download (a 40-char commit SHA, not a branch or a tag, both of which the owner can move), or --revision <sha> to huggingface-cli download. A pinned revision is what makes a swapped-weights or swapped-loader-code attack show up as a diff in your repo instead of silently landing on the next build. Pair with trust_remote_code=False (CC-034) and prefer safetensors weights over pickle.
CC-036: Unsafe deserialization of a fetched artifact (pickle RCE)
Reuses the shared unsafe_deser detector (with GHA-122 / GL-047 / BB-037 / ADO-036 / HARNESS-011 / JF-041) over every run: command. Fires in two shapes: (A) an explicit unsafe opt-in (weights_only=False on a load, or allow_pickle=True on numpy.load), always; and (B) a remote fetch (curl / wget / hf_hub_download / snapshot_download / huggingface-cli download / requests.get / urlretrieve) together with a pickle-backed loader (torch.load / pickle.load(s) / joblib.load) in the same command, with no safe path (weights_only=True / safetensors). A bare local unpickle with no fetch does not fire.
Recommended action
Don't deserialize a downloaded artifact through pickle. Load weights with safetensors, or pass weights_only=True to torch.load (the PyTorch 2.6+ default) so only tensors, not arbitrary Python, are unpickled. Drop allow_pickle=True from numpy.load. If a pickle / joblib artifact is unavoidable, pin and verify its source (a pinned revision, a checksum, a signature) and load it in a job with no production context bound.
CC-037: Untrusted PR/build context reaches an agentic AI CLI (prompt injection)
The AI analog of CC-002 (script injection). Fires when a run: command invokes an agentic CLI (claude / gemini / cursor-agent / aider / openhands / goose / q chat) AND attacker-controllable CircleCI context reaches it: an event-source env var ($CIRCLE_BRANCH / $CIRCLE_TAG / $CIRCLE_PR_NUMBER / $CIRCLE_PULL_REQUEST) or a << pipeline.git.branch >> / << pipeline.git.tag >> interpolation. Unlike CC-002 the value is flagged in any quote style: an LLM ingests it as prompt text regardless of shell quoting, so the CC-002 mitigation does not apply. << pipeline.parameters.* >> is the safe alternative.
Recommended action
Do not place attacker-controllable context (a PR's branch / tag, $CIRCLE_BRANCH / $CIRCLE_TAG / $CIRCLE_PR_*, or a << pipeline.git.* >> interpolation) in an agentic CLI's prompt. Quoting does NOT sanitize a prompt the way it does a shell command, the model still reads the value. If the agent must see PR content, run it in a job with no context / credentials bound and no tool / shell access, and treat its output as untrusted. Pass trusted inputs through typed << pipeline.parameters.* >> instead.
CC-038: Agentic CLI output lands without human review
Fires when one CircleCI job both invokes an agentic CLI (claude / gemini / cursor-agent / aider / openhands / goose / q chat) in a run: command and, in the same job, lands the result with a git push (committing straight to a branch). Coupling is per-job because a CircleCI job has its own executor / checkout; the run steps of one job share a workspace, separate jobs do not.
Does NOT fire when the agent only opens a pull request for review, nor on a push step that does not run an agent (ordinary formatting / generated-file jobs), nor when the agent and the push are in different jobs. A git push --dry-run is ignored.
Recommended action
Don't let an agentic CLI's output reach a branch without a human review gate. Have the agent open a normal pull request (no auto-merge) so a person reviews the diff before it lands, and don't pair the agent with a git push straight to a branch in the same job. If the agent's prompt can be influenced by untrusted input (a PR title / branch, a pipeline parameter), treat the committed result as attacker-controlled.
Adding a new CircleCI check
- Create a new module at
pipeline_check/core/checks/circleci/rules/ccNNN_<name>.pyexporting a top-levelRULE = Rule(...)and acheck(path, doc) -> Findingfunction. The orchestrator auto-discoversRULEand callscheckwith the parsed YAML document. - Add a mapping for the new ID in
pipeline_check/core/standards/data/owasp_cicd_top_10.py(and any other standard that applies). - Drop unsafe/safe snippets at
tests/fixtures/per_check/circleci/CC-NNN.{unsafe,safe}.ymland add aCheckCaseentry intests/test_per_check_real_examples.py::CASES. - Regenerate this doc: