SOP-15: Supply Chain Attack Detection & Response

·

Supply chain attacks weaponise trust. Instead of attacking your perimeter, the adversary compromises something upstream — an npm package you depend on (event-stream, ua-parser-js), a vendor's signed installer (SolarWinds, 3CX, Codecov), your CI/CD pipeline, or a typosquatted package similar to one you already use. The malicious payload arrives via your own normal build or update path, signed and trusted.

Detection requires both proactive controls (Software Composition Analysis, signed-package enforcement, dependency pinning, SBOM tracking) and reactive hunting (process telemetry from build agents and developer workstations, network egress from CI runners, vendor advisory feeds). When upstream advisory drops — Snyk, GitHub advisory, vendor CVE — you have hours to confirm exposure and contain.

◈ Table of Contents

01 Background 02 Identification 03 Containment 04 Eradication 05 Escalation
01

Background

CONTEXT
B1Attack Patterns & MITRE Mapping
PatternMITREKey IndicatorSeverity
Malicious package (npm / PyPI / NuGet / RubyGems)T1195.001 (Compromise Software Dependencies)Newly-published version with install/postinstall script, network egress, or obfuscated codeCRITICAL
Trojanized vendor update (SolarWinds, 3CX, Kaseya pattern)T1195.002 (Compromise Software Supply Chain)Signed legitimate binary contains backdoor; vendor advisory after the fact; specific hashesCRITICAL
Dependency confusionT1195Public package with same name as internal package + higher version pulled by buildCRITICAL
TyposquatT1195Package name 1-2 chars off from a popular library (e.g. reqeusts vs requests)HIGH
Build system / CI runner compromiseT1195 + T1078Unsigned commits in pipeline source; new self-hosted runner from foreign IP; secrets read by runnerCRITICAL
MSP / vendor admin abuseT1199 (Trusted Relationship)Vendor admin account performing actions outside scope; new vendor IP using legit credsCRITICAL
B2Log Sources & Tooling
SourceTable / ToolKey Signals
SBOM / SCASnyk, Dependabot, GitHub Advisory, OSV, GitLab Dependency ScanningVulnerable package + version + transitive path
Artifact repositoryAzure Artifacts / JFrog Artifactory / NexusPulled-version manifests, package hashes, internal vs external source
CI/CD logsGitHub Actions / Azure DevOps / GitLab CIPipeline runs, packages installed, network egress from runner
Build host / runner EDRDeviceProcessEvents, DeviceNetworkEventsPost-install scripts, unexpected outbound, secret-file reads
Vendor advisory channelsRSS / email / Slack integrationCVE drops, IOC lists, hash blocklists
02

Identification

INVESTIGATE
Supply Chain — Investigation Workflow
I1Exposure Check — Are We Affected?SBOM
  • 1
    Search SBOM / lockfiles: Query Snyk / Dependabot / GitHub Advisory for the affected package + version range across all repos. Don't trust developer memory — query the source-of-truth.
  • 2
    Search artifact repository: For each artifact pulled from the upstream registry, check if the exact bad version/hash was downloaded by your builds.
  • 3
    Search container registries: Image layers can carry the bad package even if your manifest no longer pins it. Check published images.
  • 4
    Search production runtime: Use endpoint inventory or container manifests to confirm if the bad version is actively running anywhere.
I2Build / Dev Host Compromise HuntKQL
KQL — Defender: Post-install network egress from build runner
// CI runner reaching out to unexpected destinations after package install DeviceNetworkEvents | where TimeGenerated > ago(7d) | where DeviceName has_any ("buildagent", "ci-runner", "github-runner", "azp-") | where InitiatingProcessFileName in~ ("node.exe", "npm.cmd", "python.exe", "pip.exe", "dotnet.exe", "msbuild.exe", "java.exe") | where RemoteUrl !endswith "yourdomain.com" // adjust to your own | where RemoteUrl !has_any ("npmjs.org", "pypi.org", "nuget.org", "github.com", "azureedge.net") | project TimeGenerated, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, RemoteUrl, RemotePort | order by TimeGenerated desc
KQL — Process spawned by package manager post-install
DeviceProcessEvents | where TimeGenerated > ago(7d) | where InitiatingProcessFileName in~ ("npm.cmd", "yarn.cmd", "pnpm.cmd", "pip.exe", "pip3.exe", "gem.exe", "go.exe") | where FileName in~ ("powershell.exe", "pwsh.exe", "cmd.exe", "curl.exe", "wget.exe", "bash.exe", "sh.exe", "certutil.exe", "bitsadmin.exe") | project TimeGenerated, DeviceName, AccountName, InitiatingProcessCommandLine, FileName, ProcessCommandLine | order by TimeGenerated desc

Malicious npm packages overwhelmingly run their payload in postinstall. Any cmd.exe/powershell.exe/curl/wget child of npm.cmd on a build agent is suspicious by default.

I3FP vs TP Decision Gate
True-Positive SignalsFalse-Positive Signals
Bad hash matched in your artifact cache; package version range published in advisoryYou pulled an adjacent version not on the advisory's affected list
Build runner egress to attacker C2 immediately after the package install stepBuild runner egress to your normal artifact mirror / proxy
Postinstall script with obfuscated base64 / unicode escape payloadPostinstall running a known native binary build (sharp, node-sass, etc.)
New self-hosted runner registered from foreign IP without change recordNew runner registered from your VPC IP with approved scaling event
03

Containment

STOP IT
C1Stop Further SpreadImmediate
  • 1
    Block bad version in artifact repo: Add the exact version + hash to the deny-list (JFrog Block Rule / Artifactory exclusion / npm dist-tag manipulation). New builds cannot pull it.
  • 2
    Pin to known-good version: Update lockfiles across all affected repos; create coordinated PR with explanation.
  • 3
    If vendor binary: quarantine endpoints: Defender for Endpoint → Indicators → Add SHA-256 hash → Block + Remediate. Auto-isolates anything matching.
  • 4
    Rollback recent production deploys known to contain the bad version. Use canary/blue-green to minimise customer impact.
  • 5
    Disable affected CI/CD runners until rebuilt from known-clean base image; if self-hosted, treat as compromised.
C2Rotate Anything the Bad Component Could See
  • 1
    Rotate ALL CI/CD secrets accessible to the bad package or runner: NPM_TOKEN, GitHub PAT, cloud OIDC keys, Docker Hub tokens, internal artifact tokens.
  • 2
    Rotate cloud credentials that the affected runtime could reach (managed identity, IAM role tokens are usually short-lived but rotate the bound secrets too).
  • 3
    Rotate signing keys if the bad package had access to code-signing material.
  • 4
    Audit egress logs for the period the bad version was active — flag any unfamiliar destination.
04

Eradication

CLEAN UP
E1Cleanup & Harden the Pipeline
  • 1
    Rebuild all CI runners from fresh gold images. Self-hosted runners that ran the bad code: destroy and recreate, do not patch in place.
  • 2
    Force a clean install on developer workstations that ran the bad package — wipe node_modules / venv / equivalent, reinstall from pinned lockfile.
  • 3
    Update SCA tooling rules to fail builds on packages newer than a quarantine period (e.g. block packages < 7 days old unless explicitly allow-listed).
  • 4
    Enforce package source pinning to prevent dependency confusion: scope-locked npm registry, PyPI index-url + extra-index-url discipline, NuGet packageSourceMapping.
  • 5
    Sign and verify your own internal packages with Sigstore / similar; require signed packages in production builds.
Eradication Complete When
  • Bad version removed from all lockfiles, container images, and runtime
  • CI runners rebuilt; all secrets accessible to affected runners rotated
  • SCA gate updated to prevent re-introduction
  • Customer notification drafted (if applicable) and disclosure timeline agreed
  • Post-incident review captures source-pinning + signed-package policy gaps
05

Escalation

TIERS
ESC1Escalation Tiers
TierTriggerAction / Handoff Bundle
L1 → L2Upstream advisory; SBOM hit; affected versions in our reposAdvisory link, package + version, affected repos, deploy footprint
L2 → IR + EngineeringBad version is in production OR build agent shows post-install C2 egressAffected services, customer scope, runner forensics, secret-rotation list
IR → CISO + Customer CommsCustomer-impacting compromise; signed-by-us binary contains backdoor; PII / regulated data possibly exposedCustomer notification draft, regulator clock, public-disclosure decision
ExternalActive campaign across multiple of your vendors; nation-state implicationsCISA / MS-ISAC / sector ISAC, law enforcement, external IR firm

Authorized security research & penetration testing only. Unauthorized use is illegal. Authored by Rudra Verma | Senior Security Architect | CyberHawk Threat Intel.

◈ Stay Connected

Follow CyberHawk Threat Intel for SOC SOPs, threat intelligence, and hands-on analyst tooling.

Website YouTube Twitter / X TikTok Telegram
SOPs Blog Courses

"They can't exploit you if you are the Exploit."