tealtiger-openhands
Deterministic governance hooks for OpenHands — file access control, command allowlisting, secret scanning, cost budgets, and structured audit evidence.
<2ms per evaluation. No LLM in the governance path. Apache 2.0.
What it does
tealtiger-openhands adds a governance layer to OpenHands agent sessions using the native PreToolUse hook system. Before any tool executes (terminal commands, file edits, browser actions), the hook evaluates your policy and returns ALLOW or DENY.
| Capability | What it protects |
|---|---|
| File access control | Block reads of .env, SSH keys, credentials. Restrict writes to approved paths. |
| Command allowlisting | Only approved commands execute. Block destructive/network commands. |
| Secret scanning | Detect hardcoded API keys, passwords, tokens in file writes before commit. |
| Network blocking | Prevent curl, wget, ssh and URL patterns in commands. |
| Cost budget | Per-session USD limit + iteration cap. Hard stop when exceeded. |
| Kill switch | Instantly freeze a runaway session. |
| Structured audit | Every decision logged: correlation_id, action, reason_codes, risk_score, timing. |
Quick Start
1. Install
pip install tealtiger-openhands
2. Create governance config
Create .openhands/governance.yml in your repository:
mode: ENFORCE
file_policy:
read_denylist:
- ".env*"
- "**/*.pem"
- ".ssh/**"
write_allowlist:
- "src/**"
- "tests/**"
command_policy:
allowlist:
- python
- pytest
- pip
- git
- cat
- ls
- grep
denylist:
- "rm -rf"
block_network_access: true
secret_scan:
enabled: true
action: block
budget:
per_session_usd: 5.00
max_iterations: 100
3. Register the hook
Create .openhands/hooks.json:
{
"pre_tool_use": [
{
"matcher": "*",
"hooks": [
{
"command": "tealtiger-openhands-hook --config .openhands/governance.yml",
"timeout": 5
}
]
}
]
}
4. Done
Next time OpenHands works on your repository, every tool call will be evaluated against your governance policy before execution.
Governance Modes
| Mode | Behavior |
|---|---|
ENFORCE |
Block violations. Agent receives denial reason and can adapt. |
MONITOR |
Log violations but allow execution. For rollout testing. |
OBSERVE |
Passthrough with full audit trail. Zero enforcement. |
Start with OBSERVE to see what your agent does, then move to MONITOR, then ENFORCE.
How it works
Agent decides action → OpenHands PreToolUse hook fires
→ tealtiger-openhands-hook reads event from stdin (JSON)
→ Evaluates against governance.yml policy (<2ms)
→ Outputs decision JSON to stdout
→ Exit 0 (allow) or Exit 2 (block)
→ If blocked: agent gets denial reason, adapts behavior
→ If allowed: tool executes normally
→ Audit record appended to .openhands/.tealtiger-state/audit.jsonl
Programmatic Usage (SDK)
You can also use tealtiger-openhands as a Python library alongside the OpenHands SDK:
from openhands.sdk import LLM, Conversation
from openhands.sdk.hooks import HookConfig, HookDefinition, HookMatcher
from openhands.tools.preset.default import get_default_agent
# Register TealTiger as a PreToolUse hook
hook_config = HookConfig(
pre_tool_use=[
HookMatcher(
matcher="*",
hooks=[
HookDefinition(
command="tealtiger-openhands-hook --config .openhands/governance.yml",
timeout=5,
)
],
)
],
)
agent = get_default_agent(llm=llm)
conversation = Conversation(
agent=agent,
workspace="/path/to/repo",
hook_config=hook_config,
)
Decision Contract
Every evaluation produces a structured decision:
{
"correlation_id": "uuid-v4",
"timestamp_ms": 1719849600000,
"action": "DENY",
"reason": "Command denied: matches denylist pattern 'rm -rf'",
"reason_codes": ["COMMAND_DENIED"],
"risk_score": 0.9,
"policy_version": "1",
"findings": [],
"evaluation_time_ms": 0.8,
"tool_name": "terminal",
"session_id": "abc-123"
}
Audit Trail
All decisions are logged to .openhands/.tealtiger-state/audit.jsonl (one JSON record per line). Use this for SOC2/HIPAA compliance evidence.
Configuration Reference
file_policy
| Field | Type | Description |
|---|---|---|
read_allowlist |
string[] |
Glob patterns for allowed reads. Empty = allow all. |
read_denylist |
string[] |
Glob patterns for blocked reads. Checked first. |
write_allowlist |
string[] |
Glob patterns for allowed writes. Empty = allow all. |
write_denylist |
string[] |
Glob patterns for blocked writes. Checked first. |
command_policy
| Field | Type | Description |
|---|---|---|
allowlist |
string[] |
Base commands allowed to execute. Empty = allow all. |
denylist |
string[] |
Substring patterns to block. Checked first. |
block_network_access |
bool |
Block curl, wget, ssh, and URL patterns. |
secret_scan
| Field | Type | Description |
|---|---|---|
enabled |
bool |
Enable/disable scanning. |
action |
string |
"block" or "warn". |
categories |
string[] |
Which secret types to detect. |
budget
| Field | Type | Description |
|---|---|---|
per_session_usd |
float |
Max cost per session. |
max_iterations |
int |
Max tool calls per session. 0 = unlimited. |
Related
- TealTiger — Core governance SDK
- OpenHands Hooks Docs — Hook system reference
- GitHub Issue #4273 — Feature proposal
License
Apache 2.0
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 tealtiger_openhands-0.1.0.tar.gz.
File metadata
- Download URL: tealtiger_openhands-0.1.0.tar.gz
- Upload date:
- Size: 21.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
215498902d464f9401c8549266675f3fb8a86f0d0b618c3d44e66c5de2f2c145
|
|
| MD5 |
ae9d1f9ed68222c614fd96f7f9afbbd7
|
|
| BLAKE2b-256 |
ec2cb6c6a5ffe41b09345f043ab68913ddc7317b1076636809470937515d3fbf
|
File details
Details for the file tealtiger_openhands-0.1.0-py3-none-any.whl.
File metadata
- Download URL: tealtiger_openhands-0.1.0-py3-none-any.whl
- Upload date:
- Size: 18.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
86e228fb5d73e512f9f65da34e1f7ba704c02c50d65875ff75cc3aaae4f54e87
|
|
| MD5 |
5f2bb2df88cc732cc0699f017a1622b1
|
|
| BLAKE2b-256 |
f6141c314a744a990c5316e277f32eb708de8050d09c0e9d34838ec62286f513
|