Skip to content

Commit b65c33a

Browse files
chore(v0.8): release prep -- version 0.8.0, README, SECURITY, env-race fix
* Cargo.toml: 0.7.0 -> 0.8.0; Cargo.lock updated. * README.md: - test badge bumped 192 -> 243 passing - new "What's new in v0.8" hero callout: shims (closes non-git bypass), --explain (decision transparency), +51 new tests - dedicated "Shell shims (new in v0.8)" section with install workflow, supported-commands matrix, refusal banner example, bypass + exit-code tables, coexistence/list/uninstall flows - dedicated "--explain (new in v0.8)" section with input shapes, three output formats (text, markdown, json with stable schema + example), --explain-force-* what-if knobs, exit-code policy - heads-up note that HTTP/SSE MCP transport is the v0.9 headline (scope-cut from v0.8 because it deserves its own release narrative) * SECURITY.md: - supported-versions table: v0.8.x current stable, v0.7.x previous (security-only), v0.6.x and earlier superseded - 2026-05-27 changelog entry covering the v0.8 surfaces and confirming no new advisories or network endpoints; shim install writes 0755 inside 0700 dir, never overwrites foreign files * src/shims/install.rs: PATH-prepend (vs replace) in the test with_path helper so unrelated tests that shell out to `hostname`, `git`, etc. (e.g. orgmode::state::fingerprint_is_stable) keep working while we hold the env-lock. Switched the install_skips_when_upstream_binary_not_on_path test to a guaranteed-fake command name so it doesn't pass/fail based on whether `helm` happens to be installed on the host. All 243 tests pass against the v0.8.0 release binary.
1 parent 237f5f3 commit b65c33a

5 files changed

Lines changed: 377 additions & 10 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "aperion-shield"
3-
version = "0.7.0"
3+
version = "0.8.0"
44
edition = "2021"
55
description = "Aperion Shield -- a local MCP guardrail for AI coding agents with optional biometric identity gates (ID.me). Standalone, free, open source."
66
license = "Apache-2.0"

README.md

Lines changed: 344 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
44
[![Release](https://github.com/AperionAI/shield/actions/workflows/release.yml/badge.svg)](https://github.com/AperionAI/shield/actions/workflows/release.yml)
5-
[![Tests](https://img.shields.io/badge/tests-192%20passing-brightgreen.svg)](https://github.com/AperionAI/shield/actions)
5+
[![Tests](https://img.shields.io/badge/tests-243%20passing-brightgreen.svg)](https://github.com/AperionAI/shield/actions)
66
[![Rust](https://img.shields.io/badge/rust-1.75%2B-orange.svg)](https://www.rust-lang.org/)
77
[![Docker](https://img.shields.io/badge/docker-ghcr.io%2Faperionai%2Fshield-2496ed.svg)](https://github.com/AperionAI/shield/pkgs/container/shield)
88
[![Security policy](https://img.shields.io/badge/security-SECURITY.md-red.svg)](SECURITY.md)
@@ -34,6 +34,86 @@ the relying party — no rewrite, no re-install.
3434

3535
---
3636

37+
## What's new in v0.8
38+
39+
Two strong additions that build directly on the v0.7 bypass-closing
40+
story:
41+
42+
1. **Shell shims (`--install-shims`) — closes the non-git command
43+
bypass.** v0.7 closed the "agent reaches around MCP and lets a
44+
destructive change land in a commit" bypass with git hooks. v0.8
45+
closes the parallel "agent reaches around MCP and runs a
46+
destructive shell command directly" bypass. One command installs
47+
tiny `/bin/sh` wrappers in `~/.aperion-shield/bin/` for **10
48+
high-blast-radius CLIs** (`aws`, `gcloud`, `az`, `kubectl`, `helm`,
49+
`terraform`, `psql`, `mongosh`, `redis-cli`, `rm`). The user puts
50+
that dir first on `$PATH` and every invocation routes through the
51+
active shieldset before reaching the real binary. Same engine, same
52+
YAML rules, same audit JSONL stream — the shim path reuses the
53+
`shell` tool-call scope that MCP and `--check-staged` already use,
54+
so adding a rule for one surface covers all three.
55+
```bash
56+
aperion-shield --install-shims --for aws,kubectl,terraform
57+
# next destructive call -> refused with rule + safer alternative
58+
# $ aws s3 rm --recursive s3://prod-bucket
59+
# [aperion-shield/check-cmd] APPROVAL-REQUIRED -- `aws s3 rm --recursive s3://prod-bucket`
60+
# rule : cloud.aws_s3_recursive_delete (severity=High)
61+
# reason : Bulk S3 delete -- irreversible if versioning is off.
62+
# suggest : Enable versioning, then use lifecycle rules to expire ...
63+
```
64+
Bypass for a single invocation: `SHIELD_SHIMS_DISABLE=1 aws ...`
65+
(env override, parity with `--no-verify` for hooks). Foreign-file
66+
collisions (you wrote your own `~/.aperion-shield/bin/aws`
67+
wrapper) are NEVER overwritten — Shield refuses the install with a
68+
non-zero exit and tells you what to do.
69+
70+
2. **`--explain`: first-class decision transparency.** Take any
71+
tool-call descriptor and get a complete decision walkthrough:
72+
every rule that matched, every adjustment signal applied
73+
(workspace probe, decision memory, burst detector), the full
74+
severity ladder (raw → composite + points → final), the resolved
75+
decision, and the `safer_alternative`. Three output formats —
76+
`text` for terminals, `markdown` for PR review comments, `json`
77+
with a stable schema for piping into other tooling. The
78+
`--explain-force-prod` / `--explain-force-burst` flags let you
79+
answer "what would this same call decide in a different context?"
80+
without rebuilding the environment.
81+
```bash
82+
echo '{"name":"shell","arguments":{"command":"rm -rf /"}}' \
83+
| aperion-shield --explain --input -
84+
# ----------------------------------------------------------
85+
# shield --explain
86+
# ────────────────
87+
# tool : shell
88+
# call : {"command":"rm -rf /"}
89+
#
90+
# rules matched ............................. 1
91+
# fs.recursive_delete_root Critical pts=8
92+
# ...
93+
# decision .................................. BLOCK
94+
# rule_id : fs.recursive_delete_root
95+
# severity : Critical
96+
# reason : rm -rf on filesystem root is forbidden.
97+
# suggest : Scope to a specific subdirectory, ...
98+
```
99+
100+
3. **243 tests passing** (was 192 in v0.7, 148 in v0.6, 133 in v0.5)
101+
— +51 new tests: 22 in-module + 7 end-to-end for shims (real
102+
`/bin/sh` execution against a fake real binary, foreign-file
103+
collision, bypass env, fall-through when Shield isn't on `$PATH`,
104+
`--list-shims` separation); 15 in-module + 7 end-to-end for
105+
`--explain` (text / markdown / JSON stable-schema format
106+
round-trips, force flags, legacy `tool/params` descriptor shape,
107+
missing-tool refusal).
108+
109+
> **Heads up: HTTP/SSE MCP transport is the v0.9 headline.** The
110+
> Streamable HTTP transport (POST + SSE relay with backpressure) is a
111+
> meaty piece of work that deserves to be the lead feature of its own
112+
> release, not a half-finished breadth bump here. v0.8 is the
113+
> "bypass-closing" release; v0.9 will be the "any-transport" release.
114+
115+
---
116+
37117
## What's new in v0.7
38118

39119
![aperion-shield v0.7 git hooks demo — 28-second walkthrough of pre-commit + pre-push on a real GitHub remote](docs/img/v07-hooks-demo.gif)
@@ -625,6 +705,269 @@ You can layer your own rules on top via `--rules my.yaml`.
625705

626706
---
627707

708+
## Shell shims (new in v0.8)
709+
710+
`aperion-shield --install-shims` writes tiny `/bin/sh` wrappers that
711+
route every invocation of selected CLIs through Shield's engine
712+
before the call reaches the real binary. This closes the parallel
713+
bypass surface to v0.7's git hooks: where the hooks catch destructive
714+
code landing in a commit, the shims catch destructive commands the
715+
agent runs *directly from a shell*.
716+
717+
### Install
718+
719+
```bash
720+
# install shims for every supported command (10 by default)
721+
aperion-shield --install-shims
722+
723+
# OR pick a subset
724+
aperion-shield --install-shims --for aws,kubectl,terraform
725+
726+
# OR install into a different directory (default: ~/.aperion-shield/bin/)
727+
aperion-shield --install-shims --shim-dir ~/bin/aperion
728+
```
729+
730+
Shield prints exactly what to add to your shell rc so the shim dir
731+
wins lookup against the system binaries:
732+
733+
```bash
734+
zsh : echo 'export PATH="$HOME/.aperion-shield/bin:$PATH"' >> ~/.zshrc
735+
bash : echo 'export PATH="$HOME/.aperion-shield/bin:$PATH"' >> ~/.bashrc
736+
fish : fish_add_path -p '$HOME/.aperion-shield/bin'
737+
```
738+
739+
### Supported commands (out of the box)
740+
741+
| Surface | Commands |
742+
|---|---|
743+
| AWS / GCP / Azure | `aws`, `gcloud`, `az` |
744+
| Kubernetes | `kubectl`, `helm` |
745+
| Infra-as-Code | `terraform` |
746+
| Databases | `psql`, `mongosh`, `redis-cli` |
747+
| Filesystem | `rm` |
748+
749+
(You can also shim arbitrary commands — the shieldset is the source
750+
of truth for what counts as destructive. Default list just bounds
751+
what `--install-shims` instruments without a `--for` filter.)
752+
753+
### What happens on a refused call
754+
755+
```text
756+
$ aws s3 rm --recursive s3://prod-bucket
757+
[aperion-shield/check-cmd] APPROVAL-REQUIRED -- `aws s3 rm --recursive s3://prod-bucket`
758+
rule : cloud.aws_s3_recursive_delete (severity=High)
759+
reason : Bulk S3 delete -- irreversible if versioning is off.
760+
suggest : Enable versioning, then use lifecycle rules to expire -- never `--recursive --force`.
761+
note : approvals require an MCP-mediated invocation (this shim cannot prompt)
762+
763+
bypass options for a single invocation:
764+
SHIELD_SHIMS_DISABLE=1 <command> ... (env override, one-shot)
765+
aperion-shield --uninstall-shims (remove all shims)
766+
```
767+
768+
The real `aws` binary is **never exec'd** when Shield refuses. The
769+
exit code propagates so CI scripts notice the refusal.
770+
771+
### Bypass / disable
772+
773+
| Knob | Effect |
774+
|---|---|
775+
| `SHIELD_SHIMS_DISABLE=1 <cmd>` | one-shot bypass; shim execs the real binary directly |
776+
| `aperion-shield --uninstall-shims` | remove every Shield-managed shim from the dir |
777+
| `aperion-shield missing on $PATH` | shim fails open and execs the real binary (so teammates without Shield don't have their tooling broken — fail-open by design) |
778+
779+
### Exit codes (`--check-cmd`)
780+
781+
Same table as `--check-staged` so operators only memorise one set:
782+
783+
| Code | Meaning |
784+
|---|---|
785+
| 0 | engine returned Allow (or shadow) → shim execs the real binary |
786+
| 1 | Block decision → shim refuses, banner on stderr |
787+
| 2 | Approval / IdentityVerification → can't prompt at shim time (no MCP inbox loop), refused with a note pointing the user at MCP-mediated invocation |
788+
| 3 | operational error (couldn't load shieldset, argv empty, ...) |
789+
790+
### Coexistence with existing wrappers
791+
792+
If you've hand-rolled a wrapper at `~/.aperion-shield/bin/aws` (or
793+
wherever your shim dir is) before installing Shield, `--install-shims`
794+
**refuses to overwrite it** — exits 1, leaves your file alone, and
795+
tells you what it found. Pick a different `--shim-dir`, or delete
796+
your file yourself first.
797+
798+
### List / inspect
799+
800+
```bash
801+
aperion-shield --list-shims
802+
# /Users/me/.aperion-shield/bin/:
803+
# [shield ] aws
804+
# [shield ] kubectl
805+
# [shield ] terraform
806+
# [foreign] my-custom-wrapper <- not Shield-managed
807+
```
808+
809+
### Uninstall
810+
811+
```bash
812+
aperion-shield --uninstall-shims
813+
# REMOVED aws
814+
# REMOVED kubectl
815+
# REMOVED terraform
816+
# KEPT my-custom-wrapper (no Aperion marker; left alone)
817+
```
818+
819+
---
820+
821+
## `--explain`: walk through any decision (new in v0.8)
822+
823+
Shield's adaptive scoring is one of its strengths and one of the
824+
most common sources of "wait, why did *that* call get gated?"
825+
operator confusion. `--explain` answers the question in one shot —
826+
which rules tripped, which adjustment signals fired, where the
827+
severity tiers actually chained, and what the safer alternative is.
828+
829+
### Run it
830+
831+
```bash
832+
# from a file
833+
aperion-shield --explain --input call.json
834+
835+
# from stdin
836+
echo '{"name":"shell","arguments":{"command":"rm -rf /"}}' \
837+
| aperion-shield --explain --input -
838+
839+
# from a heredoc
840+
aperion-shield --explain --input - <<'EOF'
841+
{"name": "execute_sql", "arguments": {"query": "UPDATE users SET email_verified=TRUE WHERE email_verified=FALSE"}}
842+
EOF
843+
```
844+
845+
Accepts either descriptor shape:
846+
847+
| Shape | Source |
848+
|---|---|
849+
| `{"name": ..., "arguments": ...}` | MCP-canonical (Cursor / Claude Code / etc.) |
850+
| `{"tool": ..., "params": ...}` | legacy / some custom tooling — still accepted |
851+
852+
### Output formats
853+
854+
```bash
855+
aperion-shield --explain --input call.json # text (default)
856+
aperion-shield --explain --input call.json --explain-format markdown # PR-comment friendly
857+
aperion-shield --explain --input call.json --explain-format json # stable schema
858+
```
859+
860+
#### text (default)
861+
862+
```text
863+
shield --explain
864+
────────────────
865+
tool : shell
866+
call : {"command":"rm -rf /"}
867+
868+
rules matched ............................. 1
869+
fs.recursive_delete_root Critical pts=8
870+
871+
adjustments applied ....................... 0
872+
(none)
873+
874+
severities
875+
raw : Critical
876+
composite : High (composite_points=8)
877+
final : Critical
878+
879+
decision .................................. BLOCK
880+
rule_id : fs.recursive_delete_root
881+
severity : Critical
882+
reason : rm -rf on filesystem root is forbidden.
883+
suggest : Scope to a specific subdirectory, e.g. `rm -rf ./build/`.
884+
```
885+
886+
#### markdown — drops cleanly into a PR review comment
887+
888+
```markdown
889+
### `aperion-shield --explain`
890+
891+
| field | value |
892+
|---|---|
893+
| tool | `shell` |
894+
| call | `{"command":"rm -rf /"}` |
895+
| decision | **BLOCK** |
896+
| final severity | `Critical` |
897+
898+
**Rules matched (1):**
899+
900+
| rule | severity | points | reason |
901+
|---|---|---|---|
902+
| `fs.recursive_delete_root` | `Critical` | 8 | rm -rf on filesystem root is forbidden. |
903+
904+
...
905+
```
906+
907+
#### json — stable schema for tooling
908+
909+
```json
910+
{
911+
"tool": "shell",
912+
"arguments": {"command": "rm -rf /"},
913+
"rules_matched": [
914+
{
915+
"rule_id": "fs.recursive_delete_root",
916+
"severity": "Critical",
917+
"points": 8,
918+
"reason": "rm -rf on filesystem root is forbidden.",
919+
"safer_alternative": "Scope to a specific subdirectory, ..."
920+
}
921+
],
922+
"adjustment_signals": {
923+
"workspace_is_prod": false,
924+
"burst_in_progress": false,
925+
"fingerprint_repeatedly_approved": false,
926+
"fingerprint_recently_denied": false
927+
},
928+
"severity_raw": "Critical",
929+
"severity_composite": "High",
930+
"severity_final": "Critical",
931+
"composite_points": 8,
932+
"decision": {
933+
"kind": "block",
934+
"rule_id": "fs.recursive_delete_root",
935+
"severity": "Critical",
936+
"reason": "rm -rf on filesystem root is forbidden.",
937+
"safer_alternative": "...",
938+
"contributing_rules": []
939+
}
940+
}
941+
```
942+
943+
### What-if exploration
944+
945+
The four `--explain-force-*` flags let you ask "what would the same
946+
call decide in a different context?" without rebuilding the actual
947+
environment:
948+
949+
| Flag | What it does |
950+
|---|---|
951+
| `--explain-force-prod` | pretend the workspace probe said *prod* |
952+
| `--explain-force-burst` | pretend the burst detector is firing |
953+
| `--explain-force-repeatedly-approved` | demonstrate the decision-memory **demotion** path |
954+
| `--explain-force-recently-denied` | demonstrate the decision-memory **escalation** path |
955+
956+
Use the JSON output + `--explain-force-prod` together to drive a
957+
"would this break in prod?" status check on a PR.
958+
959+
### Exit codes (`--explain`)
960+
961+
Mirror `--check-cmd` so the same CI plumbing works:
962+
963+
| Code | Meaning |
964+
|---|---|
965+
| 0 | Allow or Warn |
966+
| 1 | Block |
967+
| 2 | Approval / IdentityVerification |
968+
969+
---
970+
628971
## Identity gates (new in v0.5)
629972

630973
For the highest-blast-radius calls -- `DROP DATABASE`, force-push to a

SECURITY.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ If you only read one section, read **§4 — Open advisories** below.
2323

2424
| Version | Status | Receives fixes |
2525
|---|---|---|
26-
| `0.7.x` | current stable | yes |
27-
| `0.6.x` | previous | security-only — superseded by v0.7.0 on 2026-05-20 |
26+
| `0.8.x` | current stable | yes |
27+
| `0.7.x` | previous | security-only — superseded by v0.8.0 on 2026-05-27 |
28+
| `0.6.x` | superseded | no — superseded by v0.7.0 on 2026-05-20 |
2829
| `0.5.x` | superseded | no — superseded by v0.6.0 on 2026-05-18 |
2930
| `< 0.5` | superseded | no |
3031

@@ -291,3 +292,4 @@ If you operate Shield as part of an enterprise deployment:
291292
| 2026-05-15 | Initial policy. Documents the three open Dependabot advisories surfaced by Shield's first public release and the v0.6.0 fix plan. |
292293
| 2026-05-18 | v0.6.0 shipped. RUSTSEC-2026-0098 / -0099 / -0104 closed by `rustls-webpki 0.103.13` (transitively via the `reqwest 0.12` / `rustls 0.23` / `hyper 1.x` upgrade). `.cargo/audit.toml` ignore list trimmed back to `[]`. Supported-versions table updated. |
293294
| 2026-05-20 | v0.7.0 shipped. No new advisories or fix-required changes; this is a feature-only release. `cargo audit` clean against `Cargo.lock` at the v0.7.0 commit. New surfaces (`--install-hooks`, `--check-staged`, `--check-pushed-refs`, `--suggest-rules`) all stay within the standalone process model — no new network endpoints, no new on-disk persistence beyond `.git/hooks/` (Shield itself) and the operator-redirected audit log. Supported-versions table updated; v0.6.x dropped to security-only. |
295+
| 2026-05-27 | v0.8.0 shipped. No new advisories or fix-required changes; this is a feature-only release. `cargo audit` clean against `Cargo.lock` at the v0.8.0 commit. New surfaces (`--install-shims`, `--uninstall-shims`, `--list-shims`, `--check-cmd`, `--explain`) all stay within the standalone process model. The shim path writes per-command `/bin/sh` wrappers into `~/.aperion-shield/bin/` (or `--shim-dir PATH`) at mode `0755` inside a directory at mode `0700`; **Shield will NOT overwrite any file it didn't write itself** (foreign-file collisions exit non-zero, file untouched). `--explain` is pure-input/pure-output — no on-disk persistence, no side-effects on decision memory or audit. Supported-versions table updated; v0.7.x dropped to security-only. |

0 commit comments

Comments
 (0)