Keeps credentials, company names, personal names and PII out of the model's context. Enforcement lives in Claude Code hooks rather than in instructions to the agent: a skill alone cannot protect anything, because by the time the agent reads a rule the surrounding context has already been sent. Credentials are removed irreversibly and marked. Entities from a user-supplied dictionary become stable aliases, rewritten back to real values on their way to disk and to the shell, so code and commands referring to them still work. Published from a clean tree; development history is not included.
178 lines
7.0 KiB
Markdown
178 lines
7.0 KiB
Markdown
# ctx-tools
|
|
|
|
A Claude Code **marketplace**: one repository, plugins under `plugins/`, each carrying
|
|
its own skills, hooks and commands. Currently one plugin, `ctxguard`.
|
|
|
|
This is the maintainer's document: handing the plugin to someone else, and releases.
|
|
The usage guide — installation, daily use, the CLI, what to do when a tool call is
|
|
denied — is the main [README.md](README.md), in Russian.
|
|
|
|
## Install
|
|
|
|
```
|
|
/plugin marketplace add /path/to/skills
|
|
/plugin install ctxguard@ctx-tools
|
|
```
|
|
|
|
## Handing this to someone else
|
|
|
|
The plugin travels through the marketplace. The dictionary does not, and must not.
|
|
|
|
### 1. The code — the short way
|
|
|
|
Copy the plugin folder into `~/.claude/skills/`:
|
|
|
|
```
|
|
cp -r plugins/ctxguard ~/.claude/skills/ctxguard
|
|
```
|
|
|
|
That is the whole step. It loads next session as `ctxguard@skills-dir` with **hooks
|
|
included** — verified by a live run: `claude plugin list` shows it loaded,
|
|
`${CLAUDE_PLUGIN_ROOT}` resolves, and enforcement fires. No marketplace, no install
|
|
command, no restart beyond starting a new session.
|
|
|
|
Two things to know:
|
|
|
|
- **The name must be free.** An installed plugin of the same name wins, and
|
|
*disabling* it is not enough — the name stays reserved until
|
|
`claude plugin uninstall`. The listing says so explicitly when it happens.
|
|
- Copy the directory, not a symlink into a repository you then move.
|
|
|
|
Use this for one machine or one colleague. Use the marketplace below when several
|
|
people need the same version and you want updates to be a command rather than a
|
|
recopy.
|
|
|
|
### 2. The code — the maintainable way
|
|
|
|
Push this repository somewhere the other person can reach, then they run:
|
|
|
|
```
|
|
/plugin marketplace add <owner>/<repo> # GitHub shorthand
|
|
/plugin install ctxguard@ctx-tools
|
|
```
|
|
|
|
`claude plugin marketplace add` also takes a git URL or a filesystem path, and
|
|
`--sparse .claude-plugin plugins` limits the checkout inside a monorepo. Nothing is
|
|
fetched at runtime and there are no dependencies beyond `python3`.
|
|
|
|
For a whole team, declare it at **project** scope and commit the result, so cloning
|
|
the repo is enough:
|
|
|
|
```
|
|
claude plugin marketplace add <owner>/<repo> --scope project
|
|
claude plugin install ctxguard@ctx-tools --scope project
|
|
```
|
|
|
|
### Releases, and the trap in them
|
|
|
|
Because `plugin.json` carries a semver version, `claude plugin update` compares
|
|
**versions, not commits** — a release that is not version-bumped never reaches an
|
|
installed copy, silently. So every release is:
|
|
|
|
```
|
|
# bump version in plugins/ctxguard/.claude-plugin/plugin.json AND the marketplace entry
|
|
git commit && git push
|
|
claude plugin tag plugins/ctxguard # validates the two agree, tags ctxguard--v1.0.1
|
|
git push origin refs/tags/ctxguard--v1.0.1
|
|
```
|
|
|
|
The recipient then runs `claude plugin update ctxguard@ctx-tools` and restarts.
|
|
|
|
Iterating locally is the same trap in miniature: hooks execute the **installed copy**,
|
|
not the working tree, so `claude plugin marketplace update ctx-tools` followed by
|
|
`claude plugin update ctxguard@ctx-tools` is required after every change — and a change
|
|
without a version bump will not be picked up. Confirm with
|
|
`diff -rq ~/.claude/plugins/cache/ctx-tools/ctxguard/<version>/scripts plugins/ctxguard/scripts`.
|
|
|
|
### 3. The dictionary — copied, never re-derived
|
|
|
|
`~/.claude/ctx-guard/` is per-machine and deliberately outside every repository. It
|
|
must be handed over explicitly, because **aliases cannot be reproduced**:
|
|
|
|
- they are assigned in registration order, so whoever adds two companies in the other
|
|
order gets the mapping swapped — two people would mean *different companies* by
|
|
`CTXG_COMPANY_A`;
|
|
- the salt is generated per store, so secret markers and PII aliases do not match
|
|
either, and findings cannot be correlated across a team.
|
|
|
|
There is a test asserting exactly this, so the guidance cannot quietly rot. Copy it:
|
|
|
|
```
|
|
ctxguard entity export --out ~/team-dict.json # refuses to write inside a git repo
|
|
# transfer over a channel you would send the real values over -- it contains them
|
|
ctxguard entity import team-dict.json # run from the project directory
|
|
```
|
|
|
|
After importing, both machines produce byte-identical output, markers and PII aliases
|
|
included.
|
|
|
|
### 4. What the recipient still has to do
|
|
|
|
```
|
|
ctxguard init # create state, show what is active
|
|
ctxguard scan . # what this repository actually holds
|
|
ctxguard verify # 43 canary cases through the real hook entry points
|
|
ctxguard scan-transcript # what has actually reached the model
|
|
```
|
|
|
|
Until entities are registered or imported, only credential and PII detection is live —
|
|
company names, people and hostnames are not protected. And sanitize `CLAUDE.md` by
|
|
hand: the harness loads it directly, so no hook ever sees it.
|
|
|
|
## Layout
|
|
|
|
```
|
|
.claude-plugin/marketplace.json marketplace manifest
|
|
plugins/ctxguard/ context sanitization: skill + hooks + CLI
|
|
docs/specs/ design document (historical)
|
|
Makefile the single entry point for checks
|
|
```
|
|
|
|
### Where does a new skill go?
|
|
|
|
| The skill needs… | Put it in |
|
|
|---|---|
|
|
| hooks, slash commands, MCP servers, scripts | its own plugin under `plugins/<name>/`, plus a `marketplace.json` entry |
|
|
| nothing but instructions | a bucket plugin: `plugins/<bucket>/skills/<name>/SKILL.md` |
|
|
|
|
Hooks cannot ship inside a bare skill, which is why this repository is a marketplace
|
|
rather than a flat directory of `SKILL.md` files. Instruction-only skills belong in one
|
|
bucket plugin — a single install covers all of them, whereas plugins that install hooks
|
|
are added deliberately, one at a time. There is no such bucket here yet; create it
|
|
along with the first skill that needs it, not in advance.
|
|
|
|
Conventions, following the installed plugin ecosystem:
|
|
|
|
- `SKILL.md` frontmatter is `name` + `description`, third person, trigger-heavy
|
|
- details go into `references/*.md`, not into `SKILL.md`
|
|
- scripts are Python 3 **stdlib only**, invoked as `python3 "${CLAUDE_PLUGIN_ROOT}/..."`
|
|
|
|
## Plugins
|
|
|
|
### ctxguard — context sanitization
|
|
|
|
Keeps credentials, client and company names, personal names and PII out of the model's
|
|
context. Credentials are removed irreversibly; names and PII become stable aliases that
|
|
are translated back on their way to disk, so the agent stays able to do real work.
|
|
|
|
Enforcement is in hooks, not in the skill — the agent cannot switch it off, and its
|
|
forgetting the rules is harmless.
|
|
|
|
```
|
|
make test # engine unit tests
|
|
make verify # canary corpus through the real hook entry points
|
|
make leaks # scan transcripts for values that actually reached the model
|
|
```
|
|
|
|
Start here: `plugins/ctxguard/skills/context-sanitization/SKILL.md`, then
|
|
`references/threat-model.md` for what it deliberately does not cover.
|
|
|
|
Optional, so the expectation survives the plugin being disabled — add to your
|
|
`CLAUDE.md`:
|
|
|
|
```markdown
|
|
Sensitive data in this project is aliased (`CTXG_*`) and enforced by ctxguard hooks.
|
|
Use aliases verbatim, never reconstruct real values, and read a denial's reason
|
|
instead of working around it. See the context-sanitization skill.
|
|
```
|