lakera-red-sdk 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.
@@ -0,0 +1,65 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ # Environment variables
11
+ .env
12
+ .env.*
13
+ !.env.example
14
+ # fine for development, no secrets
15
+ !.env.local
16
+
17
+ node_modules
18
+ dist
19
+ dist-ssr
20
+ *.local
21
+
22
+ # Editor directories and files
23
+ .vscode/*
24
+ !.vscode/extensions.json
25
+ .idea
26
+ .DS_Store
27
+ *.suo
28
+ *.ntvs*
29
+ *.njsproj
30
+ *.sln
31
+ *.sw?
32
+
33
+ # Convex
34
+ .vercel
35
+ # Entire folder is regenerated by `convex codegen` — not committed so files stay fresh
36
+ backend/convex/_generated/
37
+
38
+ # Local copy of import-export data
39
+ /data
40
+
41
+ # Build outputs
42
+ frontend/dist
43
+ backend/dist
44
+
45
+ # TypeScript build info files
46
+ *.tsbuildinfo
47
+
48
+ # Storybook
49
+ *storybook.log
50
+ storybook-static
51
+
52
+ # Playwright
53
+ /test-results/
54
+ /playwright-report/
55
+
56
+ # Output files for testing
57
+ cli/scan/runs/
58
+ cli/scan/judge-comparison.md
59
+ cli/compare/output/
60
+ temp-profile-*
61
+
62
+ # Local AI assistant config
63
+ .claude/
64
+ .specify/
65
+ .specs/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lakera AI (a Check Point company)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: lakera-red-sdk
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for Lakera Red — run adversarial scans against your AI agents and chatbots.
5
+ Project-URL: Homepage, https://red.lakera.ai
6
+ Project-URL: Documentation, https://docs.lakera.ai/docs/red/sdk-quickstart
7
+ Author-email: "Lakera AI (a Check Point company)" <support@lakera.ai>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: adversarial,agent-security,ai-safety,ai-security,lakera,lakera-red,llm,red-teaming,sdk
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Security
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: httpx>=0.27
22
+ Provides-Extra: dev
23
+ Requires-Dist: mypy>=1.10; extra == 'dev'
24
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
25
+ Requires-Dist: pytest>=8; extra == 'dev'
26
+ Requires-Dist: pyyaml>=6; extra == 'dev'
27
+ Requires-Dist: ruff>=0.4; extra == 'dev'
28
+ Requires-Dist: types-pyyaml>=6; extra == 'dev'
29
+ Provides-Extra: yaml
30
+ Requires-Dist: pyyaml>=6; extra == 'yaml'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # Lakera Red SDK
34
+
35
+ Official Python SDK for [Lakera Red](https://red.lakera.ai) — run adversarial scans
36
+ against your AI agents from your own runtime.
37
+
38
+ Lakera is a Check Point company.
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install lakera-red-sdk
44
+ ```
45
+
46
+ ## Quick start
47
+
48
+ ```python
49
+ import asyncio
50
+ from lakera_red_sdk import LakeraRedClient, LakeraRedClientOptions, CreateScanOptions, Session
51
+
52
+
53
+ async def main():
54
+ async with LakeraRedClient(LakeraRedClientOptions(
55
+ api_key="sk_lr_...",
56
+ base_url="https://red-webhooks.lakera.ai",
57
+ log_level="info",
58
+ )) as client:
59
+ scan = await client.create_scan(CreateScanOptions(
60
+ target="My Agent",
61
+ name="Example scan",
62
+ concurrency=1,
63
+ objectives=["safety.hate-speech.1"],
64
+ ))
65
+
66
+ async def handler(session: Session) -> None:
67
+ async for message in session:
68
+ reply = await your_agent(session.id, message.attack)
69
+ await message.respond(reply)
70
+
71
+ await scan.run(handler)
72
+ await scan.write_results("./results.json")
73
+
74
+
75
+ asyncio.run(main())
76
+ ```
77
+
78
+ ## Key concepts
79
+
80
+ | Concept | Description |
81
+ | --------------- | -------------------------------------------------------------------------------------------------------------------------- |
82
+ | **Target** | A named configuration representing the system under test. Created once, reused across scans. |
83
+ | **Session** | A multi-turn conversation. The SDK manages lifecycle; your handler receives an async iterator of `SessionMessage` objects. |
84
+ | **Strategy** | `static` = independent single-turn probes (fast). `crescendo` = adaptive multi-turn attacks. `smoke` = canned probe set. |
85
+ | **Concurrency** | How many sessions run in parallel. For `crescendo`, automatically capped to the number of objectives. |
86
+
87
+ ## Multi-turn sessions with cleanup
88
+
89
+ For stateful agents that accumulate per-session state (conversation history, DB
90
+ connections, etc.), use `try/finally` to clean up:
91
+
92
+ ```python
93
+ async def handler(session: Session) -> None:
94
+ try:
95
+ async for message in session:
96
+ reply = await chatbot(session.id, message.attack)
97
+ await message.respond(reply)
98
+ finally:
99
+ clear_session(session.id)
100
+ ```
101
+
102
+ ## Configuration
103
+
104
+ ```python
105
+ from lakera_red_sdk import LakeraRedClient, LakeraRedClientOptions
106
+
107
+ async with LakeraRedClient(LakeraRedClientOptions(
108
+ api_key="sk_lr_...", # Lakera Red API key
109
+ base_url="https://red-webhooks.lakera.ai", # Lakera Red API endpoint
110
+ log_level="info", # "debug" | "info" | "warn" | "error" | "silent"
111
+ extra_headers={}, # additional HTTP headers (optional)
112
+ logger=custom_logger, # BYO logger implementing the Logger protocol (optional)
113
+ )) as client:
114
+ ...
115
+ ```
116
+
117
+ ## Examples
118
+
119
+ | Example | What it demonstrates |
120
+ | ----------- | --------------------------------------------------------- |
121
+ | **echo** | Simplest integration — echoes attacks back |
122
+ | **chatbot** | Stateful multi-turn chatbot with Claude + session cleanup |
123
+
124
+ ## License
125
+
126
+ [MIT](./LICENSE)
127
+
128
+ ## Development
129
+
130
+ ```bash
131
+ pip install -e ".[dev]"
132
+ mypy src/ # type checking
133
+ ruff check src/ # linting
134
+ ruff format src/ # formatting
135
+ pytest # tests
136
+ ```
@@ -0,0 +1,104 @@
1
+ # Lakera Red SDK
2
+
3
+ Official Python SDK for [Lakera Red](https://red.lakera.ai) — run adversarial scans
4
+ against your AI agents from your own runtime.
5
+
6
+ Lakera is a Check Point company.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pip install lakera-red-sdk
12
+ ```
13
+
14
+ ## Quick start
15
+
16
+ ```python
17
+ import asyncio
18
+ from lakera_red_sdk import LakeraRedClient, LakeraRedClientOptions, CreateScanOptions, Session
19
+
20
+
21
+ async def main():
22
+ async with LakeraRedClient(LakeraRedClientOptions(
23
+ api_key="sk_lr_...",
24
+ base_url="https://red-webhooks.lakera.ai",
25
+ log_level="info",
26
+ )) as client:
27
+ scan = await client.create_scan(CreateScanOptions(
28
+ target="My Agent",
29
+ name="Example scan",
30
+ concurrency=1,
31
+ objectives=["safety.hate-speech.1"],
32
+ ))
33
+
34
+ async def handler(session: Session) -> None:
35
+ async for message in session:
36
+ reply = await your_agent(session.id, message.attack)
37
+ await message.respond(reply)
38
+
39
+ await scan.run(handler)
40
+ await scan.write_results("./results.json")
41
+
42
+
43
+ asyncio.run(main())
44
+ ```
45
+
46
+ ## Key concepts
47
+
48
+ | Concept | Description |
49
+ | --------------- | -------------------------------------------------------------------------------------------------------------------------- |
50
+ | **Target** | A named configuration representing the system under test. Created once, reused across scans. |
51
+ | **Session** | A multi-turn conversation. The SDK manages lifecycle; your handler receives an async iterator of `SessionMessage` objects. |
52
+ | **Strategy** | `static` = independent single-turn probes (fast). `crescendo` = adaptive multi-turn attacks. `smoke` = canned probe set. |
53
+ | **Concurrency** | How many sessions run in parallel. For `crescendo`, automatically capped to the number of objectives. |
54
+
55
+ ## Multi-turn sessions with cleanup
56
+
57
+ For stateful agents that accumulate per-session state (conversation history, DB
58
+ connections, etc.), use `try/finally` to clean up:
59
+
60
+ ```python
61
+ async def handler(session: Session) -> None:
62
+ try:
63
+ async for message in session:
64
+ reply = await chatbot(session.id, message.attack)
65
+ await message.respond(reply)
66
+ finally:
67
+ clear_session(session.id)
68
+ ```
69
+
70
+ ## Configuration
71
+
72
+ ```python
73
+ from lakera_red_sdk import LakeraRedClient, LakeraRedClientOptions
74
+
75
+ async with LakeraRedClient(LakeraRedClientOptions(
76
+ api_key="sk_lr_...", # Lakera Red API key
77
+ base_url="https://red-webhooks.lakera.ai", # Lakera Red API endpoint
78
+ log_level="info", # "debug" | "info" | "warn" | "error" | "silent"
79
+ extra_headers={}, # additional HTTP headers (optional)
80
+ logger=custom_logger, # BYO logger implementing the Logger protocol (optional)
81
+ )) as client:
82
+ ...
83
+ ```
84
+
85
+ ## Examples
86
+
87
+ | Example | What it demonstrates |
88
+ | ----------- | --------------------------------------------------------- |
89
+ | **echo** | Simplest integration — echoes attacks back |
90
+ | **chatbot** | Stateful multi-turn chatbot with Claude + session cleanup |
91
+
92
+ ## License
93
+
94
+ [MIT](./LICENSE)
95
+
96
+ ## Development
97
+
98
+ ```bash
99
+ pip install -e ".[dev]"
100
+ mypy src/ # type checking
101
+ ruff check src/ # linting
102
+ ruff format src/ # formatting
103
+ pytest # tests
104
+ ```
@@ -0,0 +1,81 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "lakera-red-sdk"
7
+ version = "0.1.0"
8
+ description = "Official Python SDK for Lakera Red — run adversarial scans against your AI agents and chatbots."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.11"
12
+ authors = [
13
+ { name = "Lakera AI (a Check Point company)", email = "support@lakera.ai" },
14
+ ]
15
+ keywords = [
16
+ "lakera",
17
+ "lakera-red",
18
+ "ai-security",
19
+ "ai-safety",
20
+ "red-teaming",
21
+ "adversarial",
22
+ "llm",
23
+ "agent-security",
24
+ "sdk",
25
+ ]
26
+ classifiers = [
27
+ "Development Status :: 4 - Beta",
28
+ "Intended Audience :: Developers",
29
+ "License :: OSI Approved :: MIT License",
30
+ "Programming Language :: Python :: 3",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Programming Language :: Python :: 3.13",
34
+ "Topic :: Security",
35
+ "Typing :: Typed",
36
+ ]
37
+ dependencies = [
38
+ "httpx>=0.27",
39
+ ]
40
+
41
+ [project.optional-dependencies]
42
+ yaml = [
43
+ "pyyaml>=6",
44
+ ]
45
+ dev = [
46
+ "pytest>=8",
47
+ "pytest-asyncio>=0.23",
48
+ "mypy>=1.10",
49
+ "ruff>=0.4",
50
+ "pyyaml>=6",
51
+ "types-PyYAML>=6",
52
+ ]
53
+
54
+ [project.scripts]
55
+ lakera-red-sdk-echo = "lakera_red_sdk.examples.echo:run"
56
+ lakera-red-sdk-chatbot = "lakera_red_sdk.examples.chatbot:run"
57
+
58
+ [project.urls]
59
+ Homepage = "https://red.lakera.ai"
60
+ Documentation = "https://docs.lakera.ai/docs/red/sdk-quickstart"
61
+
62
+ [tool.hatch.build.targets.wheel]
63
+ packages = ["src/lakera_red_sdk"]
64
+
65
+ [tool.pytest.ini_options]
66
+ asyncio_mode = "auto"
67
+ testpaths = ["tests"]
68
+
69
+ [tool.mypy]
70
+ strict = true
71
+ python_version = "3.11"
72
+
73
+ [tool.ruff]
74
+ target-version = "py311"
75
+ line-length = 100
76
+
77
+ [tool.ruff.lint]
78
+ select = ["E", "F", "I", "UP"]
79
+
80
+ [tool.uv]
81
+ exclude-newer = "14 days"
@@ -0,0 +1,39 @@
1
+ from lakera_red_sdk.client import LakeraRedClient
2
+ from lakera_red_sdk.logger import CreateLoggerOptions, create_logger, noop_logger
3
+ from lakera_red_sdk.scan import Scan
4
+ from lakera_red_sdk.types import (
5
+ CrescendoStrategyOptions,
6
+ Logger,
7
+ LogLevel,
8
+ ReconContext,
9
+ ScanResultEntry,
10
+ ScanResults,
11
+ Session,
12
+ SessionHandler,
13
+ SessionMessage,
14
+ SmokeStrategyOptions,
15
+ StaticStrategyOptions,
16
+ StrategyName,
17
+ StrategyOptions,
18
+ )
19
+
20
+ __all__ = [
21
+ "LakeraRedClient",
22
+ "CreateLoggerOptions",
23
+ "CrescendoStrategyOptions",
24
+ "Logger",
25
+ "LogLevel",
26
+ "ReconContext",
27
+ "Scan",
28
+ "ScanResultEntry",
29
+ "ScanResults",
30
+ "Session",
31
+ "SessionHandler",
32
+ "SessionMessage",
33
+ "SmokeStrategyOptions",
34
+ "StaticStrategyOptions",
35
+ "StrategyName",
36
+ "StrategyOptions",
37
+ "create_logger",
38
+ "noop_logger",
39
+ ]
@@ -0,0 +1,134 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import time
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+ from lakera_red_sdk.types import Logger, ReconContext
11
+
12
+ INITIAL_BACKOFF_S = 1.0
13
+ MAX_BACKOFF_S = 5.0
14
+ MAX_RETRIES = 5
15
+ DEFAULT_HTTP_TIMEOUT_S = 30.0
16
+
17
+
18
+ def load_app_context_file(file_path: Path) -> ReconContext:
19
+ try:
20
+ import yaml
21
+ except ImportError as err:
22
+ raise ImportError(
23
+ "pyyaml is required for app_context_file support: pip install lakera-red-sdk[yaml]"
24
+ ) from err
25
+
26
+ resolved = file_path.resolve()
27
+ content = resolved.read_text()
28
+ parsed = yaml.safe_load(content)
29
+
30
+ if not isinstance(parsed, dict):
31
+ raise ValueError(f"app_context_file '{file_path}' must contain a YAML object")
32
+
33
+ required_keys = ["appDescription", "allowedActions", "forbiddenActions"]
34
+ for key in required_keys:
35
+ if not isinstance(parsed.get(key), str):
36
+ raise ValueError(
37
+ f"app_context_file '{file_path}' is missing required string field '{key}'"
38
+ )
39
+
40
+ return ReconContext(
41
+ app_description=parsed["appDescription"],
42
+ allowed_actions=parsed["allowedActions"],
43
+ forbidden_actions=parsed["forbiddenActions"],
44
+ )
45
+
46
+
47
+ def safe_json(res: httpx.Response) -> dict[str, Any]:
48
+ try:
49
+ data: dict[str, Any] = res.json()
50
+ return data
51
+ except Exception:
52
+ return {}
53
+
54
+
55
+ async def fetch_with_retry(
56
+ url: str,
57
+ *,
58
+ method: str = "GET",
59
+ headers: dict[str, str],
60
+ json_body: dict[str, Any] | None = None,
61
+ client: httpx.AsyncClient | None = None,
62
+ timeout: float = DEFAULT_HTTP_TIMEOUT_S,
63
+ max_retries: int = MAX_RETRIES,
64
+ logger: Logger | None = None,
65
+ ) -> httpx.Response:
66
+ last_error: Exception | None = None
67
+ backoff_s = INITIAL_BACKOFF_S
68
+ path = httpx.URL(url).path
69
+ owns_client = client is None
70
+
71
+ for attempt in range(max_retries + 1):
72
+ start = time.monotonic()
73
+ try:
74
+ http = client or httpx.AsyncClient(timeout=timeout)
75
+ try:
76
+ res = await http.request(
77
+ method,
78
+ url,
79
+ headers=headers,
80
+ json=json_body,
81
+ timeout=timeout,
82
+ )
83
+ finally:
84
+ if owns_client:
85
+ await http.aclose()
86
+ elapsed_ms = int((time.monotonic() - start) * 1000)
87
+ if res.is_success or (res.status_code < 500 and res.status_code != 429):
88
+ if logger:
89
+ logger.debug(
90
+ "http response",
91
+ {
92
+ "method": method,
93
+ "path": path,
94
+ "status": res.status_code,
95
+ "elapsed_ms": elapsed_ms,
96
+ "attempt": attempt + 1,
97
+ },
98
+ )
99
+ return res
100
+ last_error = RuntimeError(f"HTTP {res.status_code}")
101
+ except Exception as err:
102
+ last_error = err
103
+
104
+ elapsed_ms = int((time.monotonic() - start) * 1000)
105
+ if attempt < max_retries:
106
+ if logger:
107
+ logger.debug(
108
+ "http retry",
109
+ {
110
+ "method": method,
111
+ "path": path,
112
+ "attempt": attempt + 1,
113
+ "max_retries": max_retries,
114
+ "elapsed_ms": elapsed_ms,
115
+ "backoff_ms": int(backoff_s * 1000),
116
+ "error": str(last_error),
117
+ },
118
+ )
119
+ await asyncio.sleep(backoff_s)
120
+ backoff_s = min(backoff_s * 2, MAX_BACKOFF_S)
121
+ else:
122
+ if logger:
123
+ logger.warn(
124
+ "http exhausted retries",
125
+ {
126
+ "method": method,
127
+ "path": path,
128
+ "attempts": attempt + 1,
129
+ "elapsed_ms": elapsed_ms,
130
+ "error": str(last_error),
131
+ },
132
+ )
133
+
134
+ raise last_error # type: ignore[misc]