Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .github/codex/prompt_queue/progress.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
{
"queue_version": "0.1",
"next_prompt_id": 4,
"next_prompt_id": 5,
"completed_prompt_ids": [
1,
2,
3
3,
4
],
"in_flight": null,
"last_pr": {
"prompt_id": 3,
"slug": "03_entitlement_graph",
"prompt_id": 4,
"slug": "04_abuse_regressions",
"pr_url": "",
"advanced_at": "2026-06-30T13:34:17.010956+00:00"
"advanced_at": "2026-06-30T14:55:30.155500+00:00"
},
"notes": "The local Codex CLI runner updates this file inside each queued PR. Merging the PR advances the queue."
}
5 changes: 5 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ CLI and (future) UI are thin clients over the same capability.
allowlist modification, limit relaxation. **These tools do not exist in the registry by
construction** — there is no code path from the MCP/REST/agent surface to mint or widen a scope.

**CLI-only regression harness:** `heel regress add/list/run/export` turns stored findings into
abuse-control regression tests. It is a thin client over the same store and `HeelServer` run path:
regression re-runs still require an existing signed scope, never mutate scopes, emit canary-only
evidence summaries instead of repro steps, and append containment log entries.

---

## 3. The safety & authorization spine (NON-NEGOTIABLE, §10)
Expand Down
10 changes: 10 additions & 0 deletions DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,13 @@ models those relationships as typed edges and converts suspicious edges into the
systems. It is fed by sanitized ProductModel metadata, emits contained model affordances only, and
inherits the imported-target rule: any rehearsal run still requires a human-created signed scope.
MCP/REST/agent surfaces remain unable to create, widen, relax, or mutate scopes.

### D-035 — Findings can become abuse regression tests without becoming repro playbooks
**Why:** reports alone do not keep controls fixed. `heel.regressions` persists a finding's scenario,
affordance pattern, declarative success criterion, recommended control, expected status, source run,
and safety flags so teams can re-run the control check in CI or staging.

**Safety consequence:** regression specs deliberately omit reproduction steps and working payloads.
Re-runs call the same `HeelServer.heel_run` path, so signed scope verification, allowlist checks,
resource limits, canary-only findings, and containment logging are reused. The regression CLI has no
scope creation, widening, allowlist, or limit-mutation command.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ heel scope create --target synthetic-saas --operator you --confirm
heel run --scope <scope_id> --target synthetic-saas
heel coverage --run <run_id>
heel log --run <run_id> # immutable, hash-chained audit trail

# 3) turn a finding into a permanent abuse-control regression
heel regress add --run <run_id> --vector <vector_id> --name free_trial_serial_signup
heel regress run --scope <scope_id> --target synthetic-saas
```

**Connect from an MCP client.** Point Claude Desktop / Cursor / CI at the `heel-mcp` server:
Expand Down Expand Up @@ -193,7 +197,7 @@ caller cannot create, widen, or escape a signed authorization scope.* See **[TRU
## Docs

[ARCHITECTURE](ARCHITECTURE.md) · [EVAL](EVAL.md) · [DECISIONS](DECISIONS.md) · [SECURITY](SECURITY.md)
· [TRUST](TRUST.md) · [ADAPTERS](docs/ADAPTERS.md) · [ENTITLEMENTS](docs/ENTITLEMENTS.md) · [CONTRIBUTING](CONTRIBUTING.md) ·
· [TRUST](TRUST.md) · [ADAPTERS](docs/ADAPTERS.md) · [ENTITLEMENTS](docs/ENTITLEMENTS.md) · [REGRESSIONS](docs/REGRESSIONS.md) · [CONTRIBUTING](CONTRIBUTING.md) ·
[CHANGELOG](CHANGELOG.md) · red-team reports under [`docs/`](docs/)

## Status
Expand Down
13 changes: 13 additions & 0 deletions docs/CODEX_PROMPT_QUEUE_RUNBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,19 @@ Open draft queued PRs instead of ready PRs:
python3 scripts/codex/local_queue_runner.py run-next --draft-pr
```

Act on trusted Claude review feedback for an open queue PR:

```bash
python3 scripts/codex/local_queue_runner.py repair-pr 15
```

`repair-pr` only consumes formal Claude reviewer Action reviews and local
comments headed `### Local Claude Max review` from trusted authors. It checks
out the PR branch, gives Codex the original queued prompt, trusted review text,
and current PR diff, then asks Codex to verify each item, implement only valid
actionable feedback, run tests, push any repair commit to the same PR, and post
a summary comment. It refuses to run on PRs that are not labeled `codex-queue`.

## Files

- `.github/codex/prompts/` - one prompt per PR-sized task.
Expand Down
80 changes: 80 additions & 0 deletions docs/REGRESSIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Abuse Regression Testing

Abuse regression tests are unit tests for product abuse controls. When a HEEL run finds a reachable
abuse path, you can save the finding as a reusable regression and re-run it in CI, staging, or an
imported ProductModel rehearsal to check whether the recommended control is now blocking the path.

Regressions do not store weaponized reproduction steps. A regression stores the finding's scenario,
target affordance pattern, declarative success criterion, recommended control, expected status,
source run id, creation time, and safety flags. Re-runs report whether the abuse was previously
reachable and is currently reachable, blocked, or inconclusive, plus a short evidence summary.

## Safety Model

Regression runs use the same safety spine as ordinary HEEL runs:

- A human-created, HMAC-signed `AuthorizationScope` is required.
- The target must be in the signed scope allowlist.
- MCP, REST, agent, and regression CLI flows cannot create, widen, relax, or mutate scopes.
- Findings remain contained and canary-only.
- Containment log entries are written for the underlying run and the regression result.
- True software vulnerabilities remain AppSec handoffs; pure jailbreaks remain model red-team
handoffs.

## Create A Regression

Run HEEL against a scoped target, then save a finding by vector id:

```bash
heel run --scope scope-123 --target staging-saas
heel findings --run run-abc
heel regress add --run run-abc --vector av:run-abc:7 --name free_trial_serial_signup
```

New regressions default to `expected_status: blocked`, because the normal workflow is to add a
control and then keep the abuse path blocked permanently.

## List And Export

```bash
heel regress list
heel regress export --format json
```

The JSON export contains regression specs and stored regression results. It is safe to persist as a
CI artifact because it omits reproduction steps and secrets.

## Run In CI Or Staging

Create the scope out of band as a human operator, then have CI consume only that existing scope:

```bash
heel scope create --target staging-saas --operator security-reviewer --confirm
heel regress run --scope scope-123 --target staging-saas
```

For imported ProductModel rehearsals, authorize the converted target id and pass the sanitized JSON
model as the target argument:

```bash
heel import validate product_model.json
heel scope create --target imported:acme-crm --operator security-reviewer --confirm
heel regress run --scope scope-123 --target product_model.json
```

The run fails closed if the scope is missing, expired, tampered with, or does not allow the target.
CI should treat `current_status: still_reachable` as a failing abuse-control regression unless that
regression was intentionally recorded with `expected_status: still_reachable`.

## Result Fields

Each regression run result includes:

- `previously_reachable`: always true for regressions created from findings.
- `current_status`: `still_reachable`, `blocked`, or `inconclusive`.
- `control_likely`: `absent`, `present`, or `unknown`.
- `evidence_summary`: a non-weaponized summary of the canary-only observation.
- `matches_expected`: whether the current status equals the stored expected status.

Use the evidence summary to route the work. Use the original HEEL finding and recommended control to
decide the remediation; do not turn regression artifacts into exploit playbooks.
41 changes: 41 additions & 0 deletions heel/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,18 @@ def main(argv=None):
imps = imp.add_subparsers(dest="icmd", required=True)
impv = imps.add_parser("validate", help="validate a ProductModel JSON file")
impv.add_argument("path")
reg = sub.add_parser("regress", help="turn findings into reusable abuse regression tests")
regs = reg.add_subparsers(dest="rcmd", required=True)
regadd = regs.add_parser("add", help="create a regression from a stored finding")
regadd.add_argument("--run", required=True)
regadd.add_argument("--vector", required=True)
regadd.add_argument("--name", required=True)
regs.add_parser("list", help="list abuse regressions")
regrun = regs.add_parser("run", help="run stored regressions within an existing signed scope")
regrun.add_argument("--target", required=True)
regrun.add_argument("--scope", required=True)
regexp = regs.add_parser("export", help="export regression specs and results")
regexp.add_argument("--format", choices=["json"], required=True)

args = ap.parse_args(argv)
if args.cmd is None:
Expand All @@ -140,6 +152,35 @@ def main(argv=None):
if args.cmd == "import" and args.icmd == "validate":
return _import_validate(args.path)

if args.cmd == "regress":
from .regressions import (
add_regression_from_finding,
export_regressions,
resolve_target_argument,
run_regressions,
)
srv = _server()
caller = _caller()
try:
if args.rcmd == "add":
reg = add_regression_from_finding(srv.store, args.run, args.vector, args.name)
print(json.dumps({"regression": reg}, indent=2, default=str))
return 0
if args.rcmd == "list":
print(json.dumps({"regressions": srv.store.list_regressions()}, indent=2, default=str))
return 0
if args.rcmd == "run":
target = resolve_target_argument(args.target)
results = run_regressions(srv.store, srv, args.scope, target, caller)
print(json.dumps({"results": results}, indent=2, default=str))
return 0
if args.rcmd == "export":
print(json.dumps(export_regressions(srv.store), indent=2, default=str))
return 0
except Exception as e:
print(f"REJECTED: {e}")
return 1

if args.cmd == "scope" and args.scmd == "create":
if not args.confirm:
print("REFUSED: scope creation is an out-of-band human action and requires --confirm.")
Expand Down
Loading
Loading