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.
7.0 KiB
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, 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.mdfrontmatter isname+description, third person, trigger-heavy- details go into
references/*.md, not intoSKILL.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:
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.