hydracuda 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hydracuda/__init__.py +9 -0
- hydracuda/cli.py +87 -0
- hydracuda/engine.py +73 -0
- hydracuda/policy.py +84 -0
- hydracuda/proxy.py +58 -0
- hydracuda-0.1.0.dist-info/METADATA +246 -0
- hydracuda-0.1.0.dist-info/RECORD +10 -0
- hydracuda-0.1.0.dist-info/WHEEL +4 -0
- hydracuda-0.1.0.dist-info/entry_points.txt +2 -0
- hydracuda-0.1.0.dist-info/licenses/LICENSE +1 -0
hydracuda/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""HYDRACUDA - Runtime policy enforcement for AI tool calls."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
from hydracuda.engine import PolicyEngine
|
|
6
|
+
from hydracuda.policy import load_policy
|
|
7
|
+
from hydracuda.proxy import ToolCallProxy
|
|
8
|
+
|
|
9
|
+
__all__ = ["PolicyEngine", "load_policy", "ToolCallProxy"]
|
hydracuda/cli.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""CLI interface for HYDRACUDA."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from hydracuda.policy import load_policy
|
|
8
|
+
|
|
9
|
+
STARTER_POLICY = """\
|
|
10
|
+
version: 1
|
|
11
|
+
mode: enforce
|
|
12
|
+
audit_path: .hydracuda/audit.db
|
|
13
|
+
|
|
14
|
+
tools:
|
|
15
|
+
read_file:
|
|
16
|
+
allow: true
|
|
17
|
+
parameter_rules:
|
|
18
|
+
path:
|
|
19
|
+
deny_patterns:
|
|
20
|
+
- "/etc/"
|
|
21
|
+
- "\\\\.\\\\."
|
|
22
|
+
- "/root/"
|
|
23
|
+
|
|
24
|
+
delete_record:
|
|
25
|
+
allow: false
|
|
26
|
+
reason: "Destructive operation. Blocked by default."
|
|
27
|
+
|
|
28
|
+
execute_shell:
|
|
29
|
+
allow: "review"
|
|
30
|
+
rate_limit: "3/minute"
|
|
31
|
+
|
|
32
|
+
audit:
|
|
33
|
+
path: .hydracuda/audit.db
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def cmd_init(args: argparse.Namespace) -> None:
|
|
38
|
+
"""Write a starter hydracuda.yaml to the current directory."""
|
|
39
|
+
target = Path("hydracuda.yaml")
|
|
40
|
+
if target.exists():
|
|
41
|
+
print(f"{target} already exists. Not overwriting.")
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
target.write_text(STARTER_POLICY)
|
|
45
|
+
print(f"Created {target} with starter policy. Edit it to match your tools.")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def cmd_check(args: argparse.Namespace) -> None:
|
|
49
|
+
"""Validate a policy file and print a summary."""
|
|
50
|
+
try:
|
|
51
|
+
policy = load_policy(args.policy_file)
|
|
52
|
+
except ValueError as e:
|
|
53
|
+
print(f"Validation failed: {e}")
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
|
|
56
|
+
tool_count = len(policy.tools)
|
|
57
|
+
print(f"Policy valid. Mode: {policy.mode}, Tools configured: {tool_count}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def main() -> None:
|
|
61
|
+
"""Entry point for the hydracuda CLI."""
|
|
62
|
+
parser = argparse.ArgumentParser(
|
|
63
|
+
prog="hydracuda",
|
|
64
|
+
description="HYDRACUDA - Runtime policy enforcement for AI tool calls.",
|
|
65
|
+
)
|
|
66
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
67
|
+
|
|
68
|
+
subparsers.add_parser("init", help="Create a starter hydracuda.yaml in the current directory")
|
|
69
|
+
|
|
70
|
+
check_parser = subparsers.add_parser("check", help="Validate a policy file")
|
|
71
|
+
check_parser.add_argument("policy_file", help="Path to the policy YAML file")
|
|
72
|
+
|
|
73
|
+
args = parser.parse_args()
|
|
74
|
+
|
|
75
|
+
if args.command is None:
|
|
76
|
+
parser.print_usage()
|
|
77
|
+
sys.exit(0)
|
|
78
|
+
|
|
79
|
+
commands = {
|
|
80
|
+
"init": cmd_init,
|
|
81
|
+
"check": cmd_check,
|
|
82
|
+
}
|
|
83
|
+
commands[args.command](args)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
main()
|
hydracuda/engine.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Policy evaluation core for HYDRACUDA."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from hydracuda.policy import Policy
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Decision:
|
|
11
|
+
"""Result of evaluating a tool call against a policy."""
|
|
12
|
+
|
|
13
|
+
action: str
|
|
14
|
+
reason: str
|
|
15
|
+
tool: str
|
|
16
|
+
params: dict
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PolicyEngine:
|
|
20
|
+
"""Evaluates tool calls against a loaded policy."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, policy: Policy):
|
|
23
|
+
self.policy = policy
|
|
24
|
+
|
|
25
|
+
def evaluate(self, tool_name: str, params: dict) -> Decision:
|
|
26
|
+
"""Evaluate a tool call and return an allow/deny/review decision."""
|
|
27
|
+
if tool_name not in self.policy.tools:
|
|
28
|
+
return Decision(
|
|
29
|
+
action="deny",
|
|
30
|
+
reason=f"{tool_name}: not listed in policy — default deny",
|
|
31
|
+
tool=tool_name,
|
|
32
|
+
params=params,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
tool_policy = self.policy.tools[tool_name]
|
|
36
|
+
|
|
37
|
+
if tool_policy.allow is False:
|
|
38
|
+
return Decision(
|
|
39
|
+
action="deny",
|
|
40
|
+
reason="tool blocked by policy",
|
|
41
|
+
tool=tool_name,
|
|
42
|
+
params=params,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if tool_policy.allow == "review":
|
|
46
|
+
return Decision(
|
|
47
|
+
action="review",
|
|
48
|
+
reason="tool requires human review",
|
|
49
|
+
tool=tool_name,
|
|
50
|
+
params=params,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if tool_policy.parameter_rules:
|
|
54
|
+
for param_name, rules in tool_policy.parameter_rules.items():
|
|
55
|
+
if param_name not in params:
|
|
56
|
+
continue
|
|
57
|
+
deny_patterns = rules.get("deny_patterns", [])
|
|
58
|
+
param_value = str(params[param_name])
|
|
59
|
+
for pattern in deny_patterns:
|
|
60
|
+
if re.search(pattern, param_value):
|
|
61
|
+
return Decision(
|
|
62
|
+
action="deny",
|
|
63
|
+
reason=f"{tool_name}: parameter '{param_name}' matched deny pattern '{pattern}'",
|
|
64
|
+
tool=tool_name,
|
|
65
|
+
params=params,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return Decision(
|
|
69
|
+
action="allow",
|
|
70
|
+
reason="all policy checks passed",
|
|
71
|
+
tool=tool_name,
|
|
72
|
+
params=params,
|
|
73
|
+
)
|
hydracuda/policy.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""YAML policy parser and validator for HYDRACUDA."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Union
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class ToolPolicy:
|
|
12
|
+
"""Policy configuration for a single tool."""
|
|
13
|
+
|
|
14
|
+
allow: Union[bool, str] = True
|
|
15
|
+
rate_limit: str | None = None
|
|
16
|
+
parameter_rules: dict[str, dict] | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class Policy:
|
|
21
|
+
"""Top-level policy configuration parsed from hydracuda.yaml."""
|
|
22
|
+
|
|
23
|
+
version: int
|
|
24
|
+
tools: dict[str, ToolPolicy]
|
|
25
|
+
mode: str = "enforce"
|
|
26
|
+
audit_path: str = ".hydracuda/audit.db"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
VALID_MODES = {"enforce", "shadow", "review"}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_policy(path: str) -> Policy:
|
|
33
|
+
"""Load and validate a hydracuda.yaml policy file.
|
|
34
|
+
|
|
35
|
+
Raises ValueError with a descriptive message on invalid input.
|
|
36
|
+
"""
|
|
37
|
+
policy_path = Path(path)
|
|
38
|
+
if not policy_path.exists():
|
|
39
|
+
raise ValueError(f"Policy file not found: {path}")
|
|
40
|
+
|
|
41
|
+
with open(policy_path) as f:
|
|
42
|
+
raw = yaml.safe_load(f)
|
|
43
|
+
|
|
44
|
+
if not isinstance(raw, dict):
|
|
45
|
+
raise ValueError(f"Policy file must be a YAML mapping, got {type(raw).__name__}")
|
|
46
|
+
|
|
47
|
+
if "version" not in raw:
|
|
48
|
+
raise ValueError("Policy file missing required key: 'version'")
|
|
49
|
+
|
|
50
|
+
version = raw["version"]
|
|
51
|
+
if not isinstance(version, int):
|
|
52
|
+
raise ValueError(f"'version' must be an integer, got {type(version).__name__}")
|
|
53
|
+
|
|
54
|
+
mode = raw.get("mode", "enforce")
|
|
55
|
+
if mode not in VALID_MODES:
|
|
56
|
+
raise ValueError(f"'mode' must be one of {sorted(VALID_MODES)}, got '{mode}'")
|
|
57
|
+
|
|
58
|
+
if "tools" not in raw:
|
|
59
|
+
raise ValueError("Policy file missing required key: 'tools'")
|
|
60
|
+
|
|
61
|
+
raw_tools = raw["tools"]
|
|
62
|
+
if not isinstance(raw_tools, dict):
|
|
63
|
+
raise ValueError("'tools' must be a mapping of tool names to configurations")
|
|
64
|
+
|
|
65
|
+
tools: dict[str, ToolPolicy] = {}
|
|
66
|
+
for tool_name, tool_conf in raw_tools.items():
|
|
67
|
+
if not isinstance(tool_conf, dict):
|
|
68
|
+
raise ValueError(f"Tool '{tool_name}' configuration must be a mapping")
|
|
69
|
+
|
|
70
|
+
allow = tool_conf.get("allow", True)
|
|
71
|
+
if allow not in (True, False, "review"):
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"Tool '{tool_name}': 'allow' must be true, false, or 'review', got '{allow}'"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
tools[tool_name] = ToolPolicy(
|
|
77
|
+
allow=allow,
|
|
78
|
+
rate_limit=tool_conf.get("rate_limit"),
|
|
79
|
+
parameter_rules=tool_conf.get("parameter_rules"),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
audit_path = raw.get("audit_path", ".hydracuda/audit.db")
|
|
83
|
+
|
|
84
|
+
return Policy(version=version, mode=mode, tools=tools, audit_path=audit_path)
|
hydracuda/proxy.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Tool call interception layer for HYDRACUDA."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import aiosqlite
|
|
8
|
+
|
|
9
|
+
from hydracuda.engine import PolicyEngine
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ToolCallProxy:
|
|
13
|
+
"""Intercepts tool calls, enforces policy, and writes an audit log."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, engine: PolicyEngine, audit_path: str | None = None):
|
|
16
|
+
self.engine = engine
|
|
17
|
+
self.audit_path = audit_path or engine.policy.audit_path
|
|
18
|
+
|
|
19
|
+
async def call(self, tool_name: str, params: dict, handler) -> dict:
|
|
20
|
+
"""Evaluate, log, and optionally execute a tool call."""
|
|
21
|
+
decision = self.engine.evaluate(tool_name, params)
|
|
22
|
+
await self._write_audit(decision)
|
|
23
|
+
|
|
24
|
+
if decision.action == "deny":
|
|
25
|
+
raise PermissionError(decision.reason)
|
|
26
|
+
|
|
27
|
+
if decision.action == "review":
|
|
28
|
+
raise NotImplementedError(f"tool call queued for human review: {tool_name}")
|
|
29
|
+
|
|
30
|
+
return await handler(tool_name, params)
|
|
31
|
+
|
|
32
|
+
async def _write_audit(self, decision) -> None:
|
|
33
|
+
"""Append a decision to the SQLite audit log."""
|
|
34
|
+
db_path = Path(self.audit_path)
|
|
35
|
+
db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
36
|
+
|
|
37
|
+
async with aiosqlite.connect(str(db_path)) as db:
|
|
38
|
+
await db.execute(
|
|
39
|
+
"""CREATE TABLE IF NOT EXISTS audit_log (
|
|
40
|
+
id INTEGER PRIMARY KEY,
|
|
41
|
+
timestamp TEXT,
|
|
42
|
+
tool TEXT,
|
|
43
|
+
action TEXT,
|
|
44
|
+
reason TEXT,
|
|
45
|
+
params TEXT
|
|
46
|
+
)"""
|
|
47
|
+
)
|
|
48
|
+
await db.execute(
|
|
49
|
+
"INSERT INTO audit_log (timestamp, tool, action, reason, params) VALUES (?, ?, ?, ?, ?)",
|
|
50
|
+
(
|
|
51
|
+
datetime.now(timezone.utc).isoformat(),
|
|
52
|
+
decision.tool,
|
|
53
|
+
decision.action,
|
|
54
|
+
decision.reason,
|
|
55
|
+
json.dumps(decision.params),
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
await db.commit()
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hydracuda
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Hybrid Runtime Access Control for Untrusted Delegated Actions. Runtime policy enforcement proxy for AI tool calls.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Xtrinel-Group/HYDRACUDA
|
|
6
|
+
Project-URL: Issues, https://github.com/Xtrinel-Group/HYDRACUDA/issues
|
|
7
|
+
License: 404: Not Found
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: ai,guardrails,llm,mcp,policy,security
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Security
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Requires-Dist: aiosqlite>=0.19
|
|
16
|
+
Requires-Dist: pyyaml>=6.0
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="https://assets.xtrinel.com/hydracuda-full.svg" alt="HYDRACUDA" width="480" />
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
# HYDRACUDA
|
|
24
|
+
|
|
25
|
+
Hybrid Runtime Access Control for Untrusted Delegated Actions.
|
|
26
|
+
|
|
27
|
+
HYDRACUDA is a lightweight, open source policy enforcement layer for AI tool calls.
|
|
28
|
+
Define a YAML policy file, drop HYDRACUDA in front of any MCP server or LLM tool layer, and every call is either allowed, denied, or held for review before it executes.
|
|
29
|
+
|
|
30
|
+
The goal is simple: prevent tool-call abuse and out-of-scope actions while keeping everything local, auditable, and easy to reason about.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- **Language-agnostic policy file**
|
|
37
|
+
Human-readable `hydracuda.yaml` drives all decisions. Version-controlled, reviewable, and independent of any specific model or framework.
|
|
38
|
+
|
|
39
|
+
- **Pre-dispatch enforcement**
|
|
40
|
+
Tool calls are intercepted before they execute, not after. The model cannot bypass the decision by reprompting.
|
|
41
|
+
|
|
42
|
+
- **Three decisions: allow, deny, review**
|
|
43
|
+
- `allow` forwards the call to your handler
|
|
44
|
+
- `deny` blocks the call with a clear reason
|
|
45
|
+
- `review` is reserved for high-risk actions that will require human approval in a later version
|
|
46
|
+
|
|
47
|
+
- **Local audit logging**
|
|
48
|
+
Every decision is written to a SQLite audit log on disk. No telemetry, no external service, no cloud dependency.
|
|
49
|
+
|
|
50
|
+
- **Two primary use cases**
|
|
51
|
+
- **Production guardrail** for AI-integrated applications
|
|
52
|
+
- **Engagement scope enforcement** for red teams using AI assistants during assessments
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
HYDRACUDA targets Python 3.10 and above.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install hydracuda
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
To work on the project locally:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
git clone https://github.com/Xtrinel-Group/HYDRACUDA.git
|
|
68
|
+
cd HYDRACUDA
|
|
69
|
+
pip install -e .
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Quick Start
|
|
75
|
+
|
|
76
|
+
From a new or existing project directory:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
hydracuda init
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This writes a starter `hydracuda.yaml` with commented examples.
|
|
83
|
+
|
|
84
|
+
Validate the policy:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
hydracuda check hydracuda.yaml
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
If validation passes, you can integrate HYDRACUDA into your tool-calling layer.
|
|
91
|
+
|
|
92
|
+
### Minimal integration example
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import asyncio
|
|
96
|
+
from hydracuda.policy import load_policy
|
|
97
|
+
from hydracuda.engine import PolicyEngine
|
|
98
|
+
from hydracuda.proxy import ToolCallProxy
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
async def handle_tool_call(tool_name: str, params: dict) -> dict:
|
|
102
|
+
# Your existing tool dispatch logic goes here.
|
|
103
|
+
# For example, calling into an MCP server or a local command.
|
|
104
|
+
return {"status": "ok", "tool": tool_name, "params": params}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
async def main() -> None:
|
|
108
|
+
policy = load_policy("hydracuda.yaml")
|
|
109
|
+
engine = PolicyEngine(policy)
|
|
110
|
+
proxy = ToolCallProxy(engine, audit_path=policy.audit_path)
|
|
111
|
+
|
|
112
|
+
# This is what your LLM agent would have requested.
|
|
113
|
+
tool_name = "read_file"
|
|
114
|
+
params = {"path": "/tmp/example.txt"}
|
|
115
|
+
|
|
116
|
+
result = await proxy.call(tool_name, params, handler=handle_tool_call)
|
|
117
|
+
print(result)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
if __name__ == "__main__":
|
|
121
|
+
asyncio.run(main())
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
In your real application, the LLM agent calls `proxy.call(...)` instead of invoking tools directly.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Policy File
|
|
129
|
+
|
|
130
|
+
HYDRACUDA policies are defined in YAML. The file created by `hydracuda init` looks similar to this:
|
|
131
|
+
|
|
132
|
+
```yaml
|
|
133
|
+
version: 1
|
|
134
|
+
mode: enforce # enforce | shadow | review
|
|
135
|
+
|
|
136
|
+
tools:
|
|
137
|
+
read_file:
|
|
138
|
+
allow: true
|
|
139
|
+
parameterRules:
|
|
140
|
+
path:
|
|
141
|
+
denyPatterns:
|
|
142
|
+
- "\\.\\." # block path traversal
|
|
143
|
+
- "/etc/"
|
|
144
|
+
- "/root/"
|
|
145
|
+
|
|
146
|
+
delete_record:
|
|
147
|
+
allow: false
|
|
148
|
+
reason: "Destructive operation. Blocked by default."
|
|
149
|
+
|
|
150
|
+
execute_shell:
|
|
151
|
+
allow: review
|
|
152
|
+
rateLimit: "3/minute"
|
|
153
|
+
|
|
154
|
+
audit:
|
|
155
|
+
path: .hydracuda/audit.db
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Key concepts:
|
|
159
|
+
|
|
160
|
+
- **mode**
|
|
161
|
+
- `enforce`: violations are blocked
|
|
162
|
+
- `shadow`: violations are logged but not blocked
|
|
163
|
+
- `review`: intended for future human-approval workflows
|
|
164
|
+
|
|
165
|
+
- **tools**
|
|
166
|
+
Each tool has an `allow` value:
|
|
167
|
+
- `true` → allowed (subject to parameter rules)
|
|
168
|
+
- `false` → always denied
|
|
169
|
+
- `"review"` → queued for human review in a future release
|
|
170
|
+
|
|
171
|
+
- **parameterRules**
|
|
172
|
+
`denyPatterns` are Python regular expressions evaluated against the string value of the parameter.
|
|
173
|
+
|
|
174
|
+
- **audit.path**
|
|
175
|
+
Path to the SQLite audit database. The parent directory is created automatically if it does not exist.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Audit Log
|
|
180
|
+
|
|
181
|
+
HYDRACUDA writes one row per tool call decision to a local SQLite database.
|
|
182
|
+
|
|
183
|
+
Table schema:
|
|
184
|
+
|
|
185
|
+
- `id` (integer primary key)
|
|
186
|
+
- `timestamp` (ISO 8601 string)
|
|
187
|
+
- `tool` (tool name)
|
|
188
|
+
- `action` (`allow`, `deny`, `review`)
|
|
189
|
+
- `reason` (short explanation)
|
|
190
|
+
- `params` (JSON-encoded parameters)
|
|
191
|
+
|
|
192
|
+
This makes it easy to:
|
|
193
|
+
|
|
194
|
+
- Review which tools are actually used in production
|
|
195
|
+
- See which policies are firing most often
|
|
196
|
+
- Build dashboards or alerts on top of the audit data
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Relationship to VAAST
|
|
201
|
+
|
|
202
|
+
HYDRACUDA is maintained by Xtrinel, the team behind **VAAST**, an AI security scanner focused on AI attack surfaces and MCP tool-call abuse.
|
|
203
|
+
|
|
204
|
+
- VAAST is **offensive**: it discovers tool-call abuse and prompt injection vulnerabilities in AI-integrated applications before they reach production.
|
|
205
|
+
- HYDRACUDA is **defensive**: it enforces the policies that prevent those same vulnerabilities from being exploited at runtime.
|
|
206
|
+
|
|
207
|
+
They are fully decoupled. HYDRACUDA does not require VAAST, but future versions will support importing VAAST findings to auto-generate policy templates.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Documentation
|
|
212
|
+
|
|
213
|
+
For full documentation, examples, and integration guides:
|
|
214
|
+
|
|
215
|
+
- https://docs.xtrinel.com/hydracuda
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Logo and Branding
|
|
220
|
+
|
|
221
|
+
HYDRACUDA assets are available under the Xtrinel brand guidelines:
|
|
222
|
+
|
|
223
|
+
- Full wordmark: `https://assets.xtrinel.com/hydracuda-full.svg`
|
|
224
|
+
- Icon: `https://assets.xtrinel.com/hydracuda.svg`
|
|
225
|
+
|
|
226
|
+
You can use these in dashboards, internal docs, or integrations that surface HYDRACUDA decisions.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Contributing
|
|
231
|
+
|
|
232
|
+
Contributions are welcome.
|
|
233
|
+
|
|
234
|
+
1. Fork the repository
|
|
235
|
+
2. Create a feature branch
|
|
236
|
+
3. Add tests for any new behavior
|
|
237
|
+
4. Run `pytest`
|
|
238
|
+
5. Open a pull request with a clear description of the change
|
|
239
|
+
|
|
240
|
+
Please keep new features focused and security-oriented. If you are proposing a change to the policy format, open an issue first for discussion.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
HYDRACUDA is released under the MIT License. See `LICENSE` for details.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
hydracuda/__init__.py,sha256=J25xyJeQKGGI6VzEAjOcVlVgWTwmDZgXRIHRH1JGH0M,273
|
|
2
|
+
hydracuda/cli.py,sha256=H660eMtePHeQUAWTqKUfySjXra8tq08Fevz5cPIC3dY,2103
|
|
3
|
+
hydracuda/engine.py,sha256=8dPDoUS4R1_bJ39gRcONfwuJtdtZbgbPTynpO9khTEE,2228
|
|
4
|
+
hydracuda/policy.py,sha256=lS_QELw_ixIu8OHPcw4a8-cR8H4j9_NnpCE7cZXAHQY,2565
|
|
5
|
+
hydracuda/proxy.py,sha256=7GJZJwEyHMH5JJ6J0bgFLWaqobgsIWMAp85LOLBoYJ8,2002
|
|
6
|
+
hydracuda-0.1.0.dist-info/METADATA,sha256=-0o7XjBMVEryehDrXsq-rFyF0N9RyMMeUb247A_KWeM,6867
|
|
7
|
+
hydracuda-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
8
|
+
hydracuda-0.1.0.dist-info/entry_points.txt,sha256=noZB1zRElkb4Om2MqqpmgjV7IstJuoIMMRYgvWslzS0,49
|
|
9
|
+
hydracuda-0.1.0.dist-info/licenses/LICENSE,sha256=1VWM1BnI1GvclYBky5f5Y9HqeThmQUwCWQbsFQM1Eu0,14
|
|
10
|
+
hydracuda-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
404: Not Found
|