Skip to main content

agentpipe

PyPI tests Python License

Find CI agents that eat untrusted GitHub events and hold keys. A repo lets a stranger's issue title drive an AI agent step that carries an npm token, and now an issue is a publish. agentpipe finds those chains statically, before anyone opens the issue.

Not an Actions linter. Not gitleaks. It reports preconditions and never exploits.

agentpipe finds an untrusted issue -> AI agent -> npm token publish chain

Part of Agentoffense. The recon front of the line: find the exposure here, close it at runtime with Airlock and Countersign.

pip install agentpipe-scan
agentpipe scan .                          # local clone, no network
agentpipe scan github.com/org/repo        # public workflows via GitHub API
agentpipe scan github.com/org             # whole org, aggregated report
agentpipe prove .                         # canary kit: prove each finding harmlessly
                                          # (token: GITHUB_TOKEN env or --token)

What problems it solves

AI agents moved into two places that hold real power, and both are easy to leave open. agentpipe finds the exposure in each, statically, before an attacker does.

1. A stranger's text becomes your CI's actions ("clinejection"). You wire an AI agent into GitHub Actions to triage issues or review PRs. The workflow triggers on issues or pull_request_target, drops the issue title straight into the agent's prompt, and the job holds an NPM_TOKEN and packages: write. Now anyone who opens an issue is handing instructions to a process that can publish your package or exfiltrate through a bot comment. No one clicked anything. agentpipe traces the whole chain (untrusted trigger to agent step to secret or publish sink) and points at the exact file:line, so you see it as code review, not as an incident.

2. Your own laptop's agent config is wide open. The agent you run locally trusts a config you set up once and forgot. Confirmations are off (bypassPermissions), a filesystem MCP is rooted at / so the agent can read every secret you own, a hook pipes curl | sh, an MCP server installs from an unpinned npx -y pkg (a rug-pull waiting to happen), and a token sits in plaintext in the config. agentpipe local reads only, touches no network, masks secret values, and tells you exactly what to tighten.

Why static and why now. Runtime tools (Airlock, Countersign) stop a bad call in the moment. agentpipe answers the earlier question: where are you even exposed? It runs in seconds on a repo, a whole org, or a machine, needs no agent running and no exploit, and every finding carries what it proves, what it does not, and the one change that closes it. Each report ends with what the scan cannot see, which is where a real pentest begins.

The killer bit: prove

A finding says "preconditions exist". agentpipe prove turns each high/medium finding into a harmless canary kit: a unique token, the exact issue/PR text to paste into a fork you own, the three places to watch (comment, step summary, job log), what a positive result proves and what it still doesn't (that part is the pentest). Every payload is echo/read-only by construction.

The report also groups flat findings into attack chains (entry → amplify → impact, one per job, severity-tagged).

Fix it: agentpipe fix

Finding the chain is half the job. agentpipe fix writes the patch, round-trip so your comments, key order and formatting survive:

agentpipe fix .            # show the diff (dry run)
agentpipe fix . --apply    # write the patches

It only edits where the change cannot break a legitimate workflow: it removes a --dangerously-skip-permissions flag (that just re-enables the confirmation the agent was told to skip), and adds environment: release to a publish job on an untrusted trigger (which requires a human approval before the publish). Anything whose safe form depends on your intent (narrowing permissions:, isolating an untrusted field in the prompt) is printed as advisory, never edited. Breaking someone's CI is worse than staying quiet.

agentpipe local: the laptop half

CI is only half the surface. agentpipe local audits agent configs on the machine itself (~/.claude.json, .mcp.json, cursor/gemini/windsurf configs):

agentpipe local finds a whole-filesystem MCP, a curl-pipe-sh hook, and a plaintext token

id finding
LOC-01 confirmations weakened/off (bypassPermissions, unscoped tool grants)
LOC-02 MCP server via unpinned npx -y pkg (rug-pull channel)
LOC-03 filesystem MCP rooted at / or ~
LOC-04 remote MCP over plaintext http:// or with no auth header
LOC-05 hook piping the network into a shell (curl … | sh)
LOC-06 plaintext credential in an MCP env block (values are masked in the report)
LOC-07 CLAUDE.md/.cursorrules ordering the agent to never ask

Read-only, no network, secret values never printed.

Clinejection-class demo (issue title → Claude in Actions → npm token):

agentpipe scan examples/clinejection-repo

Run it in your CI (GitHub Action)

Drop agentpipe into a workflow. On a pull request it scans the diff's repo, posts one updatable comment with the chains the PR introduces, and uploads SARIF to the Security tab.

# .github/workflows/agentpipe.yml
name: agentpipe
on: [pull_request]
permissions:
  contents: read
  pull-requests: write      # the sticky PR comment
  security-events: write    # Code Scanning (SARIF)
jobs:
  agentpipe:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: cyberbobas/agentpipe@v0
        with:
          fail-on: high
          comment: true
  • One sticky comment. The bot edits its own comment instead of posting a new one per push. Only reachable + high chains go in the headline; everything else folds into a details block. Noise is uninstall, so the default is quiet.
  • SARIF to Code Scanning. Every finding shows in the Security tab with real file:line, severity, and a fingerprint GitHub tracks across runs.
  • Baseline / diff. On a repo that already carries debt, commit a baseline once and the Action goes silent on it, speaking up only for chains a PR adds:
agentpipe scan . --write-baseline .github/agentpipe-baseline.json   # once, commit it
#  then the Action runs with:  baseline: .github/agentpipe-baseline.json

The same outputs are in the CLI: agentpipe scan . --sarif out.sarif writes SARIF anywhere, and agentpipe comment . posts the sticky comment from any CI that sets GITHUB_TOKEN and GITHUB_REPOSITORY.

Policy: .github/agentpipe.yml

The Action and the CLI both read a per-repo policy file (auto-loaded from the repo, no input needed):

# .github/agentpipe.yml
fail_on: high                 # never | high | medium
comment_on: high              # PR-comment headline floor: high | medium
ignore_paths: ["examples/**"] # findings under these globs are dropped
disabled_detectors: [AWI-15]  # detector ids to silence

A CLI flag overrides the file, the file overrides the built-in default. A repo can loosen its own policy, but the change is a visible line in git history, which is the point.

An org policy (--org-policy <file|url> or AGENTPIPE_ORG_POLICY) sits above a repo's file and can only tighten it: required_detectors a repo may not disable, lock_ignore_paths it may not ignore, and a stricter fail_on wins. A single repo cannot weaken the org's floor with a faceless commit. This is the core of the org-wide App layer (see docs/APP-SPEC.md).

What the scanner flags

id chain since
AWI-01 untrusted trigger (issues, issue_comment, pull_request_target, …) + agent step + secret/write/publish 0.1
AWI-02 ${{ github.event.issue.title }} (or body/comment/PR) interpolated into the agent step 0.1
AWI-03 pull_request_target + agent + checkout of PR head 0.1
AWI-04 agent job can issues: write / gh issue comment (GitLost exfil) 0.1
AWI-05 --dangerously-skip-permissions / YOLO on that agent 0.1
AWI-06 actions/cache written on an untrusted-trigger workflow next to an agent/release 0.3
AWI-07 agent/release job downloads artifacts across workflow boundaries (workflow_run) 0.3
AWI-08 agent + gh CLI + contents/actions: write (workflow persistence) 0.2
AWI-09 OIDC / cloud login on the untrusted-prompt job 0.2
AWI-10 agent + untrusted trigger on a self-hosted runner 0.2
AWI-11 agent hidden in a local composite action or reusable workflow (recursed, caller triggers inherited) 0.2
AWI-12 GitHub Agentic Workflows: markdown engine: + untrusted on: + write surface 0.3
AWI-13 Dependabot/Renovate in repo + agent merges PRs with no dependabot[bot] actor guard 0.3
AWI-14 publish after/alongside an agent job with no environment: gate 0.2
AWI-15 agent step handed mcp_config / MCP servers (CI trifecta) 0.3

Findings carry real file:line of the agent step. A workflow that does not parse is a loud warning, not a silent skip.

Not yet: INS-01..04 (instruction-file hygiene in repos), GitHub Action wrapper, --follow-remote.

What you will not see

A 0–100 score. A badge. Telemetry. A working exploit. An Airlock pitch.

The report ends with what this scan cannot see (that list is the pentest offer).

Verdicts

reachable (trigger + agent + sink) · plausible (untrusted field in the prompt) · adjacent (one piece of the chain). Severity only on the first two.

MIT. Python ≥ 3.11. PyYAML.

python3 tests/run.py           # regression suite (41 checks)
python3 tests/adversarial.py   # parser fuzz, FP/FN matrix, recursion, ReDoS, CLI
python3 tests/matrix.py         # negative matrix per detector, GitHub semantics, live corpus

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

agentpipe_scan-0.9.0.tar.gz (40.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

agentpipe_scan-0.9.0-py3-none-any.whl (41.9 kB view details)

Uploaded Python 3

File details

Details for the file agentpipe_scan-0.9.0.tar.gz.

File metadata

  • Download URL: agentpipe_scan-0.9.0.tar.gz
  • Upload date:
  • Size: 40.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for agentpipe_scan-0.9.0.tar.gz
Algorithm Hash digest
SHA256 ad8265fb70d35fa4e4a8e6b3dd72bfba0e504db6be45ac9cb90442df44526827
MD5 6a75ddf15521d1170f1488f2f45b59e6
BLAKE2b-256 b587d80c2a0a3c7fe058651cc93408f6a8800f60b52715b498ad0b98ea787aee

See more details on using hashes here.

File details

Details for the file agentpipe_scan-0.9.0-py3-none-any.whl.

File metadata

  • Download URL: agentpipe_scan-0.9.0-py3-none-any.whl
  • Upload date:
  • Size: 41.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for agentpipe_scan-0.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 550f976d4885cc3e7c97b13c596ef5e8c92fbf56c4c276302a41c02cf226a1cc
MD5 7c1e1374e3fcd38216b220440488b837
BLAKE2b-256 72bb395ef4954b6197236a7480a64d9cfe3869bf6c39b45a8b56120655a7fd19

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.9.0 This release

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.1

2 files

0.5.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page