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
13 changes: 0 additions & 13 deletions .agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,6 @@
},
"category": "Security",
"description": "Reports secrets and personal data found in your Codex prompts before they are sent, locally, with no account, network destination or telemetry. Blocking is opt in."
},
{
"name": "shim-guard",
"source": {
"source": "local",
"path": "./plugins/shim-cli"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Security",
"description": "Former name of shim-cli. Existing installs keep updating; new installs should use shim-cli."
}
],
"description": "Local sensitive-data detection and reporting for coding agents."
Expand Down
15 changes: 0 additions & 15 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,6 @@
"security",
"hooks"
]
},
{
"name": "shim-guard",
"source": "./plugins/shim-cli",
"description": "Former name of shim-cli. Existing installs keep updating; new installs should use shim-cli.",
"author": {
"name": "shim Engineering"
},
"license": "Apache-2.0",
"category": "security",
"tags": [
"privacy",
"security",
"hooks"
]
}
]
}
10 changes: 4 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
uv export --locked --only-dev --no-emit-project --output-file /tmp/shim-dev.lock
- name: Check wheel contents
run: |
python -c 'from pathlib import Path; from zipfile import ZipFile; wheel = next(Path("dist").glob("*.whl")); names = ZipFile(wheel).namelist(); assert "shim_cli/__init__.py" in names; assert "shim_cli/guard/suffixes.py" in names; assert "shim_guard/hook.py" in names; assert any(name.endswith(".dist-info/METADATA") for name in names)'
python -c 'from pathlib import Path; from zipfile import ZipFile; wheel = next(Path("dist").glob("*.whl")); names = ZipFile(wheel).namelist(); assert "shim_cli/__init__.py" in names; assert "shim_cli/guard/suffixes.py" in names; assert any(name.endswith(".dist-info/METADATA") for name in names)'
- name: Verify no NLP dependency is declared
run: |
python - <<'PY'
Expand All @@ -70,8 +70,7 @@ jobs:
uv pip install --python /tmp/shim-cli-wheel --no-deps dist/*.whl
/tmp/shim-cli-wheel/bin/shim --help
test -x /tmp/shim-cli-wheel/bin/shim-hook
test -x /tmp/shim-cli-wheel/bin/shim-guard-hook
test -z "$(printf '%s' '{"hook_event_name":"UserPromptSubmit","prompt":"Synthetic safe prompt"}' | /tmp/shim-cli-wheel/bin/shim-guard-hook codex 2>&1)"
test -z "$(printf '%s' '{"hook_event_name":"UserPromptSubmit","prompt":"Synthetic safe prompt"}' | /tmp/shim-cli-wheel/bin/shim-hook codex 2>&1)"
/tmp/shim-cli-wheel/bin/shim demo codex --json
/tmp/shim-cli-wheel/bin/python -I -B -m pytest tests/contracts/codex/test_hook.py
/tmp/shim-cli-wheel/bin/python -I -B -m pytest tests/contracts/claude/test_claude_hook.py
Expand All @@ -85,8 +84,7 @@ jobs:
uv pip install --python /tmp/shim-cli-sdist --require-hashes -r /tmp/shim-runtime.lock -r /tmp/shim-dev.lock
uv pip install --python /tmp/shim-cli-sdist --no-deps --no-build-isolation dist/*.tar.gz
test -x /tmp/shim-cli-sdist/bin/shim-hook
test -x /tmp/shim-cli-sdist/bin/shim-guard-hook
test -z "$(printf '%s' '{"hook_event_name":"UserPromptSubmit","prompt":"Synthetic safe prompt"}' | /tmp/shim-cli-sdist/bin/shim-guard-hook codex 2>&1)"
test -z "$(printf '%s' '{"hook_event_name":"UserPromptSubmit","prompt":"Synthetic safe prompt"}' | /tmp/shim-cli-sdist/bin/shim-hook codex 2>&1)"
/tmp/shim-cli-sdist/bin/shim demo codex --json
/tmp/shim-cli-sdist/bin/python -I -B -m pytest tests/contracts/codex/test_hook.py
/tmp/shim-cli-sdist/bin/python -I -B -m pytest tests/contracts/claude/test_claude_hook.py
Expand Down Expand Up @@ -114,7 +112,7 @@ jobs:
home="$(mktemp -d)"
printf '%s' "$2" \
| env TMPDIR="$home" SHIM_CONFIG="$config" \
SHIM_GUARD_SESSION_DIR="$home/session" XDG_STATE_HOME="$home/state" \
XDG_STATE_HOME="$home/state" \
"$1" plugins/shim-cli/bin/shim.pyz claude 2>&1 \
| sed -E "s|$home/shim-redacted-[A-Za-z0-9_]+\.txt|<REDACTED>|g" \
| sed -E "s|[0-9]+ ms|N ms|g"
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ jobs:
uv pip install --python /tmp/shim-cli-wheel --require-hashes -r dist/requirements.lock -r /tmp/shim-dev.lock
uv pip install --python /tmp/shim-cli-wheel --no-deps "dist/tested/shim-${GITHUB_REF_NAME#v}-py3-none-any.whl"
test -x /tmp/shim-cli-wheel/bin/shim-hook
test -x /tmp/shim-cli-wheel/bin/shim-guard-hook
test -z "$(printf '%s' '{"hook_event_name":"UserPromptSubmit","prompt":"Synthetic safe prompt"}' | /tmp/shim-cli-wheel/bin/shim-guard-hook codex 2>&1)"
test -z "$(printf '%s' '{"hook_event_name":"UserPromptSubmit","prompt":"Synthetic safe prompt"}' | /tmp/shim-cli-wheel/bin/shim-hook codex 2>&1)"
/tmp/shim-cli-wheel/bin/shim demo codex --json
/tmp/shim-cli-wheel/bin/python -I -B -m pytest tests/contracts/codex/test_hook.py
/tmp/shim-cli-wheel/bin/python -I -B -m pytest tests/contracts/claude/test_claude_hook.py
Expand All @@ -59,8 +58,7 @@ jobs:
uv pip install --python /tmp/shim-cli-sdist --require-hashes -r dist/requirements.lock -r /tmp/shim-dev.lock
uv pip install --python /tmp/shim-cli-sdist --no-deps --no-build-isolation "dist/tested/shim-${GITHUB_REF_NAME#v}.tar.gz"
test -x /tmp/shim-cli-sdist/bin/shim-hook
test -x /tmp/shim-cli-sdist/bin/shim-guard-hook
test -z "$(printf '%s' '{"hook_event_name":"UserPromptSubmit","prompt":"Synthetic safe prompt"}' | /tmp/shim-cli-sdist/bin/shim-guard-hook codex 2>&1)"
test -z "$(printf '%s' '{"hook_event_name":"UserPromptSubmit","prompt":"Synthetic safe prompt"}' | /tmp/shim-cli-sdist/bin/shim-hook codex 2>&1)"
/tmp/shim-cli-sdist/bin/shim demo codex --json
/tmp/shim-cli-sdist/bin/python -I -B -m pytest tests/contracts/codex/test_hook.py
/tmp/shim-cli-sdist/bin/python -I -B -m pytest tests/contracts/claude/test_claude_hook.py
Expand Down
53 changes: 35 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ Two commands, two different questions:
| `shim install claude` | Mask secrets and personal data in eligible tool results, every session, automatically. |

> [!WARNING]
> shim-cli is alpha software and a best-effort guard, not a data-loss
> prevention boundary. Read the [privacy limitations](https://github.com/GetSHIM/shim-cli/blob/main/docs/privacy.md) before
> shim-cli is a best-effort guard, not a data-loss prevention boundary. Read the [privacy limitations](https://github.com/GetSHIM/shim-cli/blob/main/docs/privacy.md) before
> using it with sensitive data.

## Measure a session
Expand Down Expand Up @@ -203,18 +202,29 @@ tag; it needs Python 3.9 or newer and nothing else installed, on both clients.

### Upgrading from 0.2.0

Nothing breaks and nothing is required of you. The hook command your client
already runs keeps working, byte for byte, through a compatibility package.
1.0 no longer runs the hook 0.2.0 wrote. A client settings file that still
carries `-m shim_guard.hook` reports `No module named shim_guard` on every
prompt, and nothing is inspected until you run, once per client:

When convenient, run `shim install <client>` once. That rewrites the hook line
to the new module name and, on Copilot, replaces the old hook file. Your
settings and ledger move to `shim/` on the next `shim` command that touches
them, and each move is reported once. The Claude Code plugin keeps loading and
updating: `shim-guard@shim-guard` still resolves through a marketplace alias,
which is removed in 1.0 along with the `shim-guard-hook` script, the
`shim_guard` package and the `SHIM_GUARD_CONFIG` variable.
```console
shim install <client>
```

That rewrites the hook line and, on Copilot, replaces the old hook file.
`shim doctor <client>` reports a hook left in the 0.2.0 shape as `FAIL` with the
same command. Your settings and ledger move to `shim/` on the next `shim`
command that touches them, and each move is reported once.

A Claude Code plugin installed as `shim-guard@shim-guard` stopped updating at
0.3.2. Move it:

```text
/plugin uninstall shim-guard@shim-guard
/plugin marketplace add GetSHIM/shim-cli
/plugin install shim-cli@shim-cli
```

Codex plugin users are the one exception and need four commands; see
Codex plugin users need four commands; see
[docs/compatibility.md](docs/compatibility.md).

## Use
Expand Down Expand Up @@ -450,6 +460,10 @@ reported.
- The host client receives the raw prompt before its hook runs, and other hooks
may receive it concurrently.
- Detection is best-effort and may miss sensitive values.
- **The output of a failed tool call is not masked.** Claude Code passes it
to a separate hook event that shim does not install, so a command such as
`cat .env && cat missing-file` exits non-zero and the model reads `.env`
as it is, with nothing in the session summary.
- A disabled, untrusted, crashed, or timed-out hook may fail open according to
client behavior.
- Clients, providers, and other tools may retain data independently of shim.
Expand All @@ -465,19 +479,19 @@ Every figure here was measured on the released build, not estimated.

| | |
| --- | --- |
| Tests | **1,800+**, one command: `python scripts/check.py` — lock, lint, format, types, suite, wheel, sdist |
| Hook cost | **67 ms** median end to end, interpreter start included; **41 ms** for a session summary |
| With 32 custom patterns | **+0.8 ms** median against the same prompt with none |
| Detector corpus | **570 cases**, graded on exact redacted output rather than category presence |
| Release evidence | SBOM, provenance and Sigstore bundles on the release page from 0.3.2, with the `gh attestation verify` command in [the compatibility record](https://github.com/GetSHIM/shim-cli/blob/main/docs/compatibility.md#030-release-evidence) |
| Tests | **1,900+**, one command: `python scripts/check.py` — lock, lint, format, types, suite, wheel, sdist |
| Hook cost | **70 ms** median end to end, interpreter start included; **42 ms** for a session summary |
| With 32 custom patterns | **+0.6 ms** median against the same prompt with none |
| Detector corpus | **589 cases**, graded on exact redacted output rather than category presence |
| Release evidence | SBOM, provenance and Sigstore bundles on the release page from 0.3.2, with the `gh attestation verify` command in [the compatibility record](https://github.com/GetSHIM/shim-cli/blob/main/docs/compatibility.md#100-release-evidence) |

<p align="center">
<img src="https://raw.githubusercontent.com/GetSHIM/shim-cli/main/docs/assets/shots/shim-doctor.png" width="880"
alt="shim doctor claude: twelve checks, each PASS or WARN — the hook group is present, no 0.2.0 names are left, 12 of 12 entities are enabled, the runner protected a sensitive fixture, and coverage is 5 of 5 events.">
</p>

Hook output is asserted byte for byte, not by shape: a safe event must produce
exactly zero bytes on stdout and stderr. 312 contract tests hold that, plus the
exactly zero bytes on stdout and stderr. 346 contract tests hold that, plus the
import boundaries, the rule that no committed file carries the machine it was
written on, and a byte-identical rebuild of the shipped plugin archive.

Expand Down Expand Up @@ -528,6 +542,9 @@ reinstalling later finds your entity choices and custom patterns still there.
- [Architecture](https://github.com/GetSHIM/shim-cli/blob/main/docs/architecture.md)
- [Compatibility](https://github.com/GetSHIM/shim-cli/blob/main/docs/compatibility.md)
- [Privacy](https://github.com/GetSHIM/shim-cli/blob/main/docs/privacy.md)
- [1.0.0 release notes](https://github.com/GetSHIM/shim-cli/blob/main/docs/releases/1.0.0.md)
- [0.3.3 release notes](https://github.com/GetSHIM/shim-cli/blob/main/docs/releases/0.3.3.md)
- [0.3.2 release notes](https://github.com/GetSHIM/shim-cli/blob/main/docs/releases/0.3.2.md)
- [0.3.1 release notes](https://github.com/GetSHIM/shim-cli/blob/main/docs/releases/0.3.1.md)
- [0.3.0 release notes](https://github.com/GetSHIM/shim-cli/blob/main/docs/releases/0.3.0.md)
- [0.2.0 release notes](https://github.com/GetSHIM/shim-cli/blob/main/docs/releases/0.2.0.md)
Expand Down
11 changes: 4 additions & 7 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ The coverage line counts the events whose hook is in the client's settings
file, and the table's `Installed` column reads the same file:
`PASS Coverage: 5 of 5 events installed.` once shim is installed, and
`WARN Coverage: 0 of 5 events installed; run shim install claude.` before. A
hook still in the 0.2.0 shape counts as installed. The exit codes do not
change: the coverage `WARN` exits `0`.
hook still in the 0.2.0 shape does not count, because 1.0 does not run it:
doctor reports it as `FAIL` with `run shim install <client>`. The coverage
`WARN` on its own exits `0`.

Every `FAIL` names the command that fixes it. A malformed settings file, for
example, gives you the path, the parser's message with its line number, and
Expand Down Expand Up @@ -426,13 +427,9 @@ wrote back is `observe`, because it is not a leak.
| `SHIM_CONFIG` | Use this settings file instead of the default path. |
| `XDG_CONFIG_HOME` | Where `shim/config.toml` lives. |
| `XDG_STATE_HOME` | Where the ledger lives. |
| `SHIM_GUARD_STATE_DIR` | Pin the ledger directory outright. |
| `SHIM_GUARD_SESSION_DIR` | Pin the session spool directory. |
| `TMPDIR` | Where session records and withheld prompts are written. |
| `CLAUDE_CONFIG_DIR`, `CODEX_HOME`, `COPILOT_HOME` | Where each client keeps its settings; shim follows them. |

`SHIM_GUARD_CONFIG` is the 0.2.0 name for `SHIM_CONFIG` and still works. It is
removed in 1.0.

## Where shim keeps things

| What | Where |
Expand Down
Loading