Skip to main content

Deterministic, no-LLM tool-call guardrail for Strands agents, backed by the rein engine.

Project description

rein-strands

CI PyPI Python License

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 above block_at.
  • mode="audit" never cancels and only reports findings.
  • block_at defaults to Severity.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.

Deep mode: external scanners

By default the guard runs rein's fast, in-process native checks. Opt into the external scanners rein integrates (ruff, bandit, gitleaks, semgrep) and they run over the tool's content before it executes, folding their findings into the same verdict:

ReinToolGuard(scanners=("bandit", "gitleaks"))

This is off by default on purpose: external scanners cost real time per call, so it is opt-in and you choose which to run. bandit and gitleaks are reasonable per-call; semgrep is heavier and usually better as a commit-time gate (where rein review already runs it). The named scanners must be installed; a missing one is skipped.

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rein_strands-0.4.0.tar.gz (18.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rein_strands-0.4.0-py3-none-any.whl (13.8 kB view details)

Uploaded Python 3

File details

Details for the file rein_strands-0.4.0.tar.gz.

File metadata

  • Download URL: rein_strands-0.4.0.tar.gz
  • Upload date:
  • Size: 18.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for rein_strands-0.4.0.tar.gz
Algorithm Hash digest
SHA256 f20428e0d6f605c8d1208f2f373a9f222bebdd6bc6d96b784a301d4617d01461
MD5 417ab4d987dde1c417cb258ae5501a36
BLAKE2b-256 d8004341fd4eb4c37c08af3d2d0af344263574921ad542fde4ad8f9217135f8c

See more details on using hashes here.

Provenance

The following attestation bundles were made for rein_strands-0.4.0.tar.gz:

Publisher: publish.yml on SametAtas/rein-strands

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rein_strands-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: rein_strands-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 13.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for rein_strands-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7ebfa935c8b2fa3cbab319cd357b6a455b6e3954375a921a9ee617015069e11e
MD5 69dc0bc6686eb4bee977deb5a1f394f0
BLAKE2b-256 bd032372b328fb87f5c8aa2c72fb911135e991b6204fd9361c1ae5f0f7ce45d1

See more details on using hashes here.

Provenance

The following attestation bundles were made for rein_strands-0.4.0-py3-none-any.whl:

Publisher: publish.yml on SametAtas/rein-strands

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page