Deterministic, no-LLM tool-call guardrail for Strands agents, backed by the rein engine.
Project description
rein-strands
A deterministic, no-LLM guardrail for Strands agents. It reviews the code or command a tool is about to run and cancels the call before the tool executes when the verdict crosses a severity threshold. Backed by the rein engine.
No LLM judges the action, so the verdict is the same every run and you can see exactly why a call was stopped. The same check behaves identically whether a person, CI, or the agent triggers it, which keeps accountability clear.
See it block a real agent
examples/agent_demo.py runs an actual Strands Agent (a scripted model stands
in for the LLM, so no API key is needed) and lets rein gate its tool calls:
[1] dangerous code
agent tried: save_code('import os\nos.system(user_input)\n')
-> BLOCKED before execution; tool body never ran
rein blocked tool 'save_code': security.os-system (high): os.system/os.popen
invokes a shell; use subprocess with a list and shell=False.
[2] clean code
agent tried: save_code('def add(a, b):\n return a + b\n')
-> allowed; tool ran
The dangerous tool call is cancelled at the boundary; the tool function never runs. The clean one goes through untouched.
Install
pip install rein-strands
Use
Attach the hook to your agent. That is the whole integration:
from strands import Agent
from strands_tools import shell, file_write, python_repl
from rein_strands import ReinToolGuard
agent = Agent(
model=model,
tools=[shell, file_write, python_repl],
hooks=[ReinToolGuard()], # blocks HIGH+ verdicts before the tool runs
)
What it reviews
| Tool | Field reviewed | Analysis |
|---|---|---|
python_repl |
code |
Full rein code analysis (stateful session, so undefined-name is not enforced) |
file_write |
content (.py) |
Full rein code analysis, fails closed if the file will not parse |
editor |
file_text / new_str (.py) |
Full rein code analysis on the added text |
shell |
command |
Secrets, plus full analysis of any inline python -c "..." code |
| custom | content / code (no path) |
Treated as Python so dangerous code is still caught |
| any | non-.py file content |
Secrets only (the Python AST checks do not apply) |
rein catches hard-coded secrets, unsafe calls (os.system, eval, pickle,
weak hashes, and so on), undefined names, and (with project_root) hallucinated
imports. Each finding carries the reason and a remedy, not just an alert.
Modes and threshold
from rein_strands import ReinToolGuard, Severity
# Report only, never block (human-in-the-loop, where a person owns the call):
ReinToolGuard(mode="audit", on_finding=lambda d: print(d.reason))
# Stricter: also block MEDIUM findings (e.g. weak hashes, hallucinated imports):
ReinToolGuard(block_at=Severity.MEDIUM)
mode="enforce"(default) cancels a call whose verdict is at or aboveblock_at.mode="audit"never cancels and only reports findings.block_atdefaults toSeverity.HIGH.
Catching hallucinated imports
Pass project_root and rein also checks every Python import the agent writes
against the project's stdlib, declared dependencies, and own modules, so a
hallucinated or undeclared module is caught before the file is written or run:
ReinToolGuard(project_root=".", block_at=Severity.MEDIUM)
This needs the project to declare dependencies (a pyproject.toml [project]
table or a requirements*.txt); without one rein cannot know what is installed,
so the import check stays inert rather than guessing.
How it fits with Strands' own safety
Strands shell relies on up-front isolation (declare what the agent can reach;
everything else does not exist), and Strands offers model-level guardrails.
rein-strands is the complementary deterministic, code-level layer: isolation
controls what an action can touch, rein judges what the code itself does, before
it runs. It is intentionally narrow and precise rather than a broad shell-pattern
scanner, so it does not cry wolf on ordinary commands.
Design
The decision logic (evaluate) is a pure function with no Strands or LLM
dependency; ReinToolGuard is the thin hook that wires it into an agent, and
extraction.py holds the Strands-specific tool-shape mapping. The core has no
dependencies beyond rein. Verified against strands-agents 1.44, with an
end-to-end test that drives a real agent.
License
Apache-2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rein_strands-0.3.0.tar.gz.
File metadata
- Download URL: rein_strands-0.3.0.tar.gz
- Upload date:
- Size: 17.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d8476c2478c0d4420981c2442c9334ac589cf27de23216baa66fa4fbaa7dfd32
|
|
| MD5 |
623a9e7777ee86d2a0ecf178c186a1cb
|
|
| BLAKE2b-256 |
62e5ca69bd454c1d4ba0d456f1ec0855bfe863459fba49a18a727037681ac761
|
Provenance
The following attestation bundles were made for rein_strands-0.3.0.tar.gz:
Publisher:
publish.yml on SametAtas/rein-strands
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rein_strands-0.3.0.tar.gz -
Subject digest:
d8476c2478c0d4420981c2442c9334ac589cf27de23216baa66fa4fbaa7dfd32 - Sigstore transparency entry: 1924461364
- Sigstore integration time:
-
Permalink:
SametAtas/rein-strands@a2f0ed923d9fb87c88dc27330707e824eeb5032a -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/SametAtas
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a2f0ed923d9fb87c88dc27330707e824eeb5032a -
Trigger Event:
release
-
Statement type:
File details
Details for the file rein_strands-0.3.0-py3-none-any.whl.
File metadata
- Download URL: rein_strands-0.3.0-py3-none-any.whl
- Upload date:
- Size: 13.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98aa5d8f6f132c70ea129ecfa881c64d67a6b0131b67080c5961687cfc4b6cee
|
|
| MD5 |
46ea0c3cd7bc70301ca0c52913f46e48
|
|
| BLAKE2b-256 |
ea4ec76c2f35563e00fe5e37a93124ca5bd87a0a36e6eca5bceca422288f1c4c
|
Provenance
The following attestation bundles were made for rein_strands-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on SametAtas/rein-strands
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rein_strands-0.3.0-py3-none-any.whl -
Subject digest:
98aa5d8f6f132c70ea129ecfa881c64d67a6b0131b67080c5961687cfc4b6cee - Sigstore transparency entry: 1924461483
- Sigstore integration time:
-
Permalink:
SametAtas/rein-strands@a2f0ed923d9fb87c88dc27330707e824eeb5032a -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/SametAtas
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a2f0ed923d9fb87c88dc27330707e824eeb5032a -
Trigger Event:
release
-
Statement type: