Supply Chain Integrity

Detects supply-chain integrity risks: piping remote payloads into interpreters, unpinned package installers, fetches from raw code hosting, Galaxy requirement anti-patterns, and risky callback/action/filter/lookup/strategy plugin loading that can hand the controller to an attacker.

121 rules in supply_chain.yml

CRITICAL: 35 | HIGH: 71 | MEDIUM: 13 | LOW: 2

Rule IDSeverityTitleDescriptionRefs
bindep_profile_runs_shellCRITICALbindep profile executes a shell primitive at build timeA bindep.txt line contains $(...), backticks, or pipe-to-shell. bindep evaluates these, so this line runs arbitrary shell during every EE build - a persistent RCE primitive.
check_point_quantum_vulnerable_installCRITICALCheck Point Quantum Security Gateway arbitrary file read (CVE-2024-24919)A task installs a Check Point Quantum Security Gateway / Spark / Maestro build in the vulnerable range for CVE-2024-24919 - a pre-auth information disclosure on the HTTPS remote access VPN portal (/clients/MyCRL, /sslvpn/Login/Login) that leaks arbitrary files including /etc/shadow and Mobile Access / SNX config with clear-text credentials. Actively exploited since May 28, 2024 (Check Point sk182336); affiliate ransomware crews used the leaked creds for follow-on domain compromise. Vulnerable: Quantum Security Gateway R77.20 EOL, R80.20, R80.40, R81, R81.10, R81.20 with Mobile Access / IPsec VPN + remote-access-portal features enabled, before the May 28, 2024 hotfix.
cisco_unified_communications_vulnerable_installCRITICALCisco Unified Communications Manager / IM&P RCE (CVE-2024-20253)A task installs or upgrades a Cisco Unified Communications Manager (CUCM), Unified Communications Manager IM&P, Unity Connection, Emergency Responder, Unified Contact Center Express, or Virtualized Voice Browser build within the vulnerable range for CVE-2024-20253 - a pre-auth arbitrary file write on a TCP listener that yields root code execution on the appliance. Matches installers named Cisco_UCS*, UCSInstall_UCOS_*, ucmbiz-k9*, and explicit vulnerable versions in ios_facts / cisco.ucm.* / cisco.imp.* / cisco.unity.* invocations. Vulnerable (per Cisco advisory cisco-sa-cucm-rce-bWNzQcUm): CUCM 11.5, 12.5(1), 14 before 14SU3; IM&P parallel versions; CUCCX before 12.5SU3; VVB before 12.5(1)SU3.
citrix_netscaler_management_interface_exposed_to_internetCRITICALCitrix NetScaler / ADC management interface exposed to 0.0.0.0 (Citrix Bleed precursor)A task configures a Citrix NetScaler / ADC management (NSIP) or gateway interface to bind to 0.0.0.0, a public IP, or a non-restricted interface. This is the pre-condition that made CVE-2023-4966 (Citrix Bleed) catastrophic in late 2023 / throughout 2024 - LockBit 3.0, Medusa, and Akira affiliates session-hijacked thousands of internet-exposed NSIPs, and the same exposure keeps enabling newer NetScaler CVEs (CVE-2024-6235, CVE-2024-8534). Matches add ns ip ... -mgmtAccess ENABLED with a 0.0.0.0/0 route, Ansible community.network.citrix_* modules with mgmt_interface: all, and cloud-formation/terraform snippets with Port: 443, CidrIp: 0.0.0.0/0 tagged to a NetScaler.
cloudflared_tunnel_install_arbitraryCRITICALcloudflared Tunnel Service Installed Via Inline Token From PlaybookA task installs cloudflared and runs cloudflared service install <token> or cloudflared tunnel run --token <token> with the token pasted inline. Cloudflare Tunnels create an outbound-only reverse-tunnel that bypasses ingress firewall, zero-trust posture, and many EDRs - they were the primary C2 mechanism for the 2024 Storm-1811 / Black-Basta vishing campaign and are increasingly used by commodity actors because the traffic blends with legitimate *.trycloudflare.com / *.cfargotunnel.com workloads.
connectwise_screenconnect_vulnerable_installCRITICALConnectWise ScreenConnect authentication bypass (CVE-2024-1709 + CVE-2024-1708)A task installs or upgrades ConnectWise ScreenConnect within the vulnerable range (versions ≤ 23.9.7) for the February 2024 chain of CVE-2024-1709 (auth bypass via SetupWizard.aspx) + CVE-2024-1708 (path traversal), which yielded trivial full-admin takeover of the ScreenConnect server and was exploited en-masse by ALPHV/BlackCat, Black Basta, and multiple commodity ransomware crews throughout Q1 2024 to reach thousands of MSP downstream customers. Matches direct installer URLs (ScreenConnect_*.exe, .msi), win_package, community.windows.win_chocolatey with name: screenconnect and version: <= 23.9.7, and get_url from screenconnect.com/bin/.
container_image_over_http_registryCRITICALContainer Image Pulled From Plaintext HTTP RegistryA community.docker.docker_image, containers.podman.podman_image, kubernetes.core.k8s, or raw docker pull / podman pull / skopeo copy task references an image on an http:// registry, or sets insecure_registries / --tls-verify=false / --insecure-policy. Every pulled layer is then both unencrypted in flight and trivially modifiable by any network attacker - the same primitive used by in-the-wild container-supply-chain attackers to swap base images.
curl_pipe_to_shellCRITICALcurl Piped Directly to a Shellcurl output is piped into bash/sh/zsh with no checksum, signature, or local review. Whoever controls (or can MitM) the URL runs arbitrary code as the playbook user.
ee_additional_build_steps_append_shellCRITICALExecution Environment: additional_build_steps.append pipes to shelladditional_build_steps.append contains a curl | sh, wget | sh, or equivalent pipe-to-shell primitive. Same threat model as the prepend variant but frequently missed because append runs at the end of the EE build - a compromised append step is a build-time RCE baked into every subsequent ad-hoc run, with the added twist that it’s the last thing to run so earlier layers can’t be re-used to roll it back.
ee_arbitrary_prepend_cmdCRITICALExecution Environment: additional_build_steps.prepend runs arbitrary shelladditional_build_steps.prepend contains a RUN/shell directive that executes during image build. Attacker-controlled values here bake a backdoor into every EE-based ad-hoc run.
fortimanager_fortijndi_vulnerable_installCRITICALFortinet FortiManager FortiJndi authentication bypass (CVE-2024-47575)A task installs or upgrades a FortiManager / FortiManager Cloud build within the vulnerable range for CVE-2024-47575 - a missing-authentication flaw on the fgfmd daemon that allowed any attacker reaching TCP/541 to enroll a rogue FortiGate device and then retrieve every policy, credential, SSL-VPN config, and managed-device backup of the FortiManager tenant. Exploited as a zero-day (UNC5820, Mandiant M-Trends Q4 2024) and listed in CISA KEV. Vulnerable: FortiManager 6.2.x, 6.4.0-6.4.14, 7.0.0-7.0.12, 7.2.0-7.2.7, 7.4.0-7.4.4, 7.6.0, and FortiManager Cloud equivalents. Matches direct FortiManager installer URLs, community.fortios.fmgr_*, and explicit vulnerable versions in panos_software/fmgr_software facts.
fortios_ssl_vpn_vulnerable_version_installCRITICALFortiOS / FortiProxy SSL-VPN out-of-bounds write installation (CVE-2024-21762)A task installs a FortiOS or FortiProxy build in the vulnerable range for CVE-2024-21762 - an SSL-VPN pre-auth out-of-bounds write that yields arbitrary code execution on the appliance. Listed in the CISA Known Exploited Vulnerabilities catalog (Feb 9 2024) and exploited at scale by Volt Typhoon and CL0P affiliates throughout 2024. Matches get_url, community.fortios.* upgrade tasks, copy of FGT_*.out, and shell: execute restore image commands pinned to FortiOS 7.4.0-7.4.2, 7.2.0-7.2.6, 7.0.0-7.0.13, 6.4.0-6.4.14, 6.2.0-6.2.15, 6.0.0-6.0.17 (inclusive of 7.4.2/7.2.6/7.0.13/6.4.14/6.2.15/6.0.17 per Fortinet PSIRT FG-IR-24-015).
frp_fast_reverse_proxy_installCRITICALfrp (Fast Reverse Proxy) Client Installed For Outbound TunnelA task downloads or renders a systemd unit for frpc (Fast Reverse Proxy client) with an frps server config pointing at an arbitrary public host. frp is the most common reverse-proxy tool in the 2023-2025 Chinese-speaking ransomware / APT playbook (Mustang Panda, RedDelta, Sandman): the compromised host dials outbound to an attacker-controlled frps and exposes RDP / SSH / arbitrary TCP back through the tunnel.
gh_actions_pull_request_targetCRITICALGitHub Actions Workflow Uses pull_request_targetA workflow file triggers on pull_request_target, which runs with the base repository’s secrets and permissions, NOT the fork’s. Combining this trigger with any fork-controlled input (checkout of the PR head, running the PR’s package.json scripts, executing fork-supplied commands) hands the repository’s write token to any attacker who opens a pull request.
gha_workflow_contents_write_on_pull_request_targetCRITICALGitHub Actions pull_request_target With contents:write Or Secrets ExposureA task renders a GitHub Actions workflow under .github/workflows/*.yml that triggers on pull_request_target AND grants permissions: { contents: write } (or write-all) OR checks out ${{ github.event.pull_request.head.sha }} (the untrusted fork HEAD). This is the canonical GHA RCE / supply-chain-compromise primitive: pull_request_target runs in the base-repo context with access to GITHUB_TOKEN and secrets, and contents: write lets any forked PR push to the protected branch. The pattern caused the 2023 tj-actions/changed-files incident and is the #1 finding in every 2024-2025 GHA audit.
git_hook_or_config_writeCRITICALAnsible Task Modifies .git/hooks, .git/config, or core.hooksPathA task writes to .git/hooks/*, .git/config, .gitconfig, .gitattributes, or calls git config --local core.hooksPath / core.sshCommand / http.extraheader / credential.helper. Git hooks execute silently on every subsequent git command the developer or CI runner performs, making this a prime persistence + credential-theft primitive (the core.sshCommand form is actively used by in-the-wild malware to proxy all git auth through an attacker host).
github_actions_pull_request_target_with_checkoutCRITICALGitHub Actions workflow uses pull_request_target with checkout of PR HEAD (pwn request)A task renders a .github/workflows/*.yml containing on: pull_request_target AND an actions/checkout@v* step that checks out ${{ github.event.pull_request.head.sha }} / head.ref / refs/pull/${{ ... }}/merge / refs/pull/${{ ... }}/head. pull_request_target runs with write-scoped GITHUB_TOKEN and access to repository secrets, AND by default targets the base-branch workflow file - but checking out the PR head merges attacker-controlled code INTO that privileged context. This is the canonical ‘pwn request’ pattern (GHSA-CKM5-PF4V-FG99, NX 2024 incident, Tj-actions/changed-files 2025) and has leaked thousands of secrets across OSS repos.
github_actions_pull_request_target_with_head_ref_checkoutCRITICALGitHub Actions pull_request_target Workflow Checks Out Attacker-Controllable head.refA GitHub Actions workflow uses on: pull_request_target (trigger runs in the TARGET repo context with READ/WRITE secrets) AND performs actions/checkout@* with ref: ${{ github.event.pull_request.head.ref }} / head.sha / head.repo.full_name. This is the canonical pattern documented in GitHub’s own ‘Dangerous Workflows’ Security Advisory and written up by Dawid Czagan / NCC Group / Trail of Bits as the direct RCE-against-the-target-repo primitive: an external contributor opens a PR from their fork, the workflow checks out their branch’s code INTO the target repo’s privileged context, and executes it with full secrets access. This is the specific pattern that caused the 2024 Ultralytics PyPI compromise (CVE-2024-53899) and the 2023 Pwn Request campaigns against >100 major OSS projects. Distinct from the existing gh_actions_pull_request_target rule (which catches the trigger alone) - this catches the specific checkout + ref combo that is unambiguously exploitable.
github_actions_self_hosted_runner_on_public_repoCRITICALGitHub Actions self-hosted runner attached to public repository (fork-PR RCE)A task configures a GitHub Actions self-hosted runner (actions-runner/config.sh --url https://github.com/... --token ... OR runs-on: self-hosted in a workflow) and registers it to a repository that is public (or an organization-level runner without --runnergroup restrictions). Self-hosted runners run untrusted fork-PR workflows on persistent infrastructure by default - attackers open a PR that executes their code on your runner, reads any secret, pivots into your network. GitHub explicitly warns against this (docs.github.com/en/actions/hosting-your-own-runners#self-hosted-runner-security) but the pattern continues to ship.
github_oidc_trust_aud_wildcard_or_missing_subject_claimCRITICALGitHub OIDC Trust Policy With aud=* Or Missing sub/repo Claim ConditionA Terraform / Ansible IAM-role trust policy for GitHub Actions OIDC uses aud = '*' (wildcard audience - ANY GitHub Actions workflow in ANY org can assume the role) OR is missing the token.actions.githubusercontent.com:sub condition (no per-repo / per-branch pinning). The 2023 Mandiant / Cider Security research on OIDC misconfiguration documented that 15-25% of GitHub-OIDC-to-AWS trust policies in the wild are overly permissive - any attacker with a public repo on GitHub can assume the victim’s role if sub is unconstrained. AWS’s documented best-practice (Jan 2024 update) explicitly requires aud=sts.amazonaws.com AND sub=repo:<org>/<repo>:<environment|branch>.
gost_tunnel_listener_installCRITICALgost (Go Simple Tunnel) Multi-Hop Proxy InstalledA task installs or runs gost -L <listener> -F <forward> (or multi-hop -F chains). gost is a generic multi-protocol tunnel (HTTP, SOCKS5, WebSocket, QUIC, SSH) used heavily by 2024 cryptomining worms (TeamTNT successor Kinsing, RedTail) and by North-Korean-aligned UNC4899 operations for long-lived proxy chains. There is no legitimate sysadmin reason to install gost on a production host.
ivanti_connect_secure_vulnerable_installCRITICALIvanti Connect Secure / Policy Secure command injection (CVE-2024-21887 + CVE-2023-46805)A task installs or upgrades an Ivanti Connect Secure / Policy Secure build in the vulnerable range for the chained CVE-2023-46805 (auth bypass) + CVE-2024-21887 (command injection) exploit that yielded unauthenticated root RCE. Volt Typhoon, UNC5221, and multiple ransomware crews exploited this at internet scale January-April 2024; Ivanti’s first mitigation XML was bypassed within 48 hours and the eventual fix required a full factory reset for many customers. Vulnerable: Ivanti Connect Secure 9.x (all), 22.1R6 through 22.5R2.3, 22.6R1 through 22.6R2.2. Matches direct ISO URLs (ivanti-connect-secure-<ver>.iso, pulse-secure-*.pkg), get_url from downloads.ivanti.com, pulsesecure.net, and explicit version pins in community.general.portage / shell: install-pkg.
jenkins_agent_secret_in_url_or_plaintextCRITICALJenkins agent JNLP secret exposed in URL or plaintext configA task renders a Jenkins agent bootstrap with the JNLP -secret <hex> flag passed on the command line (visible in ps, shell history, systemd journal), OR embeds the secret in agent.jnlp / jenkins-agent.service as a literal, OR sets JENKINS_SECRET / JENKINS_AGENT_SECRET env-var to a hex literal instead of a Vault reference. Jenkins agent secrets authenticate the agent to the controller with persistent rights - leaking one yields arbitrary-pipeline injection on every agent that controller serves, including credential-store access via Credentials Binding and arbitrary Groovy via the build script.
molecule_docker_socket_mountCRITICALHost docker.sock Bind-Mounted Into a Container or Molecule ScenarioA task bind-mounts the host’s /var/run/docker.sock INTO a container or Molecule scenario - e.g. a Molecule platforms: volumes: entry, a community.docker.docker_container volumes: list, a docker run -v /var/run/docker.sock:... command, or a compose.yaml volumes: declaration. Any process inside the receiving container can then drive the host’s Docker daemon (docker run -v /:/host --privileged ...) - a full container-escape / host-takeover primitive (MITRE T1610 / T1611). This is distinct from dockerd’s OWN listen-address configuration (ExecStart=/usr/bin/dockerd -H unix:///var/run/docker.sock) which is NOT a container breakout and is NOT flagged.
npm_registry_plaintext_http_strict_ssl_falseCRITICALnpm Registry Over HTTP Or strict-ssl=false In .npmrcA task writes .npmrc / npm config set with registry=http://..., strict-ssl=false, or always-auth=false combined with http://. npm executes preinstall / install / postinstall lifecycle scripts on every npm install; a MITM or poisoned registry can substitute a malicious tarball that runs arbitrary code in the user/CI context on first install (see the 2024 web3.js supply-chain incident chain).
palo_alto_globalprotect_vulnerable_installCRITICALPalo Alto PAN-OS GlobalProtect arbitrary file creation -> RCE (CVE-2024-3400)A task installs or upgrades a PAN-OS build within the CVE-2024-3400 vulnerable range - a GlobalProtect gateway command-injection (via arbitrary file creation in /opt/panlogs/tmp/device_telemetry/...) that yields unauthenticated root RCE on the firewall. Actively exploited since March 2024 by UTA0218 (published by Volexity) and multiple ransomware affiliates. Vulnerable: PAN-OS 10.2.0-10.2.9-h1, 11.0.0-11.0.4-h1, 11.1.0-11.1.2-h3 with GlobalProtect portal/gateway AND device telemetry both enabled. Matches panos_software, paloaltonetworks.panos.panos_software, direct image URLs (PanOS_*.img), and shell: request system software install version <ver> pinned to vulnerable versions.
pip_index_url_plaintext_httpCRITICALpip install Pointed At Plaintext HTTP Index Or With –trusted-hostA task invokes pip install / ansible.builtin.pip / --extra-index-url with an http:// URL, or passes --trusted-host to silence the TLS warning on an HTTPS index that does not actually validate. pip wheel installs execute arbitrary Python at install time via setup.py / PEP 517 backends; intercepting the download (captive-portal, hostile LAN, compromised internal proxy) is the 2024 Marc-Etienne M.Léveillé class of attack and is trivial with mitmproxy.
python_remote_execCRITICALRemote Python Script Piped into pythonA .py payload is fetched and piped into python / python3 - same RCE pattern as curl|bash but via the Python interpreter.
python_typosquat_package_installCRITICALpip install Of Known Typosquat Or Removed/Malicious Package NameA task runs pip install / ansible.builtin.pip with a package name that matches a known typosquat of a legitimate package (reqeusts -> requests, urlib3 -> urllib3, python-sqlite -> pysqlite3, crypt -> cryptography, mattplotlib -> matplotlib, pytohn-dateutil -> python-dateutil, djnago -> django, beautifulsop -> beautifulsoup4) or a historically-malicious removed package (colourama -> colorama, jellyfish variants removed 2023, ctx removed 2022, discord.py-app malware 2024, torchtriton removed 2022). The PyPI security team publishes a removals log and the typos-in-pypi research dataset (Checkmarx 2024) enumerates the top-200 active typosquats; this rule catches the most dangerous.
raw_github_script_execCRITICALraw.githubusercontent.com Payload Piped to ShellFetching a script from raw.githubusercontent.com and piping it into a shell. GitHub’s raw content is effectively anonymous CDN - ANY commit on ANY branch is reachable without review.
sccm_client_push_installation_account_plaintextCRITICALSCCM / MECM Client Push Installation Account In Plaintext Or Network-Access Account MisconfigA task renders an SCCM / MECM (Microsoft Endpoint Configuration Manager) config: ccmsetup.exe command line with /mp: + SMSSITECODE= + inline domain credentials, ConfigMgr Network Access Account (NAA) set with a literal password, or Client Push Installation account with a plaintext credential in an Ansible task. The Client Push Installation and NAA accounts are prime 2023-2024 red-team targets: (1) NAA credentials are distributed IN CLEARTEXT as policy to every SCCM client - an attacker with local admin on any managed host extracts them from WMI / InstallationProperties, (2) Client Push Account has local admin across ALL sites by design - a ‘misnaa’ extraction via MalSCCM / SharpSCCM yields domain-wide lateral-movement credentials in one query. Microsoft has published mitigation guidance (move to ‘Enhanced HTTP’ + MFA + scoped permissions), but playbook deployments continue to ship the legacy NAA pattern in 2024.
self_modifying_ci_configCRITICALAnsible Task Writes to Own CI/CD ConfigurationA copy/template/lineinfile/blockinfile/replace/file task writes to .github/workflows/, .gitlab-ci.yml, Jenkinsfile, bitbucket-pipelines.yml, azure-pipelines.yml, .circleci/config.yml, .drone.yml, or Tekton PipelineRuns in the scanned repository. A playbook modifying its own CI pipeline is the canonical persistence + privilege-escalation primitive - the attacker gets every future CI token.
selfhosted_runner_untrusted_eventCRITICALSelf-Hosted Runner on Fork-Triggered WorkflowA GitHub Actions workflow declares runs-on: self-hosted (or a self-hosted label) on a job triggered by pull_request, pull_request_target, issue_comment, or workflow_run, without an if: github.actor == / github.event.pull_request.author_association == 'MEMBER' guard. Any outside contributor can open a PR that runs arbitrary code on your self-hosted runner - and self-hosted runners are by default long-lived machines with network and filesystem persistence (GitHub explicitly warns against this configuration).
wget_pipe_to_shellCRITICALwget Piped Directly to a Shellwget output is piped into bash/sh with no integrity verification - identical risk to curl|bash.
xz_liblzma_backdoored_version_installCRITICALxz-utils / liblzma Backdoored Version Installed or Built (CVE-2024-3094)An Ansible task installs, builds, or copies an xz-utils / liblzma / xz-libs package at version 5.6.0 or 5.6.1 - the versions containing the Jia Tan SSH-authentication backdoor (CVE-2024-3094, disclosed March 29 2024). These releases injected a malicious IFUNC resolver into OpenSSH via systemd’s liblzma dependency, allowing any attacker with the matching private key to bypass sshd authentication on sd_notify-linked sshd builds. Matches Ansible package-name pins (name: xz-utils version: 5.6.0), inline package-manager invocations (apt install xz-utils=5.6.0*, yum install xz-5.6.1*, dnf, zypper, pacman -U, apk add), direct tarball URLs (xz-5.6.0.tar.{gz,xz,bz2,zst}), GitHub release URLs for tukaani-project/xz at those tags, and pip install pyliblzma==5.6.0/.1. Any pin to 5.6.0/5.6.1 from a third-party mirror, tarball, or container base layer must be treated as a confirmed compromise.
action_plugin_shadow_coreHIGHCustom action_plugins/ Shadows a Core ModuleA file under action_plugins/ is named the same as a core module (command.py, shell.py, copy.py, template.py) - intercepts every call to that module and can exfiltrate arguments or modify behaviour silently.
adb_tcpip_remote_debug_enabledHIGHAndroid adb tcpip Enabled From Automation Exposing Device Over NetworkA task invokes adb tcpip <port> (typically 5555) on a connected Android device or emulator. adb tcpip disables USB-only authentication and exposes the full adb protocol (shell, push, install) on a TCP port, historically exploited by the ADB.Miner worm and the 2024 Fbot / Satori botnet resurgence - any device left in this state and reachable over the network is a one-command root shell for any attacker.
ansible_galaxy_install_force_latestHIGHansible-galaxy install –force pulls latest, bypasses lockfile–force reinstalls collections/roles at the latest available version even when a pinned version is present, silently breaking deterministic deploys.
ansible_galaxy_install_ignore_errorsHIGHansible-galaxy install –ignore-errors in CIShell task installs Galaxy content with –ignore-errors (or –force alongside unpinned versions), masking failures that could indicate a hijacked or deleted collection.
ansible_navigator_ee_from_public_registryHIGHansible-navigator Pulls Execution Environment From Public RegistryAn ansible-navigator.yml / ansible-navigator.yaml config (or --execution-environment-image CLI flag) references an EE image from quay.io/, docker.io/, ghcr.io/, or registry.hub.docker.com/ without a SHA pin, and pull.policy is always or unset. Every ansible-navigator run then pulls whatever the public tag currently points at - a single registry-side retag (accidental or malicious) silently changes the Ansible runtime, collections, and Python dependencies for every operator.
apt_allow_unauthenticated_flag_trueHIGHapt-get –allow-unauthenticated Or AllowInsecureRepositories (Bypass GPG Signature Check)A task runs apt-get install --allow-unauthenticated / --allow-insecure-repositories / --allow-downgrades, or renders /etc/apt/apt.conf.d/* with APT::Get::AllowUnauthenticated "true", Acquire::AllowInsecureRepositories "true", or Acquire::Check-Valid-Until "false". Each of these weakens apt’s trust model - --allow-unauthenticated installs packages that failed GPG signature verification (the exact path for a malicious-mirror supply-chain attack), AllowInsecureRepositories allows repos without any Release file signing at all, and Check-Valid-Until false accepts indefinitely-old Release files (replay-attack primitive with known-vulnerable package versions). Debian/Ubuntu default to all of these being strict - any weakening is a regression.
apt_force_flag_trueHIGHansible.builtin.apt With force: true (Bypasses Signature Validation + Allows Downgrade)An ansible.builtin.apt / apt task sets force: true. Apt’s --force-yes/--allow-downgrades/--allow-unauthenticated semantics disable release-file signature validation and permit the installation of older, potentially-vulnerable package versions. This is the Ansible analogue of running apt-get install -y --force-yes --allow-downgrades on the CLI - both features were deprecated by Debian in 2017 specifically because attackers used them to deliver trojanized debs via MITM’d mirror URLs and cache-poisoning attacks.
argo_workflows_templateref_cluster_scope_untrustedHIGHArgo Workflows templateRef With clusterScope=true Referencing Unpinned NameAn Argo Workflows / WorkflowTemplate / CronWorkflow resource has templateRef: with clusterScope: true AND a name: that’s templated ({{workflow.parameters.*}}) or references a name not explicitly scoped to an allow-listed ClusterWorkflowTemplate. The clusterScope: true flag resolves the template from the CLUSTER-level ClusterWorkflowTemplate resource - any user with argoproj.io/v1alpha1/clusterworkflowtemplates:get permission can cause the workflow to execute arbitrary cluster-wide templates, including ones planted by a compromised tenant namespace. This is the pattern exploited in Argo CVE-2023-47110 and generalized in the 2024 Snyk-reported Argo misconfigurations that resulted in cross-tenant container execution.
argocd_application_untrusted_repoHIGHArgoCD Application Points At Untrusted / Unpinned RepositoryAn argoproj.io/v1alpha1 Application or ApplicationSet manifest has spec.source.repoURL pointing at github.com/* (without a known_hosts-verified SSH fingerprint), docker.io/*, or a tag-only targetRevision (HEAD, main, master, latest). Every GitOps sync then pulls whatever the tip of that branch is at reconciliation time - a compromised upstream commit reaches production in the next sync interval (default: 3 minutes) with no human in the loop.
azure_container_registry_admin_user_enabledHIGHAzure Container Registry with admin_user_enabled: true (Shared Credentials Anti-Pattern)An azure.azcollection.azure_rm_containerregistry task sets admin_user_enabled: true. This activates a SHARED username + password combo on the registry (retrievable via az acr credential show) that everyone with registry RBAC can use to push/pull images - collapsing any per-identity audit trail. Worse, teams often embed these shared creds into CI secrets, Kubernetes image-pull secrets, and docker-login scripts, where they leak across environments. The modern pattern is managed-identity or service-principal authentication, which preserves per-workload attribution.
callback_plugins_path_untrustedHIGHcallback_plugins Path Points at Writable / Remote Locationcallback_plugins configured to a path in /tmp, /var/tmp, a user’s writable directory, or a mounted remote share - any file dropped there runs inside the Ansible controller on the next play.
callback_whitelist_arbitraryHIGHAnsible Callback Loads Arbitrary / External Plugincallback_whitelist / callbacks_enabled lists a plugin that is not shipped with ansible-core and not a pinned collection - any Python file under the callback_plugins path is executed in the controller process on every task event.
cargo_install_from_git_unpinnedHIGHcargo install –git Used Without –rev / –tag PinA task runs cargo install --git <url> or cargo install <crate> --git <url> WITHOUT a --rev or --tag argument pinning the install to a specific commit or tag. Without a rev/tag, cargo installs from the default branch HEAD - identical attacker-compromise window as go install @main. Rust crates installed from unpinned git are additionally built from source with no Cargo.lock verification against the crates.io signed index (the 2025 crates.io-signing-WG rollout only covers tarball installs, not git installs). This is the #2 most common Rust-CI finding after cargo install without --locked.
compose_db_port_bound_0_0_0_0HIGHdocker-compose Port Publishing Binds DB To 0.0.0.0 (Public Iface)A docker-compose / podman-compose / docker-swarm stack file has a ports: mapping 0.0.0.0:<db-port>:<db-port> (or unspecified host ip, which defaults to 0.0.0.0) for MySQL 3306, PostgreSQL 5432, MongoDB 27017, Redis 6379, Elasticsearch 9200, or Cassandra 9042. This exposes the database to the host’s public interfaces. The 2022-2024 Censys ‘Exposed Databases’ reports consistently find >1M Internet-reachable instances from this exact misconfiguration. On cloud VMs with public IPs, even a cluster-internal cloud security group + default-allow firewall rule is enough to expose them.
container_image_unpinned_tagHIGHContainer Image Pulled by Mutable Tag (No Digest Pin)A container image is referenced by a mutable tag (:latest, :stable, :prod, :main, :master, or no tag at all) instead of an immutable @sha256:... digest. Any registry-side retag silently lands a different image on every subsequent pull - the identical primitive used in the 2024 tj-actions and 2023 3CX attacks, and routinely used by cryptominer-deploying botnets to swap benign base images for compromised ones.
container_registry_tag_mutable_or_no_signature_verificationHIGHContainer Registry Tag Mutability Enabled Or Image Pull Skips Signature VerificationA task configures ECR/GCR/ACR/Harbor with mutable tags: aws_ecr_repository image_tag_mutability = MUTABLE (the non-default; default since 2022 is IMMUTABLE), gcloud artifacts repositories create --immutable-tags OMITTED, Harbor project prevent_vul + enable_content_trust set false, OR runs a pull with --disable-content-trust / DOCKER_CONTENT_TRUST=0 / cosign verify skipped in CI / --insecure-policy on skopeo. Mutable tags + unverified pulls = supply-chain nightmare: the v1.2.3 tag you pulled during build is NOT guaranteed to be the same bytes as the v1.2.3 running tomorrow. 2024 Sysdig ‘Stolen Dream’ report documented active campaigns overwriting popular :latest tags in compromised public registries. Sigstore / Notary v2 signature verification is the 2024-2025 industry standard - skipping it defeats the entire purpose.
cosign_signature_verification_disabledHIGHCosign / Sigstore Signature Verification Disabled or SkippedA task invokes cosign verify / cosign verify-attestation / cosign verify-blob with --insecure-ignore-tlog, --insecure-ignore-sct, --allow-insecure-registry, --certificate-identity '*', or the identical-blast-radius environment variables COSIGN_EXPERIMENTAL_DISABLE_CTLOG=1 / SIGSTORE_NO_CACHE=1. Alternatively a policy-controller ClusterImagePolicy sets mode: warn (vs enforce). Ignoring the Rekor transparency log or the SCT (Signed Certificate Timestamp) collapses Sigstore’s non-repudiation guarantees to nothing - an attacker with a single short-lived Fulcio cert can silently re-sign images.
crossplane_providerconfig_broad_iamHIGHCrossplane ProviderConfig Binds Broad Cloud IAM RoleA ProviderConfig (AWS / GCP / Azure) for Crossplane references a service account or IAM role with *:* permissions, AdministratorAccess, roles/owner, or Contributor at subscription scope. Because Crossplane is the single in-cluster actor that provisions cloud resources on behalf of every user’s Claim, a compromise of its ProviderConfig is a direct path from kube-apiserver RCE to full cloud-account takeover.
curl_wget_pipe_shell_install_onelinerHIGHcurl | bash / wget -O- | sh Install One-Liner (Unauthenticated RCE Pattern)A task contains curl <url> | bash / curl <url> | sh / wget -O- <url> | bash / wget -qO- <url> | sh or variants (| zsh, | python, | perl, | ruby, | tee /tmp/x.sh && bash /tmp/x.sh). The curl-pipe-bash pattern downloads AND executes attacker-controllable shell in a single step with zero signature verification, zero local review, and no atomicity - if the attacker can MitM the connection (already caught by the TLS rules), substitute the download mid-stream, or poison the source host, they have direct RCE as the Ansible-running user. Even HTTPS doesn’t help - the source can be compromised independently of transport. Install scripts for Rust (sh.rustup.rs), Docker (get.docker.com), k3s, Helm, and nvm all use this pattern and have all been separately compromised in 2023-2024. Different from download_pipe_to_shell (caught today) which catches the generic pattern - this rule catches it specifically in the context of a package-install task for 2025-2026 attribution.
dnf_disable_gpg_check_trueHIGHansible.builtin.dnf With disable_gpg_check: true (Installs Unsigned RPMs)An ansible.builtin.dnf / dnf task sets disable_gpg_check: true. DNF will install RPMs without verifying the package signature against the repository’s published GPG key, equivalent to rpm --nosignature. The 2020-2023 RHEL/Fedora supply-chain incidents (tj-actions, codecov, copr build compromise) all relied on attackers publishing unsigned or alternately-signed RPMs that would only be installed on systems that had disabled signature verification.
dnf_sslverify_falseHIGHansible.builtin.dnf With sslverify: false (Disables Repo-Level SSL Verification)An ansible.builtin.dnf / dnf task sets sslverify: false. Writes sslverify=0 into the repo config, disabling TLS verification at the daemon level - affects every subsequent dnf operation, not just this task, and persists in /etc/yum.repos.d/*.repo.
dnf_validate_certs_falseHIGHansible.builtin.dnf With validate_certs: false (Disables Mirror TLS Validation)An ansible.builtin.dnf / dnf task sets validate_certs: false. DNF fetches repo metadata and RPMs without TLS certificate-chain validation on the configured mirror URLs. Equivalent risk to yum_validate_certs_false - MITM-mirror-swap attacks deliver trojanized packages that execute root-level scriptlets on install.
docker_buildx_secret_mounted_as_envHIGHDocker BuildKit secret mounted as environment variable (persists in image layer)A Dockerfile or docker buildx build command uses --mount=type=secret AND then immediately copies the secret into an environment variable (ENV TOKEN=$(cat /run/secrets/token)) or writes it to a persistent path (RUN cp /run/secrets/token /etc/app/token) - defeating BuildKit’s secret-mount guarantee that the secret is NOT persisted in the image. Also catches docker build --build-arg SECRET=... which is stored verbatim in image history. This is the 2023-2024 top finding in Snyk / Docker Hub secret-scanner reports.
docker_content_trust_disabledHIGHDocker Content Trust Explicitly DisabledAn Ansible task sets DOCKER_CONTENT_TRUST=0 (or no, false) in the environment of a docker pull / docker push / docker build - or exports it unconditionally via environment: at play scope. With DCT disabled, the Notary signature on Docker Hub Official Images is not checked, so an image served under a trusted name but with an attacker-swapped digest will be accepted without warning.
dockerfile_add_remote_http_urlHIGHDockerfile / Containerfile Uses ADD With Remote HTTP URLA task templates or writes a Dockerfile / Containerfile / execution-environment.yml additional_build_files that contains ADD http://... or ADD https://... /path without a checksum validation layer. ADD <URL> fetches at build time with no signature check, no pin, and with the build daemon’s credentials - any MITM or DNS-hijack at build time yields a poisoned image layer that is subsequently pushed to the registry and deployed fleet-wide.
ee_additional_build_files_traversalHIGHExecution Environment: additional_build_files uses path traversal or absolute pathadditional_build_files[*].src uses ../ or an absolute path that escapes the EE’s intended build context (anything not under ./, ./files/, ./build/, or ./context/). ansible-builder copies the referenced files into the image build context; if the path escapes the project directory, a malicious or mis-configured EE can exfiltrate host secrets (/root/.ssh/, /etc/ansible/vault_pass) or bake a compromised binary from somewhere the build author didn’t expect to read from.
ee_build_arg_secretHIGHExecution Environment: ARG exposes credential-shaped variableThe EE’s Containerfile (or ansible-builder additional_build_steps) declares ARG for a variable whose name contains SECRET/TOKEN/API_KEY/PASSWORD. Values passed via --build-arg are persisted in every image layer’s metadata and are trivially extracted with docker history --no-trunc <image> or podman image inspect. Any downstream consumer of the EE image sees the secret, and it ends up in every EE registry push.
ee_dependencies_galaxy_httpHIGHExecution Environment: dependencies.galaxy references HTTP(S) URLThe EE’s dependencies.galaxy: entry points at an HTTP or HTTPS URL instead of a local requirements.yml path. ansible-builder fetches the URL contents at build time - a compromised fetch (MITM, cache poisoning, typo-squat, account takeover of the hosting repo) silently changes every collection/role pulled into every EE rebuild, with no audit trail in the EE git repo.
ee_runs_as_root_userHIGHExecution Environment Dockerfile / Containerfile Runs As RootThe execution-environment.yml build context or its generated Containerfile/Dockerfile does not include a trailing USER directive (or pins USER to 0/root). Every ansible-runner/ansible-navigator invocation then runs collection code, lookup plugins, and Jinja expressions as uid 0 inside the container - the standard Red-Hat-recommended baseline is USER 1001. Running as root inside the EE removes one entire layer of the defence-in-depth against a compromised collection (e.g. community.general.trojan).
ee_untrusted_base_imageHIGHExecution Environment: non-pinned base image (no digest)execution-environment.yml images.base_image.name points at an unpinned or :latest-style tag. Every ansible-builder run pulls whatever is live at that tag - a primitive for supply-chain persistence.
filter_plugins_path_untrustedHIGHfilter_plugins / lookup_plugins Path In Writable Locationfilter_plugins, lookup_plugins, action_plugins, or vars_plugins configured to /tmp or $HOME - same risk as callback_plugins: arbitrary Python loads in the controller process.
flux_image_automation_broad_permsHIGHFlux ImageUpdateAutomation Can Commit To Default Branch Without ReviewA image.toolkit.fluxcd.io/v1beta1 ImageUpdateAutomation is configured with spec.git.push.branch equal to the checkoutRef.branch (i.e. writes straight to the default branch) and no commit.messageTemplate filter. Combined with a broad ImagePolicy (regex .*, semver >=0.0.0), any image tag appearing in the registry reaches production with no PR review, no CODEOWNERS approval, and no signature gate.
galaxy_requirements_http_sourceHIGHGalaxy Collection / Role Fetched Over http://A galaxy-style requirement (roles: / collections: list item) specifies src: or url: pointing at http:// rather than https://. Any network-adjacent attacker can swap in a malicious tarball, role, or git repo. Anchors on the galaxy-manifest shape (nested under roles: / collections: / requirements: or paired with name:/version:/type: within a few lines) to avoid firing on every incidental url: http://... in normal tasks.
gem_cpan_cargo_insecure_source_addedHIGHRubyGems / CPAN / Cargo / Go Proxy Source Added With HTTP Or –insecure FlagA task runs gem sources -a http://..., cpan o conf http_proxy http://... (or a PERL_CPANM_OPT with --insecure), cargo install --registry http://... or renders ~/.cargo/config.toml with [source.<name>] replace-with = 'insecure', or sets GOPROXY=http://.../GOINSECURE=* / GONOSUMCHECK=1. Every modern language package ecosystem requires TLS to its registry; downgrading to HTTP or disabling hash verification (GONOSUMCHECK, --insecure, lockfile_verify = false) recreates the same MitM primitive documented in the 2024 JFrog reports for polyglot repositories.
gem_install_localHIGHgem install From a Local Pathgem install /tmp/foo.gem installs from an unsigned local file - anyone who dropped that file on disk gets code execution as the deploying user.
gh_actions_unpinned_shaHIGHGitHub Actions Third-Party Step Not Pinned to a SHAA workflow references a third-party action by mutable tag (uses: owner/action@v1, @main, @master, @latest) instead of a 40-character commit SHA. Any compromise of the action’s tag - including a retag by a malicious maintainer - silently lands in every subsequent run (tj-actions/changed-files 2025 was precisely this pattern).
gha_third_party_action_unpinned_tagHIGHThird-Party GitHub Action Referenced By Mutable Tag Or BranchA task renders a GitHub Actions workflow that references a third-party action (not actions/* or the org’s own repo) with a mutable ref: @main, @master, @v1, @v1.2 (tags are mutable in Git), rather than a full 40-character commit SHA. Tag-pinning is the root cause of the 2024 reviewdog and 2023 tj-actions/changed-files compromises - an attacker who can force-push a tag in the action’s repo can execute arbitrary code in every downstream workflow instantly.
git_clone_insecure_skip_ssl_verifyHIGHgit -c http.sslVerify=false Or GIT_SSL_NO_VERIFY Set (MitM-able Clone)A task runs git -c http.sslVerify=false clone, git config http.sslVerify false, sets GIT_SSL_NO_VERIFY=1 / GIT_SSL_NO_VERIFY=true, or uses ansible.builtin.git: verify_commit: no + repo: https://.... This disables TLS cert verification for git operations - any MitM can serve an attacker-controlled repo at the target URL and substitute malicious commits. Common in internal self-signed-cert environments but the legitimate fix is to trust the internal CA, not disable validation. The 2024 git CVE-2024-32002 (symlink handling) compounds with this - an attacker serving a malicious repo via MitM can get RCE on checkout.
git_config_credential_helper_store_plaintextHIGHgit config credential.helper=store (Plaintext Password In ~/.git-credentials)A task runs git config --global credential.helper store (or writes it into a rendered ~/.gitconfig / /etc/gitconfig). The store helper writes the raw username + password / PAT in cleartext to ~/.git-credentials - any backup, log-shipping, container-layer, or $HOME-accessible process exfiltrates every git credential at once. The correct helpers are osxkeychain (macOS), wincred (Windows), or libsecret/cache/manager-core (Linux), all of which store via OS-protected secret storage. GitHub’s own docs as of 2024 explicitly warn against store for this reason. The 2024 Mandiant-reported ‘Whispering Spider’ campaign specifically looks for ~/.git-credentials in post-exploitation.
git_hook_or_post_merge_script_fetched_from_remoteHIGHGit Hook (post-merge / pre-commit / post-checkout) Fetched From Remote Or Symlinked ExternallyA task renders a file under .git/hooks/ (or core.hooksPath location) by DOWNLOADING it (get_url, curl, wget) from a remote URL or by symlinking it to an external path, rather than committing the script content into the repo itself. Git hooks execute on every relevant git operation (post-merge runs after every git pull; post-checkout runs after every branch switch) with the shell user’s permissions. A hook fetched from an attacker-controlled URL is a persistent backdoor that auto-executes on routine developer workflow, with no visible trace in the repo history. Used in 2023-2024 by IceFire-group operators once they got write access to a victim’s dev-machine.
github_actions_run_block_injection_untrusted_contextHIGHGitHub Actions: Untrusted github.event Context Interpolated Into Shell run:A GitHub Actions workflow (or an Ansible-rendered workflow) interpolates attacker-controllable expressions such as ${{ github.event.issue.title }}, ${{ github.event.issue.body }}, ${{ github.event.pull_request.title }}, ${{ github.event.pull_request.body }}, ${{ github.event.comment.body }}, ${{ github.event.review.body }}, ${{ github.event.pages.*.page_name }}, ${{ github.head_ref }}, or ${{ github.event.head_commit.message }} directly into a shell run: block. Because GitHub substitutes these values BEFORE the shell runs, any quote, semicolon, backtick, or $(…) inside the untrusted text is executed as part of the command. This is the canonical GitHub-Actions RCE primitive.
github_actions_script_block_injection_untrusted_contextHIGHGitHub Actions: Untrusted github.event Context Interpolated Into actions/github-scriptAn actions/github-script@v* step’s script: body interpolates attacker-controllable ${{ github.event.* }} expressions (issue/pr/comment/review/pages/head_commit/discussion/release bodies or titles, or ${{ github.head_ref }}) directly into the JavaScript source. GitHub substitutes these values BEFORE the runtime evaluates the JS, so any backtick, string terminator, or template-literal inside the untrusted text completes the string and executes arbitrary JS with the workflow’s ${{ secrets.GITHUB_TOKEN }} privileges - repo-wide write access, secrets read, arbitrary-branch create/force-push.
github_actions_uses_unpinned_branch_or_main_refHIGHGitHub Actions workflow references a third-party action by unpinned branch / @main / @master / @latestA task writes a GitHub Actions workflow file (.github/workflows/*.yml) whose uses: step references a third-party action by a mutable git ref (@main, @master, @v1, @latest, @develop, any branch name) instead of a full 40-char commit SHA. The tj-actions/changed-files (CVE-2025-30066, March 2025) and reviewdog/action-setup (March 2025) incidents showed that a single upstream tag/branch compromise cascaded into secret theft across thousands of repositories in hours. GitHub’s own hardening guidance and OpenSSF Scorecard’s Pinned-Dependencies check require SHA pinning for every non-first-party action.
gitlab_ci_job_token_exported_or_echoedHIGHGitLab CI_JOB_TOKEN Exported Or Written To File (Cross-Project Pivot Primitive)A .gitlab-ci.yml or Ansible task running under GitLab CI exports CI_JOB_TOKEN to a file, echoes it, or passes it to a downstream tool that stores it (e.g. writes to ~/.netrc, ~/.gitconfig, a build artifact, or a child pipeline env). CI_JOB_TOKEN is short-lived but carries the scope of the running project’s GitLab permissions - including (since 16.1 default) the ability to read source of other projects in the same group + trigger pipelines. Leaking it via artifact or log enables lateral movement across the GitLab org. The 2024 GitLab CVE-2024-8114 acknowledged that tokens leaked via artifacts were the exploit primitive for privilege escalation.
gitlab_ci_runner_privileged_trueHIGHGitLab CI runner deployed with Docker executor privileged=true (container escape)A task configures a GitLab Runner with [runners.docker] privileged = true in config.toml OR renders a Helm values file with runners.privileged: true OR deploys via the GitLab Runner Operator with .spec.privileged: true. Privileged mode gives the build container full access to the host kernel (mount cgroups, load modules, access /dev/*) - any pipeline (including from any developer with push access) gets root on the runner host. Docker-in-Docker (DinD) is the usual excuse, but rootless DinD / Kaniko / BuildKit in userspace are viable drop-ins.
go_install_unpinned_main_or_latestHIGHgo install Of Package Pinned To @main / @master / @latest / @HEADA task runs go install <mod>@main, go install <mod>@master, go install <mod>@latest, or go install <mod>@HEAD - i.e. installs a Go module from a mutable branch rather than an immutable version tag. Go’s module-proxy caches by commit-hash but the mutable-tag resolution happens at install-time; between successive runs, the attacker who compromises the upstream repo (xz-utils 2024, typosquat-go 2024, GitHub account takeover) immediately reaches every machine that runs the playbook. The Go-Security 2024 guidance explicitly requires @v<semver> or @<40-char-commit-sha> for any production installation.
homebrew_tap_from_untrusted_user_repoHIGHHomebrew tap added from an untrusted third-party GitHub user or repoA task adds a Homebrew tap (brew tap <user>/<repo>) whose source is a personal GitHub account (not an org/owner/first-party/verified maintainer) or an HTTP URL, and then brew installs formulas from it. Homebrew taps run arbitrary Ruby formula code at install-time (install block, def caveats, def post_install, on_macos do) with the user’s permissions - effectively macOS RCE on each agent that applies the playbook. 2024 saw several community-published homebrew taps compromised via GitHub personal access tokens and used to deploy AtomicStealer and Realst variants to developer endpoints.
install_script_from_urlHIGHVendor Install-Script URL Piped to ShellClassic curl https://get.<vendor>.<tld> | sh pattern. Even when the vendor is legitimate today, the install script is opaque and can change at any time.
maven_snapshot_dependency_in_production_playbookHIGHMaven Dependency With -SNAPSHOT Version In A Production Deploy PlaybookA task templates a pom.xml, build.gradle, build.gradle.kts, or ivy.xml containing <version>*-SNAPSHOT</version> / version '*-SNAPSHOT' - i.e. a Maven snapshot (mutable) dependency version - and the play context indicates production deployment (filename deploy_*.yml, production-*.yml, handler restart app, tags: [prod, production, deploy]). Snapshot artifacts are resolved at install-time against the snapshot-repository HEAD; a compromise of the internal Maven repo or a typosquat snapshot (org.apache.commans-text vs commons-text 2022 attack-pattern) is immediately cluster-wide. Sonatype Nexus 2024 policy explicitly recommends failOnSnapshot: true for release builds.
molecule_disable_tls_verifyHIGHMolecule: driver TLS verification disabledMolecule driver has tls_verify: false (or verify_ssl: false). Pulls test images over an MITM-able channel - an attacker on the CI network swaps the image and runs arbitrary code in your test runner.
molecule_privileged_containerHIGHMolecule: scenario runs a privileged containerMolecule scenario platform has privileged: true. Every molecule test CI job spawns a kernel-capable container, giving any bad role code kernel-level access to the CI host.
npm_install_runs_postinstall_scripts_from_untrusted_registryHIGHnpm install runs lifecycle scripts without –ignore-scripts from an untrusted registryA task invokes npm install, npm ci, yarn install, pnpm install, community.general.npm, or community.general.yarn without --ignore-scripts (or ignore_scripts: true), and without a pinned, integrity-verified lockfile + a trusted registry. npm preinstall/install/postinstall lifecycle scripts execute arbitrary code with the permissions of the running user - the 2024 lottie-player tag-hijack, the Oct 2024 ultralytics compromise, the 2022 ua-parser-js incident, and the Feb 2025 reproduce.js / requestly incidents all weaponised postinstall. Also matches npm_config_registry / .npmrc pointing at an unpinned, non-corporate registry (registry.npmjs.org without --before/shrinkwrap integrity, or an http:// mirror).
olm_catalogsource_untrusted_image_pull_alwaysHIGHOLM CatalogSource Points At Untrusted Registry With imagePullPolicy: AlwaysA task renders an OperatorHub CatalogSource manifest whose spec.image is NOT on the Red-Hat / RHEL certified catalog allowlist (registry.redhat.io/redhat/*, registry.access.redhat.com/*, certified-operators.s3.amazonaws.com, registry.connect.redhat.com/*) AND whose spec.updateStrategy.registryPoll.interval is non-zero / uses implicit imagePullPolicy: Always. OLM + imagePullPolicy: Always on an untrusted catalog means every 15-60 min the cluster pulls whatever the catalog registry serves - an attacker who compromises the catalog registry (or the DNS of it) can push a malicious ClusterServiceVersion that OLM auto-installs with the operator’s existing ServiceAccount RBAC (often cluster-admin-equivalent for ‘core’ operators).
pip_index_url_or_extra_index_http_not_httpsHIGHpip –index-url / –extra-index-url Set To Plaintext HTTP (MitM-able Package Download)A task runs pip install --index-url http://... / --extra-index-url http://..., renders /etc/pip.conf or ~/.pip/pip.conf with index-url = http://..., or sets PIP_INDEX_URL=http://... / PIP_EXTRA_INDEX_URL=http://... in an env block. Plaintext HTTP means any on-path attacker (rogue Wi-Fi, compromised corporate TLS-proxy with an internal-only HTTP fallback, BGP-hijacking a PyPI mirror) can substitute malicious wheels with the correct filenames - pip has no out-of-band signature verification. PyPI itself redirects HTTP -> HTTPS since 2018; the only remaining HTTP indexes are internal mirrors that skipped TLS - exactly the targets compromised by the 2024 JFrog-reported internal-mirror-compromise incidents.
pip_install_no_versionHIGHpip install Without a Version Pinpip install <pkg> with no ==<version> or --require-hashes takes the latest release - vulnerable to dependency-confusion, typosquatting, and surprise-transitive upgrades.
pip_install_without_hash_check_from_public_indexHIGHpip install from public PyPI without –require-hashes or a hash-locked requirements fileA task runs pip install, pip3 install, uv pip install, or Ansible’s ansible.builtin.pip / community.general.pip module targeting the public PyPI index without --require-hashes and without a fully-hashed requirements file (pip-compile --generate-hashes output). A PyPI account takeover or name-confusion / typosquat will install attacker code as root. 2024-2025 incidents include the crytic-compile typosquat, ultralytics tag-hijack, fabrice (Boto3 typosquat October 2024), and the requests-darwin-lite telemetry backdoor - each shipped to thousands of build agents before detection. --require-hashes is the only mitigation that survives index / maintainer compromise.
post_install_chmod_setuid_or_setgidHIGHPost-Install Task Adds SUID/SGID Bit To Arbitrary BinaryA playbook runs chmod u+s, chmod g+s, or chmod 4755 / chmod 6755 / numeric mode ≥ 4000 on a file that is NOT a known-legitimate SUID binary (/usr/bin/sudo, /bin/ping, /usr/bin/passwd, /usr/bin/mount). New SUID binaries are one of the top-3 Linux persistence / privesc primitives (T1548.001, T1543) - any new SUID in /tmp, /opt, /usr/local/bin, or a home directory is a near-certain backdoor.
powershell_install_module_skippublishercheck_or_untrusted_repoHIGHPowerShell Install-Module -SkipPublisherCheck / -Force With Untrusted Repo (Unsigned Module RCE)A task runs Install-Module / Install-Script / Save-Module with -SkipPublisherCheck, -AllowPrerelease, -AllowClobber -Force together, OR sets Register-PSRepository / Set-PSRepository with -InstallationPolicy Trusted targeting a custom URL (non-https://www.powershellgallery.com/api/v2). -SkipPublisherCheck specifically bypasses Authenticode signature verification on the module - meaning any PowerShell code masquerading as e.g. Microsoft.PowerShell.Archive or Az gets executed with the running user’s context (often elevated in automation contexts). 2024 attack pattern ‘PipeMagic’ (Microsoft Threat Intel) used a typo-squatted PSGallery module installed with -SkipPublisherCheck. Combined with -Scope AllUsers on a domain controller / management server, this is a single-line domain-admin RCE primitive.
release_artifact_fetched_without_slsa_or_attestation_verifyHIGHRelease Binary Fetched Via get_url/unarchive With No SLSA / cosign / gh-attestation VerifyA task fetches a release artifact via ansible.builtin.get_url / ansible.builtin.unarchive / community.general.archive_extract from github.com/*/releases/download/ or *.amazonaws.com/artifacts/ AND the same play / role contains NO slsa-verifier, cosign verify-attestation --type slsaprovenance, gh attestation verify, or in-toto verify step within ±20 tasks. SLSA Level 3+ build provenance has been the default for GitHub Releases since Apr 2024 and the 2024 CISA Secure Software Development Framework explicitly requires provenance verification at install time.
role_meta_dependency_without_versionHIGHRole meta/main: dependency without a pinned versionA role dependency in meta/main.yml does not pin a version. The next ansible-galaxy install -r pulls whatever the upstream maintainer publishes at that moment.
rpm_install_no_digest_no_signature_flagsHIGHrpm –nodigest / –nosignature / –nofiledigest Flag Used Directly In InstallA task runs rpm -i / rpm -U / rpm -ivh / rpm -Uvh with --nodigest, --nosignature, --nofiledigest, or --noverify. These bypass the per-package header signature AND per-file digest checks that are rpm’s core supply-chain defences - independent of the repo-level gpgcheck=0 setting (already covered by rpm_install_nosignature_or_nogpg) which controls metadata signing. The rpm --no* flags bypass PACKAGE AND FILE-level verification, so a malicious RPM with a legitimate-looking filename (e.g. a retagged firefox-115.0-1.el9.rpm pointing at backdoored binaries) will install without error. Exploited in 2024 during the dvmpox operation that distributed malicious .rpm via a compromised mirror.
rpm_install_nosignature_or_nogpgHIGHrpm/dnf/yum Invoked With –nosignature Or gpgcheck=0A task runs rpm -i --nosignature / --nodigest, dnf install --nogpgcheck, yum install --nogpgcheck, or renders /etc/yum.repos.d/*.repo with gpgcheck=0 (or repo_gpgcheck=0 on a repo whose packages are not additionally signed). Skipping RPM signature validation defeats the entire distro trust chain and is the root cause of every Rocky/AlmaLinux/RHEL post-exploitation persistence via a typo-squatted mirror.
sigstore_policy_controller_warn_not_enforceHIGHSigstore policy-controller / Kyverno ClusterImagePolicy In Warn Mode Not EnforceA task renders a ClusterImagePolicy (sigstore policy-controller), VerifyImages ClusterPolicy (Kyverno), or ImagePolicy (Tekton Chains) with mode: warn / validationFailureAction: Audit / failurePolicy: Ignore. Warn-mode admission policies log violations but DO NOT block deploy - meaning an unsigned / untrusted image still reaches the node and executes. This defeats the entire point of image signature verification and is the 2024 finding most frequently left open from policy-controller / Kyverno rollouts that were ‘almost’ enforced.
slsa_provenance_verification_missingHIGHSLSA Provenance / in-toto Attestation Verification MissingAn Ansible task pulls a BUILT ARTIFACT (container image, wheel, tarball, Galaxy collection tarball, binary release asset) from a registry or URL but never invokes slsa-verifier verify-artifact, cosign verify-attestation --type slsaprovenance, in-toto verify, or gh attestation verify. Level-2+ SLSA guarantees depend on the consumer verifying provenance - otherwise a signed-but-forged build masquerades as legitimate. This is the gap exploited by the 2024 @solana/web3.js and 2025 actions/checkout typosquat campaigns.
terraform_local_state_backend_for_production_stackHIGHTerraform configured with local state backend (no remote state / no locking)A task renders a Terraform / OpenTofu config that uses the default local backend (or an explicit backend "local" block) for what is clearly a production / shared stack - committing terraform.tfstate to the checkout, the playbook, or a developer workstation. Local state has no locking (concurrent runs corrupt state), no encryption-at-rest by the backend, no versioning/rollback, and - most commonly - ends up in git with provider credentials / AWS access keys / Azure client secrets / DB passwords embedded in plaintext resource attributes. This is one of the most common root-cause findings in cloud-credential-theft incidents. Matches terraform { backend "local" { ... } }, terraform init with no backend config + state_file set to a local path, and community.general.terraform / cloud.terraform.terraform tasks with state_file pointing at a workspace-local path.
terraform_s3_backend_without_encryption_or_kmsHIGHTerraform S3 backend configured without encrypt=true or KMS customer-managed keyA task renders a Terraform / OpenTofu backend "s3" block without encrypt = true (pre-default-SSE eras, or bucket default explicitly disabled) or without kms_key_id for the state object - or references an S3 state bucket that has SSE disabled / lacks KMS encryption / lacks Block Public Access. Terraform state contains resource attribute values in plaintext (including database passwords, RDS master creds, static IAM access keys, private TLS keys embedded via aws_iam_server_certificate, random_password outputs). An unencrypted or loosely-scoped state bucket = full cloud-credential disclosure if the bucket is ever misconfigured / snapshot-shared / cross-account leaked. AWS enabled SSE-S3 by default in January 2023, but audits still regularly find pre-2023 buckets + explicit encrypt = false configs + missing KMS CMK policies.
terraform_state_bucket_no_lock_or_versioning_disabledHIGHTerraform / OpenTofu State Backend Without Lock Table Or Bucket Versioning (State Rollback Attack)A task renders a Terraform backend config with backend "s3" missing dynamodb_table (no lock) or with versioning { enabled = false } on the state bucket, OR backend "gcs" without bucket_versioning_policy.enabled = true, OR backend "azurerm" without blob_versioning_enabled = true. Terraform state files contain every provisioned resource’s ID, configuration, and often sensitive fields (DB passwords, TLS private keys, cloud credentials marked sensitive = true are still PLAINTEXT in state). Without a lock table, two concurrent applys corrupt state (self-inflicted DoS). Without versioning, an attacker who gains write to the state bucket can (1) download current state to harvest credentials, (2) upload a crafted state showing no resources - next terraform apply then provisions attacker-controlled replacements, (3) delete the state object entirely forcing painful resource-by-resource re-import. 2024 Unit 42 IR reports cite Terraform state bucket compromise as a primary initial-access vector in cloud breaches.
terraform_variable_with_secret_value_sensitive_falseHIGHTerraform variable / output holding a secret declared with sensitive = false (or unset)A task renders a Terraform / OpenTofu variable, output, or locals block whose name / default clearly holds a secret (password, secret, token, api_key, private_key, client_secret, connection_string) but is declared with sensitive = false - or with sensitive omitted entirely (defaults to false in Terraform < 1.3). The value then ends up in terraform plan output, CI logs, the state file (still readable, but less obviously), and any downstream provider’s log fields. Matches HashiCorp’s published anti-patterns from the Terraform Secrets Handling guidance (2023-2024).
uri_get_url_token_in_url_query_or_userinfoHIGHansible.builtin.uri / get_url URL Contains Token In Query String Or userinfo@hostAn ansible.builtin.uri, ansible.builtin.get_url, community.general.archive, ansible.posix.synchronize, or git task passes a URL containing an API token, access token, or password in the query string (?token=<value>, ?api_key=<value>, ?access_token=<value>, ?auth=<value>, ?apikey=<value>, ?pat=<value>) or in the userinfo segment (https://<user>:<token>@host/...). Token-in-URL is the most common operational credential leak vector because these URLs land in: (1) HTTP-proxy access logs (Squid, Bluecoat, corporate forward-proxies), (2) web-server request logs that forward Referer/X-Forwarded-For or log full query strings, (3) shell history (ansible -vvv / curl -v), (4) ps output while the playbook runs (git clone with embedded creds is visible to every local user via /proc), (5) Ansible callback JSON / AAP job-event archives, (6) CI-provider logs (GitHub Actions, GitLab CI, CircleCI - any runner that logs the task). The token is then almost impossible to rotate completely because you don’t know which log aggregator still has it. This is distinct from hardcoded_credentials rules that match bare-literal secret assignments in vars - those catch api_key: sk-...; this rule catches the URL-embedded form that vars-scanners miss.
yum_sslverify_falseHIGHansible.builtin.yum With sslverify: false (Disables Repo-Level SSL Verification)An ansible.builtin.yum / yum task sets sslverify: false. This writes sslverify=0 into the repo config, disabling SSL verification of the mirror URLs at the yum-daemon level (lower in the stack than validate_certs:, and harder to audit after the fact because it persists in /etc/yum.repos.d/*.repo). Equivalent to making every subsequent yum install vulnerable to mirror-swap attacks, not just this one task.
yum_validate_certs_falseHIGHansible.builtin.yum With validate_certs: false (Disables Mirror TLS Validation)An ansible.builtin.yum / yum task sets validate_certs: false. Yum will fetch repo metadata and RPM packages from the configured mirror URLs without verifying the mirror’s TLS certificate chain. Any on-path attacker (rogue WiFi, ARP-spoof, compromised CDN edge, malicious VPN gateway) can swap out RPMs for trojanized versions before they reach the target host - especially dangerous because RPMs execute pre/post install scriptlets as root.
aws_ecr_image_tag_mutability_mutableMEDIUMECR Repository image_tag_mutability = MUTABLE (Tag Overwrite Allowed)A community.aws.ecs_ecr task sets image_tag_mutability: MUTABLE (or leaves it at the AWS default, which is MUTABLE). Mutable tags let a compromised CI token or a malicious insider RE-PUSH a different image under the same tag (e.g. :prod, :v1.2.3), silently replacing a previously-reviewed image for every downstream deployer that pulls the tag. This is how the 2022 ctx PyPI takeover + the 2021 Codecov bash-uploader incident propagated. Combined with ECS’s :latest anti-pattern, a single CI compromise lets an attacker replace every running task at the next deployment.
bindep_unpinned_packageMEDIUMbindep.txt: system package without a version constraint or platform tagA bindep.txt entry is a bare package name with no platform tag ([platform:rpm]) and no version constraint. Future EE rebuilds pull whatever the distro publishes at that moment.
buildkit_cache_mount_shared_unscopedMEDIUMDockerfile RUN –mount=type=cache With sharing=shared And Unscoped targetA task templates a Dockerfile / Containerfile containing RUN --mount=type=cache,sharing=shared,target=/root/.cache/pip (or /var/cache/apk, /go/pkg/mod, /root/.npm) WITHOUT an id= that scopes the cache per-repository + per-lockfile. sharing=shared pools the cache across every concurrent build on the builder - on a multi-tenant BuildKit instance (GitHub Actions hosted runners, BuildKit rootless daemons shared across teams, Depot.dev, Docker Hub Automated Builds), a malicious build can write poisoned wheels / apks / go modules into the cache that a subsequent legitimate build ingests.
docker_pull_platform_skip_arch_verifyMEDIUMdocker pull –platform Override (Bypasses Per-Arch Signature Verification)A task runs docker pull --platform linux/amd64 <image> (or --platform linux/arm64) while the host’s native arch is different - or explicitly with cosign verify --type=cosign-signature --insecure-ignore-sct / --allow-insecure-registry. Cross-platform pulls bypass the per-architecture manifest-digest verification that Sigstore/cosign uses because the type=cosign-signature sidecar is ARCH-SPECIFIC. This is the specific issue raised in the 2024 Sigstore TUF working-group posture review - attackers can serve a clean amd64 image and a malicious arm64 sibling under the same tag.
dockerfile_healthcheck_curl_insecureMEDIUMDockerfile HEALTHCHECK Uses curl -k (Skips TLS Verification Inside Container)A rendered Dockerfile / Containerfile or an ansible-builder execution-environment definition has HEALTHCHECK (or a CI healthcheck: block) that invokes curl -k / curl --insecure / wget --no-check-certificate. Beyond the MitM risk, the -k health-check PASSES when TLS is broken - meaning a container with an expired cert, a corrupted CA bundle, or an attacker-stripped TLS proxy is reported healthy and left in the Kubernetes service load-balancer pool.
ee_dependencies_python_unpinnedMEDIUMExecution Environment: dependencies.python contains unpinned packageThe EE’s dependencies.python: block-scalar includes at least one bare package name (no ==, >=, ~=, !=, or <= version constraint). Every EE rebuild resolves the package against the current PyPI index - a single compromised upstream (shai-hulud, ctx/phpass typo-squats) gets baked into the EE and runs on every ad-hoc playbook execution.
galaxy_requirements_collection_without_signatures_keyMEDIUMrequirements.yml Collection Entry Pinned But Missing signatures: (GPG Verification Opt-Out)A requirements.yml pins a collection from Automation Hub / galaxy_ng / Ansible Galaxy via source: https://... + version: X.Y.Z but omits the signatures: key (list of GPG detached-signature URLs) and keyring: is not set at the top-level. Since ansible-galaxy collection install 2.13+ (galaxy_ng 4.7+, 2023) supports per-entry GPG signature verification (--signature <url> / signatures: [...] in the requirements file) backed by a project-owned GPG keyring, content pulled without these keys is trust-on-first-hash only - the tarball SHA256 published by Automation Hub is all that protects against a compromise of the hosting infrastructure. An attacker with Automation Hub / galaxy_ng operator credentials (the 2024 Red Hat IAM advisory scenario) can republish a matching-version tarball with a new SHA256 and every unsigned consumer picks it up silently.
galaxy_requirements_git_branch_refMEDIUMGalaxy Requirement Uses Branch / HEAD Referenceversion: main / master / HEAD / develop - pulls whatever is at the tip of a moving branch, so an attacker with push access can change what gets deployed without bumping a version.
galaxy_requirements_non_galaxy_sourceMEDIUMGalaxy Requirement From Non-Galaxy Source Without Checksumrequirements.yml pulls a collection or role via raw git/tarball URL (src: git+… or src: https://...tar.gz) without a signature or checksum - no integrity verification.
git_submodule_or_actions_pinned_to_main_master_branchMEDIUMGit Submodule / GitHub Action Pinned To main / master / HEAD (Moving Reference)A task uses ansible.builtin.git: version: main / master / HEAD / develop OR a GitHub Actions uses: org/action@main / @master / @v2 (major-tag only, which moves). Moving references mean the build is non-reproducible AND vulnerable to commit-substitution supply-chain attacks (e.g. the 2024 tj-actions/changed-files CVE-2025-30066 that retroactively modified all @v44/@v45 tag targets to exfiltrate secrets, affecting 23k+ repos). The only safe pin is a full 40-char git SHA, which cannot be silently changed by the upstream maintainer. Distinct from supply_chain_unpinned_docker_image - this catches the git-ref variant.
python_setup_py_execMEDIUMpython setup.py install (Legacy, Executes Arbitrary Code)python setup.py install runs arbitrary Python from the package’s setup.py at install time - the whole reason pip stopped doing it by default. Use pip + wheel instead.
role_meta_dependency_git_urlMEDIUMRole meta/main: dependency pulled from a git URLRole dependency is sourced from a raw git URL. Unless the URL is pinned to an immutable ref (sha), force-push on the upstream branch replaces the role.
strategy_plugin_customMEDIUMCustom / Non-Core Strategy Plugin In Usestrategy is set to a value that is not ’linear’, ‘free’, ‘debug’, or a well-known collection strategy - custom strategy plugins run arbitrary Python in the controller process.
python_build_from_sourceLOWpython -m build Inside a PlaybookBuilding Python packages from source inside a deploy playbook couples your deploy to your source tree and means artefacts aren’t produced by CI. It’s also a recipe for ‘works on my controller’ drift.
role_meta_galaxy_info_missing_licenseLOWRole meta/main: galaxy_info missing license fieldgalaxy_info has no license: key. Roles without a license can’t be safely consumed in commercial pipelines - legal risk for the downstream consumer.