safentic 1.0.5__py3-none-any.whl → 1.0.7__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.
@@ -0,0 +1,29 @@
1
+ safentic/__init__.py,sha256=v56-9XuuTSkr3ocIHQWRZzFKIm4JaN3wF3W5dpKa9XM,141
2
+ safentic/config.py,sha256=222VKyCkRYQ_WgoKTcXsIgHQJIgHGyCb0jmD305pOKM,88
3
+ safentic/decorators.py,sha256=Tg-kmMJLCT_2sx9FMArIm7otvi1unNYfcX1zMkXwO14,1547
4
+ safentic/layer.py,sha256=WQk9LXu5ISB-FgHPpksNHxCMNI2eEpLlMmemOHKtH84,3030
5
+ safentic/policy_enforcer.py,sha256=9lc4R5zu7VOKBEGp3paG5-iwHxU-_43UVuyFR0k1J1Y,4075
6
+ safentic/policy_engine.py,sha256=t7eUdYuRtJKyibrjzSkNsBuswlLeMgjnIzT7CmNhZ2I,5326
7
+ safentic/_internal/errors.py,sha256=NdP79QIqgqaiMD_iIlNNoYRHYXLTwwfEvWnKZ3qGDN4,717
8
+ safentic/adapters/mcp_adapter.py,sha256=C4gUzUg9wv6ToNhCho2kNBlyPDHnlQhoQIASUfRc1Ks,1389
9
+ safentic/cli/__init__.py,sha256=TxVn7Qjs3VBg36A_3dasco_AQPZtK4HebdViKMirE8U,49
10
+ safentic/cli/main.py,sha256=G595Yv15Q22akVXUSCNNoAi7OwrslZ-26Wouq1lBJTQ,5041
11
+ safentic/cli/utils.py,sha256=mmCaWwIcG5_IPxmaK9kuwWrSpYoHsJ4iofhyREhOfgQ,5686
12
+ safentic/cli/commands/check_tool.py,sha256=yh8p_jPVPwifaiH0X6kBFJO7jxO_vB8333lctrAqLts,1446
13
+ safentic/cli/commands/logs.py,sha256=t-dqCJ_wQz8XgF2sdamHxM2JEknCKQyYa3FkJE8xvlI,2015
14
+ safentic/cli/commands/validate_policy.py,sha256=7khzBJkOMBOqRDHCSGfqrR_udo_RmZRdSlPih5oEtgg,2062
15
+ safentic/helper/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ safentic/helper/helper.py,sha256=eLJE2GNc7-GSxotFClCUHugdTSgu0zS7EEXITWlVZd0,7030
17
+ safentic/logger/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ safentic/logger/audit.py,sha256=KeV1UyIlxfThZUxN2dEYms62oCncxdeeomJRYeAJo1Y,6295
19
+ safentic/verifiers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ safentic/verifiers/llm_verifier.py,sha256=y6C9UexB2jIqHeCD8uLlDYFMYdmbEU7C4BQdgB8WD0s,7887
21
+ safentic-1.0.7.dist-info/licenses/LICENSE.txt,sha256=Gs_ZO2z7NQDNRexLzkWeWuqynX6MQ3-SbtT48VnvxqE,2294
22
+ safentic_poc/backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ safentic_poc/backend/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ safentic_poc/backend/api/main.py,sha256=bSGR-A1I8uY52lDpZMuGwGIP2BRyspIo0MrpWofjo-I,5289
25
+ safentic-1.0.7.dist-info/METADATA,sha256=Ygrw38_gE91sqbw_gJaamgfGRvP0P4W8MA1AWzodVis,5723
26
+ safentic-1.0.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
27
+ safentic-1.0.7.dist-info/entry_points.txt,sha256=yqTSD1j6ayKd6YH5olWoOuvbCjUWyClOKmHEtuWYARk,52
28
+ safentic-1.0.7.dist-info/top_level.txt,sha256=Ft3UUfii2QowOYyn_wSVd28KH9U9hEg7WyqR8p2lY0s,22
29
+ safentic-1.0.7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ safentic = safentic.cli.main:main
@@ -1,36 +1,36 @@
1
- Safentic SDK Commercial License Agreement
2
- =========================================
3
-
4
- IMPORTANT – READ CAREFULLY:
5
-
6
- This license governs the use of the Safentic SDK (“Software”) developed and owned by Safentic. By installing or using this Software, you (“Licensee”) agree to the following terms and conditions:
7
-
8
- 1. GRANT OF LICENSE
9
- Licensor grants Licensee a non-exclusive, non-transferable, non-sublicensable license to use the Software solely for internal business purposes and only in accordance with the terms of the commercial agreement executed between the parties. This license may include limited evaluation rights, subject to expiration or usage restrictions.
10
-
11
- 2. RESTRICTIONS
12
- Licensee shall NOT:
13
- - Use the Software without a valid, active license key or subscription.
14
- - Reverse engineer, decompile, or disassemble the Software.
15
- - Modify, copy, or create derivative works based on the Software.
16
- - Distribute, sublicense, lease, or otherwise make the Software available to any third party.
17
- - Circumvent or attempt to disable any license verification, telemetry, or access control mechanisms.
18
-
19
- 3. OWNERSHIP
20
- The Software is licensed, not sold. Safentic retains all rights, title, and interest in and to the Software, including all intellectual property rights.
21
-
22
- 4. TERMINATION
23
- This license is effective until terminated. It will terminate automatically without notice if Licensee breaches any term of this agreement. Upon termination, Licensee must cease all use and destroy all copies of the Software.
24
-
25
- 5. NO WARRANTY
26
- The Software is provided "as is" without warranty of any kind. Licensor disclaims all warranties, express or implied, including but not limited to warranties of merchantability and fitness for a particular purpose.
27
-
28
- 6. LIMITATION OF LIABILITY
29
- In no event shall Licensor be liable for any damages arising out of the use or inability to use the Software, including but not limited to incidental, special, or consequential damages.
30
-
31
- 7. GOVERNING LAW
32
- This agreement shall be governed by and construed in accordance with the laws of Ireland, without regard to its conflict of law principles.
33
-
34
- For licensing inquiries, please contact: contact@safentic.com
35
-
36
- Copyright © 2025, Safentic. All rights reserved.
1
+ Safentic SDK Commercial License Agreement
2
+ =========================================
3
+
4
+ IMPORTANT – READ CAREFULLY:
5
+
6
+ This license governs the use of the Safentic SDK (“Software”) developed and owned by Safentic. By installing or using this Software, you (“Licensee”) agree to the following terms and conditions:
7
+
8
+ 1. GRANT OF LICENSE
9
+ Licensor grants Licensee a non-exclusive, non-transferable, non-sublicensable license to use the Software solely for internal business purposes and only in accordance with the terms of the commercial agreement executed between the parties. This license may include limited evaluation rights, subject to expiration or usage restrictions.
10
+
11
+ 2. RESTRICTIONS
12
+ Licensee shall NOT:
13
+ - Use the Software without a valid, active license key or subscription.
14
+ - Reverse engineer, decompile, or disassemble the Software.
15
+ - Modify, copy, or create derivative works based on the Software.
16
+ - Distribute, sublicense, lease, or otherwise make the Software available to any third party.
17
+ - Circumvent or attempt to disable any license verification, telemetry, or access control mechanisms.
18
+
19
+ 3. OWNERSHIP
20
+ The Software is licensed, not sold. Safentic retains all rights, title, and interest in and to the Software, including all intellectual property rights.
21
+
22
+ 4. TERMINATION
23
+ This license is effective until terminated. It will terminate automatically without notice if Licensee breaches any term of this agreement. Upon termination, Licensee must cease all use and destroy all copies of the Software.
24
+
25
+ 5. NO WARRANTY
26
+ The Software is provided "as is" without warranty of any kind. Licensor disclaims all warranties, express or implied, including but not limited to warranties of merchantability and fitness for a particular purpose.
27
+
28
+ 6. LIMITATION OF LIABILITY
29
+ In no event shall Licensor be liable for any damages arising out of the use or inability to use the Software, including but not limited to incidental, special, or consequential damages.
30
+
31
+ 7. GOVERNING LAW
32
+ This agreement shall be governed by and construed in accordance with the laws of Ireland, without regard to its conflict of law principles.
33
+
34
+ For licensing inquiries, please contact: contact@safentic.com
35
+
36
+ Copyright © 2025, Safentic. All rights reserved.
@@ -0,0 +1,2 @@
1
+ safentic
2
+ safentic_poc
@@ -0,0 +1,164 @@
1
+ from fastapi import FastAPI, HTTPException
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from pydantic import BaseModel, Field
4
+ from typing import Any, Optional, List
5
+ import os, json
6
+ from time import perf_counter
7
+ from pathlib import Path
8
+ from dotenv import load_dotenv # make sure python-dotenv is installed
9
+
10
+ # Internal SDK imports (OK for first-party POC)
11
+ from safentic.policy_engine import PolicyEngine
12
+ from safentic.policy_enforcer import PolicyEnforcer
13
+ from safentic.logger.audit import AuditLogger
14
+ from safentic._internal.errors import SafenticError
15
+
16
+ # -------- Load .env --------
17
+ # backend/api/main.py → go up 2 levels to reach safentic_poc/backend/.env
18
+ BACKEND_ROOT = Path(__file__).resolve().parents[1]
19
+ REPO_ROOT = Path(__file__).resolve().parents[3]
20
+ load_dotenv(BACKEND_ROOT / ".env")
21
+
22
+ def resolve_path(env_var: str, default_rel: str) -> str:
23
+ """
24
+ Resolve a path from an env var or fall back to a repo-relative default.
25
+ If env var is absolute, return as-is.
26
+ """
27
+ val = os.getenv(env_var)
28
+ if val:
29
+ return str((REPO_ROOT / val).resolve()) if not os.path.isabs(val) else val
30
+ return str((REPO_ROOT / default_rel).resolve())
31
+
32
+ # -------- Config --------
33
+ FE_ORIGIN = os.getenv("FE_ORIGIN", "http://localhost:3000")
34
+ POLICY_PATH = resolve_path("SAFENTIC_POLICY_PATH", "config/policy.yaml")
35
+
36
+ # Default: use JSON logs under safentic_poc/safentic/logs/json_logs
37
+ AUDIT_LOG_PATH = resolve_path(
38
+ "SAFENTIC_AUDIT_LOG",
39
+ "safentic_poc/backend/safentic/logs/json_logs/safentic_audit.jsonl",
40
+ )
41
+
42
+ print("ENV says:", os.getenv("SAFENTIC_AUDIT_LOG"))
43
+ print("Resolved AUDIT_LOG_PATH:", AUDIT_LOG_PATH)
44
+
45
+ # -------- App & CORS --------
46
+ app = FastAPI(title="Safentic POC API")
47
+ app.add_middleware(
48
+ CORSMiddleware,
49
+ allow_origins=[FE_ORIGIN],
50
+ allow_credentials=False,
51
+ allow_methods=["*"],
52
+ allow_headers=["*"],
53
+ )
54
+
55
+ # -------- Instantiate engine/enforcer/logger --------
56
+ engine = PolicyEngine(policy_path=POLICY_PATH)
57
+ enforcer = PolicyEnforcer(policy_engine=engine)
58
+ logger = AuditLogger()
59
+
60
+ # -------- Schemas --------
61
+ class Options(BaseModel):
62
+ dry_run: bool = False
63
+ no_llm: bool = False
64
+
65
+ class EnforceRequest(BaseModel):
66
+ tool_name: str
67
+ agent_id: str = Field(default="fe-demo")
68
+ input: dict[str, Any] = Field(default_factory=dict)
69
+ options: Options = Options()
70
+
71
+ class EnforceResponse(BaseModel):
72
+ allowed: bool
73
+ reason: str
74
+ violation: Optional[dict] = None
75
+ agent_state: Optional[dict] = None
76
+ rule_id: Optional[str] = None
77
+ took_ms: int
78
+
79
+ class LogEntry(BaseModel):
80
+ ts: str
81
+ agent_id: str
82
+ tool: str
83
+ allowed: bool
84
+ reason: Optional[str] = None
85
+ rule_id: Optional[str] = None
86
+
87
+ class LogTailResponse(BaseModel):
88
+ items: List[LogEntry]
89
+
90
+ # -------- Routes --------
91
+ @app.get("/api/health")
92
+ def health():
93
+ return {"status": "ok"}
94
+
95
+ @app.post("/api/enforce", response_model=EnforceResponse)
96
+ def enforce(req: EnforceRequest):
97
+ if hasattr(enforcer, "reset"):
98
+ try:
99
+ enforcer.reset(req.agent_id)
100
+ except Exception:
101
+ pass
102
+
103
+ t0 = perf_counter()
104
+ try:
105
+ result = enforcer.enforce(req.agent_id, req.tool_name, req.input)
106
+ except SafenticError as e:
107
+ raise HTTPException(status_code=400, detail=str(e))
108
+ except ValueError as e:
109
+ raise HTTPException(status_code=400, detail=str(e))
110
+
111
+ took_ms = int((perf_counter() - t0) * 1000)
112
+
113
+ try:
114
+ logger.log(
115
+ agent_id=req.agent_id,
116
+ tool=req.tool_name,
117
+ allowed=bool(result.get("allowed", False)),
118
+ reason=result.get("reason"),
119
+ rule_id=result.get("rule_id"),
120
+ )
121
+ except Exception:
122
+ pass
123
+
124
+ return {
125
+ "allowed": bool(result.get("allowed")),
126
+ "reason": result.get("reason", ""),
127
+ "violation": result.get("violation"),
128
+ "agent_state": result.get("agent_state"),
129
+ "rule_id": result.get("rule_id"),
130
+ "took_ms": took_ms,
131
+ }
132
+
133
+ @app.get("/api/logs/tail", response_model=LogTailResponse)
134
+ def logs_tail(limit: int = 100):
135
+ items: List[LogEntry] = []
136
+ try:
137
+ print(f"Audit log path: {AUDIT_LOG_PATH}")
138
+ if os.path.exists(AUDIT_LOG_PATH):
139
+ with open(AUDIT_LOG_PATH, "r") as f:
140
+ lines = f.readlines()[-limit:]
141
+ print(f"Read {len(lines)} lines from {AUDIT_LOG_PATH}")
142
+ for ln in lines:
143
+ ln = ln.strip()
144
+ if not ln:
145
+ continue
146
+ try:
147
+ j = json.loads(ln)
148
+ items.append(LogEntry(
149
+ ts=j.get("timestamp") or j.get("ts") or "",
150
+ agent_id=j.get("agent_id") or "",
151
+ tool=j.get("tool") or "",
152
+ allowed=bool(j.get("allowed", False)),
153
+ reason=j.get("reason"),
154
+ rule_id=j.get("rule_id") or j.get("extra", {}).get("rule"),
155
+ ))
156
+ except Exception:
157
+ print(f"Failed to parse log line: {ln}")
158
+ continue
159
+ else:
160
+ print(f"Audit log file does not exist: {AUDIT_LOG_PATH}")
161
+ except Exception as e:
162
+ print(f"Failed to read audit log: {e}")
163
+ items = []
164
+ return {"items": items}
safentic/engine.py DELETED
@@ -1,92 +0,0 @@
1
- import time
2
- from .policy import PolicyEngine
3
- from .logger.audit import AuditLogger
4
-
5
-
6
- class PolicyEnforcer:
7
- """
8
- Runtime wrapper to evaluate and enforce tool usage policies.
9
- Tracks agent-specific violations, supports audit logging, and handles TTL-based tool blocks.
10
- """
11
-
12
- TOOL_BLOCK_TTL = 60 # seconds - how long a tool remains blocked after violation
13
-
14
- def __init__(self, policy_engine: PolicyEngine = None):
15
- self.policy_engine = policy_engine or PolicyEngine()
16
- self.agent_states = {}
17
- self.audit_logger = AuditLogger()
18
-
19
- def enforce(self, agent_id: str, tool_name: str, tool_args: dict) -> dict:
20
- """
21
- Evaluates a tool action for a given agent.
22
- Returns a dict with 'allowed', 'reason', and agent state metadata.
23
- """
24
- state = self.agent_states.setdefault(agent_id, {
25
- "blocked_tools": {}, # tool_name -> timestamp of block
26
- "violation_count": 0,
27
- "last_violation": None
28
- })
29
-
30
- # Check if tool is still blocked
31
- if self._is_tool_blocked(tool_name, state):
32
- reason = "Tool is temporarily blocked due to a prior violation."
33
- self.audit_logger.log(agent_id=agent_id, tool=tool_name, allowed=False, reason=reason)
34
- return self._deny(tool_name, state, reason)
35
-
36
- # Evaluate policy
37
- violation = self.policy_engine.evaluate_policy(tool_name, tool_args)
38
-
39
- if violation:
40
- # Example violation object: {"reason": "...", "level": "block"}
41
- level = violation.get("level", "block")
42
- reason = violation.get("reason", "Policy violation")
43
-
44
- if level == "warn":
45
- # Log a warning but allow the call
46
- self.audit_logger.log(agent_id=agent_id, tool=tool_name, allowed=True, reason=f"Warning: {reason}")
47
- return {
48
- "allowed": True,
49
- "reason": f"Warning: {reason}",
50
- "agent_state": state
51
- }
52
-
53
- # Otherwise: enforce block
54
- state["blocked_tools"][tool_name] = time.time()
55
- state["violation_count"] += 1
56
- state["last_violation"] = violation
57
- self.audit_logger.log(agent_id=agent_id, tool=tool_name, allowed=False, reason=reason)
58
- return self._deny(tool_name, state, reason)
59
-
60
- # Allow
61
- self.audit_logger.log(agent_id=agent_id, tool=tool_name, allowed=True)
62
- return {
63
- "allowed": True,
64
- "reason": "Action permitted",
65
- "agent_state": state
66
- }
67
-
68
- def reset(self, agent_id: str = None):
69
- """Clears violation state for one or all agents."""
70
- if agent_id:
71
- self.agent_states.pop(agent_id, None)
72
- else:
73
- self.agent_states.clear()
74
-
75
- def _deny(self, tool_name: str, state: dict, reason: str) -> dict:
76
- return {
77
- "allowed": False,
78
- "reason": reason,
79
- "tool": tool_name,
80
- "agent_state": state
81
- }
82
-
83
- def _is_tool_blocked(self, tool_name: str, state: dict) -> bool:
84
- """Checks if a tool is still blocked based on TTL."""
85
- blocked_at = state["blocked_tools"].get(tool_name)
86
- if not blocked_at:
87
- return False
88
- if time.time() - blocked_at > self.TOOL_BLOCK_TTL:
89
- # Tool block expired
90
- del state["blocked_tools"][tool_name]
91
- return False
92
- return True
safentic/helper/auth.py DELETED
@@ -1,12 +0,0 @@
1
- import requests
2
- from ..config import *
3
-
4
- def validate_api_key(key: str) -> dict:
5
- try:
6
- response = requests.post(BASE_API_PATH + API_KEY_ENDPOINT, json={"api_key": key})
7
- if response.status_code != 200:
8
- return {"valid": False}
9
- data = response.json()
10
- return {"valid": True, **data}
11
- except Exception:
12
- return {"valid": False}
@@ -1,3 +0,0 @@
1
- from ..layer import SafetyLayer, SafenticError
2
-
3
- __version__ = "0.1"
@@ -1,33 +0,0 @@
1
- Standard Refund Policy
2
-
3
- Last Updated: February 1, 2025
4
-
5
- 1. Overview
6
- At XYZ, we strive to ensure your complete satisfaction with our platform. If you are not entirely happy with your purchase, you may be eligible for a refund as described below.
7
-
8
- 2. Eligibility
9
- - Monthly Subscriptions: You may request a full refund within 30 days of your initial subscription charge.
10
- - Annual Subscriptions: You may request a pro-rated refund (the remaining unused portion of your term) within 30 days of your initial annual subscription charge.
11
- - One-Time Purchases and Credits: Refunds on one-time feature purchases or in-app credits are handled on a case-by-case basis and must be requested within 14 days of purchase.
12
-
13
- 3. How to Request a Refund
14
- 1. Log in to your XYZ account at https://app.XYZ.ai.
15
- 2. Go to Settings -> Billing -> Refunds.
16
- 3. Select the transaction you wish to refund and click Request Refund.
17
- 4. In the "Reason" field, briefly describe why you are requesting a refund.
18
- 5. Submit your request. You will receive an email confirmation within one business day.
19
-
20
- 4. Processing Time
21
- - Refunds for credit-card or PayPal payments will be credited back to your original payment method within 5-7 business days after approval.
22
- - In-app credits or wallet balances will be restored within 24 hours of approval.
23
-
24
- 5. Exceptions and Limitations
25
- - No refunds will be issued for subscription renewals after the initial 30-day period.
26
- - Promotional, gift, or trial subscriptions are non-refundable.
27
- - Refund requests made more than 30 days after the initial charge are subject to review and may be denied.
28
- - We reserve the right to refuse a refund if we suspect abuse of the refund policy.
29
-
30
- 6. Contact Us
31
- If you have any questions or need assistance, please contact our Support Team at support@XYZ.ai or via the in-app chat.
32
-
33
- Thank you for choosing XYZ! We appreciate your business and are committed to delivering a great experience.
@@ -1,49 +0,0 @@
1
- tools:
2
- send_email:
3
- checks:
4
- - type: deny_phrase
5
- phrases:
6
- - "one device per subscription"
7
- - "24 hours"
8
- - "apology"
9
- - "apologize"
10
- - "sorry"
11
- - "refund"
12
- - "refund delay"
13
- - type: semantic
14
- trigger_phrases:
15
- - "refund policy"
16
- - "guarantee"
17
- - "our policy"
18
- reference_file: "example_policy.txt"
19
-
20
- issue_refund:
21
- checks:
22
- - type: deny_phrase
23
- phrases:
24
- - "refund"
25
- - "refunding"
26
- - "issue refund"
27
- - "send refund"
28
- - "credit customer"
29
- - "reverse charge"
30
- - "double charge"
31
- - "processed refund"
32
- - "grant compensation"
33
- - "money back"
34
- - type: semantic
35
- trigger_phrases:
36
- - "we've sent your refund"
37
- - "your money is on the way"
38
- reference_file: "refund_statement.txt"
39
-
40
- update_ticket:
41
- checks: [] # Explicitly allow all
42
-
43
- log_to_crm:
44
- checks: [] # Explicitly allow all
45
-
46
- logging:
47
- level: INFO
48
- destination: "safentic/logs/txt_logs/safentic_audit.log"
49
- jsonl: "safentic/logs/json_logs/safentic_audit.jsonl"
safentic/policy.py DELETED
@@ -1,102 +0,0 @@
1
- import os
2
- import yaml
3
- from typing import Optional, Dict, Any, Union
4
- from .verifiers.sentence_verifier import SentenceTransformerVerifier
5
- from .logger.audit import AuditLogger
6
-
7
-
8
- class PolicyEngine:
9
- """
10
- Evaluates whether a tool action complies with safety policies.
11
- Supports multiple rule types: deny_phrase, semantic.
12
- Returns structured violations for downstream enforcement.
13
- """
14
-
15
- VALID_RULE_TYPES = {"deny_phrase", "semantic"}
16
-
17
- def __init__(
18
- self,
19
- policy_path: Optional[str] = None,
20
- policy_base_dir: Optional[str] = None
21
- ):
22
- self.base_dir = policy_base_dir or os.path.dirname(__file__)
23
- policy_path = policy_path or os.path.join(self.base_dir, "policies", "policy.yaml")
24
-
25
- with open(policy_path, encoding="utf-8") as f:
26
- self.policy_cfg = yaml.safe_load(f)
27
-
28
- self.verifier = SentenceTransformerVerifier(
29
- model_name="all-MiniLM-L6-v2",
30
- low_threshold=0.50,
31
- high_threshold=0.75,
32
- )
33
-
34
- self.audit_logger = AuditLogger()
35
-
36
- def _load_reference_text(self, filename: str) -> str:
37
- path = os.path.join(self.base_dir, "policies", filename)
38
- with open(path, encoding="utf-8") as f:
39
- return f.read().strip().lower()
40
-
41
- def evaluate_policy(
42
- self,
43
- tool_name: str,
44
- args: Dict[str, Any],
45
- agent_id: str = "unknown"
46
- ) -> Optional[Dict[str, Union[str, Any]]]:
47
- """
48
- Returns:
49
- None if allowed,
50
- dict with 'reason' and 'level' if blocked or warned
51
- """
52
- tool_rules = self.policy_cfg.get("tools", {}).get(tool_name)
53
- if not tool_rules:
54
- return None # No policy = allow
55
-
56
- text = (args.get("body") or args.get("note") or "").strip().lower()
57
- if not text:
58
- return None # Empty input = allow
59
-
60
- for check in tool_rules.get("checks", []):
61
- rule_type = check.get("type")
62
- level = check.get("level", "block") # Default to block
63
-
64
- if rule_type not in self.VALID_RULE_TYPES:
65
- warning = f"Unknown rule type: '{rule_type}' for tool: '{tool_name}'"
66
- self.audit_logger.log(agent_id=agent_id, tool=tool_name, allowed=True, reason=warning)
67
- continue
68
-
69
- # ---- Phrase Matching ----
70
- if rule_type == "deny_phrase":
71
- for phrase in check.get("phrases", []):
72
- if phrase.lower() in text:
73
- reason = f"Matched deny phrase: “{phrase}”"
74
- self.audit_logger.log(agent_id=agent_id, tool=tool_name, allowed=(level == "warn"), reason=reason)
75
- return {"reason": reason, "level": level}
76
-
77
- # ---- Semantic Check ----
78
- elif rule_type == "semantic":
79
- trigger_phrases = [p.lower() for p in check.get("trigger_phrases", [])]
80
- if any(p in text for p in trigger_phrases):
81
- reference_file = check.get("reference_file")
82
- if not reference_file:
83
- continue # Skip if not configured
84
-
85
- reference_text = self._load_reference_text(reference_file)
86
- decision = self.verifier.decision(candidate=text, official=reference_text)
87
-
88
- if decision == "block":
89
- explanation = self.verifier.explain(candidate=text, official=reference_text)
90
- reason = f"Semantic block: {explanation}"
91
- self.audit_logger.log(agent_id=agent_id, tool=tool_name, allowed=(level == "warn"), reason=reason)
92
- return {"reason": reason, "level": level}
93
-
94
- # Log semantic pass
95
- self.audit_logger.log(
96
- agent_id=agent_id,
97
- tool=tool_name,
98
- allowed=True,
99
- reason=f"Semantic decision: {decision}"
100
- )
101
-
102
- return None
@@ -1,69 +0,0 @@
1
- from sentence_transformers import SentenceTransformer, util
2
- from ..logger.audit import AuditLogger
3
-
4
-
5
- class SentenceTransformerVerifier:
6
- """
7
- Verifies whether a candidate text is semantically aligned with an official policy text.
8
- Uses cosine similarity thresholds to decide: allow, verify, or block.
9
- """
10
-
11
- def __init__(
12
- self,
13
- model_name: str = "all-MiniLM-L6-v2",
14
- low_threshold: float = 0.50,
15
- high_threshold: float = 0.75,
16
- ):
17
- self.embedder = SentenceTransformer(model_name)
18
- self.low = low_threshold
19
- self.high = high_threshold
20
- self.logger = AuditLogger()
21
-
22
- def similarity(self, a: str, b: str) -> float:
23
- """
24
- Computes cosine similarity between two strings.
25
- """
26
- vecs = self.embedder.encode([a, b], convert_to_tensor=True)
27
- return util.cos_sim(vecs[0], vecs[1]).item()
28
-
29
- def decision(self, candidate: str, official: str, agent_id: str = "semantic-check") -> str:
30
- """
31
- Returns one of: "allow", "verify", or "block"
32
- based on similarity thresholds.
33
- Also logs the similarity score and decision.
34
- """
35
- score = self.similarity(candidate, official)
36
-
37
- if score >= self.high:
38
- result = "allow"
39
- elif score <= self.low:
40
- result = "block"
41
- else:
42
- result = "verify"
43
-
44
- self.logger.log(
45
- agent_id=agent_id,
46
- tool="semantic_policy_check",
47
- allowed=(result != "block"),
48
- reason=f"Semantic decision: {result} (score={score:.2f})"
49
- )
50
-
51
- return result
52
-
53
- def explain(self, candidate: str, official: str, agent_id: str = "semantic-check") -> str:
54
- """
55
- Returns a debug-friendly explanation string including the similarity score.
56
- Also logs it to structured audit log for traceability.
57
- """
58
- score = self.similarity(candidate, official)
59
- explanation = f"Semantic similarity = {score:.2f} (low={self.low}, high={self.high})"
60
-
61
- # Structured debug logging (allowed=True since it’s informational)
62
- self.logger.log(
63
- agent_id=agent_id,
64
- tool="semantic_policy_check",
65
- allowed=True,
66
- reason=explanation
67
- )
68
-
69
- return explanation