A shell-specific, static-first pre-execution verifier for shell-executing LLM agents.
Fast like a rule engine. Careful like a judge.
85.64 % F1 Β· 0.91 % FPR Β· 2.32 ms mean latency β and only ~4 % of commands ever reach the LLM.
- [2026-06-16] ππ Congratulations! Our paper "CARE: Pre-Execution Command Verification for Shell-Executing LLM Agents" has been accepted at the IEEE International Symposium on Software Reliability Engineering (ISSRE 2026)!
- [2026-06-16] π Project page is live: https://prisma-research.github.io/CARE/
CARE mediates a candidate shell command before it reaches the host shell. It canonicalizes the command into a stable verification target, derives deterministic multi-view evidence over syntax, command semantics, path context, and provenance-backed risk patterns, and escalates only underdetermined WARN cases to an LLM judge. The common case stays fast, reproducible, and auditable; neural adjudication is reserved for borderline commands.
This repository contains the reference implementation of the CARE pipeline as described in the paper "CARE: Pre-Execution Command Verification for Shell-Executing LLM Agents" (ISSRE 2026). It ships only the CARE method β no baselines and no experiment harness.
| Guard | F1 % β | DR % β | FPR % β | Latency β |
|---|---|---|---|---|
| Best static baseline (OpenClaw4Layer) | 72.62 | 57.27 | 0.30 | 0.02 ms |
| Best LLM judge (LLMJudge) | 73.09 | 67.27 | 11.25 | 45.9 ms |
| CARE (w/o Resolution) β static only | 84.99 | 75.91 | 1.82 | 0.34 ms |
| CARE β full pipeline | 85.64 | 75.91 | 0.91 | 2.32 ms |
Benign utility is essentially untouched (57.00 % NL2SH resolve rate vs 57.33 % unguarded; 1 deny in 300 tasks), and on 600 Docker-executed, LLM-generated attack commands the static profile cuts realised harm from 74.8 % to 37.3 %.
CARE is a three-stage pipeline (paper Sec. III, Algorithm 1):
raw command c
β
Stage 1 βββββββββββΌβββββββββββ
Canonical- β N(c) -> Δ β wrapper unwrap, IFS/var expand,
ization β β base64/printf decode, shell -c unwrap
βββββββββββ¬βββββββββββ
β Δ
Stage 2 βββββββββββΌβββββββββββ
Attributionβ L1 Structure Ξ΄_strβ Eq. 1
β L2 Semantic s_semβ Eq. 2
β L3 Path s_pthβ Eq. 3β4
β L4 Pattern s_patβ Eq. 5 (139 provenance-tagged rules)
β L5 Policy β score, β Eq. 6β7
β d_prov β
βββββββββββ¬βββββββββββ
β d_prov β {ALLOW, WARN, DENY}
Stage 3 βββββββββββΌβββββββββββ
Resolution β ALLOW / DENY: finalβ
β WARN: skip(c)? β Eq. 8β11
β yes β DENY β (no LLM call)
β no β LLM judge β SAFE / DANGEROUS
βββββββββββ¬βββββββββββ
βΌ final decision d*
Composite score (Eq. 6):
score(c) = w_semΒ·s_sem + w_pathΒ·s_path + w_patΒ·s_pat + w_structΒ·Ξ΄_struct
with the default balanced weights w_sem = w_path = w_pat = 0.30,
w_struct = 0.10.
Provisional triage (Eq. 7):
d_prov = ALLOW if score < Ο_low
WARN if Ο_low β€ score < Ο_high
DENY if score β₯ Ο_high
with balanced thresholds Ο_low = 0.15, Ο_high = 0.35.
Skip predicates (Eq. 8): a WARN command retains its static DENY and bypasses the LLM when any of
p_rule (Eq. 9) : a fired L4 rule has MITRE provenance and confidence β₯ ΞΈ_rule (0.80)
p_sem (Eq. 10) : an L2 atom is in a high-risk class with score β₯ ΞΈ_sem (0.70)
p_spath(Eq. 11) : the L3 path layer fires (sensitive-location access)
holds. Otherwise the command and its evidence trace go to a single safety-biased LLM judge that returns SAFE/DANGEROUS; any judge error fails closed to DENY.
| File | Paper concept |
|---|---|
care/engine.py β CAREEngine |
Stages 1β2 orchestrator (Algorithm 1) |
care/canonicalization.py |
Stage 1 β canonicalization operator N |
care/structure.py |
L1 β syntax/structural analysis (Ξ΄_struct, Eq. 1) |
care/semantic.py |
L2 β semantic attribution (s_sem, Eq. 2) |
care/path.py |
L3 β path-sensitive attribution (s_path, Eq. 3β4) |
care/pattern.py |
L4 β pattern & provenance attribution (s_pat, Eq. 5) |
care/policy.py |
L5 β weighted aggregation + triage (Eq. 6β7) |
care/modes.py |
L5 β strict/balanced/auto operating modes (Appendix A.5) |
care/resolution.py β CARE |
Stage 3 β skip predicates + LLM judge (Eq. 8β11) |
care/common.py |
shared types + L2 base-score table |
care/rules/rule_provenance.json |
139-rule bank artifact (Appendix A.4) |
git clone https://github.com/prisma-research/CARE.git
cd CARE
pip install -e . # installs bashlex; add [resolution] for the LLM judge
# or, minimally:
pip install bashlex # openai only needed for Stage 3Python β₯ 3.9. bashlex is the only core dependency; if it is unavailable the
L1 layer degrades to a regex fallback.
from care import CAREEngine
eng = CAREEngine()
r = eng.analyze("rm -rf /var/log/*")
print(r.decision, r.score) # DENY 0.765
print(r.triggered_layers) # ['L2_Semantic', 'L3_Path', 'L4_Pattern']
print(r.fired_rules[0]['rule_id']) # e.g. SE-P-042analyze() returns an AnalysisResult with the full evidence trace
(details['scoring'], per-layer scores, fired rules) so every decision is
auditable.
Stage 3 calls an OpenAI-compatible LLM endpoint on WARN commands that no skip predicate resolves. Point CARE at your server via environment variables:
export CARE_LLM_BASE_URL=http://127.0.0.1:8006/v1
export CARE_LLM_MODEL=Qwen3-Coder-30B-A3B-Instruct
export CARE_LLM_API_KEY=not-needed-local-vllmfrom care import CARE
guard = CARE() # use_judge=True by default
print(guard.is_dangerous("rsync -avz ./data user@host:/backup/"))The reported results use Qwen3-Coder-30B-A3B-Instruct served by vLLM,
temperature=0, max_tokens=8, single-shot.
from care import CARE
guard = CARE(use_judge=False) # WARN and DENY both enforced as blockedfrom care import CAREEngine
strict = CAREEngine(mode="strict") # Ο_low=0.10, Ο_high=0.20
balanced = CAREEngine(mode="balanced") # Ο_low=0.15, Ο_high=0.35 (default)
auto = CAREEngine(mode="auto") # Ο_low=0.20, Ο_high=0.50care/rules/rule_provenance.json contains the 139 provenance-tagged rules
used by L4, split by provenance tier (Sec. IV, Appendix A.4):
| Tier | Count | Weight Ο |
|---|---|---|
| MITRE ATT&CK | 92 | 1.00 |
| GTFOBins | 31 | 0.85 |
| Manual Cases | 16 | 0.60 |
| Total | 139 |
Each rule's effective contribution is Ο(tier) Β· conf(rule); L4 reports the
maximum over all fired rules. The JSON artifact is regenerated from the
in-code rule specification with python -m care.pattern.
python examples/quickstart.py
python tests/test_pipeline.py # or: python -m pytest tests/The tests assert the paper's constants (139 = 92/31/16 rules; weights 0.3/0.3/0.3/0.1; Ο_low/Ο_high; aggregation formula) and the three-way triage on representative commands.
CARE is a single-command, pre-execution verifier operating on the command string plus bounded path context. It does not observe agent prompts, reasoning, or conversation history, and is a complement to β not a replacement for β sandboxing and host hardening. Session-level and trajectory-level hazards are out of scope.
If you use CARE, please cite our ISSRE 2026 paper:
@inproceedings{liu2026care,
author = {Liu, Yu and Zhang, Wenxiao and Yang, Zhiwei and Zhang, Zhongyi and
Feng, Hanqi and Wang, Xinyu and Qiu, Peng and Liu, Yanbing and
Poczos, Barnabas and Hong, Jin B.},
title = {{CARE}: Pre-Execution Command Verification for Shell-Executing {LLM} Agents},
booktitle = {IEEE International Symposium on Software Reliability Engineering (ISSRE)},
year = {2026}
}MIT β see LICENSE.
