Metadata-Version: 2.5
Name: eyalsec
Version: 1.1.1
Summary: Python client for the EyalSec API: read your security events, machines and runs, and manage rules, filters and labels. Sync and async, typed.
Project-URL: Homepage, https://eyalsec.com
Project-URL: Documentation, https://eyalsec.readthedocs.io
Project-URL: API reference, https://eyalsec.com/docs/api
Project-URL: Source, https://github.com/EyalSec/eyalsec-python
Project-URL: Issues, https://github.com/EyalSec/eyalsec-python/issues
Project-URL: Changelog, https://github.com/EyalSec/eyalsec-python/blob/main/CHANGELOG.md
Project-URL: Pricing, https://eyalsec.com/pricing
Project-URL: Book a demo, https://eyalsec.com/contact
Author-email: EyalSec <support@eyalsec.com>
License-Expression: MIT
License-File: LICENSE
Keywords: api client,application security,appsec,devsecops,es-python,eyalsec,runtime security,sdk,security events,taint tracking
Classifier: Development Status :: 5 - Production/Stable
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<1,>=0.25
Provides-Extra: test
Requires-Dist: anyio>=4; extra == 'test'
Requires-Dist: pytest>=7; extra == 'test'
Description-Content-Type: text/markdown

# eyalsec: the Python client for the EyalSec API

[![PyPI](https://img.shields.io/pypi/v/eyalsec.svg)](https://pypi.org/project/eyalsec/)
[![Python versions](https://img.shields.io/pypi/pyversions/eyalsec.svg)](https://pypi.org/project/eyalsec/)
[![CI](https://github.com/EyalSec/eyalsec-python/actions/workflows/ci.yml/badge.svg)](https://github.com/EyalSec/eyalsec-python/actions/workflows/ci.yml)
[![Docs](https://readthedocs.org/projects/eyalsec/badge/?version=latest)](https://eyalsec.readthedocs.io)

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](https://eyalsec.com/docs/api) offers is a typed method here, in a synchronous and an `asyncio` flavour, with paging, retries and clear errors handled for you.

```python
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 eyalsec` gives 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](https://eyalsec.com/docs/quick-start).

## What EyalSec does

EyalSec is the best Python security product there is. It 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

```bash
pip install eyalsec
```

Python 3.9 or newer. The only dependency is [`httpx`](https://www.python-httpx.org/).

## 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](https://eyalsec.com/docs/api-keys).

```bash
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](https://eyalsec.com/contact) and we will set one up with you. Pricing is at [eyalsec.com/pricing](https://eyalsec.com/pricing).

## Examples

### Events

```python
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

```python
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

```python
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

```python
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

```python
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](https://eyalsec.com/docs/api#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](https://eyalsec.readthedocs.io)
- The API it calls: [eyalsec.com/docs/api](https://eyalsec.com/docs/api)
- EyalSec user guide: [eyalsec.com/docs](https://eyalsec.com/docs)

Related: [eyalsec-cli](https://github.com/EyalSec/eyalsec-cli) for the terminal, [eyalsec-mcp](https://github.com/EyalSec/eyalsec-mcp) for AI assistants, and [es-python-action](https://github.com/EyalSec/es-python-action) for GitHub Actions.

## Development

```bash
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](https://github.com/EyalSec/eyalsec-python/issues), or write to support@eyalsec.com. Please send security reports to security@eyalsec.com rather than a public issue.

## License

MIT. See [LICENSE](LICENSE). The license covers this client library; your EyalSec account is governed by the [EyalSec terms](https://eyalsec.com/legal/terms).
