The CI/CD pipeline security decision record: why the pipeline trust model you chose determines your supply chain attack surface and your secrets exposure ceiling

Published 2026-07-23 · WhyChose

CI/CD pipeline security decisions are made when an engineer sets up a new repository's GitHub Actions workflow for the first time (creates a build-and-test workflow, adds cloud credentials as repository secrets, pins the third-party actions by whatever tag the action's README suggests — @v2, @latest, @main — because the getting-started documentation consistently shows tags rather than commit digests), when a pull request introduces a new capability and the engineer adds a marketplace action to handle it (installs the action, adds any required secrets to the repository settings, does not review the action's permissions or source code because the action has 50,000 stars and the README is clear), and when a compliance audit or security review flags a finding and the engineer adds SAST scanning to the workflow to remediate it (the scanner runs as a step, the finding is closed, the scanning action is also pinned by tag). The AI session that handles the initial pipeline setup is velocity-oriented and terminates at "build passes and deployment works": it creates the workflow file, adds the required secrets to the repository settings via the web UI, verifies that the pipeline builds the application and deploys to the target environment, and closes. The pipeline is functional. The session succeeds. The session closes.

What the AI session does not produce is the second half of the pipeline security decision. The session answers "how do I create a GitHub Actions workflow that builds my application and deploys to AWS?" and delivers a working pipeline with a successful deployment. It does not ask: what is the action pinning policy for third-party actions — are we pinning by digest or by tag, what review process applies when a new action is added, and what is the plan when an action maintainer's account is compromised and their v2 tag is updated to a malicious commit; what is the credential model for cloud access in the pipeline — are we storing long-lived AWS access keys as repository secrets, and if so what is the rotation cadence, the scope per environment, and the response plan if a key is exfiltrated by a malicious action or leaked through an artifact; what is the deployment authorization model — does a successful build on any push to main deploy directly to production, or is there a required reviewer approval gate between CI success and production deployment; what is the artifact integrity model — are build artifacts signed or attested so that the deployment process can verify that the artifact being deployed is the same artifact that was built from the reviewed commit, not a substitution; and what is the pipeline secret scope — are production cloud credentials accessible to all branches and all pull requests, or are they scoped to the main branch and the production deployment environment with a separate lower-privilege set for pull request validation. Each of these questions has an answer that is not derivable from "the build passes and the deployment succeeds in the main branch pipeline" as the session completion criterion. Each answer determines whether a compromised third-party action can exfiltrate your cloud credentials in the next CI run, whether a debug step added by a well-intentioned engineer captures database connection strings in an artifact that outlives the pipeline run, whether a single malicious commit merged by an inattentive reviewer can deploy arbitrary code to production without a second human seeing the deployment, and whether your pipeline satisfies an enterprise customer's vendor security questionnaire asking about your build integrity controls and credential management practices. The answers are in the AI sessions. They are almost never written down.

Two ways CI/CD pipeline security decisions produce the wrong outcome in production

The compromised action tag that exfiltrated AWS credentials in the next CI run

A developer tools startup builds its backend on AWS and deploys via GitHub Actions. The founding engineer sets up a three-action pipeline: actions/checkout@v3 for repository checkout, actions/setup-node@v3 for Node.js installation, and docker/build-push-action@v4 for container builds. Each action is referenced by the tag shown in the respective action's README. The startup later adds a GitHub Actions action from the marketplace for Docker layer caching — a less widely known action with approximately 8,000 stars that reduces their average build time from 6 minutes to 2.5 minutes. The action is added with uses: cache-org/docker-layer-cache@v2 following the same tag-reference pattern established for the other actions. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are stored as repository secrets for the ECR push and ECS deployment steps.

Fourteen months after the pipeline is set up, the cache action maintainer's GitHub account is compromised through a phishing attack targeting their personal email address. The attacker uses the compromised account to push a new commit to the cache action's repository. The commit adds 11 lines to the action's index.js: a script that reads all environment variables (including the GitHub Actions secrets context), base64-encodes them, and POSTs them to an attacker-controlled HTTPS endpoint before proceeding with the normal caching logic. The attacker updates the v2 tag to point to this new commit. The cache action's README still shows @v2 as the recommended install reference. No change appears in the startup's repository — the workflow file is unchanged.

The next time a developer pushes a commit to the main branch, the pipeline runs. It checks out the repository, sets up Node.js, and then executes uses: cache-org/docker-layer-cache@v2 — which now resolves to the malicious commit. The action runs, reads AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY from the runner environment, encodes them, and transmits them to the attacker's endpoint. The remaining pipeline steps complete normally. The build succeeds. The deployment succeeds. No alert fires. The pipeline run shows green in the GitHub Actions UI. The credentials are now in the attacker's possession and valid with no expiration date.

The startup discovers the breach four days later when an AWS billing alert triggers at $4,200 in unexpected charges: the attacker has used the credentials to download 52 GB of S3 data, create four IAM users with AdministratorAccess, and deploy 19 EC2 spot instances running cryptocurrency mining software across three regions. The incident response requires revoking the compromised access keys and all keys created by the attacker (requiring auditing all IAM users and keys created in the past four days), rotating all 41 secrets across 12 services that used the same or related credentials, auditing nine weeks of CloudTrail logs to determine what data was accessed and what actions were taken, notifying four enterprise customers whose data was stored in the affected S3 buckets, and implementing digest-based pinning for all actions — a remediation that takes six hours to implement but was available from the day the pipeline was created. If the pipeline had pinned uses: cache-org/docker-layer-cache@sha256:3f8a2c1... (the digest of the reviewed version of the action), the malicious commit published to the same tag would have produced a different digest; the runner would have rejected the action execution with a digest verification failure; the attack would have been visible as a pipeline failure instead of a silent credential exfiltration in a successful build.

The debug step that bypassed secret masking and reached a contractor's laptop

A B2B SaaS company stores its database connection string as a GitHub Actions secret named DATABASE_URL and injects it into the workflow environment via env: DATABASE_URL: ${{ secrets.DATABASE_URL }} on the deployment job. GitHub Actions masks the secret value in log output — any occurrence of the raw secret value in job output is replaced with *** before being stored in the log. The team's security documentation notes that secrets are "protected by GitHub's built-in masking" as the primary security control for pipeline credentials.

A new backend engineer is debugging a database migration failure. The migration runs as a workflow step that fails with a timeout error. The engineer cannot reproduce the failure locally and needs to understand the environment differences. They add a debug step to the workflow: run: env | sort. GitHub Actions correctly masks the DATABASE_URL value in the job log output: the log shows DATABASE_URL=*** where the actual connection string would appear. The engineer sees the masked value and adds a follow-up step: run: echo $DATABASE_URL | base64. Base64 encoding is not a format that GitHub Actions recognizes as masking bypass for the raw secret value — the masking is applied to the literal secret string, not to transformed representations of it. The base64-encoded connection string appears in the log in plain text because it is not the literal value of DATABASE_URL. The engineer adds continue-on-error: true to make debugging easier and uploads the job output as an artifact for easier inspection: uses: actions/upload-artifact@v3 with path: /tmp/debug.log after redirecting the debug output to a file.

The artifact contains the base64-encoded connection string. A contractor working on the migration issue downloads the artifact through the GitHub Actions artifact download interface. The artifact download is not logged in the same way as secret access is logged — it appears in GitHub's audit log as a standard artifact download with no flag for potential secret exposure. The contractor's task is completed three weeks later and their access is revoked. Seven weeks after the incident, the contractor's laptop is stolen from a car. The laptop contains a local copy of the downloaded debug artifact, including the base64-encoded connection string, which decodes to the production database credentials. The startup is notified when the contractor's company reports the laptop theft. The database credentials must be rotated with a brief maintenance window; the rotation triggers a cascade of application configuration updates; and a post-incident review finds that four other workflow files in the repository have the same pattern of env injection with no OIDC migration. If the credential model had used OIDC-issued short-lived tokens rather than a static DATABASE_URL secret — or if the database credential injection had used a runtime secrets manager call (AWS Secrets Manager, HashiCorp Vault) rather than a CI environment variable — the artifact would have contained a short-lived token that had already expired rather than long-lived credentials. The masking bypass through encoding transformation would still have appeared, but the exfiltrated value would have been inert.

Three structural properties that CI/CD pipeline security decisions determine

The action pinning model and the supply chain attack surface

The action pinning model specifies how third-party actions are referenced in workflow files, the review process for adding new actions, the update cadence for pinned action versions, and the monitoring strategy for action repository changes. Without a documented pinning model, actions accumulate across workflow files in whatever reference format the engineer who added them used most recently — tags in the early files, because tag references are what every action's README shows; tags in the later files, because engineers copying from existing workflows copy the tag reference pattern. The action pinning model must specify five properties. First, the canonical reference format: digest pinning (uses: actions/checkout@sha256:abc123..., where the sha256 string is the commit hash of the specific action version being pinned) is the only format that makes the action reference immutable; tag pinning (uses: actions/checkout@v4) and branch pinning (uses: actions/checkout@main) are both mutable references that resolve to different code at different points in time. The model must state explicitly whether the standard is digest-only, tag-with-digest-comment (a human-readable tag followed by a pinned digest on the same line, the pattern used by tools like Harden-Runner), or whether tag pinning is acceptable for actions from GitHub's own organization (github.com/actions) under an argument that those actions are maintained by a more trusted party than third-party marketplace actions. Each choice has a different trust level and a different supply chain attack surface — digest-only eliminates the supply chain attack vector entirely; tag-with-digest-comment requires that the comment is actually the digest used and that the workflow tooling enforces the digest rather than allowing the tag to be overridden; distinguishing first-party from third-party pinning by maintainer creates a categorical rule that engineers can apply consistently but introduces the judgment call of what counts as a trusted first party. Second, the action addition review process: adding a new third-party action should require a review of the action's source code (or at least its main entry point), its permissions requirements (what inputs it reads from the GitHub context, what API calls it makes, what outputs it produces), and whether a higher-trust alternative exists (a built-in GitHub Actions feature, a first-party action from the software vendor, or a well-maintained action with a history of security disclosure handling). The review does not need to be exhaustive — the goal is to check that the action does not request permissions it does not need and does not make calls to external endpoints that the action description does not explain. Third, the update process for pinned action versions: digest-pinned actions do not update automatically (unlike tag-pinned actions, which silently update when the tag moves); the model must specify whether Dependabot or Renovate is configured to submit pull requests when new action versions are released with the correct updated digest, and whether the pull request review process for action version updates requires the same code review as the initial addition or whether a lower-friction process applies for patch updates with known changelogs. Fourth, the organization-level enforcement: GitHub allows organization-level policies that restrict which actions can be used in repositories — allowing only actions from verified creators, requiring actions to be in the GitHub marketplace with a published source, or maintaining an organization-level approved action list; the pinning model must specify whether such policies are enforced at the organization level (enforcing compliance automatically for all repositories) or documented as a convention that individual repository owners are responsible for following. Fifth, the incident response for compromised actions: the model must specify the monitoring strategy for detecting action compromises — whether repository webhook events from action repositories trigger alerts, whether Dependabot security alerts cover compromised action versions, or whether the team relies on security disclosure mailing lists and GitHub's security advisories for notification; and the response procedure when a compromised action version is discovered after it has been used in pipeline runs, including the credential rotation scope (all secrets accessible in the affected pipeline jobs, with the scope depending on what the pipeline's environment contained at the time the action ran). The build system decision record specifies the build tool and the artifact model; the action pinning model determines the trustworthiness of the build environment — an unpinned action can modify the build environment, inject malicious code into the build process, or replace build artifacts, turning the build system's output from a product of the reviewed source code into a product of whatever the compromised action decided to produce. The security ADR and threat model decision record must enumerate the CI/CD supply chain as an attack surface: a workflow file that runs on every push to main and has access to production credentials is a high-value target for supply chain attackers, and the pipeline's trust model — what code runs, where it comes from, and what it has access to — must be part of the threat model.

The pipeline secrets management model and the secrets exposure ceiling

The pipeline secrets management model specifies the credential type used for cloud access (static long-lived credentials stored as secrets versus OIDC-issued short-lived tokens), the scope of each secret (repository-level, environment-level, or organization-level), the audit and rotation model for static secrets, and the policy on what categories of information are permitted in pipeline environments versus fetched at runtime from a dedicated secrets manager. Without a documented secrets management model, secrets accumulate at the repository level as engineers add integrations — AWS credentials here, Stripe keys there, a Slack webhook token for deployment notifications, a database connection string copied from the local .env file — without a consistent scoping strategy and without a rotation cadence. The secrets management model must specify six properties. First, the credential type for cloud provider access: OIDC-based short-lived credentials (where the pipeline requests a temporary token from the cloud provider's OIDC endpoint using the pipeline's identity claims as the trust assertion) are the current best practice for cloud access from CI/CD pipelines for AWS, GCP, and Azure; the model must specify whether the team has migrated to OIDC or is using static access keys, and if static keys are still in use, the migration timeline and the interim rotation cadence (90-day rotation is the common standard for static CI keys; 30 days is more conservative; annual rotation is too long for credentials with production access). Second, the OIDC trust relationship scope: an OIDC trust relationship that allows the pipeline to assume an IAM role must specify the conditions under which the trust is granted — at minimum, the GitHub organization and repository, and optionally the branch (the production deployment role should only be assumable from the main branch), the environment (the production deployment role should only be assumable from a job running in the production GitHub Actions environment, which is subject to the environment's required reviewer policy), and the job (specific workflow files or job names, if the cloud provider's OIDC implementation supports job-level claims). Third, the secret scope per environment: secrets that authorize production deployments — production cloud credentials, production database connection strings, production API keys — must be scoped to the production environment rather than the repository, so that workflows running on feature branches and pull requests cannot access production credentials; the scoping prevents a malicious pull request from exfiltrating production credentials in a pull request check workflow, which runs before the pull request is reviewed and merged. Fourth, the artifact and log secrets policy: secrets injected into a workflow environment via the secrets context are masked in job logs by GitHub Actions, but the masking applies to the literal secret value and can be bypassed by encoding transformations (base64, hex, URL encoding of the secret value); the model must specify that secrets must not be written to files that are uploaded as artifacts, that debug steps that dump environment variables must be removed before merging, and that artifact uploads must not include files that could contain secret values (log files, environment files, configuration files generated from secret values). Fifth, the secrets manager integration for application secrets: pipeline secrets should be limited to credentials that the pipeline needs to operate (cloud credentials for deployments, container registry credentials for pushing images); application secrets (database connection strings, payment processor API keys, external service tokens) should be injected into the application at runtime by a dedicated secrets manager (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager) rather than passed as pipeline environment variables, so that the secrets are not exposed to the pipeline environment at all; the model must specify which secret categories are permitted as pipeline-level secrets and which must use runtime injection. Sixth, the secret rotation cadence and audit: any remaining static secrets must have a documented rotation cadence, an owner responsible for rotation, and an audit mechanism that verifies rotation completion; the GitHub audit log records secret creation, update, and deletion events that can be used to verify that rotation occurred; the model must specify the alert threshold for secrets that are approaching the rotation deadline without a verified update event. The secrets management decision record specifies the secrets management platform and the rotation model for application secrets; the pipeline secrets management model is the interface between the secrets management platform and the CI/CD pipeline — the deployment workflow must have credentials to read secrets from the secrets manager at deployment time, and those credentials are themselves a secret that follows the pipeline secrets management model rather than the application secrets management model.

The deployment authorization model and the blast radius per pipeline run

The deployment authorization model specifies the conditions under which a pipeline run is authorized to deploy to a given environment, the human approval requirements for sensitive environments, the branch protection model that prevents unauthorized code from reaching the production deployment trigger, and the emergency change process for bypassing normal authorization gates under incident conditions. Without a documented deployment authorization model, the pipeline deploys to production on every successful build from the main branch with no human in the loop between the merge and the deployment, which means that the blast radius of a malicious merge to main includes a production deployment of whatever code was merged. The deployment authorization model must specify five properties. First, the environment protection rules: GitHub Actions environments (and their equivalents in GitLab, Bitbucket, and other platforms) support required reviewers — named individuals or teams who must approve the deployment before the pipeline proceeds; a production environment with two required reviewers converts a successful CI build into a pending deployment that does not proceed until two named reviewers approve it in the GitHub UI; the model must specify the number of required reviewers for each environment, the reviewer group composition (whether the engineering manager is required, whether a security team member is required for specific deployment types), and whether the reviewer must be different from the author of the pull request being deployed. Second, the branch protection model for the deployment trigger: a deployment that only fires from the main branch is only as secure as the branch protection rules on main; branch protection must require pull request reviews before merging (with a count that matches the risk tolerance — one reviewer for feature work, two reviewers for security-sensitive changes), prohibit force pushes (which would allow a reviewer to approve a benign commit and then the author to replace it with a malicious commit via force push before the deployment runs), and require status checks to pass before merging (so that a failing CI build cannot be merged and deployed in a hurry); the model must specify the branch protection configuration for every branch from which production deployments are triggered. Third, the environment-scoped secret authorization: the deployment authorization gate is only meaningful if the production credentials required for the deployment are scoped to the protected environment — if production AWS credentials are available as a repository-level secret accessible to all branches, a pull request check workflow can access the credentials without going through the required reviewer gate; the secrets must be scoped to the environment so that the required reviewer approval is the only path to the secrets, not an alternative to accessing repository-level credentials from a pull request workflow. Fourth, the deployment scope per pipeline credential: the IAM role or service account assumed by the production deployment pipeline should have only the permissions required for the deployment actions it performs — pushing to ECR, updating an ECS service, running a database migration — and not broader administrative permissions; a deployment credential with AdministratorAccess means that a compromised deployment (through a malicious merge that passed review, or through a supply chain attack that replaced the artifact after review) can make any change to the cloud account; a deployment credential with the minimum required permissions limits the blast radius of a compromised deployment to the specific actions the pipeline is authorized to take. Fifth, the emergency change process: the required reviewer gate adds latency to every deployment, which is usually acceptable but becomes a problem during an incident where a one-line hotfix needs to reach production in two minutes; the model must specify the emergency change process — whether the required reviewer count can be reduced to one during declared incidents, whether specific engineers are designated as authorized to approve emergency deployments without the normal reviewer count, and what audit trail is required when the emergency process is used; an emergency process that is documented and logged is preferable to a process that is ad hoc and relies on force-pushing to main and disabling branch protection, which leaves no audit trail and bypasses every security control simultaneously. The GitOps decision record specifies whether the deployment model is push-based (the CI pipeline deploys directly to infrastructure) or pull-based (the pipeline pushes a manifest to Git and a GitOps controller deploys from Git); the deployment authorization model differs significantly between the two: in a push-based model, the pipeline job itself is the deployment authorization boundary; in a pull-based model, the GitOps controller's sync approval (ArgoCD sync gate, Flux automation policies) is the deployment authorization boundary, and the pipeline's role is limited to updating the manifest in the GitOps repository rather than executing the deployment directly. The access control model decision record specifies the IAM permission model for cloud resources; the deployment authorization model is the pipeline-specific instantiation of the access control model — the IAM roles assumed by pipeline jobs must follow the same least-privilege principle as the application's IAM roles, with permissions scoped to the specific actions each pipeline job performs rather than broad administrative access.

Three AI session types that embed pipeline security decisions without documenting them

The initial pipeline setup session is where the action pinning policy, the credential model, and the deployment authorization architecture are established without being named as decisions. The session is pipeline-setup-oriented and terminates at the working deployment: it creates the workflow file, adds the required cloud credentials as repository secrets via the settings UI, pins actions by whatever tag the README suggests (the getting-started documentation for every widely-used action shows tag references — actions/checkout@v4, actions/setup-node@v4 — because tags are human-readable and memorable in a way that sha256 digests are not), configures the deployment step with the stored credentials, pushes the workflow file, and watches the first pipeline run succeed. The deployment is operational. The session succeeds. The session closes. The credential type is static keys rather than OIDC because the getting-started documentation for GitHub Actions shows static key setup and the OIDC configuration requires additional steps (creating an IAM OIDC provider in AWS, configuring a trust policy on the IAM role, using a different authentication action in the workflow) that the initial session does not need to implement to produce a working pipeline. The action pinning policy is tag-based because that is what every referenced action's README shows and because no engineer in the session asks "what is the policy if this tag moves to a malicious commit?" The deployment runs on push to main with no required reviewer gate because the getting-started guide does not discuss required reviewers and because adding one would require knowing that GitHub Actions environments exist and that they support approval gates — knowledge that is not in scope for a session focused on "make the deployment work." The initial setup session result is "pipeline builds and deploys successfully" — not "actions pinned by digest per the supply chain pinning model; production credentials provisioned via OIDC trust relationship scoped to main branch with the production deployment role; production environment requires two named reviewers before deployment proceeds; static credentials are scoped to the production environment and not accessible to pull request workflows." The second result requires treating each configuration choice as a security property of the pipeline rather than as a setup step with a binary functional completion criterion. The new CTO onboarding problem with CI/CD pipeline security is that the incoming technical leader can read the workflow files in the repository and understand the pipeline structure — what actions are used, what secrets are referenced, what branches trigger which jobs — but cannot determine from the workflow files alone whether the action pinning model was a deliberate security decision or the default pattern from the getting-started guide, whether the static AWS credentials predate OIDC support on the platform or whether OIDC was evaluated and rejected for a reason, or whether the absence of required reviewers on the production environment reflects a deliberate velocity-versus-security trade-off or an oversight that was never addressed.

The dependency addition session embeds action selection and permission decisions as individual integration choices that accumulate without a coherent trust model. The session is triggered by a new integration requirement: the engineer needs to add Terraform plan/apply to the pipeline, or deploy to a new environment, or add a security scanner, or cache Docker layers. The engineer searches the GitHub Actions marketplace or a web search for an action that handles the task, finds a well-starred option, reads the README, adds the action to the workflow with the tag shown in the example, adds any required secrets to the repository settings, and verifies that the pipeline succeeds with the new action. The session does not check whether the new action's tag is pinned to the specific version reviewed, what the action's source code does with the credentials it receives (whether it makes external API calls, logs the credentials, or passes them to subprocesses in ways that are not covered by GitHub's standard secret masking), whether the action's permissions requirements have been reviewed against the principle of least privilege, or whether a first-party action from the service vendor would provide the same capability with a more auditable trust relationship. The action's star count is used as a proxy for trustworthiness because it is the most visible signal available in the marketplace interface, but star count does not predict maintainer account security (the 8,000-star action whose maintainer's account is compromised does not become less trusted because of the incident; the marketplace shows the same star count and the same "verified" badge before and after the account compromise). The CI/CD pipeline decision record for the general pipeline architecture specifies the pipeline tool, the artifact model, and the deployment strategy; the dependency addition session adds to the pipeline's attack surface each time it introduces a new third-party action, and the aggregate supply chain risk of the pipeline is the cumulative risk of every action in every workflow file, not just the risk of the most recently added action — which means the risk grows with each addition without a corresponding review of the total action portfolio.

The compliance remediation session embeds pipeline security changes as point solutions that address the finding without establishing the model. The session is triggered by a security audit, a penetration test report, or a compliance framework requirement: the auditor flags that the pipeline does not have SAST scanning, or that secrets are not rotated on the required cadence, or that there is no artifact signing. The engineer adds the required control — installs a SAST scanning action, updates a secret rotation reminder in the team's task tracker, generates a SLSA provenance artifact — and closes the finding. The SAST scanning action is also pinned by tag because the remediation session follows the same getting-started documentation as the initial setup session, and no one questions the pinning policy during a compliance remediation. The secret rotation update adds a new entry to the task tracker but does not change the credential model — the static keys remain, the rotation cadence is now documented, but the root cause (that static keys have a non-zero exfiltration surface that OIDC eliminates) is not addressed. The SLSA provenance artifact is generated by the pipeline but is not consumed by the deployment step — the deployment pulls the container image from ECR without verifying the provenance, so the artifact exists but provides no actual integrity guarantee. The compliance finding is closed. The session result is "compliance finding remediated" — not "pipeline trust model updated to eliminate the credential model vulnerability the audit was proxying for; action pinning policy now enforced for all workflow files including the three new actions added during remediation; deployment process updated to verify SLSA provenance before deploying, so that the attestation protects against artifact substitution rather than being an unused artifact in the build output." The decisions never written down in a compliance remediation session are the model that should govern future pipeline additions — the session produces control additions but not the architectural model that makes those additions systematic — and the gap between the compliance finding and its root cause, which means the remediated finding can recur in the next audit cycle when new pipeline configurations repeat the original pattern.

The five sections of a CI/CD pipeline security decision record

The first section documents the third-party action pinning model and supply chain verification. The canonical reference format must be specified explicitly: digest pinning with a human-readable tag comment (uses: actions/checkout@sha256:11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2) is the current recommended format for workflows that require both auditability and human readability; digest-only pinning without a tag comment is acceptable for automated tooling but makes the workflow files harder for humans to audit; tag-only pinning is not acceptable for any action with access to production credentials. The review requirement for new third-party actions must specify what is checked before an action is added: at minimum, whether the action's source is publicly available and the listed source matches the compiled artifact distributed by the marketplace; whether the action makes external network calls and what endpoints those calls reach; whether the action's permissions requirements (read access to specific directories, write access to specific files, access to specific environment variables or secrets context values) are proportional to what it claims to do; and whether a first-party alternative exists. The Dependabot or Renovate configuration for automatic digest update pull requests must be specified, including the review requirement for update pull requests (patch updates with clear changelogs may be approved with lower friction than major version updates). The organization-level action policy — whether the GitHub organization restricts which actions are permitted in repositories — must be specified with the rationale for the chosen restriction level. The incident response procedure for compromised actions must specify the detection mechanism (GitHub's security advisories, third-party supply chain monitoring services, or community disclosure channels), the credential rotation scope triggered by a confirmed action compromise, and whether pipeline runs that used the compromised action version are treated as potential credential exposure events requiring proactive rotation or only if exfiltration is confirmed. The container image build policy decision record specifies the base image and image scanning requirements; the action pinning model is the supply chain equivalent for the pipeline layer — in the same way that a container image built from an unpinned base image tag can include a different base OS version than the one tested, a workflow that uses an unpinned action tag can execute different code than the version reviewed when the action was added.

The second section documents the pipeline secrets management model. The credential type for cloud provider access must specify whether the team uses OIDC-based short-lived credentials or static long-lived credentials for each cloud provider, the OIDC trust relationship conditions (organization, repository, branch, environment, and job-level claims) for each credential scope, and the migration timeline if static credentials are in use. For any remaining static credentials, the model must specify the rotation cadence, the owner responsible for rotation, the audit mechanism (GitHub audit log queries that confirm the secret was updated within the rotation window), and the alert for secrets approaching the rotation deadline. The secret scope per environment must specify which secrets are scoped to the repository level (accessible to all branches and all pull requests), which are scoped to specific environments (accessible only to jobs running in that environment with all required approvals in place), and which require runtime injection from a secrets manager rather than storage as a pipeline secret. The artifact and log secrets policy must specify that environment variable dumps must not be committed to workflow files without a review gate, that artifacts must not include files that contain secret values or transformations of secret values (base64, hex), and that any step that writes environment state to a file that is then uploaded as an artifact must be audited against the list of secrets in scope for that job. The secrets manager integration policy must specify which secret categories are permitted as pipeline secrets (cloud credentials, container registry tokens, the secrets manager access credential itself) and which must use runtime injection (application database credentials, payment processor keys, third-party API tokens). The secrets management decision record specifies the secrets management platform; the pipeline secrets management model is the integration specification between the CI/CD platform and the secrets management platform — the pipeline jobs that read from the secrets manager must have credentials to authenticate to it, those credentials follow the pipeline secrets model, and the runtime injection of application secrets follows the secrets management platform's access model.

The third section documents the deployment authorization model. The environment protection rules must specify the required reviewer count for each deployment environment (development, staging, production), the reviewer group composition, whether self-approval is permitted (the PR author approving their own deployment is generally not permitted for production), and whether the required reviewer gate can be bypassed and under what conditions. The branch protection model for each deployment trigger branch must specify the minimum PR review count before merge, the prohibition on force pushes, the required status checks, and whether the rules are enforced by the platform (GitHub branch protection rules that cannot be overridden by repository administrators) or by convention. The environment-scoped secret authorization must confirm that all production credentials are scoped to the protected production environment and that no production credentials are accessible at the repository level or from pull request workflows. The deployment scope per pipeline credential must specify the IAM permissions granted to each deployment role — the list of IAM actions permitted, not a broad permission like AdministratorAccess — and the rationale for each permission (ECS UpdateService for the application deployment job, ECR PutImage for the container push job, RDS-DB Connect for the migration job). The emergency change process must be documented before it is needed: the maximum number of required reviewers during an incident, the designated emergency approvers, the audit trail required (an incident ticket linked to the deployment, a post-incident review entry noting the emergency gate bypass), and the post-incident requirement to verify that the emergency bypass was not misused. The infrastructure decision record specifies the cloud account structure and the IAM organizational model; the OIDC trust relationship between the CI/CD pipeline and the cloud provider is a configuration in the cloud provider's IAM that must be provisioned and maintained alongside the infrastructure, and the deployment roles assumed by pipeline jobs are IAM principals governed by the same access control model as the application's service accounts.

The fourth section documents the build artifact integrity and attestation model. The artifact storage model must specify where build artifacts are stored (container image registry, S3, Artifactory), the immutability requirement (whether artifacts can be overwritten after upload, or whether the registry enforces content-addressable storage), the access control model for the artifact registry (who can pull, who can push, whether unauthenticated pulls are permitted), and the retention and deletion policy for old artifacts. The SLSA provenance generation must specify the target SLSA level for each artifact class (container images may target Level 2 or 3; internal tooling binaries may target Level 1; test utilities may not require attestation), the provenance generation mechanism (GitHub Actions supports artifact attestation via attest-build-provenance for workflows with appropriate permissions), and how the provenance is stored alongside the artifact. The consumer-side verification requirement must specify whether the deployment process verifies SLSA provenance before deploying an artifact — checking that the artifact being deployed was built from the expected source commit by the expected build platform — and the failure behavior when provenance verification fails (deployment rejected, deployment continues with an alert, deployment continues with a logged exception). Artifact signing must specify whether container images are signed using Sigstore/cosign or a similar tool, the signing key management (whether keys are stored in a cloud KMS system, the rotation cadence, and the verification key distribution model), and whether the signing verification is enforced at the container runtime (Kubernetes admission control, AWS ECR image signing policies) or performed as a manual audit step. The container runtime enforcement model determines whether artifact signing actually prevents unsigned or improperly signed artifacts from being deployed, or whether it is an audit control that detects violations after the fact. The container image build policy decision record specifies the base image selection, vulnerability scanning requirements, and image update cadence; the artifact integrity model is the security layer that verifies the container image built from a reviewed base image has not been modified between the build step and the deployment step — an artifact that is built correctly but can be substituted after build without detection provides only the appearance of supply chain security rather than the substance.

The fifth section documents the pipeline security monitoring and secret rotation model. The event ingestion model must specify which pipeline platform events are ingested into the security monitoring system (SIEM or equivalent): workflow file changes (a change to a workflow file is a high-interest event because it can alter the pipeline's behavior and the scope of secrets it can access); action version updates (a tag moving to a new commit without a corresponding workflow file change is a supply chain attack indicator); secret creation, update, and deletion events (the GitHub audit log records these events, and a secret update that does not correspond to a scheduled rotation event or an incident response is anomalous); deployment authorization override events (an environment required reviewer gate being bypassed should trigger an immediate alert for human review); and runner registration and deregistration events (a new self-hosted runner registered to the organization without a corresponding infrastructure provisioning event is a potential attacker-installed runner). The alert thresholds must specify which events trigger immediate security alerts (new runner registered outside of the infrastructure provisioning process; workflow file change that adds or modifies a step with cloud credential access without a corresponding pull request review; deployment to production outside of the normal deployment window without a recorded incident) and which events are recorded for periodic audit (action version changes, secret update events, new secret additions). The secret rotation cadence must specify the rotation schedule for all remaining static credentials — cloud access keys, API tokens, container registry credentials — the owner for each credential, the rotation procedure (updating the secret in the credential store, updating the pipeline secret, verifying that the pipeline still functions, revoking the old credential), and the automated alert when a credential's rotation due date passes without a confirmed update event. The runner isolation model must specify whether the team uses cloud-hosted runners (where each job runs in a fresh VM provided by the CI platform), self-hosted runners (where the team maintains the runner infrastructure), or a hybrid, and for self-hosted runners, the isolation requirements (ephemeral runners that terminate after each job, or persistent runners with job isolation enforced by the runner configuration); self-hosted persistent runners that share state between jobs are a supply chain attack surface — a malicious job can write to the runner's filesystem and a later legitimate job on the same runner will read the malicious file if it is placed in a path the legitimate job reads from. The security ADR and threat model decision record must model the CI/CD pipeline as an asset in the threat model, enumerating the attack surfaces (action code execution in the runner environment with access to secrets, artifact storage with the ability to substitute built artifacts, the deployment credential's scope of cloud access) and the controls that reduce each surface (digest pinning, OIDC credentials, required reviewer gates, artifact attestation, runner isolation) so that the threat model reflects the actual pipeline security posture rather than the pipeline's intended security posture at initial setup.

The initial pipeline setup session that added AWS access keys as repository secrets because that is what the getting-started guide showed, pinned third-party actions by tag because that is what the action READMEs showed, and deployed to production from a push to main because adding a required reviewer gate was out of scope for a session focused on making the deployment work — the dependency addition session that added a marketplace action without reviewing its source code or its permissions, following the pinning pattern established by the initial setup because there was no documented model that specified an alternative — and the compliance remediation session that added SAST scanning and marked the security finding closed without addressing the credential model or the action pinning policy that the finding was proxying for — each produced pipeline security properties whose consequences (a supply chain attack through a compromised action tag that exfiltrated production AWS credentials because the credentials were long-lived static keys accessible to every action in every workflow; a database connection string on a contractor's laptop because a debug artifact bypassed secret masking; a production deployment authorization model with no human gate between a merged pull request and a live deployment) exceed what establishing the pinning model at initial setup, migrating to OIDC credentials when the pipeline was first built rather than when an incident made the migration urgent, and adding production environment required reviewers as a first-class workflow configuration rather than a post-hardening addition would have cost to implement. The decisions are in the AI sessions: the credential type that was chosen by default because OIDC required additional configuration and the getting-started guide showed static keys, the action pinning format that was copied from the action README without a policy decision about immutability requirements, and the deployment authorization architecture that was implicit in the push-to-main-deploys-to-production model without a documented gate or an explicit decision that this trade-off between velocity and authorization was acceptable. WhyChose's open-source extractor surfaces these initial pipeline setup sessions, dependency addition sessions, and compliance remediation sessions as structured decision records before a compromised action tag runs in the next CI push and finds long-lived credentials in the runner environment, before a debug artifact captures a database connection string in a format that bypasses secret masking, and before the outgoing engineer who knew why the pipeline used static keys instead of OIDC and why there was no required reviewer gate on the production environment leaves without those constraints being findable by the engineer who inherits the pipeline. The decisions never written down in a CI/CD pipeline setup are the action pinning model that determines the supply chain attack surface of every future pipeline run, the credential type that determines the blast radius of a supply chain compromise, and the deployment authorization architecture that determines how many independent controls an attacker must defeat to deploy arbitrary code to the production environment.

Further reading