Metadata-Version: 2.4
Name: agentpipe-scan
Version: 0.5.0
Summary: Find CI agents that eat untrusted GitHub events and hold secrets
Author: Agentpipe
License-Expression: MIT
Project-URL: Homepage, https://github.com/cyberbobas/agentpipe
Project-URL: Issues, https://github.com/cyberbobas/agentpipe/issues
Keywords: ai-agent,github-actions,prompt-injection,ci,security,clinejection,supply-chain,sast,agent-security
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Security
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: PyYAML>=6.0
Dynamic: license-file

# agentpipe

[![PyPI](https://img.shields.io/pypi/v/agentpipe-scan?color=8b7cf6)](https://pypi.org/project/agentpipe-scan/)
[![tests](https://github.com/cyberbobas/agentpipe/actions/workflows/tests.yml/badge.svg)](https://github.com/cyberbobas/agentpipe/actions/workflows/tests.yml)
[![Python](https://img.shields.io/pypi/pyversions/agentpipe-scan)](https://pypi.org/project/agentpipe-scan/)
[![License](https://img.shields.io/badge/license-MIT-blue)](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](docs/agentpipe-scan.gif)

Part of [Agentoffense](https://agentoffense.com). The recon front of the line:
find the exposure here, close it at runtime with
[Airlock](https://github.com/cyberbobas/airlock) and
[Countersign](https://github.com/cyberbobas/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)
```

## 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).

## `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):

| 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
```

## 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
```
