Guardrails for
AI-written code.
rein reviews the code your agents write and blocks unsafe, broken, or hallucinated calls before they run. The same verdict every time, with the reason, and a fix where one applies.
pip install rein-engine
A real agent + rein guardrail (deterministic, no LLM):
[1] dangerous code
agent tried: save_code('import os\nos.system(user_input)')
-> BLOCKED before execution; tool body never ran
security.os-system (high): os.system invokes a shell; use
subprocess with a list and shell=False.
[2] clean code
agent tried: save_code('def add(a, b): return a + b')
-> allowed; tool ran
Same verdict every run, with no model in the review loop.
The problem
Agents write real code now.
Autonomous coding agents write files and run commands on their own. The
failure modes are specific and dangerous: a hallucinated import that does not
exist, a hard-coded secret, an os.system call on user input, code
that will not even parse. Letting a second LLM review it is non-deterministic,
it passes once and fails the next run, and unauditable, you cannot see why.
rein is the deterministic layer that sits at the boundary and decides the same
way, every time.
How it works
One hook, gated at the boundary.
The agent proposes
It is about to write a file or run a command. rein hooks that moment, before anything executes.
rein reviews
Plain functions parse and check the code: security, secrets, hallucinated imports, undefined names. No LLM.
Block or allow
At or above your threshold, the call is cancelled with the reason and a fix. Otherwise it runs untouched.
Coverage
What it catches.
Unsafe code
os.system, eval, exec, pickle, subprocess shell=True, weak hashes, by AST, not guesswork.
Hard-coded secrets
API keys and tokens committed into source, caught before they ship.
Hallucinated imports
An import that resolves to no stdlib, dependency, or project module, the classic agent failure a linter misses.
Undefined names
A name used but bound nowhere reachable: a NameError caught before it runs.
Best-of-breed scanners
Opt into bandit, gitleaks, ruff, and semgrep, run over the agent's code before it touches disk.
A reason and a fix
Findings come with why they fired and, where one applies, how to remedy it, not just an alert.
Explainability
Every verdict, explained.
No black box. Each finding names the rule, the severity, why it fired, and how to fix it where there is a known remedy, the same output whether a person, CI, or an agent triggers it.
security.os-systemblockedos.system invokes a shell; whoever controls the argument controls the machine.
subprocess.run(["ls", "-l"], shell=False)Severity → action
Blocks at HIGH by default. Configurable per project, lower it to also gate MEDIUM (weak hashes, hallucinated imports).
Why deterministic
Not a second opinion.
LLM reviewer
- Non-deterministic: passes once, fails the next run
- Costs tokens on every check
- Can be prompt-injected by the very code it reviews
- Cannot explain its verdict
rein
- Deterministic: identical verdict every run
- No tokens, no model, no network round-trip
- Plain functions over an AST, nothing to manipulate
- Every finding has a reason and a fix
Who it's for
Built for teams shipping agents.
Agent builders
Building on Claude Code, OpenHands, or Strands? Gate every tool call deterministically, before it runs.
Platform & devtools teams
Shipping an agent product? Drop in a guardrail your users can trust: no model cost, fully auditable.
Security-minded orgs
Adopting AI codegen? Keep unsafe, secret-leaking, and hallucinated code out of your repos, the same way every time.
Quickstart
Three ways to use it.
from rein_strands import ReinToolGuard
agent = Agent(model=model, tools=tools,
hooks=[ReinToolGuard()])
Works with Strands and OpenHands. Blocks HIGH+ before the tool runs.
# .github/workflows/rein.yml
- uses: SametAtas/rein@v0.3.0
with:
path: src/
Reviews every pull request and emits SARIF.
pip install rein-engine
rein review .
Or wire it as a pre-commit hook for local gating.
Integrations
Plugs in where your agents run.
FAQ
Questions.
Does rein use an LLM?
No. Detection is plain deterministic functions over the code's AST plus regex for secrets. Same input, same verdict, every time.
Is it slow?
The native checks run locally, a few milliseconds per file, with no network round-trip. The external scanners (bandit, semgrep, and so on) are opt-in, since they cost real time per call.
What languages?
Python today for the code analysis; secret detection works on any text. More languages are on the roadmap.
Will it cry wolf on working code?
Precision is the point. On large real codebases (requests, Flask, click, and rich), rein's import- and name-resolution checks produced zero false positives. It biases toward false negatives over false positives, so a real, working tool call is not blocked.
What does deep mode add?
It runs bandit, gitleaks, ruff, or semgrep over the agent's code before it executes, folding their findings into the same verdict. Opt-in per scanner.
Is it open source?
Yes, Apache-2.0. The engine is on PyPI as rein-engine, with adapters for the major agent frameworks.
Stop it before the agent runs.
An LLM reviewer guesses, and forgets. rein decides the same way, every time.
pip install rein-engine