eyalsec: the Python client for the EyalSec API
Work with your EyalSec account from Python: pull this week's critical events into a report, fail a pipeline on a new finding, label and annotate events in bulk, keep rules and filters in code, or add a machine from your provisioning script. Everything the EyalSec API offers is a typed method here, in a synchronous and an asyncio flavour, with paging, retries and clear errors handled for you.
import eyalsec
client = eyalsec.EyalSec() # reads EYALSEC_API_KEY
for event in client.iter_events(severity=["critical", "high"], from_date="2026-10-01"):
print(event.created_at, event.severity, event.where, event.machine_name)
This package is the API client, not the runtime.
pip install eyalsecgives you this library. It is not es-python, the EyalSec Python runtime, and importing it adds no security checks to your program. The runtime is installed from your EyalSec dashboard: see the Quick start.
What EyalSec does
EyalSec follows untrusted data (requests, files, environment variables and more) through your running program and reports when it reaches a risky operation: a SQL query, a shell command, deserialization, a file path. Each event names the operation, the line of code, the value it received and where the untrusted part came from. You run your existing app through es-python, unchanged, and the events arrive on your dashboard. This library reads and manages them.
Install
pip install eyalsec
Python 3.9 or newer. The only dependency is httpx.
Get a key
Create an API key on your dashboard under Settings > API access and put it in EYALSEC_API_KEY. A read-only key can call every read, and it is the one to use for reports and dashboards; scripts that change things need a full key. See API keys.
export EYALSEC_API_KEY=es2_your_key
python -m eyalsec --check # calls the API once and prints the account it reached
No account yet? Book a live demo and we will set one up with you. Pricing is at eyalsec.com/pricing.
Examples
Events
page = client.query_events(view="python", sort="severity") # one page, up to 100
for event in page.events:
print(event.id, event.severity, event.where, event.str_repr)
detail = client.get_event(page.events[0].id) # the large fields
print(detail.trace)
print(detail.source_code, detail.sink_code)
# Every event, following the cursor for you (including past empty pages).
critical = list(client.iter_events(severity="critical", machine="web-01"))
# What one run of a program produced.
run = next(client.iter_runs(q="manage.py"))
for event in client.iter_events(view="python", run=run.run_uid):
print(event.where, event.count)
csv_bytes = client.export_events("csv", view="python", from_date="2026-10-01")
Triage
label = client.create_label("needs review", "amber")
for event in client.iter_events(tags=["sqli"], bucket="live"):
client.attach_label(event.id, label.id)
client.set_note(event.id, "Queued for the Thursday review.")
Rules and filters as code
from eyalsec import RuleMode
client.create_rule(event_pattern="^healthcheck", mode=RuleMode.HIDE, label="health checks")
client.create_filter("^socket-recv$", "blacklist", label="noise")
client.set_taint("socket", "on") # your account default
client.apply_rule_template(template_id=3, machine=None) # None means your global rules
Machines and CI
machine = client.add_machine("web-02") # build detected at install time
command = client.get_install_command(machine.public_id, attested=True)
print(command.script_path, command.expires_in) # the secret is single-use
install = client.ci_install("ci-myrepo", attested=True, distro="ubuntu_24_04", arch="x86_64")
# ... run your tests under es-python ...
result = client.ci_findings("ci-myrepo", since=install.server_time, min_severity="high")
if result.findings:
raise SystemExit(f"{result.findings} findings at high or above: {result.url}")
attested=True is your confirmation that you are authorized to monitor that machine, the same box the dashboard asks you to tick.
asyncio
import asyncio
import eyalsec
async def main() -> None:
async with eyalsec.AsyncEyalSec() as client:
async for event in client.iter_events(severity=["critical"]):
print(event.id, event.where)
asyncio.run(main())
AsyncEyalSec has exactly the same methods as EyalSec.
Errors
Every error the client raises is an eyalsec.EyalSecError. An error status becomes a subclass you can catch by meaning:
| Status | Exception |
|---|---|
| 400 | BadRequestError |
| 401 | AuthenticationError (missing, wrong, revoked or expired key) |
| 403 | PermissionDeniedError (for example a read-only key trying to write) |
| 404 | NotFoundError (also for things on another account, so their existence is never confirmed) |
| 409 | ConflictError (for example the machine limit is reached) |
| 422 | UnprocessableEntityError |
| 429 | RateLimitError, with retry_after |
| 5xx | InternalServerError |
Each carries status, message, method and path. A request that got no answer raises APIConnectionError (or APITimeoutError).
The client retries a rate-limited request after the server's Retry-After (up to max_retries, default 2), and retries reads after a dropped connection or a 502, 503 or 504. Writes are never retried after a dropped connection, because there is no way to know whether they applied. The API's rate limits are 300 requests a minute per account.
Configuration
| Argument | Environment | Default |
|---|---|---|
api_key |
EYALSEC_API_KEY |
none (required for everything but the two public lists) |
base_url |
EYALSEC_URL |
https://eyalsec.com |
timeout |
30 seconds | |
max_retries |
2 | |
http_client |
your own httpx.Client / httpx.AsyncClient, for proxies or custom TLS |
The key is sent only to base_url, only over HTTPS (plain http:// is accepted for localhost alone), and never appears in a repr.
Documentation
- This library: eyalsec.readthedocs.io
- The API it calls: eyalsec.com/docs/api
- EyalSec user guide: eyalsec.com/docs
Related: eyalsec-cli for the terminal, eyalsec-mcp for AI assistants, and es-python-action for GitHub Actions.
Development
git clone https://github.com/EyalSec/eyalsec-python && cd eyalsec-python
python -m venv .venv && . .venv/bin/activate
pip install -e '.[test]'
pytest
src/eyalsec/_client.py is generated from _async_client.py: edit the async file and run python scripts/unasync.py. The tests run against a fake API; EYALSEC_LIVE=1 adds a read-only pass against your own account (see tests/test_live.py).
Help
Questions and bug reports are welcome in issues, or write to support@eyalsec.com. Please send security reports to security@eyalsec.com rather than a public issue.
License
MIT. See LICENSE. The license covers this client library; your EyalSec account is governed by the EyalSec terms.
Metadata
Release files for eyalsec 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| eyalsec-1.1.0.tar.gz | 40.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| eyalsec-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 84.6 kB
Release files / eyalsec-1.1.0.tar.gz
| Download URL | eyalsec-1.1.0.tar.gz |
|---|---|
| Size | 40.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4eedd06accbf5f9d08b838b3c370c941c693efe05846cbe948d7facf345a836f
|
|
BLAKE2b-256 checksum How to use checksums |
a52146517f7b05084c74d411b5af437e2ef74ca30e69b40374cbb75667070b38
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.13
|
Release files / eyalsec-1.1.0-py3-none-any.whl
| Download URL | eyalsec-1.1.0-py3-none-any.whl |
|---|---|
| Size | 44.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
828c1fb8d8ae037bbb4f928f9da6fa64a94bf324a6f8eef8d39cee84f7f9ff1a
|
|
BLAKE2b-256 checksum How to use checksums |
e0eef277f74bc2f8d66fcefac0ac895ab6c932dfb70ed1de7dfe34d1fdcaa3cd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.13
|