canary-ops-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.
- canary_ops_sdk-0.1.0/.github/workflows/ci.yml +59 -0
- canary_ops_sdk-0.1.0/.gitignore +20 -0
- canary_ops_sdk-0.1.0/CHANGELOG.md +23 -0
- canary_ops_sdk-0.1.0/PKG-INFO +232 -0
- canary_ops_sdk-0.1.0/README.md +206 -0
- canary_ops_sdk-0.1.0/pyproject.toml +56 -0
- canary_ops_sdk-0.1.0/src/canary_ops/__init__.py +65 -0
- canary_ops_sdk-0.1.0/src/canary_ops/_client.py +595 -0
- canary_ops_sdk-0.1.0/src/canary_ops/_patterns.py +273 -0
- canary_ops_sdk-0.1.0/src/canary_ops/_pricing.py +49 -0
- canary_ops_sdk-0.1.0/src/canary_ops/_redact.py +84 -0
- canary_ops_sdk-0.1.0/src/canary_ops/_transport.py +116 -0
- canary_ops_sdk-0.1.0/src/canary_ops/_types.py +161 -0
- canary_ops_sdk-0.1.0/src/canary_ops/instrument/__init__.py +4 -0
- canary_ops_sdk-0.1.0/src/canary_ops/instrument/_anthropic.py +210 -0
- canary_ops_sdk-0.1.0/src/canary_ops/instrument/_openai.py +214 -0
- canary_ops_sdk-0.1.0/tests/__init__.py +0 -0
- canary_ops_sdk-0.1.0/tests/test_client.py +212 -0
- canary_ops_sdk-0.1.0/tests/test_instrument.py +172 -0
- canary_ops_sdk-0.1.0/tests/test_patterns.py +116 -0
- canary_ops_sdk-0.1.0/tests/test_pricing.py +32 -0
- canary_ops_sdk-0.1.0/tests/test_redact.py +73 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, develop]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, develop]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Python ${{ matrix.python-version }}
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: ${{ matrix.python-version }}
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install --upgrade pip
|
|
29
|
+
pip install -e .
|
|
30
|
+
pip install pytest pytest-asyncio
|
|
31
|
+
|
|
32
|
+
- name: Run tests
|
|
33
|
+
run: pytest tests/ -v
|
|
34
|
+
|
|
35
|
+
publish:
|
|
36
|
+
name: Publish to PyPI
|
|
37
|
+
needs: test
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
|
40
|
+
environment: pypi
|
|
41
|
+
permissions:
|
|
42
|
+
id-token: write # for trusted publishing
|
|
43
|
+
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/checkout@v4
|
|
46
|
+
|
|
47
|
+
- name: Set up Python
|
|
48
|
+
uses: actions/setup-python@v5
|
|
49
|
+
with:
|
|
50
|
+
python-version: "3.12"
|
|
51
|
+
|
|
52
|
+
- name: Install build tools
|
|
53
|
+
run: pip install build
|
|
54
|
+
|
|
55
|
+
- name: Build distribution
|
|
56
|
+
run: python -m build
|
|
57
|
+
|
|
58
|
+
- name: Publish to PyPI
|
|
59
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Initial release: Python SDK for Canary AI Security.
|
|
8
|
+
- **`Canary`** class — main client with background batch flushing, redaction, firewall, and cost auto-computation.
|
|
9
|
+
- **`track_prompt()`**, **`track_response()`**, **`track_tool_call()`**, **`track_error()`** — telemetry event tracking.
|
|
10
|
+
- **`scan(prompt)`** — local + remote prompt firewall returning a `ScanResult`.
|
|
11
|
+
- **`guard(prompt)`** — raises `CanaryBlockedError` on blocked prompts.
|
|
12
|
+
- **`scan_response(text)`** — output-side guardrails: secret/PII regex + remote LLM-as-judge.
|
|
13
|
+
- **`plant_token()`** — synthetic canary token generation; auto-detected by `scan_response`.
|
|
14
|
+
- **`scan_context(docs)`** — parallel RAG document scanning (OWASP LLM04).
|
|
15
|
+
- **`trace(name)`** — context manager for recording arbitrary async steps as spans.
|
|
16
|
+
- **`wrap_fetch(fn)`** — decorator wrapping any callable with latency + error tracking.
|
|
17
|
+
- **`wrap_openai(client)`** — instrument `openai.OpenAI` / `openai.AsyncOpenAI` (sync + streaming + async).
|
|
18
|
+
- **`wrap_anthropic(client)`** — instrument `anthropic.Anthropic` / `anthropic.AsyncAnthropic` (sync + streaming + async).
|
|
19
|
+
- **19 local threat patterns** — ported from JS SDK: injection, jailbreak, system-prompt extraction, data exfiltration, leet-speak / encoding evasion.
|
|
20
|
+
- **Redaction engine** — masks/hashes/removes secrets (OpenAI, AWS, GitHub, Anthropic, Slack, Google, bearer tokens, JWTs, private keys) and optional PII (email, phone, SSN, credit card, IP).
|
|
21
|
+
- **Auto cost** — built-in pricing table for OpenAI, Anthropic, Gemini, Bedrock, and Mistral models.
|
|
22
|
+
- **Resilient transport** — `urllib`-based HTTP with retry + exponential backoff + jitter + circuit breaker.
|
|
23
|
+
- Zero runtime dependencies. Python 3.8+.
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: canary-ops-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The Operating System for AI Security — monitor, secure, and audit every AI agent and LLM application in production.
|
|
5
|
+
Project-URL: Homepage, https://canary-ops.base44.app
|
|
6
|
+
Project-URL: Repository, https://github.com/CryptoSuess/canary-sdk-python
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/CryptoSuess/canary-sdk-python/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/CryptoSuess/canary-sdk-python/blob/main/CHANGELOG.md
|
|
9
|
+
Author: CryptoSuess & Txnchi
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: agent-monitoring,ai-firewall,ai-security,canary,llm,llmops,observability,prompt-injection,telemetry
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Security
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# canary-ops-sdk
|
|
28
|
+
|
|
29
|
+
**The Operating System for AI Security** — Python SDK for monitoring, securing, and auditing every AI agent and LLM application in production.
|
|
30
|
+
|
|
31
|
+
Zero-dependency telemetry, redaction, and firewall SDK. Works with any Python LLM library.
|
|
32
|
+
|
|
33
|
+
[](https://pypi.org/project/canary-ops-sdk/)
|
|
34
|
+
[](https://pypi.org/project/canary-ops-sdk/)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install canary-ops-sdk
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
No runtime dependencies. Python 3.8+.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from canary_ops import Canary
|
|
52
|
+
|
|
53
|
+
canary = Canary(
|
|
54
|
+
api_key="sk-canary-...", # from your Canary dashboard
|
|
55
|
+
agent_id="my-agent",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Manual tracking
|
|
59
|
+
canary.track_prompt("What is the capital of France?", model="gpt-4o")
|
|
60
|
+
canary.track_response("Paris.", model="gpt-4o", tokens_in=10, tokens_out=3)
|
|
61
|
+
canary.track_tool_call("web_search", {"query": "Paris"}, {"results": [...]})
|
|
62
|
+
|
|
63
|
+
canary.flush() # or call shutdown() at process exit
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## OpenAI instrumentation
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from openai import OpenAI
|
|
72
|
+
from canary_ops import Canary
|
|
73
|
+
|
|
74
|
+
canary = Canary(api_key="sk-canary-...", agent_id="my-agent")
|
|
75
|
+
client = canary.wrap_openai(OpenAI())
|
|
76
|
+
|
|
77
|
+
# All chat completions are now tracked automatically
|
|
78
|
+
response = client.chat.completions.create(
|
|
79
|
+
model="gpt-4o",
|
|
80
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Streaming is also supported — just pass `stream=True`.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Anthropic instrumentation
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import anthropic
|
|
92
|
+
from canary_ops import Canary
|
|
93
|
+
|
|
94
|
+
canary = Canary(api_key="sk-canary-...", agent_id="my-agent")
|
|
95
|
+
client = canary.wrap_anthropic(anthropic.Anthropic())
|
|
96
|
+
|
|
97
|
+
response = client.messages.create(
|
|
98
|
+
model="claude-opus-4-5",
|
|
99
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
100
|
+
max_tokens=256,
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Prompt firewall
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from canary_ops import Canary, CanaryBlockedError, ScanOptions
|
|
110
|
+
|
|
111
|
+
canary = Canary(api_key="sk-canary-...", agent_id="my-agent")
|
|
112
|
+
|
|
113
|
+
# scan() — returns a ScanResult, never raises
|
|
114
|
+
result = canary.scan(user_prompt, ScanOptions(remote=False))
|
|
115
|
+
if not result.allowed:
|
|
116
|
+
return "I can't help with that."
|
|
117
|
+
|
|
118
|
+
# guard() — raises CanaryBlockedError if the prompt is blocked
|
|
119
|
+
try:
|
|
120
|
+
safe_prompt = canary.guard(user_prompt)
|
|
121
|
+
except CanaryBlockedError as e:
|
|
122
|
+
print(f"Blocked: {e.scan_result.threats}")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Output scanning
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
# Scan a response for leaked secrets, PII, or exfiltration
|
|
131
|
+
from canary_ops import ScanResponseOptions
|
|
132
|
+
|
|
133
|
+
result = canary.scan_response(
|
|
134
|
+
llm_output,
|
|
135
|
+
ScanResponseOptions(throw_on_unsafe=True),
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Canary tokens
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
# Plant a decoy credential in your agent's context
|
|
145
|
+
token = canary.plant_token()
|
|
146
|
+
system_prompt = f"Company API key: {token.token_value}\n{your_context}"
|
|
147
|
+
|
|
148
|
+
# If the token appears in any response, Canary fires a data_exfiltration alert
|
|
149
|
+
result = canary.scan_response(response_text)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## RAG context scanning
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from canary_ops import ScanContextOptions
|
|
158
|
+
|
|
159
|
+
docs = retriever.get_relevant_documents(query)
|
|
160
|
+
results = canary.scan_context(
|
|
161
|
+
[d.page_content for d in docs],
|
|
162
|
+
ScanContextOptions(concurrency=5),
|
|
163
|
+
)
|
|
164
|
+
safe_docs = [d for d, r in zip(docs, results) if r.allowed]
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Configuration reference
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from canary_ops import Canary, GuardConfig, RedactionConfig, RetryConfig
|
|
173
|
+
|
|
174
|
+
canary = Canary(
|
|
175
|
+
api_key="sk-canary-...",
|
|
176
|
+
agent_id="my-agent",
|
|
177
|
+
# Privacy
|
|
178
|
+
capture_content=True, # set False for metadata-only mode
|
|
179
|
+
max_content_length=10_000, # truncate long prompts
|
|
180
|
+
redaction=RedactionConfig(
|
|
181
|
+
secrets=True, # strip API keys, tokens, private keys
|
|
182
|
+
pii=True, # strip emails, phones, SSNs, credit cards
|
|
183
|
+
mode="mask", # "mask" | "hash" | "remove"
|
|
184
|
+
),
|
|
185
|
+
sample_rate=0.5, # only send 50% of events
|
|
186
|
+
# Firewall
|
|
187
|
+
guard=GuardConfig(
|
|
188
|
+
local=True, # run in-process heuristics
|
|
189
|
+
remote=True, # call the remote deep scanner
|
|
190
|
+
block_on_local=True, # short-circuit on high-confidence local hit
|
|
191
|
+
responses=False, # auto-scan every LLM response
|
|
192
|
+
),
|
|
193
|
+
fail_open=True, # allow prompts if the remote scanner is unreachable
|
|
194
|
+
# Resilience
|
|
195
|
+
timeout_ms=10_000,
|
|
196
|
+
retry=RetryConfig(retries=3, backoff_ms=250),
|
|
197
|
+
# Batching
|
|
198
|
+
flush_interval_ms=5_000, # drain queue every 5 s
|
|
199
|
+
max_batch_size=20, # flush immediately when queue hits 20
|
|
200
|
+
# Hooks
|
|
201
|
+
before_send=lambda event: event, # inspect/mutate/drop events
|
|
202
|
+
on_error=lambda exc: print(exc), # custom error handler
|
|
203
|
+
# Tagging
|
|
204
|
+
environment="production",
|
|
205
|
+
release="v1.2.3",
|
|
206
|
+
debug=False,
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Error reference
|
|
213
|
+
|
|
214
|
+
| Exception | When it's raised |
|
|
215
|
+
|---|---|
|
|
216
|
+
| `CanaryBlockedError` | `guard()` or `scan_response(..., throw_on_unsafe=True)` when the firewall blocks |
|
|
217
|
+
| `CanaryApiError` | `plant_token()` when the API call fails |
|
|
218
|
+
| `CanaryNotInitializedError` | Calling SDK methods before `Canary(...)` is constructed |
|
|
219
|
+
|
|
220
|
+
`CanaryBlockedError` carries a `.scan_result` attribute with the full `ScanResult`.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Async support
|
|
225
|
+
|
|
226
|
+
All wrapper patching detects whether the underlying client's `create` method is a coroutine and patches it with an async wrapper automatically. The `Canary` client itself is thread-safe; background flushing runs on a daemon thread.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
MIT © CryptoSuess & Txnchi
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# canary-ops-sdk
|
|
2
|
+
|
|
3
|
+
**The Operating System for AI Security** — Python SDK for monitoring, securing, and auditing every AI agent and LLM application in production.
|
|
4
|
+
|
|
5
|
+
Zero-dependency telemetry, redaction, and firewall SDK. Works with any Python LLM library.
|
|
6
|
+
|
|
7
|
+
[](https://pypi.org/project/canary-ops-sdk/)
|
|
8
|
+
[](https://pypi.org/project/canary-ops-sdk/)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install canary-ops-sdk
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
No runtime dependencies. Python 3.8+.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from canary_ops import Canary
|
|
26
|
+
|
|
27
|
+
canary = Canary(
|
|
28
|
+
api_key="sk-canary-...", # from your Canary dashboard
|
|
29
|
+
agent_id="my-agent",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Manual tracking
|
|
33
|
+
canary.track_prompt("What is the capital of France?", model="gpt-4o")
|
|
34
|
+
canary.track_response("Paris.", model="gpt-4o", tokens_in=10, tokens_out=3)
|
|
35
|
+
canary.track_tool_call("web_search", {"query": "Paris"}, {"results": [...]})
|
|
36
|
+
|
|
37
|
+
canary.flush() # or call shutdown() at process exit
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## OpenAI instrumentation
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from openai import OpenAI
|
|
46
|
+
from canary_ops import Canary
|
|
47
|
+
|
|
48
|
+
canary = Canary(api_key="sk-canary-...", agent_id="my-agent")
|
|
49
|
+
client = canary.wrap_openai(OpenAI())
|
|
50
|
+
|
|
51
|
+
# All chat completions are now tracked automatically
|
|
52
|
+
response = client.chat.completions.create(
|
|
53
|
+
model="gpt-4o",
|
|
54
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
55
|
+
)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Streaming is also supported — just pass `stream=True`.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Anthropic instrumentation
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import anthropic
|
|
66
|
+
from canary_ops import Canary
|
|
67
|
+
|
|
68
|
+
canary = Canary(api_key="sk-canary-...", agent_id="my-agent")
|
|
69
|
+
client = canary.wrap_anthropic(anthropic.Anthropic())
|
|
70
|
+
|
|
71
|
+
response = client.messages.create(
|
|
72
|
+
model="claude-opus-4-5",
|
|
73
|
+
messages=[{"role": "user", "content": "Hello!"}],
|
|
74
|
+
max_tokens=256,
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Prompt firewall
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from canary_ops import Canary, CanaryBlockedError, ScanOptions
|
|
84
|
+
|
|
85
|
+
canary = Canary(api_key="sk-canary-...", agent_id="my-agent")
|
|
86
|
+
|
|
87
|
+
# scan() — returns a ScanResult, never raises
|
|
88
|
+
result = canary.scan(user_prompt, ScanOptions(remote=False))
|
|
89
|
+
if not result.allowed:
|
|
90
|
+
return "I can't help with that."
|
|
91
|
+
|
|
92
|
+
# guard() — raises CanaryBlockedError if the prompt is blocked
|
|
93
|
+
try:
|
|
94
|
+
safe_prompt = canary.guard(user_prompt)
|
|
95
|
+
except CanaryBlockedError as e:
|
|
96
|
+
print(f"Blocked: {e.scan_result.threats}")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Output scanning
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
# Scan a response for leaked secrets, PII, or exfiltration
|
|
105
|
+
from canary_ops import ScanResponseOptions
|
|
106
|
+
|
|
107
|
+
result = canary.scan_response(
|
|
108
|
+
llm_output,
|
|
109
|
+
ScanResponseOptions(throw_on_unsafe=True),
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Canary tokens
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
# Plant a decoy credential in your agent's context
|
|
119
|
+
token = canary.plant_token()
|
|
120
|
+
system_prompt = f"Company API key: {token.token_value}\n{your_context}"
|
|
121
|
+
|
|
122
|
+
# If the token appears in any response, Canary fires a data_exfiltration alert
|
|
123
|
+
result = canary.scan_response(response_text)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## RAG context scanning
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from canary_ops import ScanContextOptions
|
|
132
|
+
|
|
133
|
+
docs = retriever.get_relevant_documents(query)
|
|
134
|
+
results = canary.scan_context(
|
|
135
|
+
[d.page_content for d in docs],
|
|
136
|
+
ScanContextOptions(concurrency=5),
|
|
137
|
+
)
|
|
138
|
+
safe_docs = [d for d, r in zip(docs, results) if r.allowed]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Configuration reference
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from canary_ops import Canary, GuardConfig, RedactionConfig, RetryConfig
|
|
147
|
+
|
|
148
|
+
canary = Canary(
|
|
149
|
+
api_key="sk-canary-...",
|
|
150
|
+
agent_id="my-agent",
|
|
151
|
+
# Privacy
|
|
152
|
+
capture_content=True, # set False for metadata-only mode
|
|
153
|
+
max_content_length=10_000, # truncate long prompts
|
|
154
|
+
redaction=RedactionConfig(
|
|
155
|
+
secrets=True, # strip API keys, tokens, private keys
|
|
156
|
+
pii=True, # strip emails, phones, SSNs, credit cards
|
|
157
|
+
mode="mask", # "mask" | "hash" | "remove"
|
|
158
|
+
),
|
|
159
|
+
sample_rate=0.5, # only send 50% of events
|
|
160
|
+
# Firewall
|
|
161
|
+
guard=GuardConfig(
|
|
162
|
+
local=True, # run in-process heuristics
|
|
163
|
+
remote=True, # call the remote deep scanner
|
|
164
|
+
block_on_local=True, # short-circuit on high-confidence local hit
|
|
165
|
+
responses=False, # auto-scan every LLM response
|
|
166
|
+
),
|
|
167
|
+
fail_open=True, # allow prompts if the remote scanner is unreachable
|
|
168
|
+
# Resilience
|
|
169
|
+
timeout_ms=10_000,
|
|
170
|
+
retry=RetryConfig(retries=3, backoff_ms=250),
|
|
171
|
+
# Batching
|
|
172
|
+
flush_interval_ms=5_000, # drain queue every 5 s
|
|
173
|
+
max_batch_size=20, # flush immediately when queue hits 20
|
|
174
|
+
# Hooks
|
|
175
|
+
before_send=lambda event: event, # inspect/mutate/drop events
|
|
176
|
+
on_error=lambda exc: print(exc), # custom error handler
|
|
177
|
+
# Tagging
|
|
178
|
+
environment="production",
|
|
179
|
+
release="v1.2.3",
|
|
180
|
+
debug=False,
|
|
181
|
+
)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Error reference
|
|
187
|
+
|
|
188
|
+
| Exception | When it's raised |
|
|
189
|
+
|---|---|
|
|
190
|
+
| `CanaryBlockedError` | `guard()` or `scan_response(..., throw_on_unsafe=True)` when the firewall blocks |
|
|
191
|
+
| `CanaryApiError` | `plant_token()` when the API call fails |
|
|
192
|
+
| `CanaryNotInitializedError` | Calling SDK methods before `Canary(...)` is constructed |
|
|
193
|
+
|
|
194
|
+
`CanaryBlockedError` carries a `.scan_result` attribute with the full `ScanResult`.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Async support
|
|
199
|
+
|
|
200
|
+
All wrapper patching detects whether the underlying client's `create` method is a coroutine and patches it with an async wrapper automatically. The `Canary` client itself is thread-safe; background flushing runs on a daemon thread.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## License
|
|
205
|
+
|
|
206
|
+
MIT © CryptoSuess & Txnchi
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "canary-ops-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "The Operating System for AI Security — monitor, secure, and audit every AI agent and LLM application in production."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [{name = "CryptoSuess & Txnchi"}]
|
|
13
|
+
keywords = [
|
|
14
|
+
"canary",
|
|
15
|
+
"ai-security",
|
|
16
|
+
"llm",
|
|
17
|
+
"observability",
|
|
18
|
+
"telemetry",
|
|
19
|
+
"ai-firewall",
|
|
20
|
+
"prompt-injection",
|
|
21
|
+
"agent-monitoring",
|
|
22
|
+
"llmops",
|
|
23
|
+
]
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Development Status :: 3 - Alpha",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"License :: OSI Approved :: MIT License",
|
|
28
|
+
"Programming Language :: Python :: 3",
|
|
29
|
+
"Programming Language :: Python :: 3.8",
|
|
30
|
+
"Programming Language :: Python :: 3.9",
|
|
31
|
+
"Programming Language :: Python :: 3.10",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Topic :: Security",
|
|
35
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
36
|
+
"Typing :: Typed",
|
|
37
|
+
]
|
|
38
|
+
dependencies = []
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://canary-ops.base44.app"
|
|
42
|
+
Repository = "https://github.com/CryptoSuess/canary-sdk-python"
|
|
43
|
+
"Bug Tracker" = "https://github.com/CryptoSuess/canary-sdk-python/issues"
|
|
44
|
+
Changelog = "https://github.com/CryptoSuess/canary-sdk-python/blob/main/CHANGELOG.md"
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.wheel]
|
|
47
|
+
packages = ["src/canary_ops"]
|
|
48
|
+
|
|
49
|
+
[tool.pytest.ini_options]
|
|
50
|
+
testpaths = ["tests"]
|
|
51
|
+
asyncio_mode = "auto"
|
|
52
|
+
|
|
53
|
+
[tool.mypy]
|
|
54
|
+
python_version = "3.8"
|
|
55
|
+
strict = true
|
|
56
|
+
ignore_missing_imports = true
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
canary-ops-sdk — The Operating System for AI Security.
|
|
3
|
+
|
|
4
|
+
Quick start::
|
|
5
|
+
|
|
6
|
+
from canary_ops import Canary
|
|
7
|
+
|
|
8
|
+
canary = Canary(api_key="sk-canary-...", agent_id="my-agent")
|
|
9
|
+
canary.track_prompt("Hello, world!")
|
|
10
|
+
canary.flush()
|
|
11
|
+
"""
|
|
12
|
+
from ._client import (
|
|
13
|
+
Canary,
|
|
14
|
+
CanaryApiError,
|
|
15
|
+
CanaryBlockedError,
|
|
16
|
+
CanaryNotInitializedError,
|
|
17
|
+
)
|
|
18
|
+
from ._types import (
|
|
19
|
+
CanaryConfig,
|
|
20
|
+
CanaryToken,
|
|
21
|
+
DetectedThreat,
|
|
22
|
+
FirewallAction,
|
|
23
|
+
GuardConfig,
|
|
24
|
+
PlantTokenOptions,
|
|
25
|
+
RedactionConfig,
|
|
26
|
+
RetryConfig,
|
|
27
|
+
ScanContextOptions,
|
|
28
|
+
ScanOptions,
|
|
29
|
+
ScanResponseOptions,
|
|
30
|
+
ScanResult,
|
|
31
|
+
TelemetryEvent,
|
|
32
|
+
TokenType,
|
|
33
|
+
)
|
|
34
|
+
from .instrument import wrap_anthropic, wrap_openai
|
|
35
|
+
|
|
36
|
+
__version__ = "0.1.0"
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
# Main client
|
|
40
|
+
"Canary",
|
|
41
|
+
# Errors
|
|
42
|
+
"CanaryNotInitializedError",
|
|
43
|
+
"CanaryBlockedError",
|
|
44
|
+
"CanaryApiError",
|
|
45
|
+
# Types
|
|
46
|
+
"CanaryConfig",
|
|
47
|
+
"TelemetryEvent",
|
|
48
|
+
"ScanResult",
|
|
49
|
+
"ScanOptions",
|
|
50
|
+
"ScanResponseOptions",
|
|
51
|
+
"PlantTokenOptions",
|
|
52
|
+
"ScanContextOptions",
|
|
53
|
+
"CanaryToken",
|
|
54
|
+
"TokenType",
|
|
55
|
+
"DetectedThreat",
|
|
56
|
+
"FirewallAction",
|
|
57
|
+
"RedactionConfig",
|
|
58
|
+
"GuardConfig",
|
|
59
|
+
"RetryConfig",
|
|
60
|
+
# Standalone instrumentors
|
|
61
|
+
"wrap_openai",
|
|
62
|
+
"wrap_anthropic",
|
|
63
|
+
# Version
|
|
64
|
+
"__version__",
|
|
65
|
+
]
|