infralayer 0.2.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.
- infralayer/__init__.py +107 -0
- infralayer/cli.py +285 -0
- infralayer/client.py +729 -0
- infralayer/core.py +588 -0
- infralayer/exceptions.py +179 -0
- infralayer/factory.py +26 -0
- infralayer/types.py +253 -0
- infralayer-0.2.0.dist-info/METADATA +373 -0
- infralayer-0.2.0.dist-info/RECORD +11 -0
- infralayer-0.2.0.dist-info/WHEEL +4 -0
- infralayer-0.2.0.dist-info/entry_points.txt +3 -0
infralayer/__init__.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
InfraLayer Python SDK
|
|
3
|
+
=====================
|
|
4
|
+
|
|
5
|
+
Pre-LLM security filtering, sanitization, and telemetry client.
|
|
6
|
+
Supports both local (in-process) and remote (API) modes.
|
|
7
|
+
|
|
8
|
+
Quick Start:
|
|
9
|
+
from infralayer import InfraLayer, create_local_infralayer, create_remote_infralayer
|
|
10
|
+
|
|
11
|
+
# Local mode (zero-dependency, in-process)
|
|
12
|
+
infra = create_local_infralayer()
|
|
13
|
+
result = await infra.sanitize(
|
|
14
|
+
"What is our PTO policy?",
|
|
15
|
+
user_context={"user_id": "user-123", "tenant_id": "tenant-456", "role": "employee", "permissions": ["read:documents"]}
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Remote mode (API)
|
|
19
|
+
infra = create_remote_infralayer(
|
|
20
|
+
api_url="https://api.infralayer.io/v1",
|
|
21
|
+
api_key="il_api_abc123...",
|
|
22
|
+
default_user_context={"user_id": "service-user", "tenant_id": "tenant-456", "role": "system", "permissions": ["read:all"]}
|
|
23
|
+
)
|
|
24
|
+
result = await infra.sanitize("What is our PTO policy?")
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from .client import InfraLayer, create_infralayer
|
|
28
|
+
from .factory import create_local_infralayer, create_remote_infralayer
|
|
29
|
+
from .types import (
|
|
30
|
+
UserContext,
|
|
31
|
+
RawContext,
|
|
32
|
+
SanitizedContext,
|
|
33
|
+
SanitizeOptions,
|
|
34
|
+
SanitizeOptionsInner,
|
|
35
|
+
SanitizeResult,
|
|
36
|
+
PermissionEvaluation,
|
|
37
|
+
InjectionDetail,
|
|
38
|
+
InjectionPattern,
|
|
39
|
+
SecurityFlag,
|
|
40
|
+
SanitizeTelemetry,
|
|
41
|
+
SanitizationReason,
|
|
42
|
+
InjectionCategory,
|
|
43
|
+
InjectionSeverity,
|
|
44
|
+
SecurityFlagSeverity,
|
|
45
|
+
InfraLayerConfig,
|
|
46
|
+
SanitizeRequestPayload,
|
|
47
|
+
SanitizeResponse,
|
|
48
|
+
AuthRequestPayload,
|
|
49
|
+
AuthResponse,
|
|
50
|
+
HealthResponse,
|
|
51
|
+
)
|
|
52
|
+
from .exceptions import (
|
|
53
|
+
InfraLayerError,
|
|
54
|
+
AuthenticationError,
|
|
55
|
+
RateLimitError,
|
|
56
|
+
ValidationError,
|
|
57
|
+
SanitizationError,
|
|
58
|
+
PermissionDeniedError,
|
|
59
|
+
ConnectionError,
|
|
60
|
+
TimeoutError,
|
|
61
|
+
LocalModeError,
|
|
62
|
+
RemoteModeError,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
__version__ = "0.2.0"
|
|
66
|
+
|
|
67
|
+
__all__ = [
|
|
68
|
+
# Main client
|
|
69
|
+
"InfraLayer",
|
|
70
|
+
# Factory functions
|
|
71
|
+
"create_infralayer",
|
|
72
|
+
"create_local_infralayer",
|
|
73
|
+
"create_remote_infralayer",
|
|
74
|
+
# Types
|
|
75
|
+
"UserContext",
|
|
76
|
+
"RawContext",
|
|
77
|
+
"SanitizedContext",
|
|
78
|
+
"SanitizeOptions",
|
|
79
|
+
"SanitizeOptionsInner",
|
|
80
|
+
"SanitizeResult",
|
|
81
|
+
"PermissionEvaluation",
|
|
82
|
+
"InjectionDetail",
|
|
83
|
+
"InjectionPattern",
|
|
84
|
+
"SecurityFlag",
|
|
85
|
+
"SanitizeTelemetry",
|
|
86
|
+
"SanitizationReason",
|
|
87
|
+
"InjectionCategory",
|
|
88
|
+
"InjectionSeverity",
|
|
89
|
+
"SecurityFlagSeverity",
|
|
90
|
+
"InfraLayerConfig",
|
|
91
|
+
"SanitizeRequestPayload",
|
|
92
|
+
"SanitizeResponse",
|
|
93
|
+
"AuthRequestPayload",
|
|
94
|
+
"AuthResponse",
|
|
95
|
+
"HealthResponse",
|
|
96
|
+
# Exceptions
|
|
97
|
+
"InfraLayerError",
|
|
98
|
+
"AuthenticationError",
|
|
99
|
+
"RateLimitError",
|
|
100
|
+
"ValidationError",
|
|
101
|
+
"SanitizationError",
|
|
102
|
+
"PermissionDeniedError",
|
|
103
|
+
"ConnectionError",
|
|
104
|
+
"TimeoutError",
|
|
105
|
+
"LocalModeError",
|
|
106
|
+
"RemoteModeError",
|
|
107
|
+
]
|
infralayer/cli.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""
|
|
2
|
+
InfraLayer Python SDK - CLI
|
|
3
|
+
============================
|
|
4
|
+
|
|
5
|
+
Command-line interface for InfraLayer.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
import typer
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.json import JSON
|
|
18
|
+
from rich.panel import Panel
|
|
19
|
+
from rich.syntax import Syntax
|
|
20
|
+
|
|
21
|
+
from . import (
|
|
22
|
+
create_local_infralayer,
|
|
23
|
+
create_remote_infralayer,
|
|
24
|
+
InfraLayer,
|
|
25
|
+
UserContext,
|
|
26
|
+
RawContext,
|
|
27
|
+
SanitizeOptionsInner,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
app = typer.Typer(
|
|
31
|
+
name="infralayer",
|
|
32
|
+
help="InfraLayer CLI - Pre-LLM security filtering and sanitization",
|
|
33
|
+
add_completion=False,
|
|
34
|
+
)
|
|
35
|
+
console = Console()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@app.command()
|
|
39
|
+
def sanitize(
|
|
40
|
+
text: str = typer.Argument(..., help="Text to sanitize"),
|
|
41
|
+
user_id: str = typer.Option(..., "--user-id", "-u", help="User ID"),
|
|
42
|
+
tenant_id: str = typer.Option(..., "--tenant-id", "-t", help="Tenant ID"),
|
|
43
|
+
role: str = typer.Option("employee", "--role", "-r", help="User role"),
|
|
44
|
+
permissions: str = typer.Option("", "--permissions", "-p", help="Comma-separated permissions"),
|
|
45
|
+
department: Optional[str] = typer.Option(None, "--department", "-d", help="User department"),
|
|
46
|
+
access_level: Optional[int] = typer.Option(None, "--access-level", "-a", help="User access level"),
|
|
47
|
+
remote: bool = typer.Option(False, "--remote", help="Use remote API mode"),
|
|
48
|
+
api_url: Optional[str] = typer.Option(None, "--api-url", help="Remote API URL"),
|
|
49
|
+
api_key: Optional[str] = typer.Option(None, "--api-key", help="Remote API key"),
|
|
50
|
+
strict: bool = typer.Option(False, "--strict", help="Enable strict mode"),
|
|
51
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Sanitize text using InfraLayer."""
|
|
54
|
+
user_context = UserContext(
|
|
55
|
+
user_id=user_id,
|
|
56
|
+
tenant_id=tenant_id,
|
|
57
|
+
role=role,
|
|
58
|
+
department=department,
|
|
59
|
+
access_level=access_level,
|
|
60
|
+
permissions=permissions.split(",") if permissions else [],
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if remote:
|
|
64
|
+
if not api_url or not api_key:
|
|
65
|
+
console.print("[red]Error: --api-url and --api-key required for remote mode[/red]")
|
|
66
|
+
sys.exit(1)
|
|
67
|
+
infra = create_remote_infralayer(api_url, api_key, user_context)
|
|
68
|
+
else:
|
|
69
|
+
infra = create_local_infralayer()
|
|
70
|
+
|
|
71
|
+
options = SanitizeOptionsInner(strict_mode=strict)
|
|
72
|
+
|
|
73
|
+
async def run() -> None:
|
|
74
|
+
try:
|
|
75
|
+
result = await infra.sanitize(text, user_context, options)
|
|
76
|
+
if json_output:
|
|
77
|
+
console.print(JSON.from_data(result.model_dump()))
|
|
78
|
+
else:
|
|
79
|
+
_print_result(result)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
82
|
+
sys.exit(1)
|
|
83
|
+
finally:
|
|
84
|
+
await infra.close()
|
|
85
|
+
|
|
86
|
+
asyncio.run(run())
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@app.command()
|
|
90
|
+
def scan(
|
|
91
|
+
text: str = typer.Argument(..., help="Text to scan for injections"),
|
|
92
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
93
|
+
) -> None:
|
|
94
|
+
"""Scan text for injection attempts (local mode only)."""
|
|
95
|
+
infra = create_local_infralayer()
|
|
96
|
+
|
|
97
|
+
async def run() -> None:
|
|
98
|
+
try:
|
|
99
|
+
details = await infra.scan_for_injections(text)
|
|
100
|
+
if json_output:
|
|
101
|
+
console.print(JSON.from_data([d.model_dump() for d in details]))
|
|
102
|
+
else:
|
|
103
|
+
_print_injection_details(details)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
finally:
|
|
108
|
+
await infra.close()
|
|
109
|
+
|
|
110
|
+
asyncio.run(run())
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@app.command()
|
|
114
|
+
def check_permission(
|
|
115
|
+
user_id: str = typer.Option(..., "--user-id", "-u", help="User ID"),
|
|
116
|
+
tenant_id: str = typer.Option(..., "--tenant-id", "-t", help="Tenant ID"),
|
|
117
|
+
role: str = typer.Option("employee", "--role", "-r", help="User role"),
|
|
118
|
+
permissions: str = typer.Option("", "--permissions", "-p", help="Comma-separated user permissions"),
|
|
119
|
+
required_permissions: str = typer.Option(..., "--required", help="Comma-separated required permissions"),
|
|
120
|
+
required_role: Optional[str] = typer.Option(None, "--required-role", help="Required role"),
|
|
121
|
+
required_access_level: Optional[int] = typer.Option(None, "--required-access-level", help="Required access level"),
|
|
122
|
+
required_department: Optional[str] = typer.Option(None, "--required-department", help="Required department"),
|
|
123
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
124
|
+
) -> None:
|
|
125
|
+
"""Check if user has permission for specific action (local mode only)."""
|
|
126
|
+
user_context = UserContext(
|
|
127
|
+
user_id=user_id,
|
|
128
|
+
tenant_id=tenant_id,
|
|
129
|
+
role=role,
|
|
130
|
+
permissions=permissions.split(",") if permissions else [],
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
infra = create_local_infralayer()
|
|
134
|
+
|
|
135
|
+
async def run() -> None:
|
|
136
|
+
try:
|
|
137
|
+
result = await infra.check_permission(
|
|
138
|
+
user_context,
|
|
139
|
+
required_permissions.split(",") if required_permissions else [],
|
|
140
|
+
required_role,
|
|
141
|
+
required_access_level,
|
|
142
|
+
required_department,
|
|
143
|
+
)
|
|
144
|
+
if json_output:
|
|
145
|
+
console.print(JSON.from_data(result.model_dump()))
|
|
146
|
+
else:
|
|
147
|
+
_print_permission_result(result)
|
|
148
|
+
except Exception as e:
|
|
149
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
150
|
+
sys.exit(1)
|
|
151
|
+
finally:
|
|
152
|
+
await infra.close()
|
|
153
|
+
|
|
154
|
+
asyncio.run(run())
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@app.command()
|
|
158
|
+
def health(
|
|
159
|
+
remote: bool = typer.Option(False, "--remote", help="Check remote API health"),
|
|
160
|
+
api_url: Optional[str] = typer.Option(None, "--api-url", help="Remote API URL"),
|
|
161
|
+
api_key: Optional[str] = typer.Option(None, "--api-key", help="Remote API key"),
|
|
162
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
163
|
+
) -> None:
|
|
164
|
+
"""Check health status."""
|
|
165
|
+
if remote:
|
|
166
|
+
if not api_url or not api_key:
|
|
167
|
+
console.print("[red]Error: --api-url and --api-key required for remote mode[/red]")
|
|
168
|
+
sys.exit(1)
|
|
169
|
+
infra = create_remote_infralayer(api_url, api_key)
|
|
170
|
+
else:
|
|
171
|
+
infra = create_local_infralayer()
|
|
172
|
+
|
|
173
|
+
async def run() -> None:
|
|
174
|
+
try:
|
|
175
|
+
result = await infra.health_check()
|
|
176
|
+
if json_output:
|
|
177
|
+
console.print(JSON.from_data(result.model_dump()))
|
|
178
|
+
else:
|
|
179
|
+
_print_health(result)
|
|
180
|
+
except Exception as e:
|
|
181
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
182
|
+
sys.exit(1)
|
|
183
|
+
finally:
|
|
184
|
+
await infra.close()
|
|
185
|
+
|
|
186
|
+
asyncio.run(run())
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@app.command()
|
|
190
|
+
def version() -> None:
|
|
191
|
+
"""Show version information."""
|
|
192
|
+
from . import __version__
|
|
193
|
+
console.print(f"InfraLayer Python SDK v{__version__}")
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _print_result(result) -> None:
|
|
197
|
+
"""Print sanitization result in human-readable format."""
|
|
198
|
+
if result.sanitized_context:
|
|
199
|
+
ctx = result.sanitized_context
|
|
200
|
+
console.print(Panel(
|
|
201
|
+
f"[green]Success[/green]\n\n"
|
|
202
|
+
f"Original length: {ctx.original_length}\n"
|
|
203
|
+
f"Sanitized length: {ctx.sanitized_length}\n"
|
|
204
|
+
f"Segments removed: {len(ctx.removed_segments)}\n"
|
|
205
|
+
f"Injections detected: {'Yes' if ctx.injection_detected else 'No'}\n"
|
|
206
|
+
f"Processing time: {result.telemetry.processing_time_ms}ms",
|
|
207
|
+
title="Sanitization Result",
|
|
208
|
+
border_style="green",
|
|
209
|
+
))
|
|
210
|
+
|
|
211
|
+
if ctx.removed_segments:
|
|
212
|
+
console.print("\n[bold]Removed Segments:[/bold]")
|
|
213
|
+
for seg in ctx.removed_segments:
|
|
214
|
+
console.print(f" [{seg.reason.value}] {seg.original_text[:50]}...")
|
|
215
|
+
|
|
216
|
+
if ctx.injection_details:
|
|
217
|
+
console.print("\n[bold]Injection Details:[/bold]")
|
|
218
|
+
for det in ctx.injection_details:
|
|
219
|
+
console.print(f" [{det.severity.value}] {det.category.value}: {det.match}")
|
|
220
|
+
|
|
221
|
+
console.print("\n[bold]Sanitized Text:[/bold]")
|
|
222
|
+
console.print(Syntax(ctx.text, "text", theme="monokai", line_numbers=False))
|
|
223
|
+
|
|
224
|
+
if result.permission_evaluation:
|
|
225
|
+
perm = result.permission_evaluation
|
|
226
|
+
style = "green" if perm.allowed else "red"
|
|
227
|
+
console.print(Panel(
|
|
228
|
+
f"Allowed: {'Yes' if perm.allowed else 'No'}\n"
|
|
229
|
+
f"Reason: {perm.reason}\n"
|
|
230
|
+
f"Required: {', '.join(perm.required_permissions) or 'None'}\n"
|
|
231
|
+
f"User has: {', '.join(perm.user_permissions) or 'None'}",
|
|
232
|
+
title="Permission Evaluation",
|
|
233
|
+
border_style=style,
|
|
234
|
+
))
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _print_injection_details(details: list) -> None:
|
|
238
|
+
"""Print injection scan results."""
|
|
239
|
+
if not details:
|
|
240
|
+
console.print(Panel("No injections detected", title="Scan Result", border_style="green"))
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
console.print(Panel(
|
|
244
|
+
f"[red]Found {len(details)} injection attempt(s)[/red]",
|
|
245
|
+
title="Injection Scan",
|
|
246
|
+
border_style="red",
|
|
247
|
+
))
|
|
248
|
+
for det in details:
|
|
249
|
+
console.print(f" [{det.severity.value}] {det.category.value}: {det.match} (pos: {det.position})")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _print_permission_result(result) -> None:
|
|
253
|
+
"""Print permission check result."""
|
|
254
|
+
style = "green" if result.allowed else "red"
|
|
255
|
+
console.print(Panel(
|
|
256
|
+
f"Allowed: {'Yes' if result.allowed else 'No'}\n"
|
|
257
|
+
f"Reason: {result.reason}\n"
|
|
258
|
+
f"Required permissions: {', '.join(result.required_permissions) or 'None'}\n"
|
|
259
|
+
f"User permissions: {', '.join(result.user_permissions) or 'None'}\n"
|
|
260
|
+
f"Missing: {', '.join(result.missing_permissions or []) or 'None'}",
|
|
261
|
+
title="Permission Check",
|
|
262
|
+
border_style=style,
|
|
263
|
+
))
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _print_health(result) -> None:
|
|
267
|
+
"""Print health check result."""
|
|
268
|
+
style = "green" if result.status == "healthy" else ("yellow" if result.status == "degraded" else "red")
|
|
269
|
+
console.print(Panel(
|
|
270
|
+
f"Status: {result.status}\n"
|
|
271
|
+
f"Version: {result.version}\n"
|
|
272
|
+
f"Timestamp: {result.timestamp}\n"
|
|
273
|
+
f"Checks: {', '.join(f'{k}: {v}' for k, v in result.checks.items())}",
|
|
274
|
+
title="Health Check",
|
|
275
|
+
border_style=style,
|
|
276
|
+
))
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def main() -> None:
|
|
280
|
+
"""Entry point for CLI."""
|
|
281
|
+
app()
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
if __name__ == "__main__":
|
|
285
|
+
main()
|