Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8d4c22c
docs(todo): close-the-loop night run — campaign 1 research brief and …
kridaydave Sep 12, 2026
c2b6f39
feat(refs): static install-reference scanner for lifecycle and data s…
kridaydave Sep 12, 2026
74d6584
feat(refs): plumb PKGBUILD npm-delivery specs for recursive review
kridaydave Sep 12, 2026
461e40f
fix(refs): review follow-ups — multi-spec capture, disclosed unscanna…
kridaydave Sep 12, 2026
97b9bf4
feat(recursive): second-order review engine with depth, cycle, and bu…
kridaydave Sep 12, 2026
d44d8c9
feat(render): recursive delivery chains on the card; R23 graduates to…
kridaydave Sep 12, 2026
185008e
test(recursive): end-to-end adversarial delivery chain blocks through…
kridaydave Sep 12, 2026
a833fa5
fix(render): worst-first child findings on the card; pin truncation a…
kridaydave Sep 12, 2026
072c661
docs(todo): mark campaign 1 complete
kridaydave Sep 12, 2026
47235ab
docs(todo): campaign 2 research brief and rulings
kridaydave Sep 12, 2026
79a040a
feat(agent): policy-bound non-interactive review and hook gate
kridaydave Sep 12, 2026
baf0b76
feat(shim): fail-closed PATH shims routing installs through agent gate
kridaydave Sep 12, 2026
6f8495a
docs(readme): agent enforcement recipes with honest bypass list
kridaydave Sep 12, 2026
9e1150f
feat(policy): BLUELINE_POLICY env scoping for shims and hooks, fail c…
kridaydave Sep 12, 2026
03707a4
fix(agent): gate fails closed on errors, per-registry routing, deny u…
kridaydave Sep 12, 2026
d7c7fd6
fix(agent): wire gate hard-denies, capture package-naming flags, deny…
kridaydave Sep 12, 2026
ce1c6d7
fix(scanner): find verbs behind global flags, deny npm registry overr…
kridaydave Sep 12, 2026
b988abc
fix(scanner): deny env/config/tag registry overrides, disclose dynami…
kridaydave Sep 12, 2026
8a3da20
docs(todo): mark campaign 2 complete
kridaydave Sep 12, 2026
2b61ab8
feat(recall): local-first revocation index — serve, sync, advisory fo…
kridaydave Sep 12, 2026
b92d275
docs(todo): mark campaign 3 complete
kridaydave Sep 12, 2026
887c970
fix(recall): unique sync tmp file, per-process snapshot cache, pypi n…
kridaydave Sep 12, 2026
99e7066
docs(todo): campaign 3 research brief and rulings
kridaydave Sep 12, 2026
2ec845e
docs(todo): campaign 4 research brief and rulings
kridaydave Sep 12, 2026
281892d
feat(distribution): crates.io metadata, homebrew formula, AUR scaffol…
kridaydave Sep 12, 2026
3f6cdbd
feat(release): SLSA build-provenance attestation for GitHub release b…
kridaydave Sep 12, 2026
6e5c9cc
docs: distribution notes and changelog for packaging completion
kridaydave Sep 12, 2026
fd0b2e1
docs(todo): mark campaign 4 complete
kridaydave Sep 12, 2026
3f64da8
fix(release): build and publish the missing linux-arm64-musl binary p…
kridaydave Sep 12, 2026
44dc772
test(dogfood): our own AUR scaffold and npm shims reviewed by our own…
kridaydave Sep 12, 2026
6038b3a
docs(goal): mark campaigns 1-4 complete; PR pending
kridaydave Sep 12, 2026
acf82c1
docs(changelog): merge duplicate unreleased sections
kridaydave Sep 12, 2026
07b805c
docs(goal): PR opened
kridaydave Sep 12, 2026
f834c7f
fix(ci): dogfood gate checks scanner health, not a hardcoded evaluate…
kridaydave Sep 12, 2026
7dff7db
test(recursive): pin the reference-cap boundary that mutation testing…
kridaydave Sep 12, 2026
ac68abc
fix(review): close P1 gate bypasses and P2 hardening gaps from PR review
kridaydave Sep 13, 2026
92813d3
fix(ci): kill mutation survivors in new trust-boundary code, repair d…
kridaydave Sep 13, 2026
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
34 changes: 26 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,27 @@ jobs:

r = json.load(open("blueline-dogfood.json"))
names = {i["name"] for i in r["items"]}
expected = {"@bluelinecli/cli", "blueline", "@bluelinecli/binary-linux-x64-gnu"}

expected = {
"@bluelinecli/cli", "blueline",
"@bluelinecli/binary-darwin-arm64", "@bluelinecli/binary-darwin-x64",
"@bluelinecli/binary-linux-arm64-gnu", "@bluelinecli/binary-linux-arm64-musl",
"@bluelinecli/binary-linux-x64-musl",
"@bluelinecli/binary-linux-x64-gnu", "@bluelinecli/binary-win32-arm64",
"@bluelinecli/binary-win32-x64",
}

# Scanner health: everything evaluated must be a shipped package
# and the lockfile delta must produce evaluations. Which shipped
# packages land in the delta is PR-dependent, so a hardcoded
# missing-set is wrong. An empty delta (docs-only PR) is healthy,
# not a scanner failure — and a version-bump PR legitimately has
# zero unchanged packages, so unchanged_count is reported but
# never gated.
unknown = names - expected
assert not unknown, f"scanner evaluated unknown packages: {unknown}"
if r["total_evaluated"] == 0:
assert r["unchanged_count"] > 0, "no delta and nothing unchanged — scan saw nothing"
else:
missing = expected - names
assert not missing, f"scanner failed to evaluate: {missing}"
print("dogfood scanner healthy: empty lockfile delta, nothing to evaluate")
sys.exit(0)

print(f"dogfood scanner healthy: evaluated {r['total_evaluated']}, "
f"unchanged {r['unchanged_count']}, max band {r['max_band']}")
Expand Down Expand Up @@ -177,7 +191,9 @@ jobs:
--file src/registry/pypi.rs --file src/wheel_extract.rs --file src/baseline.rs \
--file src/version.rs --file src/review.rs --file src/store.rs \
--file src/executor.rs --file src/lockfile.rs --file src/ci.rs \
--file src/mcp.rs \
--file src/mcp.rs --file src/install_ref.rs --file src/recursive.rs \
--file src/agent.rs --file src/recall.rs --file src/shim.rs \
--file src/pkgbuild.rs --file src/render.rs --file src/policy.rs \
--in-place --baseline=skip --timeout 30
- uses: actions/upload-artifact@v4
if: always()
Expand Down Expand Up @@ -225,7 +241,9 @@ jobs:
--file src/registry/pypi.rs --file src/wheel_extract.rs --file src/baseline.rs \
--file src/version.rs --file src/review.rs --file src/store.rs \
--file src/executor.rs --file src/lockfile.rs --file src/ci.rs \
--file src/mcp.rs \
--file src/mcp.rs --file src/install_ref.rs --file src/recursive.rs \
--file src/agent.rs --file src/recall.rs --file src/shim.rs \
--file src/pkgbuild.rs --file src/render.rs --file src/policy.rs \
--in-place --baseline=skip --timeout 30
- uses: actions/upload-artifact@v4
if: always()
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ jobs:
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
cross: true
- target: aarch64-unknown-linux-musl
os: ubuntu-latest
cross: true
- target: aarch64-apple-darwin
os: macos-latest
- target: x86_64-apple-darwin
Expand Down Expand Up @@ -112,6 +115,7 @@ jobs:
[x86_64-unknown-linux-gnu]=binary-linux-x64-gnu
[x86_64-unknown-linux-musl]=binary-linux-x64-musl
[aarch64-unknown-linux-gnu]=binary-linux-arm64-gnu
[aarch64-unknown-linux-musl]=binary-linux-arm64-musl
[aarch64-apple-darwin]=binary-darwin-arm64
[x86_64-apple-darwin]=binary-darwin-x64
[x86_64-pc-windows-msvc]=binary-win32-x64
Expand Down Expand Up @@ -245,10 +249,18 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestation: write
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts
- name: Attest build provenance for the release binaries
uses: actions/attest-build-provenance@v2
with:
subject-path: |
artifacts/binary-*/blueline
artifacts/binary-*/blueline.exe
- name: Create release with all binaries
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
70 changes: 70 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ and any `postinstall`/`preinstall` script is surfaced for a *separate* human dec
│ approval overrides, policy │
│ extract ── verify hash → bounded sandbox extract │
│ diff ── file-level + line-level (similar crate) │
│ install_ref ── scan payload for referenced installs │
│ recursive ── re-review referenced installs: depth caps,│
│ cycle detection, roll-up │
│ heuristic ── rule engine → risk score → verdict │
│ revocation ── OSV / GitHub Advisory cache + hosted idx │
│ provenance ── sigstore/SLSA attestation *surfaced*, │
Expand Down Expand Up @@ -138,6 +141,73 @@ Recommend the explicit tool to avoid breaking agent toolchains.

---

## 5. Second-order lanes (agent / recall / shim)

Install-time references (npm lifecycle scripts, wheel `.data/scripts`,
PKGBUILD npm/bun delivery) are first-class findings, reviewed recursively:

- **R24** — every statically visible reference is disclosed; band reflects
pinnability (HIGH pinned, MEDIUM unpinned/dynamic, HIGH non-registry).
- **R25** — depth/budget caps (`[recursion] max_depth`, `max_child_reviews`)
fail closed as HIGH findings, never silent skips.
- **R26** — install-reference cycles (A → B → A) are cut with a HIGH finding.
- **R27** — roll-up: a child finding at/above `child_block_band` escalates
the parent via a second-order finding carrying the delivery chain.
- **R28** — recall-index staleness: MEDIUM past `[recall] max_age_hours`,
HIGH when unreadable, BLOCK with `block_on_stale`.

### `ReviewContext` cycle (`src/recursive.rs`)

One `ReviewContext` spans a top-level evaluation. `enter_scope` pushes the
cycle key `(ecosystem, name, version)` and the human-readable delivery-chain
label; `exit_scope` pops both after the evaluation *including its children*.
Name identity in keys is ecosystem-scoped (`canonicalize_for_ecosystem`):
PEP 503 applies to PyPI only — npm/cargo/AUR `foo_bar` vs `foo-bar` are
distinct. The verdict schema (D7) carries the outcome in its `recursive`
field: `Vec<ChildReview>` with chain, band, score, and findings per child.

### Agent lane (`src/agent.rs`)

`agent review` (JSON verdict, exit 0/2, never marks clean) and `agent gate`
(hook binding policing one command line through the same scanner + engine).
Both load policy via `Policy::load_for_agent`, which **ignores
`BLUELINE_POLICY` unless `--policy` names the file** — ambient env is
attacker-shaped at the hook boundary — and warns on stderr when it does.
Redirect-capable env (`PIP_*`, `NPM_CONFIG_*`, `CARGO_*`) present at gate
time is disclosed by name (never value) in the reason and audit trail.

### Recall lane (`src/recall.rs`)

Curated revocation snapshot synced wholesale (`recall sync`, monotonic
`sequence`, backward moves refused without writing). Lookup normalizes
PyPI names on both sides (PEP 503) and compares versions by grammar
(`1.0` fires on `1.0.0`); other ecosystems match exactly. A hit BLOCKs via
the advisory engine *before* the `check_advisories` switch — disabling OSV
never silences recall — and never routes through the advisory cache.

### Shim lane (`src/shim.rs`)

Fail-closed bash shims for all eleven scanned managers (`npm`, `npx`,
`pnpm`, `yarn`, `bun`, `bunx`, `pip`, `pip3`, `cargo`, `yay`, `paru`)
routing through `agent gate --policy` before exec'ing the real binary.
Known bypasses stay documented in the README.

### Policy tables (`blueline.toml`)

| Table | Keys |
|---|---|
| `thresholds` | `max_low_score` (19), `max_medium_score` (49), `block_score` (80) |
| `policy` | `require_provenance`, `block_unreviewed_scripts`, `allow_git_dependencies`, `check_advisories`, `fail_closed_network` |
| `advisories` | `block_on_malware`, `block_on_critical_cve`, cache TTLs |
| `provenance` | `require_provenance`, `require_signatures`, builders/repos |
| `allowlist.packages` | exact `name` (+optional `ecosystem`), `allowed_scripts`, `allow_unreviewed_baseline` |
| `blocklist` | glob `packages` (+optional `ecosystem`), `maintainers` |
| `ci` | `fail_on`, `max_evaluations`, `include_dev` |
| `recursion` | `max_depth` (3, cap 16), `max_child_reviews` (8, cap 256), `child_block_band` (HIGH) |
| `recall` | `max_age_hours` (48), `block_on_stale` |

---

## 3. Tech Stack (Rust core)

| Concern | Crate / Tool |
Expand Down
Loading
Loading