UNPKG

@cyclonedx/cdxgen

Version:

Creates CycloneDX Software Bill of Materials (SBOM) from source or container image

803 lines (785 loc) 34.7 kB
# CI/CD Permission Security Rules # Category: ci-permission # Evaluates GitHub Actions, GitLab CI, Azure Pipelines, etc. for privilege risks - id: CI-001 name: "Unpinned action in write-permission workflow" description: "GitHub Actions referenced by tag/branch in workflows with write permissions pose supply chain risk" severity: high category: ci-permission dry-run-support: full attack: tactics: [TA0001] techniques: [T1195.001] condition: | $auditComponents($)[ $prop($, 'cdx:github:action:isShaPinned') = 'false' and ( $prop($, 'cdx:github:workflow:hasWritePermissions') = 'true' or $prop($, 'cdx:github:job:hasWritePermissions') = 'true' ) ] location: | { "bomRef": $."bom-ref", "purl": purl, "file": $prop($, 'cdx:github:workflow:file') } message: "Unpinned GitHub Action '{{ $prop($, 'cdx:github:action:uses') }}' in workflow with write permissions" mitigation: "Pin action to full SHA: actions/setup-node@abc123def456..." evidence: | { "pinningType": $prop($, 'cdx:github:action:versionPinningType'), "workflowTriggers": $prop($, 'cdx:github:workflow:triggers'), "jobName": $prop($, 'cdx:github:job:name') } - id: CI-002 name: "OIDC token issuance to non-official action" description: "Workflows or jobs granting id-token:write to third-party actions may enable token exfiltration" severity: high category: ci-permission dry-run-support: full attack: tactics: [TA0006] techniques: [T1528] condition: | $auditComponents($)[ ( $prop($, 'cdx:github:workflow:hasIdTokenWrite') = 'true' or $prop($, 'cdx:github:job:hasIdTokenWrite') = 'true' ) and $prop($, 'cdx:actions:isOfficial') = 'false' ] location: | { "bomRef": $."bom-ref", "purl": purl, "file": $prop($, 'cdx:github:workflow:file') } message: "Workflow grants OIDC token access to non-official action '{{ $prop($, 'cdx:github:action:uses') }}'" mitigation: "Restrict id-token scope or use only verified/official actions for OIDC operations" evidence: | { "isVerified": $prop($, 'cdx:actions:isVerified'), "actionGroup": group, "jobHasIdTokenWrite": $prop($, 'cdx:github:job:hasIdTokenWrite'), "workflowHasIdTokenWrite": $prop($, 'cdx:github:workflow:hasIdTokenWrite') } - id: CI-003 name: "Action pinned to mutable tag" description: "GitHub Actions pinned to tags (vs SHA) can change behavior unexpectedly if tag is moved" severity: medium category: ci-permission dry-run-support: full attack: tactics: [TA0001] techniques: [T1195.001] condition: | $auditComponents($)[ $prop($, 'cdx:github:action:versionPinningType') = 'tag' ] location: | { "bomRef": $."bom-ref", "purl": purl } message: "GitHub Action '{{ $prop($, 'cdx:github:action:uses') }}' pinned to mutable tag (not SHA)" mitigation: "Consider pinning to full commit SHA for immutability: actions/setup-node@<full-sha>" evidence: | { "currentVersion": version, "isOfficial": $prop($, 'cdx:actions:isOfficial') } - id: CI-004 name: "Workflow uses pull_request_target trigger" description: "pull_request_target can execute code in the context of the base branch, risking secret exposure" severity: medium category: ci-permission dry-run-support: full attack: tactics: [TA0001, TA0004] condition: | $auditWorkflows($)[ $prop($, 'cdx:github:workflow:hasPullRequestTargetTrigger') = 'true' ] location: | { "bomRef": $."bom-ref", "file": $prop($, 'cdx:github:workflow:file') } message: "Workflow '{{ $prop($, 'cdx:github:workflow:name') }}' uses pull_request_target trigger" mitigation: "Ensure workflow does not checkout or run code from the PR head; use pull_request with careful permissions" evidence: | { "triggers": $prop($, 'cdx:github:workflow:triggers'), "hasWritePermissions": $prop($, 'cdx:github:workflow:hasWritePermissions') } - id: CI-005 name: "Checkout step persists credentials unnecessarily" description: "actions/checkout with persist-credentials=true (default) exposes GITHUB_TOKEN to subsequent steps" severity: medium category: ci-permission dry-run-support: full attack: tactics: [TA0006] techniques: [T1552] condition: | $auditComponents($)[ $contains($nullSafeProp($, 'cdx:github:action:uses'), 'actions/checkout') and $propBool($, 'cdx:github:checkout:persistCredentials') != false and ( $propBool($, 'cdx:github:workflow:hasWritePermissions') = true or $propBool($, 'cdx:github:job:hasWritePermissions') = true ) ] location: | { "bomRef": $."bom-ref", "purl": purl, "file": $prop($, 'cdx:github:workflow:file') } message: "Checkout step uses persist-credentials=true in privileged workflow; consider persist-credentials: false;" mitigation: "Add persist-credentials: false to checkout steps that don't require git push operations" evidence: | { "persistCredentials": $prop($, 'cdx:github:checkout:persistCredentials'), "workflowPermissions": $prop($, 'cdx:github:workflow:hasWritePermissions') } - id: CI-006 name: "Cache usage in untrusted trigger context" description: "GitHub Actions cache can be poisoned when used in workflows triggered by untrusted input (e.g., pull_request from forks)" severity: high category: ci-permission dry-run-support: full attack: tactics: [TA0001] techniques: [T1195.001] condition: | $auditComponents($)[ $nullSafeProp($, 'cdx:github:action:uses') ~> $contains('actions/cache') and ( $prop($, 'cdx:github:workflow:hasPullRequestTrigger') = 'true' or $prop($, 'cdx:github:workflow:hasPullRequestTargetTrigger') = 'true' ) and ( $propBool($, 'cdx:github:workflow:hasWritePermissions') = true or $propBool($, 'cdx:github:job:hasWritePermissions') = true or $prop($, 'cdx:github:cache:hasRestoreKeys') = 'true' or $prop($, 'cdx:github:cache:keyUsesHashFiles') != 'true' ) ] location: | { "bomRef": $."bom-ref", "purl": purl, "file": $prop($, 'cdx:github:workflow:file') } message: "Cache action used in pull-request-reachable workflow; cache key '{{ $prop($, 'cdx:github:cache:key') }}' may be writable by untrusted code" mitigation: "Scope cache keys to PR-specific values, validate restored cache contents, or avoid caching in untrusted workflows" evidence: | { "cacheKey": $prop($, 'cdx:github:cache:key'), "hasRestoreKeys": $prop($, 'cdx:github:cache:hasRestoreKeys'), "keyUsesHashFiles": $prop($, 'cdx:github:cache:keyUsesHashFiles'), "cachePath": $prop($, 'cdx:github:cache:path'), "triggers": $prop($, 'cdx:github:workflow:triggers') } - id: CI-007 name: "Script injection via untrusted context interpolation" description: "Direct interpolation of github.event.* or inputs.* into run: blocks enables command injection" severity: critical category: ci-permission dry-run-support: full attack: tactics: [TA0002] techniques: [T1059] condition: | $auditComponents($)[ $prop($, 'cdx:github:step:hasUntrustedInterpolation') = 'true' and $prop($, 'cdx:github:step:type') = 'run' ] location: | { "bomRef": $."bom-ref", "file": $prop($, 'cdx:github:workflow:file') } message: "Untrusted input interpolated into shell command: variables '{{ $prop($, 'cdx:github:step:interpolatedVars') }}'" mitigation: "Use intermediate environment variables: env: TITLE: ${{ github.event.pull_request.title }} then reference $TITLE in run:" evidence: | { "interpolatedVars": $prop($, 'cdx:github:step:interpolatedVars'), "stepCommand": $prop($, 'cdx:github:step:command') } - id: CI-008 name: "High-risk trigger with write permissions" description: "Triggers like pull_request_target, issue_comment, or workflow_run combined with write permissions enable privilege escalation" severity: high category: ci-permission dry-run-support: full attack: tactics: [TA0001, TA0004] condition: | $auditWorkflows($)[ ( $prop($, 'cdx:github:workflow:hasPullRequestTargetTrigger') = 'true' or $prop($, 'cdx:github:workflow:hasIssueCommentTrigger') = 'true' or $prop($, 'cdx:github:workflow:hasWorkflowRunTrigger') = 'true' ) and $prop($, 'cdx:github:workflow:hasWritePermissions') = 'true' ] location: | { "bomRef": $."bom-ref", "file": $prop($, 'cdx:github:workflow:file') } message: "Workflow '{{ $prop($, 'cdx:github:workflow:name') }}' uses high-risk trigger with write permissions" mitigation: "Use pull_request instead of pull_request_target; require manual approval for issue_comment triggers; restrict permissions per job" evidence: | { "triggers": $prop($, 'cdx:github:workflow:triggers'), "hasWritePermissions": $prop($, 'cdx:github:workflow:hasWritePermissions') } - id: CI-009 name: "Workflow file contains hidden Unicode characters" description: "Hidden Unicode in workflow files can disguise malicious logic, comments, or diffs and should be reviewed before merge" severity: medium category: ci-permission dry-run-support: full attack: tactics: [TA0005] techniques: [T1027] condition: | $auditWorkflows($)[ $prop($, 'cdx:github:workflow:hasHiddenUnicode') = 'true' ] location: | { "bomRef": $."bom-ref", "file": $prop($, 'cdx:github:workflow:file') } message: "Workflow '{{ $prop($, 'cdx:github:workflow:name') }}' contains hidden Unicode characters" mitigation: "Review the file in an editor that reveals bidirectional, zero-width, and control characters; remove suspicious hidden Unicode before merge" evidence: | { "codePoints": $prop($, 'cdx:github:workflow:hiddenUnicodeCodePoints'), "lineNumbers": $prop($, 'cdx:github:workflow:hiddenUnicodeLineNumbers'), "inComments": $prop($, 'cdx:github:workflow:hiddenUnicodeInComments') } - id: CI-010 name: "Legacy token-based package publishing step" description: "npm and PyPI publishing should prefer trusted publishing or OIDC-backed flows instead of long-lived token secrets or explicit --token arguments" severity: medium category: ci-permission dry-run-support: full attack: tactics: [TA0006] techniques: [T1528] condition: | $auditComponents($)[ $prop($, 'cdx:github:step:isPublishCommand') = 'true' and $prop($, 'cdx:github:step:usesLegacyPublishToken') = 'true' ] location: | { "bomRef": $."bom-ref", "file": $prop($, 'cdx:github:workflow:file') } message: "Workflow publish step '{{ name }}' uses legacy {{ $prop($, 'cdx:github:step:publishEcosystem') }} token-based publishing" mitigation: "Prefer trusted publishing or short-lived OIDC-backed release flows instead of persistent package tokens or explicit --token arguments" evidence: | { "ecosystem": $prop($, 'cdx:github:step:publishEcosystem'), "tokenSources": $prop($, 'cdx:github:step:legacyPublishTokenSources'), "stepCommand": $prop($, 'cdx:github:step:command') } - id: CI-011 name: "External reusable workflow inherits caller secrets" description: "Reusable workflows invoked from external repositories with secrets: inherit expand the trust boundary and can expose repository credentials" severity: high category: ci-permission dry-run-support: full attack: tactics: [TA0006] techniques: [T1528, T1552] condition: | $auditComponents($)[ $prop($, 'cdx:github:reusableWorkflow:isExternal') = 'true' and $prop($, 'cdx:github:reusableWorkflow:secretsInherit') = 'true' ] location: | { "bomRef": $."bom-ref", "purl": purl, "file": $prop($, 'cdx:github:workflow:file') } message: "Reusable workflow '{{ $prop($, 'cdx:github:reusableWorkflow:uses') }}' inherits caller secrets from an external repository" mitigation: "Avoid secrets: inherit for external reusable workflows; pass only the minimum explicit secrets and pin the reusable workflow to a full SHA" evidence: | { "uses": $prop($, 'cdx:github:reusableWorkflow:uses'), "isShaPinned": $prop($, 'cdx:github:reusableWorkflow:isShaPinned'), "withKeys": $prop($, 'cdx:github:reusableWorkflow:withKeys') } - id: CI-012 name: "External reusable workflow pinned to mutable ref" description: "Reusable workflows referenced by tag or branch can change behavior without review and should be pinned to immutable SHAs" severity: medium category: ci-permission dry-run-support: full attack: tactics: [TA0001] techniques: [T1195.001] condition: | $auditComponents($)[ $prop($, 'cdx:github:reusableWorkflow:isExternal') = 'true' and $prop($, 'cdx:github:reusableWorkflow:isShaPinned') = 'false' ] location: | { "bomRef": $."bom-ref", "purl": purl, "file": $prop($, 'cdx:github:workflow:file') } message: "External reusable workflow '{{ $prop($, 'cdx:github:reusableWorkflow:uses') }}' is pinned to a mutable ref" mitigation: "Pin reusable workflows to full SHAs and review updates through a normal change-management process" evidence: | { "pinningType": $prop($, 'cdx:github:reusableWorkflow:versionPinningType'), "ref": $prop($, 'cdx:github:reusableWorkflow:ref') } - id: CI-013 name: "High-risk trigger reaches a self-hosted runner" description: "High-risk triggers executing on self-hosted runners can expose internal network access, credentials, and long-lived runner state" severity: high category: ci-permission dry-run-support: full attack: tactics: [TA0004, TA0008] condition: | $auditComponents($)[ ( $prop($, 'cdx:github:workflow:hasPullRequestTargetTrigger') = 'true' or $prop($, 'cdx:github:workflow:hasIssueCommentTrigger') = 'true' or $prop($, 'cdx:github:workflow:hasWorkflowRunTrigger') = 'true' ) and $prop($, 'cdx:github:job:isSelfHosted') = 'true' ] location: | { "bomRef": $."bom-ref", "purl": purl, "file": $prop($, 'cdx:github:workflow:file') } message: "High-risk workflow trigger reaches self-hosted runner in job '{{ $prop($, 'cdx:github:job:name') }}'" mitigation: "Restrict self-hosted runners to trusted workflows only, isolate runner credentials, and gate high-risk triggers with manual approval" evidence: | { "triggers": $prop($, 'cdx:github:workflow:triggers'), "jobRunner": $prop($, 'cdx:github:job:runner'), "jobName": $prop($, 'cdx:github:job:name') } - id: CI-014 name: "Privileged workflow mutates downstream runner state" description: "Writing to GITHUB_ENV, GITHUB_PATH, or GITHUB_OUTPUT in privileged workflows can persist attacker-controlled state across later steps and jobs" severity: high category: ci-permission dry-run-support: full attack: tactics: [TA0002] techniques: [T1059] condition: | $auditComponents($)[ $prop($, 'cdx:github:step:type') = 'run' and $prop($, 'cdx:github:step:mutatesRunnerState') = 'true' and ( $prop($, 'cdx:github:workflow:hasWritePermissions') = 'true' or $prop($, 'cdx:github:job:hasWritePermissions') = 'true' or $prop($, 'cdx:github:workflow:hasIdTokenWrite') = 'true' or $prop($, 'cdx:github:job:hasIdTokenWrite') = 'true' ) ] location: | { "bomRef": $."bom-ref", "file": $prop($, 'cdx:github:workflow:file') } message: "Privileged workflow step '{{ name }}' mutates runner state via '{{ $prop($, 'cdx:github:step:runnerStateTargets') }}'" mitigation: "Avoid mutating shared runner state in privileged jobs; prefer tightly-scoped step outputs and review all GITHUB_ENV/GITHUB_PATH/GITHUB_OUTPUT writes" evidence: | { "runnerStateTargets": $prop($, 'cdx:github:step:runnerStateTargets'), "stepCommand": $prop($, 'cdx:github:step:command'), "jobName": $prop($, 'cdx:github:job:name') } - id: CI-015 name: "Outbound network command references sensitive context" description: "Run steps that invoke outbound network tools while transmitting secrets, github.token, or OIDC request context are strong exfiltration indicators" severity: high category: ci-permission dry-run-support: full attack: tactics: [TA0010] techniques: [T1048] condition: | $auditComponents($)[ $prop($, 'cdx:github:step:type') = 'run' and $prop($, 'cdx:github:step:hasOutboundNetworkCommand') = 'true' and $prop($, 'cdx:github:step:referencesSensitiveContext') = 'true' and $prop($, 'cdx:github:step:likelyExfiltration') = 'true' ] location: | { "bomRef": $."bom-ref", "file": $prop($, 'cdx:github:workflow:file') } message: "Outbound command in step '{{ name }}' references sensitive context '{{ $prop($, 'cdx:github:step:sensitiveContextRefs') }}'" mitigation: "Review outbound network steps for secret or token use, disable unnecessary network egress, and move sensitive values into least-privileged isolated jobs" evidence: | { "networkTools": $prop($, 'cdx:github:step:outboundNetworkTools'), "sensitiveContextRefs": $prop($, 'cdx:github:step:sensitiveContextRefs'), "exfiltrationIndicators": $prop($, 'cdx:github:step:exfiltrationIndicators'), "stepCommand": $prop($, 'cdx:github:step:command') } - id: CI-016 name: "Privileged reusable workflow accepts caller secrets" description: "workflow_call producers that request caller-provided secrets while also holding write or OIDC permissions expand the blast radius across repositories and workflows" severity: high category: ci-permission dry-run-support: full attack: tactics: [TA0006] techniques: [T1528, T1552] condition: | $auditWorkflows($)[ $prop($, 'cdx:github:workflow:isWorkflowCallProducer') = 'true' and $safeStr($prop($, 'cdx:github:workflow:workflowCallSecrets')) != '' and ( $propBool($, 'cdx:github:workflow:hasWritePermissions') = true or $propBool($, 'cdx:github:workflow:hasIdTokenWrite') = true ) ] location: | { "bomRef": $."bom-ref", "file": $prop($, 'cdx:github:workflow:file') } message: "Reusable workflow '{{ $prop($, 'cdx:github:workflow:name') }}' accepts caller secrets while running with privileged permissions" mitigation: "Avoid broad secret interfaces on reusable workflows with write or OIDC permissions; split privileged work into a narrowly scoped follow-up job and require only explicit minimal secrets" evidence: | { "workflowCallSecrets": $prop($, 'cdx:github:workflow:workflowCallSecrets'), "writeScopes": $prop($, 'cdx:github:workflow:writeScopes'), "hasWritePermissions": $prop($, 'cdx:github:workflow:hasWritePermissions'), "hasIdTokenWrite": $prop($, 'cdx:github:workflow:hasIdTokenWrite') } - id: CI-017 name: "Privileged reusable workflow exports caller-influenced outputs" description: "workflow_call producers that both accept caller-controlled inputs and emit outputs from privileged execution contexts can propagate unsafe values into downstream trusted jobs" severity: medium category: ci-permission dry-run-support: full attack: tactics: [TA0003, TA0004] condition: | $auditWorkflows($)[ $prop($, 'cdx:github:workflow:isWorkflowCallProducer') = 'true' and $safeStr($prop($, 'cdx:github:workflow:workflowCallInputs')) != '' and $safeStr($prop($, 'cdx:github:workflow:workflowCallOutputs')) != '' and ( $propBool($, 'cdx:github:workflow:hasWritePermissions') = true or $propBool($, 'cdx:github:workflow:hasIdTokenWrite') = true ) ] location: | { "bomRef": $."bom-ref", "file": $prop($, 'cdx:github:workflow:file') } message: "Reusable workflow '{{ $prop($, 'cdx:github:workflow:name') }}' exports outputs from a privileged workflow_call interface that also accepts caller inputs" mitigation: "Review workflow_call outputs for trust-boundary crossings, sanitize caller-controlled data before emitting outputs, and avoid combining privileged permissions with broad producer interfaces" evidence: | { "workflowCallInputs": $prop($, 'cdx:github:workflow:workflowCallInputs'), "workflowCallOutputs": $prop($, 'cdx:github:workflow:workflowCallOutputs'), "hasWritePermissions": $prop($, 'cdx:github:workflow:hasWritePermissions'), "hasIdTokenWrite": $prop($, 'cdx:github:workflow:hasIdTokenWrite') } - id: CI-018 name: "Fork-reachable workflow dispatches downstream workflow or repository events" description: "Dispatching workflow_dispatch or repository_dispatch from fork-reachable or privileged jobs can create a lateral-movement path into downstream workflows with broader credentials" severity: high category: ci-permission dry-run-support: full attack: tactics: [TA0006] techniques: [T1528] condition: | $auditComponents($)[ $prop($, 'cdx:github:step:dispatchesWorkflow') = 'true' and $prop($, 'cdx:github:step:referencesSensitiveContext') = 'true' and ( $propBool($, 'cdx:github:workflow:hasPullRequestTrigger') = true or $propBool($, 'cdx:github:workflow:hasPullRequestTargetTrigger') = true or $propBool($, 'cdx:github:workflow:hasIssueCommentTrigger') = true or $propBool($, 'cdx:github:workflow:hasWorkflowRunTrigger') = true or $propBool($, 'cdx:github:workflow:hasWritePermissions') = true or $propBool($, 'cdx:github:job:hasWritePermissions') = true or $propBool($, 'cdx:github:workflow:hasIdTokenWrite') = true or $propBool($, 'cdx:github:job:hasIdTokenWrite') = true ) ] location: | { "bomRef": $."bom-ref", "purl": purl, "file": $prop($, 'cdx:github:workflow:file') } message: "Workflow step '{{ name }}' dispatches '{{ $prop($, 'cdx:github:step:dispatchKinds') }}' toward '{{ $prop($, 'cdx:github:step:dispatchTargets') }}' from a fork-reachable or privileged context" mitigation: "Avoid chaining downstream workflow_dispatch or repository_dispatch calls from pull-request/fork-reachable jobs; isolate release dispatchers into trusted workflows with narrowly scoped credentials" evidence: | { "dispatchKinds": $prop($, 'cdx:github:step:dispatchKinds'), "dispatchMechanisms": $prop($, 'cdx:github:step:dispatchMechanisms'), "dispatchTargets": $prop($, 'cdx:github:step:dispatchTargets'), "localReceiverWorkflowFiles": $prop($, 'cdx:github:step:dispatchReceiverWorkflowFiles'), "localReceiverWorkflowNames": $prop($, 'cdx:github:step:dispatchReceiverWorkflowNames'), "sensitiveContextRefs": $prop($, 'cdx:github:step:sensitiveContextRefs'), "workflowTriggers": $prop($, 'cdx:github:workflow:triggers'), "writeScopes": $prop($, 'cdx:github:workflow:writeScopes') } - id: CI-019 name: "Workflow dispatch step references explicit fork context" description: "Dispatch chains that inspect pull_request head-repository or fork context before invoking downstream workflows are strong signals of fork-to-privileged lateral movement" severity: critical category: ci-permission dry-run-support: full attack: tactics: [TA0006] techniques: [T1528, T1552] condition: | $auditComponents($)[ $prop($, 'cdx:github:step:dispatchesWorkflow') = 'true' and $prop($, 'cdx:github:step:referencesForkContext') = 'true' and $prop($, 'cdx:github:step:referencesSensitiveContext') = 'true' and ( $prop($, 'cdx:github:step:hasLocalDispatchReceiver') = 'true' or $safeStr($prop($, 'cdx:github:step:hasLocalDispatchReceiver')) = '' ) ] location: | { "bomRef": $."bom-ref", "purl": purl, "file": $prop($, 'cdx:github:workflow:file') } message: "Workflow step '{{ name }}' combines explicit fork/head-repository context with downstream dispatch '{{ $safeStr($prop($, 'cdx:github:step:dispatchReceiverWorkflowNames')) != '' ? $prop($, 'cdx:github:step:dispatchReceiverWorkflowNames') : $prop($, 'cdx:github:step:dispatchTargets') }}'" mitigation: "Do not route fork-derived context into workflow_dispatch or repository_dispatch calls. Split privileged follow-up workflows behind trusted branch-only triggers and avoid sharing long-lived tokens into fork-reachable jobs" evidence: | { "forkContextRefs": $prop($, 'cdx:github:step:forkContextRefs'), "dispatchKinds": $prop($, 'cdx:github:step:dispatchKinds'), "dispatchTargets": $prop($, 'cdx:github:step:dispatchTargets'), "hasLocalDispatchReceiver": $prop($, 'cdx:github:step:hasLocalDispatchReceiver'), "localReceiverWorkflowFiles": $prop($, 'cdx:github:step:dispatchReceiverWorkflowFiles'), "localReceiverWorkflowNames": $prop($, 'cdx:github:step:dispatchReceiverWorkflowNames'), "localReceiverMatchBasis": $prop($, 'cdx:github:step:dispatchReceiverMatchBasis'), "sensitiveContextRefs": $prop($, 'cdx:github:step:sensitiveContextRefs') } - id: CI-020 name: "pull_request_target checks out PR head or fork context" description: "Checking out github.event.pull_request.head.* repository or ref inside pull_request_target executes untrusted fork code with base-repository privileges" severity: critical category: ci-permission dry-run-support: full attack: tactics: [TA0001, TA0006] techniques: [T1195.001, T1552] condition: | $auditComponents($)[ $contains($nullSafeProp($, 'cdx:github:action:uses'), 'actions/checkout') and $prop($, 'cdx:github:workflow:hasPullRequestTargetTrigger') = 'true' and ( $prop($, 'cdx:github:checkout:checksOutUntrustedRef') = 'true' or $prop($, 'cdx:github:checkout:referencesForkContext') = 'true' ) ] location: | { "bomRef": $."bom-ref", "purl": purl, "file": $prop($, 'cdx:github:workflow:file') } message: "Checkout step '{{ $prop($, 'cdx:github:action:uses') }}' pulls PR head or fork context inside pull_request_target" mitigation: "Do not checkout github.event.pull_request.head.* in pull_request_target workflows. Prefer pull_request with read-only permissions or split trusted follow-up work into a separate branch-only workflow" evidence: | { "checkoutRef": $prop($, 'cdx:github:checkout:ref'), "checkoutRepository": $prop($, 'cdx:github:checkout:repository'), "untrustedRefContexts": $prop($, 'cdx:github:checkout:untrustedRefContexts'), "forkContextRefs": $prop($, 'cdx:github:checkout:forkContextRefs'), "persistCredentials": $prop($, 'cdx:github:checkout:persistCredentials'), "workflowHasWritePermissions": $prop($, 'cdx:github:workflow:hasWritePermissions') } - id: CI-021 name: "Heuristic: high-risk trigger with implicit permissions and sensitive operations" description: "High-risk GitHub Actions workflows that omit explicit permissions blocks while still performing sensitive operations may rely on repository-default token scopes. This is a review heuristic, not proof of write access." severity: medium category: ci-permission dry-run-support: full attack: tactics: [TA0006] techniques: [T1528, T1552] condition: | $auditComponents($)[ $prop($, 'cdx:github:workflow:hasHighRiskTrigger') = 'true' and $prop($, 'cdx:github:workflow:hasAnyExplicitPermissionsBlock') = 'false' and $prop($, 'cdx:github:step:hasSensitiveOperations') = 'true' ] location: | { "bomRef": $."bom-ref", "purl": purl, "file": $prop($, 'cdx:github:workflow:file') } message: "Heuristic review: workflow '{{ $prop($, 'cdx:github:workflow:name') }}' uses a high-risk trigger without an explicit permissions block while step '{{ $safeStr($prop($, 'cdx:github:step:name')) != '' ? $prop($, 'cdx:github:step:name') : name }}' performs sensitive operation(s) '{{ $prop($, 'cdx:github:step:sensitiveOperations') }}'" mitigation: "Add an explicit top-level permissions: block (for example permissions: {}) and re-grant only the minimum per-job scopes needed for the sensitive step" evidence: | { "workflowTriggers": $prop($, 'cdx:github:workflow:triggers'), "workflowHasExplicitPermissionsBlock": $prop($, 'cdx:github:workflow:hasExplicitPermissionsBlock'), "workflowHasAnyExplicitPermissionsBlock": $prop($, 'cdx:github:workflow:hasAnyExplicitPermissionsBlock'), "jobHasExplicitPermissionsBlock": $prop($, 'cdx:github:job:hasExplicitPermissionsBlock'), "sensitiveOperations": $prop($, 'cdx:github:step:sensitiveOperations'), "sensitiveContextRefs": $prop($, 'cdx:github:step:sensitiveContextRefs'), "dispatchKinds": $prop($, 'cdx:github:step:dispatchKinds') } - id: CI-022 name: "npm setup action disables build cache despite resolved package distributions" description: "Explicitly disabling setup-node caching reduces tamper resistance and reviewability when npm dependencies are resolved from remote package distributions" severity: medium category: ci-permission dry-run-support: full attack: tactics: [TA0001] techniques: [T1195.001] condition: | $auditComponents($)[ $prop($, 'cdx:github:action:disablesBuildCache') = 'true' and $prop($, 'cdx:github:action:buildCacheEcosystem') = 'npm' and $count($$.components[ $startsWith(purl, 'pkg:npm/') and ( $contains($lowercase($nullSafeProp($, 'cdx:npm:manifestSourceType')), 'git') or $contains($lowercase($nullSafeProp($, 'cdx:npm:manifestSourceType')), 'url') ) and $count(externalReferences[ type = 'distribution' and ( $startsWith($lowercase(url), 'git+') or $startsWith($lowercase(url), 'http://') or $startsWith($lowercase(url), 'https://') ) ]) > 0 ]) > 0 ] location: | { "bomRef": $."bom-ref", "purl": purl, "file": $prop($, 'cdx:github:workflow:file') } message: "GitHub Action '{{ $prop($, 'cdx:github:action:uses') }}' explicitly disables npm build caching while resolved npm package distributions are present in the BOM" mitigation: "Keep setup-node caching enabled unless you have a reviewed exception; disabling cache can weaken integrity checks and provenance review for resolved npm artifacts" evidence: | { "cacheDisableInput": $prop($, 'cdx:github:action:buildCacheDisableInput'), "cacheDisableValue": $prop($, 'cdx:github:action:buildCacheDisableValue'), "matchingPackages": $$.components[ $startsWith(purl, 'pkg:npm/') and ( $contains($lowercase($nullSafeProp($, 'cdx:npm:manifestSourceType')), 'git') or $contains($lowercase($nullSafeProp($, 'cdx:npm:manifestSourceType')), 'url') ) and $count(externalReferences[ type = 'distribution' and ( $startsWith($lowercase(url), 'git+') or $startsWith($lowercase(url), 'http://') or $startsWith($lowercase(url), 'https://') ) ]) > 0 ].purl } - id: CI-023 name: "Python setup action disables build cache despite resolved package distributions" description: "Explicitly disabling setup-python caching reduces tamper resistance and reviewability when PyPI dependencies are resolved from remote archives or VCS sources" severity: medium category: ci-permission dry-run-support: full attack: tactics: [TA0001] techniques: [T1195.001] condition: | $auditComponents($)[ $prop($, 'cdx:github:action:disablesBuildCache') = 'true' and $prop($, 'cdx:github:action:buildCacheEcosystem') = 'pypi' and $count($$.components[ $startsWith(purl, 'pkg:pypi/') and ( $contains($lowercase($nullSafeProp($, 'cdx:pypi:manifestSourceType')), 'git') or $contains($lowercase($nullSafeProp($, 'cdx:pypi:manifestSourceType')), 'url') ) ]) > 0 ] location: | { "bomRef": $."bom-ref", "purl": purl, "file": $prop($, 'cdx:github:workflow:file') } message: "GitHub Action '{{ $prop($, 'cdx:github:action:uses') }}' explicitly disables Python build caching while resolved PyPI package distributions are present in the BOM" mitigation: "Keep setup-python caching enabled when lockfiles resolve remote archives or VCS sources unless you have a reviewed exception" evidence: | { "cacheDisableInput": $prop($, 'cdx:github:action:buildCacheDisableInput'), "cacheDisableValue": $prop($, 'cdx:github:action:buildCacheDisableValue'), "matchingPackages": $$.components[ $startsWith(purl, 'pkg:pypi/') and ( $contains($lowercase($nullSafeProp($, 'cdx:pypi:manifestSourceType')), 'git') or $contains($lowercase($nullSafeProp($, 'cdx:pypi:manifestSourceType')), 'url') ) ].purl } - id: CI-024 name: "Cargo setup action disables build cache despite manifest-declared git dependencies" description: "Explicitly disabling Cargo setup caching reduces tamper resistance and reviewability when Cargo manifests rely on git dependencies" severity: medium category: ci-permission dry-run-support: full attack: tactics: [TA0001] techniques: [T1195.001] condition: | $auditComponents($)[ $prop($, 'cdx:github:action:disablesBuildCache') = 'true' and $prop($, 'cdx:github:action:buildCacheEcosystem') = 'cargo' and $count($$.components[ $startsWith(purl, 'pkg:cargo/') and $hasProp($, 'cdx:cargo:git') ]) > 0 ] location: | { "bomRef": $."bom-ref", "purl": purl, "file": $prop($, 'cdx:github:workflow:file') } message: "GitHub Action '{{ $prop($, 'cdx:github:action:uses') }}' explicitly disables Cargo build caching while manifest-declared Cargo git dependencies are present in the BOM" mitigation: "Keep Cargo setup caching enabled when manifests rely on git dependencies unless you have a reviewed exception" evidence: | { "cacheDisableInput": $prop($, 'cdx:github:action:buildCacheDisableInput'), "cacheDisableValue": $prop($, 'cdx:github:action:buildCacheDisableValue'), "matchingPackages": $$.components[ $startsWith(purl, 'pkg:cargo/') and $hasProp($, 'cdx:cargo:git') ].purl }