agsec 0.1.0__tar.gz

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.
agsec-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,230 @@
1
+ Metadata-Version: 2.4
2
+ Name: agsec
3
+ Version: 0.1.0
4
+ Summary: AI Agent Action Firewall core SDK
5
+ Home-page: https://github.com/yourusername/agsec
6
+ Author: Riyandhiman
7
+ Author-email: Riyandhiman <noreply@example.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/yourusername/agsec
10
+ Project-URL: Repository, https://github.com/yourusername/agsec
11
+ Project-URL: Documentation, https://github.com/yourusername/agsec#readme
12
+ Keywords: agent,security,policy,sandbox
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: PyYAML>=6.0
19
+ Dynamic: author
20
+ Dynamic: home-page
21
+ Dynamic: requires-python
22
+
23
+ # agsec
24
+
25
+ [![PyPI version](https://badge.fury.io/py/agsec.svg)](https://pypi.org/project/agsec/)
26
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
27
+
28
+ AI Agent Action Firewall - A minimal, control layer for agent actions.
29
+
30
+ ## Overview
31
+
32
+ `agsec` provides a simple yet powerful way to add safety controls to AI agents. It acts as a "firewall" between agents and real-world actions, allowing you to define policies that approve, block, or review actions before execution.
33
+
34
+ ### Why agsec?
35
+
36
+ - **Agent-neutral**: Works with any agent framework (LangChain, custom, etc.)
37
+ - **Declarative policies**: Define rules in YAML or code
38
+ - **Extensible**: Plugin system for custom actions and policies
39
+ - **Production-ready**: Lightweight, fast, and secure
40
+
41
+ ## Features
42
+
43
+ - ✅ **Action Registry**: Register and manage agent actions
44
+ - ✅ **Policy Engine**: Flexible rule-based decision making
45
+ - ✅ **YAML Policies**: Human-readable policy definitions
46
+ - ✅ **Context Awareness**: Rules can access parameters and context
47
+ - ✅ **Priority & Matching**: Advanced rule evaluation (priority, all/any matching)
48
+ - ✅ **Audit Logging**: Built-in logging for all decisions
49
+ - ✅ **Python Package**: Easy installation via PyPI
50
+
51
+ ## Installation
52
+
53
+ ### Runtime (for users)
54
+
55
+ ```bash
56
+ pip install agsec
57
+ ```
58
+
59
+ ### Development (for contributors)
60
+
61
+ ```bash
62
+ git clone https://github.com/yourusername/agsec.git
63
+ cd agsec
64
+ pip install -e .[dev]
65
+ pre-commit install
66
+ ```
67
+
68
+ ## Quick Start
69
+
70
+ ### Basic Usage
71
+
72
+ ```python
73
+ from agsec import ControlLayer
74
+
75
+ # Create control layer
76
+ control = ControlLayer()
77
+
78
+ # Register an action
79
+ @control.register_action("send_email")
80
+ def send_email(to, subject, body):
81
+ return {"sent_to": to, "status": "success"}
82
+
83
+ # Execute with default allow policy
84
+ result = control.execute("send_email", {"to": "user@example.com", "subject": "Hello", "body": "Hi!"})
85
+ print(result.result) # {"sent_to": "user@example.com", "status": "success"}
86
+ ```
87
+
88
+ ### With YAML Policies
89
+
90
+ ```python
91
+ from agsec import ControlLayer
92
+
93
+ policy_yaml = """
94
+ rules:
95
+ - action: payment
96
+ status: block
97
+ reason: "High-value payment blocked"
98
+ conditions:
99
+ amount:
100
+ op: ">"
101
+ value: 10000
102
+ """
103
+
104
+ control = ControlLayer(policy_yaml=policy_yaml)
105
+
106
+ @control.register_action("payment")
107
+ def payment(amount):
108
+ return {"charged": amount}
109
+
110
+ try:
111
+ control.execute("payment", {"amount": 15000})
112
+ except Exception as e:
113
+ print(e) # PolicyViolationError: High-value payment blocked
114
+ ```
115
+
116
+ ## API Reference
117
+
118
+ ### ControlLayer
119
+
120
+ Main class for managing agent actions and policies.
121
+
122
+ ```python
123
+ ControlLayer(
124
+ policy_engine=None, # PolicyEngine instance
125
+ action_registry=None, # ActionRegistry instance
126
+ logger=None, # Custom logger
127
+ policy_yaml=None, # YAML policy string
128
+ policy_yaml_path=None # Path to YAML policy file
129
+ )
130
+ ```
131
+
132
+ #### Methods
133
+
134
+ - `register_action(name)`: Decorator to register an action function
135
+ - `execute(action, params, context=None)`: Execute an action with policy check
136
+
137
+ ### PolicyEngine
138
+
139
+ Handles policy evaluation.
140
+
141
+ #### Methods
142
+
143
+ - `add_rule(rule)`: Add a programmatic rule function
144
+ - `load_rules_from_yaml(yaml_text)`: Load rules from YAML string
145
+ - `load_rules_from_yaml_file(path)`: Load rules from YAML file
146
+ - `evaluate(action, params, context=None)`: Evaluate policy for action
147
+
148
+ ### Policy Status
149
+
150
+ - `PolicyStatus.ALLOW`: Allow action execution
151
+ - `PolicyStatus.BLOCK`: Block action execution
152
+ - `PolicyStatus.REVIEW`: Mark for manual review
153
+
154
+ ### YAML Policy Schema
155
+
156
+ ```yaml
157
+ rules:
158
+ - action: "action_name" # Action to match (* for all)
159
+ status: "allow|block|review" # Decision
160
+ reason: "Optional reason" # Human-readable explanation
161
+ priority: 0 # Higher = evaluated first
162
+ match: "all|any" # Condition matching mode
163
+ conditions: # Parameter/context checks
164
+ param_name:
165
+ op: "==|!=|>|<|>=|<=|in|not_in"
166
+ value: "expected_value"
167
+ context.user_role:
168
+ op: "=="
169
+ value: "admin"
170
+ ```
171
+
172
+ ## Development
173
+
174
+ ### Setup
175
+
176
+ ```bash
177
+ pip install -e .[dev]
178
+ pre-commit install
179
+ ```
180
+
181
+ ### Testing
182
+
183
+ ```bash
184
+ pytest
185
+ ```
186
+
187
+ ### Building
188
+
189
+ ```bash
190
+ python -m build
191
+ ```
192
+
193
+ ### Releasing
194
+
195
+ ```bash
196
+ ./release.sh # Requires PYPI_API_TOKEN env var
197
+ ```
198
+
199
+ ## Contributing
200
+
201
+ 1. Fork the repository
202
+ 2. Create a feature branch
203
+ 3. Make your changes
204
+ 4. Add tests
205
+ 5. Run `pre-commit run --all-files`
206
+ 6. Submit a pull request
207
+
208
+ ### Code Style
209
+
210
+ - Black for formatting
211
+ - isort for import sorting
212
+ - flake8 for linting
213
+ - pytest for testing
214
+
215
+ ## License
216
+
217
+ MIT License - see [LICENSE](LICENSE) file for details.
218
+
219
+ ## Roadmap
220
+
221
+ - [ ] Web dashboard for policy management
222
+ - [ ] Advanced risk scoring
223
+ - [ ] Multi-agent coordination
224
+ - [ ] Enterprise integrations
225
+
226
+ ## Support
227
+
228
+ - Issues: [GitHub Issues](https://github.com/riyandhiman14/agsec/issues)
229
+ - Discussions: [GitHub Discussions](https://github.com/riyandhiman14/agsec/discussions)
230
+
agsec-0.1.0/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # agsec
2
+
3
+ [![PyPI version](https://badge.fury.io/py/agsec.svg)](https://pypi.org/project/agsec/)
4
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
5
+
6
+ AI Agent Action Firewall - A minimal, control layer for agent actions.
7
+
8
+ ## Overview
9
+
10
+ `agsec` provides a simple yet powerful way to add safety controls to AI agents. It acts as a "firewall" between agents and real-world actions, allowing you to define policies that approve, block, or review actions before execution.
11
+
12
+ ### Why agsec?
13
+
14
+ - **Agent-neutral**: Works with any agent framework (LangChain, custom, etc.)
15
+ - **Declarative policies**: Define rules in YAML or code
16
+ - **Extensible**: Plugin system for custom actions and policies
17
+ - **Production-ready**: Lightweight, fast, and secure
18
+
19
+ ## Features
20
+
21
+ - ✅ **Action Registry**: Register and manage agent actions
22
+ - ✅ **Policy Engine**: Flexible rule-based decision making
23
+ - ✅ **YAML Policies**: Human-readable policy definitions
24
+ - ✅ **Context Awareness**: Rules can access parameters and context
25
+ - ✅ **Priority & Matching**: Advanced rule evaluation (priority, all/any matching)
26
+ - ✅ **Audit Logging**: Built-in logging for all decisions
27
+ - ✅ **Python Package**: Easy installation via PyPI
28
+
29
+ ## Installation
30
+
31
+ ### Runtime (for users)
32
+
33
+ ```bash
34
+ pip install agsec
35
+ ```
36
+
37
+ ### Development (for contributors)
38
+
39
+ ```bash
40
+ git clone https://github.com/yourusername/agsec.git
41
+ cd agsec
42
+ pip install -e .[dev]
43
+ pre-commit install
44
+ ```
45
+
46
+ ## Quick Start
47
+
48
+ ### Basic Usage
49
+
50
+ ```python
51
+ from agsec import ControlLayer
52
+
53
+ # Create control layer
54
+ control = ControlLayer()
55
+
56
+ # Register an action
57
+ @control.register_action("send_email")
58
+ def send_email(to, subject, body):
59
+ return {"sent_to": to, "status": "success"}
60
+
61
+ # Execute with default allow policy
62
+ result = control.execute("send_email", {"to": "user@example.com", "subject": "Hello", "body": "Hi!"})
63
+ print(result.result) # {"sent_to": "user@example.com", "status": "success"}
64
+ ```
65
+
66
+ ### With YAML Policies
67
+
68
+ ```python
69
+ from agsec import ControlLayer
70
+
71
+ policy_yaml = """
72
+ rules:
73
+ - action: payment
74
+ status: block
75
+ reason: "High-value payment blocked"
76
+ conditions:
77
+ amount:
78
+ op: ">"
79
+ value: 10000
80
+ """
81
+
82
+ control = ControlLayer(policy_yaml=policy_yaml)
83
+
84
+ @control.register_action("payment")
85
+ def payment(amount):
86
+ return {"charged": amount}
87
+
88
+ try:
89
+ control.execute("payment", {"amount": 15000})
90
+ except Exception as e:
91
+ print(e) # PolicyViolationError: High-value payment blocked
92
+ ```
93
+
94
+ ## API Reference
95
+
96
+ ### ControlLayer
97
+
98
+ Main class for managing agent actions and policies.
99
+
100
+ ```python
101
+ ControlLayer(
102
+ policy_engine=None, # PolicyEngine instance
103
+ action_registry=None, # ActionRegistry instance
104
+ logger=None, # Custom logger
105
+ policy_yaml=None, # YAML policy string
106
+ policy_yaml_path=None # Path to YAML policy file
107
+ )
108
+ ```
109
+
110
+ #### Methods
111
+
112
+ - `register_action(name)`: Decorator to register an action function
113
+ - `execute(action, params, context=None)`: Execute an action with policy check
114
+
115
+ ### PolicyEngine
116
+
117
+ Handles policy evaluation.
118
+
119
+ #### Methods
120
+
121
+ - `add_rule(rule)`: Add a programmatic rule function
122
+ - `load_rules_from_yaml(yaml_text)`: Load rules from YAML string
123
+ - `load_rules_from_yaml_file(path)`: Load rules from YAML file
124
+ - `evaluate(action, params, context=None)`: Evaluate policy for action
125
+
126
+ ### Policy Status
127
+
128
+ - `PolicyStatus.ALLOW`: Allow action execution
129
+ - `PolicyStatus.BLOCK`: Block action execution
130
+ - `PolicyStatus.REVIEW`: Mark for manual review
131
+
132
+ ### YAML Policy Schema
133
+
134
+ ```yaml
135
+ rules:
136
+ - action: "action_name" # Action to match (* for all)
137
+ status: "allow|block|review" # Decision
138
+ reason: "Optional reason" # Human-readable explanation
139
+ priority: 0 # Higher = evaluated first
140
+ match: "all|any" # Condition matching mode
141
+ conditions: # Parameter/context checks
142
+ param_name:
143
+ op: "==|!=|>|<|>=|<=|in|not_in"
144
+ value: "expected_value"
145
+ context.user_role:
146
+ op: "=="
147
+ value: "admin"
148
+ ```
149
+
150
+ ## Development
151
+
152
+ ### Setup
153
+
154
+ ```bash
155
+ pip install -e .[dev]
156
+ pre-commit install
157
+ ```
158
+
159
+ ### Testing
160
+
161
+ ```bash
162
+ pytest
163
+ ```
164
+
165
+ ### Building
166
+
167
+ ```bash
168
+ python -m build
169
+ ```
170
+
171
+ ### Releasing
172
+
173
+ ```bash
174
+ ./release.sh # Requires PYPI_API_TOKEN env var
175
+ ```
176
+
177
+ ## Contributing
178
+
179
+ 1. Fork the repository
180
+ 2. Create a feature branch
181
+ 3. Make your changes
182
+ 4. Add tests
183
+ 5. Run `pre-commit run --all-files`
184
+ 6. Submit a pull request
185
+
186
+ ### Code Style
187
+
188
+ - Black for formatting
189
+ - isort for import sorting
190
+ - flake8 for linting
191
+ - pytest for testing
192
+
193
+ ## License
194
+
195
+ MIT License - see [LICENSE](LICENSE) file for details.
196
+
197
+ ## Roadmap
198
+
199
+ - [ ] Web dashboard for policy management
200
+ - [ ] Advanced risk scoring
201
+ - [ ] Multi-agent coordination
202
+ - [ ] Enterprise integrations
203
+
204
+ ## Support
205
+
206
+ - Issues: [GitHub Issues](https://github.com/riyandhiman14/agsec/issues)
207
+ - Discussions: [GitHub Discussions](https://github.com/riyandhiman14/agsec/discussions)
208
+
@@ -0,0 +1,12 @@
1
+ from .control import ControlLayer
2
+ from .policy import PolicyEngine
3
+ from .registry import ActionRegistry
4
+ from .types import PolicyResult, PolicyStatus
5
+
6
+ __all__ = [
7
+ "ControlLayer",
8
+ "PolicyEngine",
9
+ "PolicyResult",
10
+ "PolicyStatus",
11
+ "ActionRegistry",
12
+ ]
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import Any, Dict, Optional
5
+
6
+ from .exceptions import ActionNotFoundError, PolicyViolationError
7
+ from .policy import PolicyEngine
8
+ from .registry import ActionRegistry
9
+ from .types import ActionExecutionResult, PolicyResult, PolicyStatus
10
+
11
+
12
+ class ControlLayer:
13
+ def __init__(
14
+ self,
15
+ policy_engine: Optional[PolicyEngine] = None,
16
+ action_registry: Optional[ActionRegistry] = None,
17
+ logger: Optional[logging.Logger] = None,
18
+ policy_yaml: Optional[str] = None,
19
+ policy_yaml_path: Optional[str] = None,
20
+ ):
21
+ self.policy_engine = policy_engine or PolicyEngine()
22
+ if policy_yaml is not None:
23
+ self.policy_engine.load_rules_from_yaml(policy_yaml)
24
+ elif policy_yaml_path is not None:
25
+ self.policy_engine.load_rules_from_yaml_file(policy_yaml_path)
26
+
27
+ self.action_registry = action_registry or ActionRegistry()
28
+ self.logger = logger or logging.getLogger("agsec")
29
+ self.logger.setLevel(logging.DEBUG)
30
+
31
+ def register_action(self, name: str):
32
+ def decorator(func):
33
+ self.action_registry.register(name, func)
34
+ return func
35
+
36
+ return decorator
37
+
38
+ def execute(self, action: str, params: Dict[str, Any], context: Optional[Dict[str, Any]] = None) -> ActionExecutionResult:
39
+ context = context or {}
40
+
41
+ policy: PolicyResult = self.policy_engine.evaluate(action, params, context)
42
+ self.logger.info(f"policy evaluation for action=%s -> %s", action, policy)
43
+
44
+ if policy.status == PolicyStatus.BLOCK:
45
+ self.logger.warning("Blocked action: %s, reason=%s", action, policy.reason)
46
+ raise PolicyViolationError(policy.reason)
47
+
48
+ if policy.status == PolicyStatus.REVIEW:
49
+ self.logger.info("Action requires manual review: %s, reason=%s", action, policy.reason)
50
+ return ActionExecutionResult(action=action, params=params, result=None, policy=policy)
51
+
52
+ if policy.status != PolicyStatus.ALLOW:
53
+ raise PolicyViolationError(f"Unexpected policy status: {policy.status}")
54
+
55
+ try:
56
+ act = self.action_registry.get(action)
57
+ except ActionNotFoundError as exc:
58
+ self.logger.error("Action not found: %s", action)
59
+ raise
60
+
61
+ result = act(**params)
62
+ exec_result = ActionExecutionResult(action=action, params=params, result=result, policy=policy)
63
+
64
+ self.logger.info("Executed action: %s, result=%s", action, result)
65
+ return exec_result
@@ -0,0 +1,6 @@
1
+ class ActionNotFoundError(Exception):
2
+ pass
3
+
4
+
5
+ class PolicyViolationError(Exception):
6
+ pass
@@ -0,0 +1,144 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from typing import Any, Callable, Dict, List, Optional
5
+
6
+ import yaml
7
+
8
+ from .exceptions import PolicyViolationError
9
+ from .types import PolicyResult, PolicyStatus
10
+
11
+ PolicyRule = Callable[[str, Dict[str, Any], Optional[Dict[str, Any]]], Optional[PolicyResult]]
12
+
13
+
14
+ def _evaluate_condition(value: Any, condition: Any) -> bool:
15
+ if isinstance(condition, dict):
16
+ op = condition.get("op")
17
+ expected = condition.get("value")
18
+
19
+ if op is None or expected is None:
20
+ raise ValueError("Condition must have 'op' and 'value'")
21
+
22
+ if op == "==":
23
+ return value == expected
24
+ if op == "!=":
25
+ return value != expected
26
+ if op == ">":
27
+ return value > expected
28
+ if op == "<":
29
+ return value < expected
30
+ if op == ">=":
31
+ return value >= expected
32
+ if op == "<=":
33
+ return value <= expected
34
+ if op == "in":
35
+ return value in expected
36
+ if op == "not_in":
37
+ return value not in expected
38
+ raise ValueError(f"Unsupported condition operator: {op}")
39
+
40
+ # Scalar condition is treated as equals.
41
+ return value == condition
42
+
43
+
44
+ def _resolve_condition_value(key: str, params: Dict[str, Any], context: Optional[Dict[str, Any]]) -> Any:
45
+ if key.startswith("params."):
46
+ return params.get(key.split(".", 1)[1])
47
+ if key.startswith("context."):
48
+ if context is None:
49
+ return None
50
+ return context.get(key.split(".", 1)[1])
51
+
52
+ if key in params:
53
+ return params[key]
54
+ if context and key in context:
55
+ return context[key]
56
+ return None
57
+
58
+
59
+ def _build_rule_from_definition(definition: Dict[str, Any]) -> PolicyRule:
60
+ action_name = definition.get("action")
61
+ status = definition.get("status")
62
+ reason = definition.get("reason", "")
63
+ conditions = definition.get("conditions", {})
64
+ match_type = definition.get("match", "all")
65
+ priority = int(definition.get("priority", 0))
66
+
67
+ if action_name is None or status is None:
68
+ raise ValueError("Each policy rule must include 'action' and 'status'")
69
+
70
+ status_enum = PolicyStatus(status)
71
+
72
+ def rule(action: str, params: Dict[str, Any], context: Optional[Dict[str, Any]] = None) -> Optional[PolicyResult]:
73
+ if action_name != "*" and action != action_name:
74
+ return None
75
+
76
+ if conditions:
77
+ hits = []
78
+ for key, cond in conditions.items():
79
+ value = _resolve_condition_value(key, params, context)
80
+ if value is None:
81
+ hits.append(False)
82
+ continue
83
+
84
+ result = _evaluate_condition(value, cond)
85
+ hits.append(bool(result))
86
+
87
+ if match_type == "all" and not all(hits):
88
+ return None
89
+ if match_type == "any" and not any(hits):
90
+ return None
91
+
92
+ return PolicyResult(status=status_enum, reason=reason)
93
+
94
+ setattr(rule, "priority", priority)
95
+ return rule
96
+
97
+
98
+ class PolicyEngine:
99
+ def __init__(self, rules: Optional[List[PolicyRule]] = None):
100
+ self.rules = rules or []
101
+ self._sort_rules()
102
+
103
+ def add_rule(self, rule: PolicyRule) -> None:
104
+ if not hasattr(rule, "priority"):
105
+ setattr(rule, "priority", 0)
106
+ self.rules.append(rule)
107
+ self._sort_rules()
108
+
109
+ def _sort_rules(self) -> None:
110
+ self.rules.sort(key=lambda r: getattr(r, "priority", 0), reverse=True)
111
+
112
+ def load_rules_from_yaml(self, yaml_text: str) -> None:
113
+ parsed = yaml.safe_load(yaml_text)
114
+ if not isinstance(parsed, dict):
115
+ raise ValueError("YAML policy must contain a top-level mapping with 'rules'")
116
+
117
+ rules = parsed.get("rules")
118
+ if not isinstance(rules, list):
119
+ raise ValueError("'rules' must be a list")
120
+
121
+ for rule_def in rules:
122
+ if not isinstance(rule_def, dict):
123
+ raise ValueError("Each rule must be a mapping")
124
+ self.add_rule(_build_rule_from_definition(rule_def))
125
+
126
+ def load_rules_from_yaml_file(self, path: str) -> None:
127
+ if not os.path.exists(path):
128
+ raise FileNotFoundError(f"YAML policy file not found: {path}")
129
+
130
+ with open(path, "r", encoding="utf-8") as f:
131
+ text = f.read()
132
+ self.load_rules_from_yaml(text)
133
+
134
+ def evaluate(self, action: str, params: Dict[str, Any], context: Optional[Dict[str, Any]] = None) -> PolicyResult:
135
+ context = context or {}
136
+
137
+ for rule in self.rules:
138
+ decision = rule(action, params, context)
139
+ if decision is not None:
140
+ if not isinstance(decision, PolicyResult):
141
+ raise PolicyViolationError(f"Policy rule returned invalid type: {type(decision)}")
142
+ return decision
143
+
144
+ return PolicyResult(status=PolicyStatus.ALLOW, reason="default allow")
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Dict
4
+
5
+ from .exceptions import ActionNotFoundError
6
+
7
+ ActionFunc = Callable[..., Any]
8
+
9
+
10
+ class ActionRegistry:
11
+ def __init__(self):
12
+ self._actions: Dict[str, ActionFunc] = {}
13
+
14
+ def register(self, name: str, func: ActionFunc) -> ActionFunc:
15
+ if name in self._actions:
16
+ raise ValueError(f"Action '{name}' is already registered")
17
+ self._actions[name] = func
18
+ return func
19
+
20
+ def get(self, name: str) -> ActionFunc:
21
+ if name not in self._actions:
22
+ raise ActionNotFoundError(f"Action '{name}' not registered")
23
+ return self._actions[name]
24
+
25
+ def list_actions(self) -> Dict[str, ActionFunc]:
26
+ return dict(self._actions)
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+ from typing import Any, Dict, Optional
6
+
7
+
8
+ class PolicyStatus(str, Enum):
9
+ ALLOW = "allow"
10
+ BLOCK = "block"
11
+ REVIEW = "review"
12
+
13
+
14
+ @dataclass
15
+ class PolicyResult:
16
+ status: PolicyStatus
17
+ reason: str = ""
18
+ metadata: Dict[str, Any] = None
19
+
20
+
21
+ @dataclass
22
+ class ActionExecutionResult:
23
+ action: str
24
+ params: Dict[str, Any]
25
+ result: Any
26
+ policy: PolicyResult
@@ -0,0 +1,230 @@
1
+ Metadata-Version: 2.4
2
+ Name: agsec
3
+ Version: 0.1.0
4
+ Summary: AI Agent Action Firewall core SDK
5
+ Home-page: https://github.com/yourusername/agsec
6
+ Author: Riyandhiman
7
+ Author-email: Riyandhiman <noreply@example.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/yourusername/agsec
10
+ Project-URL: Repository, https://github.com/yourusername/agsec
11
+ Project-URL: Documentation, https://github.com/yourusername/agsec#readme
12
+ Keywords: agent,security,policy,sandbox
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: PyYAML>=6.0
19
+ Dynamic: author
20
+ Dynamic: home-page
21
+ Dynamic: requires-python
22
+
23
+ # agsec
24
+
25
+ [![PyPI version](https://badge.fury.io/py/agsec.svg)](https://pypi.org/project/agsec/)
26
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
27
+
28
+ AI Agent Action Firewall - A minimal, control layer for agent actions.
29
+
30
+ ## Overview
31
+
32
+ `agsec` provides a simple yet powerful way to add safety controls to AI agents. It acts as a "firewall" between agents and real-world actions, allowing you to define policies that approve, block, or review actions before execution.
33
+
34
+ ### Why agsec?
35
+
36
+ - **Agent-neutral**: Works with any agent framework (LangChain, custom, etc.)
37
+ - **Declarative policies**: Define rules in YAML or code
38
+ - **Extensible**: Plugin system for custom actions and policies
39
+ - **Production-ready**: Lightweight, fast, and secure
40
+
41
+ ## Features
42
+
43
+ - ✅ **Action Registry**: Register and manage agent actions
44
+ - ✅ **Policy Engine**: Flexible rule-based decision making
45
+ - ✅ **YAML Policies**: Human-readable policy definitions
46
+ - ✅ **Context Awareness**: Rules can access parameters and context
47
+ - ✅ **Priority & Matching**: Advanced rule evaluation (priority, all/any matching)
48
+ - ✅ **Audit Logging**: Built-in logging for all decisions
49
+ - ✅ **Python Package**: Easy installation via PyPI
50
+
51
+ ## Installation
52
+
53
+ ### Runtime (for users)
54
+
55
+ ```bash
56
+ pip install agsec
57
+ ```
58
+
59
+ ### Development (for contributors)
60
+
61
+ ```bash
62
+ git clone https://github.com/yourusername/agsec.git
63
+ cd agsec
64
+ pip install -e .[dev]
65
+ pre-commit install
66
+ ```
67
+
68
+ ## Quick Start
69
+
70
+ ### Basic Usage
71
+
72
+ ```python
73
+ from agsec import ControlLayer
74
+
75
+ # Create control layer
76
+ control = ControlLayer()
77
+
78
+ # Register an action
79
+ @control.register_action("send_email")
80
+ def send_email(to, subject, body):
81
+ return {"sent_to": to, "status": "success"}
82
+
83
+ # Execute with default allow policy
84
+ result = control.execute("send_email", {"to": "user@example.com", "subject": "Hello", "body": "Hi!"})
85
+ print(result.result) # {"sent_to": "user@example.com", "status": "success"}
86
+ ```
87
+
88
+ ### With YAML Policies
89
+
90
+ ```python
91
+ from agsec import ControlLayer
92
+
93
+ policy_yaml = """
94
+ rules:
95
+ - action: payment
96
+ status: block
97
+ reason: "High-value payment blocked"
98
+ conditions:
99
+ amount:
100
+ op: ">"
101
+ value: 10000
102
+ """
103
+
104
+ control = ControlLayer(policy_yaml=policy_yaml)
105
+
106
+ @control.register_action("payment")
107
+ def payment(amount):
108
+ return {"charged": amount}
109
+
110
+ try:
111
+ control.execute("payment", {"amount": 15000})
112
+ except Exception as e:
113
+ print(e) # PolicyViolationError: High-value payment blocked
114
+ ```
115
+
116
+ ## API Reference
117
+
118
+ ### ControlLayer
119
+
120
+ Main class for managing agent actions and policies.
121
+
122
+ ```python
123
+ ControlLayer(
124
+ policy_engine=None, # PolicyEngine instance
125
+ action_registry=None, # ActionRegistry instance
126
+ logger=None, # Custom logger
127
+ policy_yaml=None, # YAML policy string
128
+ policy_yaml_path=None # Path to YAML policy file
129
+ )
130
+ ```
131
+
132
+ #### Methods
133
+
134
+ - `register_action(name)`: Decorator to register an action function
135
+ - `execute(action, params, context=None)`: Execute an action with policy check
136
+
137
+ ### PolicyEngine
138
+
139
+ Handles policy evaluation.
140
+
141
+ #### Methods
142
+
143
+ - `add_rule(rule)`: Add a programmatic rule function
144
+ - `load_rules_from_yaml(yaml_text)`: Load rules from YAML string
145
+ - `load_rules_from_yaml_file(path)`: Load rules from YAML file
146
+ - `evaluate(action, params, context=None)`: Evaluate policy for action
147
+
148
+ ### Policy Status
149
+
150
+ - `PolicyStatus.ALLOW`: Allow action execution
151
+ - `PolicyStatus.BLOCK`: Block action execution
152
+ - `PolicyStatus.REVIEW`: Mark for manual review
153
+
154
+ ### YAML Policy Schema
155
+
156
+ ```yaml
157
+ rules:
158
+ - action: "action_name" # Action to match (* for all)
159
+ status: "allow|block|review" # Decision
160
+ reason: "Optional reason" # Human-readable explanation
161
+ priority: 0 # Higher = evaluated first
162
+ match: "all|any" # Condition matching mode
163
+ conditions: # Parameter/context checks
164
+ param_name:
165
+ op: "==|!=|>|<|>=|<=|in|not_in"
166
+ value: "expected_value"
167
+ context.user_role:
168
+ op: "=="
169
+ value: "admin"
170
+ ```
171
+
172
+ ## Development
173
+
174
+ ### Setup
175
+
176
+ ```bash
177
+ pip install -e .[dev]
178
+ pre-commit install
179
+ ```
180
+
181
+ ### Testing
182
+
183
+ ```bash
184
+ pytest
185
+ ```
186
+
187
+ ### Building
188
+
189
+ ```bash
190
+ python -m build
191
+ ```
192
+
193
+ ### Releasing
194
+
195
+ ```bash
196
+ ./release.sh # Requires PYPI_API_TOKEN env var
197
+ ```
198
+
199
+ ## Contributing
200
+
201
+ 1. Fork the repository
202
+ 2. Create a feature branch
203
+ 3. Make your changes
204
+ 4. Add tests
205
+ 5. Run `pre-commit run --all-files`
206
+ 6. Submit a pull request
207
+
208
+ ### Code Style
209
+
210
+ - Black for formatting
211
+ - isort for import sorting
212
+ - flake8 for linting
213
+ - pytest for testing
214
+
215
+ ## License
216
+
217
+ MIT License - see [LICENSE](LICENSE) file for details.
218
+
219
+ ## Roadmap
220
+
221
+ - [ ] Web dashboard for policy management
222
+ - [ ] Advanced risk scoring
223
+ - [ ] Multi-agent coordination
224
+ - [ ] Enterprise integrations
225
+
226
+ ## Support
227
+
228
+ - Issues: [GitHub Issues](https://github.com/riyandhiman14/agsec/issues)
229
+ - Discussions: [GitHub Discussions](https://github.com/riyandhiman14/agsec/discussions)
230
+
@@ -0,0 +1,16 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ agsec/__init__.py
5
+ agsec/control.py
6
+ agsec/exceptions.py
7
+ agsec/policy.py
8
+ agsec/registry.py
9
+ agsec/types.py
10
+ agsec.egg-info/PKG-INFO
11
+ agsec.egg-info/SOURCES.txt
12
+ agsec.egg-info/dependency_links.txt
13
+ agsec.egg-info/requires.txt
14
+ agsec.egg-info/top_level.txt
15
+ tests/__init__.py
16
+ tests/test_control.py
@@ -0,0 +1 @@
1
+ PyYAML>=6.0
@@ -0,0 +1,2 @@
1
+ agsec
2
+ tests
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "agsec"
7
+ version = "0.1.0"
8
+ description = "AI Agent Action Firewall core SDK"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = {text = "MIT"}
12
+ keywords = ["agent", "security", "policy", "sandbox"]
13
+ authors = [
14
+ {name = "Riyandhiman", email = "noreply@example.com"}
15
+ ]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ ]
21
+ dependencies = [
22
+ "PyYAML>=6.0"
23
+ ]
24
+
25
+ [project.urls]
26
+ "Homepage" = "https://github.com/yourusername/agsec"
27
+ "Repository" = "https://github.com/yourusername/agsec"
28
+ "Documentation" = "https://github.com/yourusername/agsec#readme"
29
+
30
+ [tool.pytest.ini_options]
31
+ minversion = "6.0"
32
+ addopts = "-ra -q"
33
+ testpaths = ["tests"]
34
+
35
+ [tool.black]
36
+ line-length = 88
37
+ target-version = ['py38']
38
+
39
+ [tool.isort]
40
+ profile = "black"
41
+ line_length = 88
42
+
43
+ [tool.flake8]
44
+ max-line-length = 88
45
+ extend-ignore = ["E203", "W503"]
agsec-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
agsec-0.1.0/setup.py ADDED
@@ -0,0 +1,36 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r", encoding="utf-8") as fh:
4
+ long_description = fh.read()
5
+
6
+ setup(
7
+ name="agsec",
8
+ version="0.1.0",
9
+ author="Riyandhiman",
10
+ author_email="noreply@example.com",
11
+ description="AI Agent Action Firewall core SDK",
12
+ long_description=long_description,
13
+ long_description_content_type="text/markdown",
14
+ url="https://github.com/yourusername/agsec",
15
+ packages=find_packages(),
16
+ classifiers=[
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ ],
21
+ python_requires=">=3.8",
22
+ install_requires=[
23
+ "PyYAML>=6.0",
24
+ ],
25
+ extras_require={
26
+ "dev": [
27
+ "pre-commit>=3.0",
28
+ "black>=24.0",
29
+ "isort>=5.0",
30
+ "flake8>=7.0",
31
+ "pytest>=6.0",
32
+ "build>=1.0",
33
+ "twine>=4.0",
34
+ ],
35
+ },
36
+ )
File without changes
@@ -0,0 +1,190 @@
1
+ import logging
2
+
3
+ from agsec import ControlLayer, PolicyEngine, PolicyResult, PolicyStatus
4
+ from agsec.exceptions import PolicyViolationError
5
+
6
+
7
+ def test_control_execute_allow(monkeypatch):
8
+ control = ControlLayer(policy_engine=PolicyEngine())
9
+
10
+ @control.register_action("noop")
11
+ def noop():
12
+ return "ok"
13
+
14
+ result = control.execute("noop", {})
15
+ assert result.result == "ok"
16
+ assert result.policy.status == PolicyStatus.ALLOW
17
+
18
+
19
+ def test_control_execute_block():
20
+ engine = PolicyEngine()
21
+
22
+ def deny_all(action, params, context):
23
+ return PolicyResult(status=PolicyStatus.BLOCK, reason="denied")
24
+
25
+ engine.add_rule(deny_all)
26
+ control = ControlLayer(policy_engine=engine)
27
+
28
+ @control.register_action("noop")
29
+ def noop():
30
+ return "ok"
31
+
32
+ try:
33
+ control.execute("noop", {})
34
+ assert False, "Expected PolicyViolationError"
35
+ except PolicyViolationError:
36
+ pass
37
+
38
+
39
+ def test_policy_review():
40
+ engine = PolicyEngine()
41
+
42
+ def review_payment(action, params, context):
43
+ if action == "payment" and params.get("amount") > 5000:
44
+ return PolicyResult(status=PolicyStatus.REVIEW, reason="manual review required")
45
+ return None
46
+
47
+ engine.add_rule(review_payment)
48
+ control = ControlLayer(policy_engine=engine)
49
+
50
+ @control.register_action("payment")
51
+ def payment(amount):
52
+ return {"charged": amount}
53
+
54
+ result = control.execute("payment", {"amount": 6000})
55
+ assert result.result is None
56
+ assert result.policy.status == PolicyStatus.REVIEW
57
+
58
+
59
+ def test_policy_engine_load_yaml_block():
60
+ yaml_rules = """
61
+ rules:
62
+ - action: payment
63
+ status: block
64
+ reason: "Amount over limit"
65
+ conditions:
66
+ amount:
67
+ op: ">"
68
+ value: 10000
69
+ """
70
+
71
+ engine = PolicyEngine()
72
+ engine.load_rules_from_yaml(yaml_rules)
73
+
74
+ control = ControlLayer(policy_engine=engine)
75
+
76
+ @control.register_action("payment")
77
+ def payment(amount):
78
+ return {"charged": amount}
79
+
80
+ try:
81
+ control.execute("payment", {"amount": 15000})
82
+ assert False, "Expected PolicyViolationError"
83
+ except PolicyViolationError as e:
84
+ assert "Amount over limit" in str(e)
85
+
86
+
87
+ def test_control_layer_load_policy_yaml_direct():
88
+ yaml_rules = """
89
+ rules:
90
+ - action: send_email
91
+ status: allow
92
+ reason: "always allow"
93
+ """
94
+
95
+ control = ControlLayer(policy_yaml=yaml_rules)
96
+
97
+ @control.register_action("send_email")
98
+ def send_email(to):
99
+ return {"sent_to": to}
100
+
101
+ result = control.execute("send_email", {"to": "x@example.com"})
102
+ assert result.result == {"sent_to": "x@example.com"}
103
+ assert result.policy.status == PolicyStatus.ALLOW
104
+
105
+
106
+ def test_policy_engine_yaml_priority():
107
+ yaml_rules = """
108
+ rules:
109
+ - action: payment
110
+ status: allow
111
+ priority: 0
112
+ - action: payment
113
+ status: block
114
+ reason: "Higher priority block"
115
+ priority: 100
116
+ """
117
+
118
+ engine = PolicyEngine()
119
+ engine.load_rules_from_yaml(yaml_rules)
120
+
121
+ control = ControlLayer(policy_engine=engine)
122
+
123
+ @control.register_action("payment")
124
+ def payment(amount):
125
+ return {"charged": amount}
126
+
127
+ try:
128
+ control.execute("payment", {"amount": 10})
129
+ assert False, "Expected PolicyViolationError due to higher priority block"
130
+ except PolicyViolationError as e:
131
+ assert "Higher priority block" in str(e)
132
+
133
+
134
+ def test_policy_engine_yaml_match_any():
135
+ yaml_rules = """
136
+ rules:
137
+ - action: data_export
138
+ status: block
139
+ match: any
140
+ conditions:
141
+ table:
142
+ op: "=="
143
+ value: "sensitive"
144
+ export_type:
145
+ op: "=="
146
+ value: "external"
147
+ """
148
+
149
+ engine = PolicyEngine()
150
+ engine.load_rules_from_yaml(yaml_rules)
151
+
152
+ control = ControlLayer(policy_engine=engine)
153
+
154
+ @control.register_action("data_export")
155
+ def data_export(table, export_type):
156
+ return {"ok": True}
157
+
158
+ # should block on table match
159
+ try:
160
+ control.execute("data_export", {"table": "sensitive", "export_type": "internal"})
161
+ assert False
162
+ except PolicyViolationError:
163
+ pass
164
+
165
+
166
+ def test_policy_engine_yaml_context_condition():
167
+ yaml_rules = """
168
+ rules:
169
+ - action: password_reset
170
+ status: block
171
+ conditions:
172
+ context.user_role:
173
+ op: "=="
174
+ value: "guest"
175
+ """
176
+
177
+ engine = PolicyEngine()
178
+ engine.load_rules_from_yaml(yaml_rules)
179
+
180
+ control = ControlLayer(policy_engine=engine)
181
+
182
+ @control.register_action("password_reset")
183
+ def password_reset(user_id):
184
+ return {"reset": user_id}
185
+
186
+ try:
187
+ control.execute("password_reset", {"user_id": "u1"}, context={"user_role": "guest"})
188
+ assert False
189
+ except PolicyViolationError:
190
+ pass