EyalSec User Guide

What is EyalSec?

How EyalSec watches untrusted data flow from a source, through your code, to a risky sink, and forks into Report or Report and Raise.

EyalSec is a secure Python. You install it next to your normal Python and run your programs through it exactly as you do today; nothing in your code has to change. We call the EyalSec runtime es-python, and you run it in place of python.

While your program runs, es-python quietly watches for one specific danger: untrusted data (a.k.a. taint), meaning data that came from the outside world, reaching a risky action in your code. Where that data enters is called a source: a network socket, a file, standard input, the program's environment variables and command-line arguments, or foreign code (Python code on disk that another user can write to). The risky action it flows into is called a sink: things like running a system command, opening a file, or running a database query. Untrusted data reaching a sink is how most real-world hacks happen, and that is exactly the pattern es-python is built to catch.

When es-python sees untrusted data reach a sink, it can do one of two things, and you choose which:

You can read more about the difference, and when to use each, in Report mode vs Report and Raise mode.

You manage everything from one website: your dashboard. It shows which of your machines have es-python installed, what they have detected, and lets you change your settings, all in one place.

The three pieces

EyalSec is made of three parts that work together:

Quick start: your first 10 minutes

This is the fastest path from zero to seeing your first event: create an account, add a machine, install es-python (the EyalSec runtime), run a script through it, and watch what it finds appear on your dashboard. Plan on about ten minutes.

1. Create your account

  1. Open EyalSec and go to the Register page.
  2. Pick a username, enter your email address, and choose a password (typed twice to confirm). Your password must be 8 to 72 characters. Tick the box to accept the Terms of Service and Privacy Policy.
  3. Submit. EyalSec emails a verification link to that address; click it to activate your account. That signs you in and drops you on your dashboard, and starts your 24-hour trial.

Verifying your email starts a 24-hour Trial: the defaults are 5 machines and a 5,000-events-per-month view, plenty for this walkthrough. After 24 hours the account goes read-only (you can still read everything) until an admin lifts the trial or adjusts your account. See Plans & roles for the details.

2. Log in (next time)

Clicking the verification link signed you in, so you can skip this for now. Next time you visit, go to the Login page and enter your username and password. If you later turn on two-factor authentication, you'll also be asked for a 6-digit code. See Accounts & signing in.

3. Add a machine

A machine is any host where you'll run your code through es-python: your laptop, a server, a container.

The Machines page: each machine shows a status pill, an OS chip, a build badge, and an install panel with the one-line install command.

  1. Go to the Machines page and choose Add machine.
  2. Give it a Name (anything memorable, 1 to 128 characters).
  3. Pick the Distro (for example Ubuntu 24.04, Debian 12, RHEL 9, Amazon Linux 2023, Fedora, or Arch) and the Arch (x86_64 or aarch64) that match the host.
  4. Pick the Python version for this machine (3.13 is the default).
  5. Save. The machine appears with a created status pill, waiting for you to install es-python on it.

More on the full machine lifecycle is in Machines.

4. Copy the install command

With the machine selected (status created, OS set), the page shows a single install command. Select Copy to grab it. It looks like this:

source <(curl -sSL -d "secret=<token>" https://eyalsec.com/install.sh)

The <token> is a one-time secret that's valid for 10 minutes, so run it soon. If it expires, just copy a fresh command from the same panel.

5. Run the installer on the host

Paste the command into a terminal on the machine itself and run it. It:

The machine's status moves created → pending → installed. Once it shows installed (a green, blinking pill) on the dashboard, you're ready to go: es-python works immediately, in that same terminal, with no extra step.

If the download is interrupted, you can re-run the command to resume where it left off.

Note: the source <(...) form is what makes es-python available in the current shell. If you instead pipe the script (curl ... | bash), the install still works, but you'll need to open a fresh terminal (or run source ~/.bashrc) afterwards.

Privacy notice: es-python may capture values from your program's memory (including secrets and personal data) when untrusted data reaches a monitored sink. Only run it on systems and data you are authorized to monitor.

6. Run your script through es-python

Anywhere you'd normally type python, type es-python instead:

es-python your_script.py

Your program runs exactly as before, but now es-python is watching for untrusted data reaching a risky action and reporting what it sees. You don't need to change a single line of code. For more ways to run it, see Running es-python.

7. Watch events appear

Back on the dashboard, open the Events page.

The Events page: a filterable table of detections, with one row expanded to show where, repr, origin, and trace.

As your program runs, detections show up here, newest first. Each row is one kind of event; the count rises each time the same thing happens again, and the severity column tells you at a glance how serious it looks. Click a row to expand it and see the details. Full coverage of the table, filters, and the detail pane is in Events.

If nothing shows up yet, give it a few seconds (events are sent in the background), make sure you ran your script with es-python and not python, and confirm the machine reads installed on the Machines page.

8. (Optional) Turn on Report and Raise mode

So far es-python is in Report mode: it records events but never interferes with your program. When you're ready to actually block a risky action, switch that machine to Report and Raise mode; then es-python stops the action (your program gets a RuntimeError) instead of just recording it.

You set this up per machine in the Configure modal by adding a Raise rule. The how-to is in Machine configuration, and the difference between the two modes is explained in Report mode vs Report and Raise mode.

Note: Raise rules must be enabled on your account. If they aren't, adding one is rejected with "Raise rules are not enabled for your account. Ask an administrator to enable them." To have it turned on, contact sales@eyalsec.com.

That's the whole loop: install once, run with es-python, watch your events. From here, explore Settings to secure your account, Filters to cut noise, and the Dashboard overview for a high-level summary. If you get stuck, see Getting help.

Accounts & signing in

Register and login card with a 2FA code field

Everything in EyalSec lives behind your account. This page walks through creating an account, signing in, the two-factor (2FA) check at login, signing out, and what to do when you forget your password.

Register

New here? Create an account from the Register page.

  1. Go to /register/.
  2. Pick a username and enter your email address.
  3. Enter a password, then type it again to confirm (it must follow the Password rules below).
  4. Tick the box to accept the Terms of Service and Privacy Policy. Depending on how the site is configured, a background human check (CAPTCHA) may also run.
  5. Submit.

You're not signed in yet: EyalSec emails a verification link to the address you gave, and your account stays inactive until you click it. Following that link activates the account, signs you in, and drops you on your dashboard at /scanner/, so keep a working email handy at signup. See Quick start: your first 10 minutes.

Verifying your email starts a 24-hour Trial with the default limits (5 machines, a 5,000-events-per-month view). After 24 hours the account becomes read-only: you can still view everything you've collected, but you can't make changes until an admin lifts the trial or adjusts your account. See Plans & roles for what your account includes. To have limits raised, email sales@eyalsec.com.

Some accounts are created from an invite link instead. An invite link opens a special registration page that works the same way (username, password, and accepting the terms); it simply starts the account with the settings an admin chose for you, skips the trial, and the link expires 24 hours after it was created.

Log in

Already have an account? Sign in from the Login page.

  1. Go to /login/.
  2. Enter your username and password.
  3. Submit.

If two-factor authentication is turned on for your account, you'll be sent to a second screen to finish signing in (see The 2FA challenge below).

For your protection, repeated failed sign-in attempts from the same place are slowed down. If you've been locked out by too many wrong guesses, wait a little and try again, or use Forgot password to reset.

The 2FA challenge

If you've enabled two-factor authentication, logging in takes one extra step. After your username and password are accepted, you land on the 2FA page (/login/2fa). There you can enter either:

Enter one of those and you're in. Backup codes are your safety net for when you don't have your phone, so keep them somewhere safe. You can turn 2FA on, see how many backup codes you have left, and generate fresh ones from Settings: your account.

Log out

To sign out, use the Log out link (/login/logout). This clears your session and returns you to the login page. Signing out is a good idea on shared or public computers.

Forgot password

Can't remember your password? You can reset it by email.

  1. Go to /login/forgot.
  2. Enter your username or email address and submit. For privacy, EyalSec always shows the same neutral message here; it won't tell you whether an account exists, so nobody can use this page to fish for valid accounts.
  3. If the account exists and has an email on file, a 6-digit code is emailed to it. That code is valid for 15 minutes.
  4. On the verify page (/login/forgot/verify), enter your username or email, the 6-digit code, and your new password twice. The new password must meet the Password rules; that's checked before the code is, so fix any password problem first.
  5. On success, your old codes are used up and you're sent to the login page.

Note that resetting your password does not sign you in automatically. Once the reset succeeds, log in normally with your new password.

To set or update the recovery email used here, see the email card in Settings: your account. Without an email on file there's nowhere to send the code, so add one before you need it.

Password rules

The same rules apply everywhere you set a password: when you register, change it in Settings, or reset it after a forgot-password. Your password must:

If a password is turned down, you'll see a message telling you which rule it broke; adjust it and try again.

Still stuck signing in? Email support@eyalsec.com.

Settings: your account

Settings page with Account, Two-factor, and API access cards

The Settings page (/scanner/settings) is where you manage your own account: your recovery email, your password, two-factor authentication, and your API key. Each area is a separate card, described below.

Change your email

Your email is the address EyalSec uses to send you a password-reset code if you ever forget your password (see Forgot password).

  1. Open the Account card.
  2. Type a valid email address, or leave the field blank to remove the one on file.
  3. Click Save.

Setting or changing the address takes one extra step: EyalSec emails a confirmation link to the new address, and the change only takes effect when you click it. Until then the old address stays in place. This proves you control the inbox before it becomes your recovery address; if you had an old address on file, it also gets a heads-up notice about the requested change. The confirmation link expires after a short while, and only one change can be pending at a time.

Removing the address (saving an empty field) happens immediately, and any pending confirmation link is cancelled.

We recommend keeping a working email here so the Forgot password flow can reach you. Without one, there's nowhere to send a reset code.

Change your password

To change your password from the Account card:

  1. Enter your current password.
  2. Enter your new password, then type it again to confirm.
  3. Save.

The new password must follow the Password rules: 8 to 72 characters, and it can't be a common password.

Two-factor authentication

Two-factor authentication (2FA) adds a second step at login: after your password, you also enter a one-time 6-digit code from an authenticator app on your phone. It's the single best thing you can do to protect your account. The Two-factor card shows your current status and the actions below.

Enable 2FA

  1. Click to start setup. EyalSec shows a QR code (and a base32 secret you can type in by hand if you can't scan).
  2. Scan the QR code with an authenticator app (such as Google Authenticator, 1Password, Authy, or similar).
  3. Enter the current 6-digit code the app shows, to prove the setup worked.
  4. Confirm to enable.

The moment 2FA turns on, you're shown 10 one-time backup codes. These are shown only once; copy them and store them somewhere safe (a password manager is ideal). Each backup code works a single time and is your way back in if you ever lose your phone. At login you can use either a 6-digit code or a backup code; see The 2FA challenge.

Status card

When 2FA is on, the card shows Enabled along with Backup codes remaining: N so you can tell at a glance how many you have left. When it's off, the card prompts you to turn it on.

Regenerate backup codes

Running low on backup codes, or think they may have leaked? Regenerate them to get a fresh set of 10, again shown only once. Generating a new set replaces the old one, so your previous backup codes stop working.

Disable 2FA

You can turn 2FA off from the same card. Disabling it clears your saved authenticator secret and your backup codes, so your account goes back to password-only login.

Email sign-in codes

Besides the authenticator app, the card has an Email sign-in codes option: a one-time 6-digit code sent to your recovery email at sign-in. If you also use an authenticator app, the emailed code is an extra fallback; if you don't, the emailed code becomes your second factor on its own. It needs a recovery email on file (set one in the Account card first), and you can turn it on or off here at any time.

API access

An API key lets your own scripts and tools talk to the EyalSec dashboard API directly, without logging in through the website. It gives the same access your account has, so treat it like a password. You can have one key at a time. For the endpoints you can call with it, see the API reference.

Generate a key

  1. Open the API access card.
  2. Optionally set an expiry date (in YYYY-MM-DD form), or leave it blank for a key that doesn't expire.
  3. Generate.

You're shown the full key once. It starts with es2_, like this:

es2_3f9a8c2b7d1e4a6f0b5c9d8e7a2f1c3b

Copy it now; the full key is shown only once. If you lose it, you'll have to make a new one. The first time you generate a key, you don't need to re-enter your password.

View your key's details

After a key exists, the card shows its metadata, never the full secret again. You'll see:

You send the key in a request header named X-API-Key:

curl -H "X-API-Key: es2_3f9a8c2b7d1e4a6f0b5c9d8e7a2f1c3b" \
  https://eyalsec.com/api/get_all_machines/

See the API reference for the full list of endpoints, request bodies, and example responses.

Regenerate or revoke a key

A note on re-entering your password

Some sensitive actions ask you to re-enter your current password even though you're already signed in. This is a deliberate extra check so that nobody who walks up to your open session can quietly weaken your account. You'll be asked for your password to:

(Generating your very first API key doesn't require the password; only later regenerate/revoke actions do.)

Need a hand with any of this? Email support@eyalsec.com.

Machines

The Machines page: a table of machines with colored status pills, an OS chip and build badge per row, a kebab menu, and an install panel showing the curl command

A machine is one computer (a laptop, a server, a container) where you run your code through es-python, the EyalSec runtime. The Machines page is where you add a machine, install EyalSec on it, and keep it up to date. Everything a machine detects shows up under Events, attributed to that machine.

You manage machines from Machines in the left sidebar.

What you can do here

Add a machine

  1. Click Add machine.
  2. Give it a Name: anything from 1 to 128 characters that helps you recognize the host (for example prod-web-1 or my-laptop).
  3. Pick the Distro (the Linux distribution it runs):
    • Ubuntu 24.04 LTS
    • Ubuntu 22.04 LTS
    • Ubuntu 20.04 LTS
    • Debian 13 (trixie)
    • Debian 12 (bookworm)
    • Debian 11 (bullseye)
    • RHEL / Rocky / Alma 9
    • Amazon Linux 2023
    • Fedora (latest)
    • Arch Linux (rolling)
  4. Pick the Arch (the processor architecture): x86_64 (most servers and desktops) or aarch64 (ARM machines, such as AWS Graviton or a Raspberry Pi). Each distro is built for both, so there are 20 build targets in all.
  5. Pick the Python version the machine should run. The default is 3.13; 3.14 is also available. This is the version of the es-python runtime the machine installs.
  6. Click Add.

The new machine appears in the table with a created status, ready to install.

How many machines you can add is a per-account limit (5 by default). If you hit the cap you will see a message like "Machine limit reached (N). Contact sales@eyalsec.com to upgrade your plan." See Plans & roles for the limits, and email sales@eyalsec.com to raise yours.

A note on socket-only machines

You may still see a socket-only badge on some older machines. A socket-only machine runs a slimmer build of the runtime that watches network data only: the file, stdin, foreign, and env instrumentation is compiled out, so on such a machine only the socket source is live and the other four toggles do nothing. See Running es-python for what each source watches.

Socket-only is no longer a choice you make when adding a machine: new machines are always created with the full-instrumentation build. The badge and behavior remain only for machines created under the older option.

Status lifecycle

Each machine moves through three states, shown as a colored pill in its row:

created  →  pending  →  installed

Row badges

Once a machine is installed, its row can show a few badges next to the status pill:

Set the OS if it is missing

If a machine was added without an operating system, you will see an amber set OS chip on its row. Click it, pick the Distro and Arch (the same lists as when adding a machine), and save. You need the OS set before you can install, because EyalSec ships a build matched to that exact distro and architecture.

Get the install command

For a created machine that has its OS set:

  1. Click Install on the machine's row.

  2. EyalSec shows a one-line install command that looks like this:

    source <(curl -sSL -d "secret=<token>" https://eyalsec.com/install.sh)
    
  3. Click Copy to copy it.

The <token> is a one-time secret that is valid for 10 minutes. It can only be used once. If it expires or you need a fresh one, click Install again to get a new command.

Run the installer

Run the copied command on the host itself (in a terminal on that machine). The installer:

As it runs, the machine's status moves from created to pending and then to installed.

It installs into your home directory, so you do not need root or sudo.

If the download is interrupted, the machine stays in pending and the install is resumable: run the command again (the installer continues with curl -C - from where it left off) and it picks up where it stopped.

Once the status turns green, run your programs with es-python instead of python, right away, in the same terminal. (Only if you ran the installer by piping it to bash instead of the source <(...) form do you need a fresh terminal or source ~/.bashrc first.) See Running es-python for how to use it.

Rename

To rename a machine, open its kebab menu (the icon on the row) and choose Rename. The new name can be up to 128 characters. Renaming is purely cosmetic; all of the machine's existing events stay attributed to it.

Reinstall (taking a newer build / "update available")

Reinstalling is how you update a machine to a newer EyalSec build. When a machine's build badge shows update available, reinstall it to pick up the latest build.

  1. Open the machine's kebab menu and choose Reinstall.
  2. Confirm within 3 seconds.

This resets the machine to created and mints a fresh install command. Run that command on the host (exactly like a first install) to lay down the new build. Your old events are safe: the machine's internal identity is unchanged, so everything it reported before stays attributed to it.

Configure

To change what a machine watches for and how it reacts, open its kebab menu and choose Configure. This opens a modal with Taint and Rules sections. This is also where you turn on Report and Raise mode for a machine. See Machine configuration for the full walkthrough.

Delete

To remove a machine, open its kebab menu, choose Delete, and confirm.

Warning: deleting a machine removes the machine and all of its events. This is irreversible; there is no undo. If you only want to stop using a machine, you can leave it in place instead; deleting is permanent.

Report mode vs Report and Raise mode

Side by side: in Report mode an event is recorded and the app keeps running; in Report and Raise mode a RuntimeError is thrown and the risky action is blocked

When EyalSec spots something dangerous (untrusted data, that is, data that came from the outside world, also called taint, about to be used in a risky action) it can do one of two things, and you choose which. We call these Report and Report and Raise.

The difference is simple: Report watches; Report and Raise watches and blocks.

Report mode

In Report mode, EyalSec lets your program keep running, but records what it saw on your dashboard. Nothing about your program's behavior changes; it runs exactly as it would under normal Python, and you get a full account of the risky moment under Events: what data was involved, where it came from, and the line of code that triggered it.

Report mode is the default. It is the right starting point because it can never break a working program. You get full visibility with zero risk, so you can see what EyalSec finds before deciding whether to block anything.

Use Report mode when you want to:

Report and Raise mode

In Report and Raise mode, EyalSec stops the risky action before it runs. Instead of letting the dangerous operation happen, it aborts it by raising a Python error, a RuntimeError, at the exact spot. The dangerous thing never happens, so an attack is blocked. The event is still recorded on your dashboard, exactly as in Report mode, so you see what was blocked. Report and Raise does both: it reports and raises.

Here's what that looks like when a program tries to run a system command built from untrusted data:

import os
user_input = get_data_from_the_internet()   # untrusted (tainted) data
os.system(user_input)                        # risky action: a "sink"

Under Report mode this runs and is recorded. Under Report and Raise mode it stops with an error like:

RuntimeError: EyalSec blocked a dangerous operation: untrusted data reached a
system command

Because Report and Raise mode throws a real Python error, your program sees it like any other exception. If your code already catches errors around that operation, the attack is blocked and your app can recover gracefully. If it doesn't, the program stops there, which is usually exactly what you want when something genuinely malicious is in flight.

Use Report and Raise mode when you want EyalSec to actively defend an app, not just observe it: typically once you've watched it in Report mode and trust what it's detecting.

How you turn each one on

See Machine configuration for how to add a mode-3 rule, choose which events it covers, and whitelist specific events out of raising.

Account note: Report and Raise mode (mode-3 rules) needs to be enabled on your account. If it isn't, adding a mode-3 rule is rejected with "Raise rules are not enabled for your account. Ask an administrator to enable them." Reporting works on every account. See Plans & roles, and email sales@eyalsec.com to have it turned on.

Machine configuration

The Configure modal with two sections: Taint sources (socket, file, stdin, foreign, env) and Rules (list, source, event pattern, mode, enabled)

The Configure modal is where you tune what a machine watches for and how it reacts. Open it from a machine's kebab menu () on the Machines page → Configure. The modal has two sections:

Changes in the Configure modal auto-save as you make them; there is no Save button.

Global vs per-machine scope

Configuration lives at two levels:

A per-machine setting overrides the global default for that machine. So you can set a sensible baseline globally and then tighten or loosen it on individual machines.

The Taint sources section

Taint is untrusted data, meaning data that came from the outside world. A source is where that data enters your program. EyalSec tracks five sources, and each one has its own dropdown:

Each source shows a short description and a Read more link into The taint sources, the full reference for what each source watches and the event origins it produces. On a socket-only machine only socket is live (the other four are compiled out; see Machines).

Each source can be set to one of three states:

Because unset = inherit, you only flip a source to on or off when you want this machine to differ from your global setting. The per-machine choice always wins over the global one. A Set all sources dropdown at the top applies On / Off / Unset to all five sources in one step.

When it takes effect: taint changes apply on the machine's next process restart. A program that is already running keeps the taint settings it started with; start it again (or start your next program) to pick up the new ones.

The Rules section

A rule decides what happens with a particular kind of event. Each rule has:

There are four modes. The first two only change what you see; the last two change what the machine does:

How List combines with each mode:

Mode Whitelist Blacklist
Show (UI) Show only matching events Hide matching events, show the rest
Hide (UI) Hide matching events Hide everything except matching
Don't send (drop) "Send only matching": drop everything else at the machine "Drop matching": drop matching, send the rest
Raise (machine) Block (raise on) matching events Block everything except matching

A few important notes:

Your account starts with two default global drop rules; see Filters.

Plan notes: Mode 3 (Raise) needs the Report-and-Raise capability enabled on your account; without it, creating or updating a mode-3 rule is rejected. Modes 1, 2, and 4 work on every account. See Plans & roles, and email sales@eyalsec.com to enable it.

When configuration reaches the machine

The machine's runtime checks for its latest configuration shortly after startup and then roughly every 30 seconds, so most changes reach a running machine within about half a minute:

So if you add a raise rule, a long-running program starts honoring it within about half a minute. If you change which sources are tracked, restart the program to apply it.

Events

The Events page is where you read what EyalSec has detected across all your machines. Every time your code uses untrusted data (data that came from the outside world, also called taint) in a risky way, an event lands here.

Events table with a filter toolbar and one expanded detail row showing where, repr, origin, and trace

The events table

Events are listed newest first. The table loads 100 rows at a time and fetches more automatically as you scroll, so you can keep going back in time without clicking through pages.

EyalSec doesn't store one row per occurrence; that would flood the page. Instead it groups and counts identical events. Two events are treated as the same when they share the same combination of:

When a matching event happens again, EyalSec just bumps a counter instead of adding a new row. The count column shows how many times that exact event has fired. A high count is a strong signal: something is hitting that code path over and over.

Each row also carries a severity: one of critical, high, medium, low, or info, computed on the server from what the event shows (for example, how attacker-reachable the data source looks). It's shown as a colored word in its own column so the rows that deserve attention stand out at a glance.

Filtering the list

A toolbar above the table lets you narrow what you see. None of these filters change what's stored; they only change what's displayed.

Your filter choices are saved in the page's URL. That means you can bookmark a filtered view, share the link, or reload the page and land right back where you were.

Opening a row for detail

Click any row to expand it in place and see the full story behind the event. Only one row is open at a time; opening another closes the first. The detail pane can include:

Every event shows its label (where), a text representation, the stack trace, and the file origin. To turn noisy detections into standing rules, use the Filters page.

Where this fits

Events are the raw detections. To turn them into standing rules, like always hiding a known-safe pattern, dropping noise at the machine, or blocking a dangerous action at runtime, use Filters for global rules and Machine configuration for per-machine rules and Raise behavior. For a high-level summary instead of the full list, see the Dashboard overview.

Filters

Filters are standing rules that shape what shows up across your whole account. Where Events lets you narrow a single view on the fly, the Filters page lets you save rules that apply everywhere, every time.

Filters page showing saved filter rows with pattern, label, mode, target, and enabled columns

Saved whitelist and blacklist filters

A filter is a saved regular expression (regex) that EyalSec matches against your events. Each filter has:

These filters affect display only: they change which events you see on the dashboard, never which events get recorded. Nothing is ever lost; a hidden event is still there if you turn the filter off.

Creating, editing, and deleting filters

A quick way to make a blacklist filter is the Block this event action on a row in the Events page, which drops a ready-made rule onto this page for you.

Global taint and rules live here too

Besides whitelist/blacklist filters, the Filters page also hosts your global taint settings and global rules: the same controls you'd set on a single machine, but applied to all your machines at once.

These are exactly the controls described in Machine configuration; the only difference is scope. A setting on an individual machine overrides the global one; leave a machine's control as unset to inherit whatever you've chosen globally here. For the full explanation of taint sources, rule modes, and when Report and Raise applies, read Machine configuration.

The default rules

The first time you open this page, your global rule list starts with two default rules instead of an empty table: two enabled "Send only matching" drop rules on source any, one with the event pattern re and one with write. Together they tell every machine to send only events whose name matches re or write and to drop the rest at the machine, a conservative starting point that keeps early noise down.

They are ordinary rules, and they are yours to change: edit their patterns, switch them off, or delete them to have machines send everything. Deleting them sticks; the defaults are seeded only once per account, so they never come back on their own. While any "send only" rule is active, the rules editor shows a warning banner reminding you that non-matching events are being dropped.

Dashboard overview

The dashboard is your landing page, the first thing you see after signing in. It gives you a read-only, at-a-glance summary of everything EyalSec has detected, so you can spot trends without digging through the full Events list.

Dashboard with stat cards for total hits, last 24h, and unique types, plus a 14-day timeline

What the cards show

The overview is built from a set of summary cards and a timeline:

Read-only by design

The dashboard doesn't let you change anything; it's a window, not a control panel. When you spot something worth investigating, follow it up elsewhere:

If your dashboard is empty, your machines may not have detected anything yet. Check that EyalSec is installed and running on the Machines page.

Activity log

The activity log is your own audit trail. Every meaningful action on your account is recorded here: who did what, when, from where, and whether it worked. It's a read-only record that lets you review your history and spot anything you don't recognize.

Activity log table showing time, action, target, source IP, auth method, and outcome columns

You'll find it on the Activity page (/scanner/activity) in the dashboard.

What each row records

Every entry is one action you (or something using your credentials) took. The table has these columns:

Filtering the log

The log is shown reverse-chronologically, newest first, and you can narrow it down with two filters:

Loading more history

The log shows 50 entries per page. When there are older entries, a Load more button appears at the bottom; click it to pull in the next batch. It keeps your current filters as you go, so you can page back through just the actions you care about.

How long entries are kept

The activity log keeps the last 30 days of history. Older entries are removed automatically, so this is a recent-activity view, not a permanent archive. If you need a longer record for compliance or an investigation, export or note down what you need before it ages out.

Where this fits

The activity log tracks what you did to your account and machines. To see what EyalSec detected in your code, head to the Events page or the Dashboard overview. If something in the log looks wrong (a login or an API request you didn't make), change your password right away on the Settings page, and consider turning on two-factor authentication and revoking your API key there. For anything you can't explain, contact support@eyalsec.com.

Running es-python

es-python is EyalSec secure Python: the EyalSec runtime that watches your program as it runs and reports (or blocks) risky behavior. After you install EyalSec on a machine, this is the program you run your code with instead of stock python.

Running your code

Once the installer has finished, just swap python for es-python:

es-python script.py

It behaves like the Python you already know (same language, same standard library), but it's watching for the moment your code uses untrusted data (data that came from the outside world, also called taint) in a risky way. When that happens, EyalSec records an event, which you'll see on the Events page.

If es-python isn't found, make sure you've opened a new shell since installing, or that ~/.local/bin is on your PATH. See Machines for the install steps.

Report vs Raise at runtime

EyalSec has two ways to respond when it catches something:

Most of the time you'll run in Report mode. You normally turn on Raise from the dashboard with a mode-3 rule, which is the recommended way and doesn't require touching the command line; see Machine configuration and Report mode vs Report and Raise mode. At the command line, the legacy --raise (and --raise-on-found) flags switch the whole run into Report and Raise mode. Raising on events must be enabled on your account; see Plans & roles.

The taint sources

A source is a place untrusted data can enter your program. By default EyalSec is conservative about what it treats as untrusted, so each source is off in the standard build; you switch the ones you want on. There are three ways to enable a source, in increasing order of how you'll use them day to day:

  1. A CLI flag: --make-<source>-vuln, handy for a quick local run.
  2. An environment variable: the mirror ES2_MAKE_<SOURCE>_VULN=1, the same switch for when you can't change the command line.
  3. The dashboard: the per-source toggle on the Filters page (global) or the Machine configuration Configure modal (per machine). This is how you'll normally do it.
Source What it watches CLI flag Env mirror Default
socket data received over the network (incl. TLS) --make-socket-vuln ES2_MAKE_SOCKET_VULN=1 off¹
file data read from files on disk --make-file-vuln ES2_MAKE_FILE_VULN=1 off
stdin data on standard input --make-stdin-vuln ES2_MAKE_STDIN_VULN=1 off
foreign code loaded from another user's files --make-foreign-vuln ES2_MAKE_FOREIGN_VULN=1 off
env environment variables and sys.argv --make-env-vuln ES2_MAKE_ENV_VULN=1 off

¹ socket is on by default in the socket-only build (tainting a machine's sockets is that build's whole purpose); it is off by default in the standard build like every other source.

Two shortcuts apply to the whole set:

Every source is off by default (except socket in the socket-only build), so plain es-python app.py with no flags, env vars, or dashboard rules enabled runs with no taint instrumentation at all, equivalent to stock Python.

For example, to treat everything coming off a socket as untrusted for one run, and combine that with Raise to block it:

ES2_MAKE_SOCKET_VULN=1 es-python app.py
es-python --make-everything-vuln --raise app.py

When a source catches untrusted data reaching a risky operation, the recorded event carries an origin label naming where the data came from (for example socket:fd or stdin:input). The sections below list each source's origins so you can recognise them on the Events page.

Socket-only build: a socket-only machine runs a build where file, stdin, foreign, and env are compiled out. On such a machine those four toggles do nothing, and only socket is live. Socket-only is no longer selectable when adding a machine, but some older machines still run it. See Machines.

The socket source

What it is. Data arriving from the network: anything your program receives from another computer over TCP, UDP, or a local socket, including the plaintext read out of a TLS connection.

What's instrumented. The socket object is marked when it is created or connected, and the data returned by socket.recv(), recvfrom(), recv_into(), and recvmsg() (and the ssl.SSLSocket read methods) comes out tainted. The C site is Modules/socketmodule.c.

Enable it. --make-socket-vuln / ES2_MAKE_SOCKET_VULN=1, or the socket toggle on the dashboard. Off by default in the standard build; on by default in the socket-only build.

Event origins. socket:fd, socket:connected, socket:unbound; TLS reads carry ssl:fd; a memory-mapped socket fd carries mmap-socket:fd.

import socket
s = socket.create_connection(("example.com", 80))
s.sendall(b"GET / HTTP/1.0\r\n\r\n")
data = s.recv(4096)        # tainted, origin socket:connected

Limitations. In a socket-only build, data read with a raw os.read(fd, n) on a socket fd is not tainted: socket taint flows through the socket API (recv*), which carries no per-read fstat.

The file source

What it is. Data read from a file on disk: the contents of any file your program opens and reads.

What's instrumented. The bytes returned by open(path).read(), os.read(), os.pread(), and mmap of a file descriptor. The interpreter classifies the fd with a per-read fstat(2) and taints reads from regular files. The C sites are Modules/_io/fileio.c and Modules/posixmodule.c.

Enable it. --make-file-vuln / ES2_MAKE_FILE_VULN=1, or the file toggle. Off by default.

Event origins. fd:posix_read, fd:posix_pread, fileio.read(candidate), fileio.readall(candidate); a memory-mapped file fd carries mmap-file:fd.

ES2_MAKE_FILE_VULN=1 es-python -c 'print(open("/etc/hostname").read())'
# the file contents are tainted, origin fileio.readall(candidate)

Limitations. The per-read fstat adds cost on file-heavy workloads; that overhead is exactly what the socket-only build removes by compiling this source out.

The stdin source

What it is. Data fed to your program on standard input: text piped in or typed at the keyboard.

What's instrumented. The string returned by the input() builtin and reads from sys.stdin (file descriptor 0). The C site is Python/bltinmodule.c (the input builtin) plus the file-read path on fd 0.

Enable it. --make-stdin-vuln / ES2_MAKE_STDIN_VULN=1, or the stdin toggle. Off by default.

Event origins. stdin:input (the input() builtin) and stdin (stream reads via sys.stdin).

name = input("name? ")           # tainted, origin stdin:input
blob = sys.stdin.buffer.read()   # tainted, origin stdin

Limitations. Compiled out in a socket-only build.

The foreign source

What it is. Foreign code: Python loaded from a file that another user on the machine can write to. Loading code you don't fully control is a classic way for an attacker to slip in their own logic. This is the one source that is a detection rather than a taint flow: it fires on the act of loading the file, independent of any data flow, even during interpreter startup.

What's instrumented. Importing a .py/.pyc, running the main script, or compile/exec/eval of file source, whenever the resolved path is writable by another user.

Enable it. --make-foreign-vuln / ES2_MAKE_FOREIGN_VULN=1, or the foreign toggle. Off by default. Note --make-everything-vuln does not include it; turn it on explicitly.

Event. Loading such a file emits foreign-code:<path>, naming the exact file; you get one event per resolved path per process, so a noisy import loop won't flood you. Under Raise, EyalSec aborts the load with a RuntimeError.

Exemptions. If some paths are trusted on purpose (say, a shared tools directory you maintain), exempt them by listing their absolute path prefixes in the ES2_FOREIGN_CODE_ALLOW environment variable, separated by colons. There are no automatic exemptions: even the standard library is checked unless you add it here.

ES2_MAKE_FOREIGN_VULN=1 ES2_FOREIGN_CODE_ALLOW=/opt/team-tools:/srv/shared es-python app.py
# importing /tmp/evil.py (writable by another user) -> event foreign-code:/tmp/evil.py

Limitations. Compiled out in a socket-only build.

The env source

What it is. Data describing how the program was launched: its environment variables and command-line arguments. Both are influenced by whoever started the process.

What's instrumented. The values read from os.environ (and os.getenv) and the elements of sys.argv. The environment value is tainted, not the key (a fixed variable name is not attacker data). The C sites are Modules/posixmodule.c (environment) and Python/sysmodule.c (sys.argv).

Enable it. --make-env-vuln / ES2_MAKE_ENV_VULN=1, or the env toggle. Off by default.

Event origins. env (environment values) and argv (argument-vector elements).

import os, sys
token  = os.environ["API_TOKEN"]   # tainted, origin env
target = sys.argv[1]               # tainted, origin argv

Limitations. Only the value is tainted, not the variable name. Compiled out in a socket-only build.

Installing packages: es-pip and es-uv

The install also drops two package-manager helpers next to es-python:

Use them exactly like pip and uv:

es-pip install requests
es-uv pip install flask

Packages you install this way go into the es-python environment, so your code finds them when it runs under es-python. Behind the scenes, es-pip/es-uv first ask your EyalSec server (briefly, with a one-second budget) for packages it has prebuilt for this exact runtime, and otherwise fall back to PyPI plus the wheels bundled with the install. That means installs keep working even when the server is unreachable. For a handful of database drivers (PostgreSQL, MySQL, MariaDB, Oracle), EyalSec ships its own instrumented builds of the standard packages, so SQL-injection detection works out of the box; they behave identically to the stock drivers.

Your regular pip and python are untouched and keep working as before.

How the machine picks up your settings

You don't have to restart everything every time you change a setting in the dashboard. When es-python starts, it fetches that machine's configuration from EyalSec, then refreshes it in the background roughly every 30 seconds, so new settings reach the running program on their own.

Two kinds of change behave a little differently:

A note on fuzz mode

es-python also carries some --fuzz-* flags. These are experimental and are not needed for normal use; you can safely ignore them.

Where this fits

The runtime is the part of EyalSec that lives on your host and produces the events you read in the dashboard. To control its behavior from the web instead of the command line, see Machine configuration; to understand the two response modes, see Report mode vs Report and Raise mode; and to read what it detects, see Events. If something isn't behaving as expected, reach out to support@eyalsec.com.

On-machine docs and AI assistants

Every install drops a small docs home at ~/.eyalsec/:

es-python -h also prints these paths at the end of its help.

Uninstalling removes ~/.eyalsec/. Any AGENTS.md symlink or @-include you added to a project with eyalsec agents-init stays in that project and becomes a dangling reference; remove it by hand if you no longer want it.

Plans & roles

Your EyalSec account has a set of limits and capabilities that we set for you. Most things (installing machines, viewing Events, running es-python, Report mode) work on every account. A few limits and capabilities vary per account; the table below shows the defaults.

What your account includes

Your account
Machines Limited (5 by default)
Events visible Monthly quota (5,000/month by default)
Report and Raise Only if enabled

A few things to note:

What you'll experience as a regular user

Your account mostly stays out of your way. You'll only notice its limits at a few specific moments, each of which gives you a clear message.

The machine cap (409)

Every regular user has a machine limit (5 by default). Try to add one past your cap and EyalSec stops you with an error:

Machine limit reached (5). Contact sales@eyalsec.com to upgrade your plan.

The number in parentheses is your current limit. To get more machines, ask us to raise your machine limit (see below). Deleting a machine you no longer need also frees a slot; see Machines.

The event quota

Every regular user also has a monthly event quota (5,000/month by default) capping how many event rows are visible in Events, including during an active 24-hour Trial. It resets every calendar month: the first N events that arrive each month are shown, and anything past that is hidden. Nothing is deleted; each new month starts a fresh quota, and an event that fires again in a later month counts against (and can appear in) that month's quota.

When you scroll to the end of your events list, the footer reminds you of your quota. To see more events per month, ask us to raise it (see below).

The trial clock

A brand-new self-signup account has a 24-hour clock that starts the moment you verify your email (the confirmation step at signup). During those 24 hours everything works normally. After 24 hours the trial goes read-only:

Your trial has ended…

Your events view also freezes at the moment the trial ended, so you won't see new detections come in until we update your account. Once that happens the trial clock clears and full write access is restored right away.

The Raise gate

Report and Raise mode (a "mode-3" rule that tells the runtime to block an action) needs to be enabled on your account. If you try to add a Raise rule before it's enabled, the request is rejected with:

Raise rules are not enabled for your account. Ask an administrator to enable them.

Email sales@eyalsec.com to turn it on. Reporting works for every account either way; only blocking (Raise) needs the capability enabled. See Machine configuration for how Raise rules work.

How to get more

There's no self-service upgrade button. To raise your machine limit or event quota, or to enable Report and Raise, email sales@eyalsec.com and tell us what you need; we set it for you.

Once your grants change, the new limits apply immediately: a higher machine cap unlocks, Raise becomes available the moment it's granted, and any grant change clears an active trial clock and restores write access. You don't need to reinstall anything on your machines.

If you're not sure what you need, or you have a question about your account, reach out to support@eyalsec.com. See also Getting help.

API reference

Everything you can do in the dashboard, you can also do over HTTP. The EyalSec JSON API lets you read your data (events, stats, machines, activity) and automate every action the web UI performs: adding machines, generating install commands, creating filters and rules, configuring taint, and more. It's the same backend the dashboard itself talks to.

A curl request carrying an X-API-Key: es2_… header returning a JSON response

Base URL. All endpoints live under your EyalSec origin; for the hosted service that's https://eyalsec.com. If you run on your own domain, use that origin instead. Every path below is relative to it (e.g. the full URL for GET /api/get_all_machines/ is https://eyalsec.com/api/get_all_machines/).

Authentication

Every /api/* endpoint except the public GET /api/supported_os_types/ and GET /api/supported_python_versions/ needs you to prove who you are. There are two ways:

A few rules that apply to API-key requests specifically:

A worked example

Fetch your event statistics with nothing but the API key:

curl -s https://eyalsec.com/api/get_event_stats/ \
  -H "X-API-Key: es2_3f9a…"

A typical response:

{
  "total_hits": 1842,
  "unique_types": 27,
  "last_1h": 12,
  "last_24h": 305,
  "last_7d": 1402,
  "top_events": [],
  "recent": [],
  "timeline_14d": [],
  "first_detection": "2026-05-20T09:14:02Z",
  "latest_detection": "2026-06-05T11:48:31Z"
}

Add the same -H "X-API-Key: …" header to any request below.

Events & stats

Read your detected events, the aggregate statistics, and the list of known event names.

Get events (with counts)

POST /api/get_all_events_with_count/

The request body is JSON; every field is optional:

The response is a list of events plus a paging cursor:

{
  "events": [
    [
      "2026-06-05T11:48:31Z", "socket.recv -> eval", "<str 'id'>",
      "Traceback (most recent call last)…", "", 14,
      "web-prod-01", "{...origin...}", "<str 'id'>", "critical"
    ]
  ],
  "next_cursor": "eyJ0IjoiMjAyNi0wNi0wNVQxMTo0ODozMVoiLCJpZCI6OTl9",
  "event_limit": 5000,
  "event_limit_monthly": true
}

Each event is a 10-element array. The positions are fixed:

Index Field Meaning
0 created_at When the event was first seen
1 where Display label for what happened
2 str_repr Text representation of the data
3 trace Stack trace at detection
4 (reserved) Always blank on your account
5 count How many identical occurrences
6 machine_name Which machine reported it
7 origin File metadata of the data's source
8 repr_created Repr at the moment the data was created
9 severity Computed severity (critical/high/medium/low/info)

Paging. The API returns up to 100 events per page, newest first. To fetch the next page, send the next_cursor value from the previous response as cursor in your next request. When next_cursor is empty there are no more pages.

Your event quota. The API sees exactly what the dashboard sees, so your account's event quota applies (event_limit in the response; -1 means unlimited, and event_limit_monthly tells you whether it resets each calendar month). Limits are set per account, not by a plan tier.

This endpoint is a read, so it still works for expired trials. Errors: 400 (bad JSON, bad cursor, invalid regex, or unparseable timestamp), 500 (server error).

curl -s https://eyalsec.com/api/get_all_events_with_count/ \
  -H "X-API-Key: es2_3f9a…" \
  -H "Content-Type: application/json" \
  -d '{"from":"2026-06-01","severity":["critical","high"],"blacklist_repr":"^healthcheck$"}'

Get event statistics

GET /api/get_event_stats/

Returns the dashboard summary:

{
  "total_hits": 1842, "unique_types": 27,
  "last_1h": 12, "last_24h": 305, "last_7d": 1402,
  "top_events": [], "recent": [], "timeline_14d": [],
  "first_detection": "2026-05-20T09:14:02Z",
  "latest_detection": "2026-06-05T11:48:31Z"
}

first_detection and latest_detection may be absent if you have no events yet.

Get event names

GET /api/event_names/
{ "names": ["socket.recv -> eval", "open -> exec", "…"] }

Machines

List your machines and perform every action on the Machines page.

List machines

GET /api/get_all_machines/
{
  "machines": [
    [
      "2026-05-20T09:00:00Z", "web-prod-01", "installed",
      "m_8f3c2a1b", "ubuntu_24_04_x86_64",
      "bc0ef13-20260604T120000Z", "bc0ef13-20260605T080000Z",
      "3.13", false, false, false, false
    ]
  ]
}

Each machine is a 12-element array:

Index Field Meaning
0 created_at When you added the machine
1 machine_name The name you gave it
2 status created, pending, or installed
3 public_id Safe machine handle used in other API calls
4 os_type Distro + arch target
5 installed_build Build version currently installed on the host
6 current_build Latest build available for this machine's target, Python version, and variant
7 python_version The Python version this machine runs (e.g. 3.13)
8 socket_only Dormant. Machines created now are never socket-only, so this reads false; older machines may still carry true.
9 clone_locked true when a clone/tamper identity mismatch was flagged
10 stalled true when an install has sat in pending too long
11 stale_agent true when an old install's runtime (revoked credential) is still phoning in

The machine's secret is never exposed by the API. You reference machines by their public_id. When installed_build and current_build differ, an update is available (see Machines).

curl -s https://eyalsec.com/api/get_all_machines/ \
  -H "X-API-Key: es2_3f9a…"

Add a machine

POST /api/add_new_machine/

Form fields:

Returns 201:

{ "Name": "web-prod-02", "OsType": "ubuntu_24_04_x86_64",
  "Status": "created", "PublicID": "m_a1b2c3d4",
  "PythonVersion": "3.13", "SocketOnly": "false" }

Errors: 422 (bad/missing field, unknown target or Python version), 409 (machine limit reached), 403 (CSRF). SocketOnly always comes back "false": machines created now are never socket-only (the field is retained but dormant).

Set a machine's OS

POST /api/set_machine_os/

Form fields: machine_name, distro, arch. Returns 200:

{ "machine_name": "web-prod-02", "os_type": "debian_12_aarch64" }

Rename a machine

POST /api/rename_machine/

JSON body {"public_id": "m_…", "machine_name": "new-name"}200.

Delete a machine

POST /api/delete_machine/

JSON body {"public_id": "m_…"}204. This cascades: the machine and all of its events are removed (see Machines).

Get the install command

POST /api/get_eyalsec_install_script_data/

JSON body {"public_id": "m_…"} returns a one-time install secret:

{ "secret": "…", "script_path": "/install.sh", "os_type": "ubuntu_24_04_x86_64" }

The secret is single-use with a 10-minute TTL. Errors: 422 (no OS set yet), 404 (unknown machine), 409 (machine is already pending or installed).

Reinstall (update) a machine

POST /api/reinstall_machine/

JSON body {"public_id": "m_…"} returns a fresh install command (the same shape as above) and resets the machine to created. This is how you pull a newer build onto a host. Errors: 409 if the machine isn't installed or pending.

curl -s https://eyalsec.com/api/reinstall_machine/ \
  -H "X-API-Key: es2_3f9a…" \
  -H "Content-Type: application/json" \
  -d '{"public_id":"m_8f3c2a1b"}'

Get the uninstall command

POST /api/get_uninstall_script_data/

JSON body {"public_id": "m_…"} returns a one-time secret and the uninstall script path, the same command the Machines page shows:

{ "secret": "…", "script_path": "/uninstall.sh" }

The secret is single-use with a short life.

Supported OS types (public)

GET /api/supported_os_types/

Needs no authentication:

{ "distros": [ { "id": "ubuntu_24_04", "label": "Ubuntu 24.04 LTS",
                 "archs": ["x86_64", "aarch64"] } ] }

Supported Python versions (public)

GET /api/supported_python_versions/

Also needs no authentication. Returns the versions you can pick when adding a machine, and the default:

{ "versions": ["3.13", "3.14"], "default": "3.13" }

Filters

Manage the global whitelist/blacklist filters from the Filters page.

List filters

GET /api/filters/
{ "filters": [ { "id": 7, "pattern": "^healthcheck$", "label": "noise",
                 "mode": "blacklist", "target": "where", "enabled": true } ] }

Create a filter

POST /api/filters/create/

JSON body:

Returns 201. A bad, empty, or invalid regex returns 400.

curl -s https://eyalsec.com/api/filters/create/ \
  -H "X-API-Key: es2_3f9a…" \
  -H "Content-Type: application/json" \
  -d '{"pattern":"^healthcheck$","mode":"blacklist","label":"noise"}'

Update a filter

PATCH /api/filters/{id}/update/

Send a partial JSON body with just the fields you want to change → 200. A missing filter returns 404.

Delete a filter

DELETE /api/filters/{id}/delete/

Returns 204.

Machine rules

Machine rules drive the dashboard display (modes 1 and 2), machine-side event dropping (mode 4), and runtime Raise behavior (mode 3). For the full concept, including how polarity (whitelist/blacklist) combines with each mode, see Machine configuration. The machine field is a public_id, or '' (empty string) to target the global scope.

List rules

GET /api/machine_rules/?machine=<public_id|>
{
  "rules": [
    { "ID": 12, "Source": "any", "EventPattern": ".*eval.*",
      "Mode": 3, "Action": "raise", "Polarity": "whitelist",
      "Label": "block eval", "Enabled": true }
  ]
}

Note the capitalized field names in rule objects. Action is a legacy field (always raise for new rules); Polarity is what matters.

Create a rule

POST /api/machine_rules/create/

JSON body:

Returns 201 with the created rule (same shape as the list).

Mode 3 (Raise) has to be enabled for your account. It is a per-account setting rather than something you switch on yourself. Without it the request returns 403 with a message saying Raise rules are not enabled for your account, and who to ask. Check grant_raise on GET /api/account/ to see where you stand before you try.

curl -s https://eyalsec.com/api/machine_rules/create/ \
  -H "X-API-Key: es2_3f9a…" \
  -H "Content-Type: application/json" \
  -d '{"machine":"m_8f3c2a1b","polarity":"whitelist","event_pattern":".*eval.*","mode":3,"label":"block eval"}'

Update a rule

PATCH /api/machine_rules/{id}/update/

Partial JSON (any of polarity, event_pattern, mode, label, enabled) → 200. Changing a rule's mode to 3 re-checks that Raise is enabled for your account.

Delete a rule

DELETE /api/machine_rules/{id}/delete/

Returns 204.

Taint

Read and set which taint sources are tracked, per machine or globally. For what the sources mean and when changes take effect, see Machine configuration.

Read taint settings

GET /api/machine_taint/?machine=<public_id|>

All five sources are always present:

{ "taint": { "socket": "on", "file": "off", "stdin": "unset",
             "foreign": "unset", "env": "unset" } }

Set a taint source

PUT /api/machine_taint/

JSON body:

Returns 204. Remember: taint changes take effect on the next process restart of es-python.

curl -s -X PUT https://eyalsec.com/api/machine_taint/ \
  -H "X-API-Key: es2_3f9a…" \
  -H "Content-Type: application/json" \
  -d '{"machine":"m_8f3c2a1b","source":"socket","state":"on"}'

Activity

Read your own audit trail, the same data shown on the Activity log page.

GET /api/activity/?action=&outcome=&before_ts=&before_id=

Results are newest-first and keyset-paged, 50 per page. Filter with:

Activity is retained for 30 days.

curl -s "https://eyalsec.com/api/activity/?action=machine.create&outcome=success" \
  -H "X-API-Key: es2_3f9a…"

Your account

Everything on the Settings page is here too: your profile, password, two-factor auth, API keys and billing.

These endpoints ask for the same proof the dashboard asks for. Changing your password over the API needs your current password, exactly as the form does. Generating a key needs your password and a 2FA code. An API key on its own is never enough. That is deliberate: the API should not be an easier way into your account than the browser, so a leaked key still cannot take it over.

Send the credentials as form fields alongside the rest of the request: password, and totp when your account has two-factor auth on. A backup code works anywhere a totp code does.

Three of these destroy the key you are calling with. Read the warnings.

Read your profile

GET /api/account/
{
  "username": "alice",
  "email": "alice@example.com",
  "timezone": "Asia/Jerusalem",
  "email_verified": true,
  "totp_enabled": true,
  "email_2fa_enabled": false,
  "machine_limit": 5,
  "event_limit": 5000,
  "event_limit_monthly": true,
  "grant_raise": false
}

This is where you check what your account is actually allowed to do: machine_limit, event_limit (-1 means unlimited) and grant_raise. It never returns your password, your two-factor secret, or any key.

Set your timezone

POST /api/account/timezone

Form field timezone: an IANA name such as Asia/Jerusalem. Send it empty to go back to automatic (your browser's zone). No password needed, same as the dashboard. Returns 200 with {"timezone": "Asia/Jerusalem"}; an unknown zone returns 422 and changes nothing.

Change your recovery email

POST /api/account/email

Needs password, plus totp when 2FA is on. Form field email.

A new address is not applied immediately. A confirmation link is emailed to it, and the change only lands once you click that link, exactly like the dashboard. This is what stops someone with a stolen session pointing your recovery address at themselves.

{
  "status": "confirmation_sent",
  "email": "new@example.com",
  "note": "not applied yet: click the link sent to the new address to confirm"
}

Sending an empty email removes your recovery address straight away. That also disables email 2FA (there would be nowhere to send the code) and signs out every session.

Change your password

POST /api/account/password

Needs current_password. Then new_password, and optionally confirm (if you send it, it has to match).

This revokes your API key. A password change signs out every session and deletes your key, so the key making this call stops working the instant it succeeds. Generate a new one afterwards.

{
  "status": "password_changed",
  "note": "your API key and all sessions were revoked; generate a new key"
}

Delete your account

POST /api/account/delete

Needs password. Returns 204 and removes the account and everything in it. There is no undo.

Set up an authenticator app

GET /api/account/2fa/setup
{
  "secret": "JBSWY3DPEHPK3PXP",
  "otpauth_url": "otpauth://totp/EyalSec:alice?secret=…",
  "expires_in_seconds": 600,
  "note": "call /api/account/2fa/enable with a code from this secret to activate it"
}

The secret is held for you server-side for 10 minutes. Turn otpauth_url into a QR code, or type secret into your app by hand. Calling this again starts over with a fresh secret.

Turn on the authenticator app

POST /api/account/2fa/enable

Needs password, and totp: a code generated from the secret you just got.

Note the difference: that totp proves you hold the authenticator, it is not a second factor on this request. The password is the re-auth.

Returns your backup codes, shown once:

{
  "status": "2fa_enabled",
  "backup_codes": ["…", "…"],
  "note": "backup codes are shown once; store them now. All sessions were revoked."
}

A wrong code returns 422 and enables nothing. Calling this without doing setup first returns 409.

Turn off the authenticator app

POST /api/account/2fa/disable

Needs password. Signs out every session, so your other devices have to log in again.

New backup codes

POST /api/account/2fa/backup/regenerate

Needs password. Replaces any codes you already had, so the old ones stop working. Shown once. Requires the authenticator app to be set up (409 if not).

Email second factor

POST /api/account/2fa/email/enable
POST /api/account/2fa/email/disable

Both need password. Enabling requires a recovery email on file, since that is where the code is sent (409 without one).

Look at your API key

GET /api/account/apikey
{
  "exists": true,
  "key_prefix": "es2_3f9a…",
  "scope": "full",
  "created_at": "2026-07-01T09:00:00Z",
  "expires_at": "2027-01-01T00:00:00Z",
  "last_used_at": "2026-07-16T06:20:00Z",
  "last_ip": "203.0.113.4"
}

Useful for checking whether a key is still in use, and from where. It never returns the key itself: only a hash is stored.

Generate a key

POST /api/account/apikey/generate

Needs password, plus totp when 2FA is on. Optional expires (a future YYYY-MM-DD; omit for a key that never expires) and read_only (any non-empty value).

This replaces your current key. You get one key per account, so generating a new one kills the one you are calling with. Copy api_key from the response or you will lock yourself out.

{
  "api_key": "es2_NEWKEY…",
  "note": "shown once, and it replaces any previous key for this account"
}

A past expires returns 422 and creates nothing.

Revoke your key

POST /api/account/apikey/revoke

Needs password. Returns 204. If you revoke the key you are calling with, that call is the last thing it does.

GET /api/account/billing/portal

Returns the URL behind the dashboard's Manage billing button:

{ "portal_url": "https://customer-portal.paddle.com/…" }

404 when there is no active subscription.

What is not here

Rate limits

Every response that hits a limit returns 429 with a Retry-After header, and a JSON body.

Limit Applies to
300 requests/minute per API key Everything you call with a key. Counted against your account, so making a new key does not reset it.
300 requests/minute per IP address All traffic from one address, keyed or not.
120 requests/minute POST /api/get_all_events_with_count/, the heaviest read.
10 requests/minute Anything under /api/account/ that asks for your password.

The per-key and per-IP limits are counted separately and both apply, so whichever runs out first is the one you feel.

Getting your password wrong repeatedly locks the account. Five wrong attempts in a row and it locks for a minute, doubling up to 15 minutes. This is the same counter the login page uses, so a locked account is locked everywhere. While it is locked you get 429 even with the right password.

Agent endpoints

These endpoints are used automatically by es-python on your monitored machines; you normally never call them by hand. They're listed here for completeness. They authenticate with the machine's secret, not your API key.

Report an event

POST /api/create_event/        (alias: POST /events)

The runtime sends a Secret: <machine-uuid> header and the event fields base64-encoded: where, repr, trace, location, and origin. The server always responds 202 Accepted; it's fire-and-forget. Events are aggregated and counted server-side, which is why the dashboard shows counts rather than one row per occurrence (see Events).

Fetch machine configuration

GET /api/machine_config

The runtime sends the Secret header and receives the machine's encrypted configuration (its taint settings and rules). The response is conditional: the agent sends an X-Config-Version header and gets 304 Not Modified when nothing has changed. es-python polls this at startup and roughly every 30 seconds (see Running es-python).

Install bootstrap (public)

POST /install.sh
POST /install/payload

These serve the self-extracting installer when you run the install command from the Machines page. They're public and always reachable so a new machine can bootstrap, and they support HTTP Range requests so an interrupted download can resume with curl -C -. The public GET /api/supported_os_types/ (documented under Machines) is also used here to list the available targets.

Errors

The API uses standard HTTP status codes. API-key requests always return a JSON body of the form {"error": "…"} rather than an HTML page or a redirect.

Status Meaning
400 Bad request: malformed JSON, an invalid regex, or an unparseable cursor or timestamp.
403 Forbidden: failed CSRF check, missing or invalid auth, a read-only key trying to change something, a capability that is not enabled for your account (Raise rules, source filtering), or a write on an expired trial.
404 Not found: the object doesn't exist. This is also returned to hide objects that belong to another user.
409 Conflict: the action clashes with current state (machine already pending/installed, or the machine limit is reached).
422 Unprocessable: a required field is missing or invalid (e.g. an unknown os_type or python_version, a name longer than 128 characters, or no OS set).
500 Server error: an unexpected failure on our side, including build or blob-storage problems.

A 401 Unauthorized is returned specifically when an API key is bad, revoked, or expired (see Authentication).

Glossary

A plain-English reference for the terms used throughout this guide. Each one is also defined where it first appears, but this is the quick lookup.

Untrusted data (taint)

Data that came from the outside world rather than from your own program, for example bytes read off a network socket, the contents of a file someone else can write, or text typed on standard input. EyalSec calls this untrusted data, and its internal name for it is taint. The two words mean the same thing. EyalSec tracks untrusted data as it flows through your program so it can tell when that data is about to be used in a dangerous way.

Source

Where untrusted data entered your program. EyalSec recognises five sources:

You choose which sources EyalSec watches per machine; see Machine configuration.

Sink

A risky action: the dangerous thing untrusted data might end up doing. Common sinks are running a system command, executing code, or opening a file path built from outside input. EyalSec records (or blocks) the moment untrusted data reaches a sink. The pairing is the whole idea: a source brings data in, a sink could do harm with it, and EyalSec watches the path between them.

Report

The default behavior: when untrusted data reaches a sink, EyalSec records an event on your dashboard and lets your program keep running. Nothing about how your app behaves changes. See Report mode vs Report and Raise mode.

Raise

The blocking behavior: instead of just recording, EyalSec stops the risky action by throwing a Python RuntimeError at that exact spot, so the dangerous operation never happens. Raise must be enabled on your account; see Plans & roles.

Modes 1 / 2 / 3 / 4

The four "modes" a dashboard rule can have:

Modes 1 and 2 only change what you see on the dashboard; they never affect what's collected or how your program runs. Modes 3 and 4 change what the machine does. Each rule also has a polarity (whitelist or blacklist) that decides whether the mode acts on the matching events or on everything except them. See Machine configuration.

Severity

A per-event rating (critical, high, medium, low, or info) computed on the server from what the event shows, so the rows that deserve attention stand out. Shown as a colored word on the Events page, and filterable there and in the API.

Socket-only build

A slimmer build of the es-python runtime that watches network data only: the file, stdin, foreign, and env instrumentation is compiled out. It is no longer selectable when adding a machine (new machines always get the full build), but some older machines still run it and show a socket-only badge. See Machines.

Origin

The file metadata of the data's source: details about the file the untrusted data came from, such as its type, permissions, size, owner, and timestamps. Origin helps you judge whether a file should have been trusted in the first place. It appears in an event's detail pane.

Foreign code

Python code loaded from a file that another user on the machine can write to. If a file you import or run could be modified by someone other than you, that's a risk: they could change what your program does. When foreign-code detection is on, loading such a file emits a foreign-code:<path> event, and under Raise it aborts the load. See Running es-python.

public_id vs secret

Every machine has two identifiers:

Build version

A short label identifying exactly which build of the es-python runtime a machine is running. The dashboard compares the build installed on a machine against the latest available build and shows an "up to date" or "update available" badge. To take a newer build, reinstall the machine; see Machines.

API key

A token (it starts with es2_) that lets a program talk to the dashboard's JSON API on your behalf, without a browser login. You generate one in Settings; it's shown only once, so save it somewhere safe. An API key has the same access your account does.

CVE

Short for Common Vulnerabilities and Exposures: the industry-standard naming scheme for publicly known security flaws (for example, CVE-2021-44228). When people talk about a specific known vulnerability, they usually refer to it by its CVE identifier.

FAQ & troubleshooting

Quick answers to the questions that come up most. If yours isn't here, see Getting help.

Do I have to change my code?

No. You run the same programs the same way, just with es-python instead of python. EyalSec watches from the runtime; your source code stays exactly as it is.

Will Report and Raise mode break my app?

Only if untrusted data actually reaches a risky action. For normal traffic nothing changes; Report and Raise mode only steps in at the exact moment something dangerous is about to happen, and it does so by throwing a normal Python RuntimeError. If your code already catches exceptions around that operation, the attack is blocked and your app can carry on. Many people start in Report mode and switch to Raise once they're confident in what EyalSec is detecting.

I don't see any events. What's wrong?

Check these things in order:

  1. You ran your program with es-python, not plain python.
  2. The machine shows as installed (green) on the Machines page.
  3. You don't have a filter or rule hiding the events. Check Filters and the Events filter toolbar, and remember that new accounts start with two default "send only matching" drop rules (patterns re and write); events that match neither are dropped at the machine until you edit or remove those rules (see Filters).
  4. The sources you care about are switched on for that machine. Taint sources are off until you enable them; see Machine configuration. Remember that taint changes take effect on the next process restart.

How do I go back to normal Python?

Just use python as you always have. EyalSec is added alongside it, so your original Python is always still there. Switching between python and es-python takes nothing more than which command you type.

How do I update a machine to a newer build?

When the dashboard shows an "update available" badge on a machine, you update it by reinstalling it. On the Machines page, open the machine's menu and choose Reinstall. That mints a fresh install command for you to run on the host, which pulls down the latest es-python build. Your machine's internal identity is unchanged, so all your existing events stay attributed to it; nothing is lost. Reinstalling is the normal, expected way to take a newer build.

Why can't I create a Raise rule?

Raise rules (mode-3 rules that block an action at runtime) must be enabled on your account. If they aren't, you'll see:

Raise rules are not enabled for your account. Ask an administrator to enable them.

Report mode works for every account; only blocking (Raise) needs the capability turned on. To have it enabled, email sales@eyalsec.com. See Plans & roles.

My trial ended and the dashboard is read-only. What now?

A new self-signup account gets a 24-hour trial clock that starts when you verify your email. Once it runs out the dashboard goes read-only: you can still browse everything, but creating or changing things is blocked, and your events view is frozen at the moment the trial ended. To unlock full access, email sales@eyalsec.com. As soon as an admin lifts the trial or adjusts your account, the clock clears and write access comes back. See Plans & roles.

How do I use the API?

EyalSec has a JSON API that mirrors what you can do in the dashboard. To use it:

  1. Generate an API key in Settings. It starts with es2_ and is shown only once, so copy it somewhere safe.

  2. Send it on each request in the X-API-Key header, like this:

    curl -H "X-API-Key: es2_3f9a…" https://eyalsec.com/api/get_all_machines/
    

API-key requests always return JSON and never redirect to a login page. The key carries the same access your account has, so keep it private. For the full list of endpoints, see the API reference.

I lost my 2FA device. How do I log in?

When you set up two-factor authentication, EyalSec showed you a set of backup codes (in the form xxxxx-xxxxx). Each one works exactly once in place of the 6-digit app code on the two-factor screen, and they're not case-sensitive. Use one to get in, then go to Settings to re-enroll your authenticator on your new device and regenerate a fresh set of backup codes. If you've also run out of backup codes and can't get in at all, email support@eyalsec.com. See Getting help.

Getting help

Still stuck, or want to talk to a person? We're here.

Before you write in, the FAQ & troubleshooting section answers the most common questions, and the Glossary explains any term you're unsure about. If you do reach out, telling us which machine and roughly when something happened helps us help you faster.

EyalSec Pricing Docs Security Contact Login Start free