Guard config
The agent hooks read ~/.config/jes/config.json to decide which guards run.
jes login writes it the first time. Edit it by hand after that. A library
Guard doesn't read this file; you pass policies to it in Python.
If XDG_CONFIG_HOME is set, the file is $XDG_CONFIG_HOME/jes/config.json.
The default file
injection, indirect_injection, and hazards are on at 0.5. Every other
guard is listed but off, with its fields filled in so you can see them.
model is the decision model the judgments ask, jev-latest by default.
{
"model": "jev-latest",
"guards": {
"injection": {
"enabled": true,
"threshold": 0.5
},
"indirect_injection": {
"enabled": true,
"threshold": 0.5
},
"hazards": {
"enabled": true,
"threshold": 0.5
},
"toxicity": {
"enabled": false,
"threshold": 0.5
},
"topics": {
"enabled": false,
"threshold": 0.5,
"deny": []
},
"invisible_text": {
"enabled": false,
"mode": "targeted",
"block": false
},
"allowed_tools": {
"enabled": false,
"names": []
},
"tool_safety": {
"enabled": false,
"threshold": 0.5
},
"canary": {
"enabled": false,
"token": ""
},
"regex": {
"enabled": false,
"patterns": [],
"action": "block",
"match": "search",
"require": false,
"fold": false,
"timeout_ms": 50
},
"substrings": {
"enabled": false,
"terms": [],
"action": "block",
"whole_words": false,
"fold": true
},
"token_limit": {
"enabled": false,
"limit": 8000,
"encoding": "cl100k_base",
"mode": "block"
},
"pii": {
"enabled": false,
"entities": [
"EMAIL_ADDRESS",
"PHONE_NUMBER",
"CREDIT_CARD",
"US_SSN",
"IBAN_CODE",
"CRYPTO"
],
"input_mode": "redact",
"untrusted_mode": "mask",
"output_mode": "flag",
"tool_call_mode": "block",
"restore": true
},
"secrets": {
"enabled": false,
"redact": "all"
}
}
}
0.5 is a starting point, not a measured recommendation. See
Choosing a threshold.
Rules
- The file is one object with two keys:
guards, and an optionalmodel. Any other key is an error. modelis a model id on the TypeSafe API, such asjev-latest(the default) or a pinned Jev release likejev-1.13.0.- Each guard has
enabled."enabled": falseskips it. - An unknown guard, an unknown field, or a wrong type is an error, and the hook blocks until you fix it.
- A
thresholdis a number from0to1. It's required when a judgment guard is enabled. stagesandnameare fixed at each policy's defaults. A customjudgequestion can't go in this file. Use the Python library for those.
Judgments
These send text to the decision model in model. Each takes threshold.
| Guard | Other fields | Checks |
|---|---|---|
injection | The user's prompt, or a tool call's arguments, tries to override the agent's instructions. | |
indirect_injection | A tool result carries instructions aimed at the agent. | |
hazards | categories: a list such as ["S1", "S2"]. Omit for all of S1–S14. Each category is its own question. | Harmful requests and replies. See hazards. |
toxicity | labels: any of toxicity, severe_toxicity, obscene, threat, insult, identity_attack, sexual_explicit. Omit for all. | Toxic text. |
topics | deny: the topics to block. Required and non-empty when enabled. | Text about a topic you name. |
tool_safety | The tool call is destructive, exfiltrates data, changes privileges, or goes beyond what the user asked. |
Transforms
These run inside the hook process. They don't call the model.
| Guard | Fields |
|---|---|
allowed_tools | names: the tools that may run. Required and non-empty when enabled. Every other tool call is blocked. |
invisible_text | mode: targeted (default) or all. block: block instead of stripping (default false). |
canary | token: a marker that must never appear in a reply or a tool call. Required when enabled. |
secrets | redact: all (default), partial, or hmac. key: base64 for at least 32 bytes, required with hmac and not allowed otherwise. |
pii | entities: omit for EMAIL_ADDRESS, PHONE_NUMBER, CREDIT_CARD, US_SSN, IBAN_CODE, and CRYPTO. PERSON, UUID, IP_ADDRESS, and US_BANK_NUMBER are opt-in. input_mode: redact (default), mask, or block. untrusted_mode: mask (default), redact, or block. output_mode: flag (default), redact, or block. tool_call_mode: block (default) or flag. restore: default true. |
regex | patterns (required and non-empty when enabled), action (block or redact), match (search or fullmatch), require (default false), fold (default false), timeout_ms (default 50). |
substrings | terms (required and non-empty when enabled), action (block or redact), whole_words (default false), fold (default true). |
token_limit | limit (required), encoding (default cl100k_base), mode (block or truncate). |
secrets, regex, and token_limit need an extra: jes[secrets],
jes[regex], and jes[tokens]. With uvx, add it to the hook command, for
example uvx --from 'jes[secrets]==0.0.1' jes claude-hook. Without it the hook
fails with that policy's error.
pii with the default entities is pattern-based and needs no extra. The hook
default leaves out PERSON, because uvx installs no spaCy model. Listing
PERSON in entities needs jes[pii] and a spaCy English model, and the hook
fails without them; see Install.
The fields match the Python factories. Policies has the full behavior of each one.
Example: lock down tools
Allow only read-only tools, and judge the rest of each call against the user's request:
{
"guards": {
"injection": {"enabled": true, "threshold": 0.5},
"indirect_injection": {"enabled": true, "threshold": 0.5},
"allowed_tools": {"enabled": true, "names": ["Read", "Grep", "Glob"]},
"tool_safety": {"enabled": true, "threshold": 0.5},
"secrets": {"enabled": true, "redact": "all"}
}
}
Guards you leave out are off.