Taint sources
A taint source is a place where untrusted data can enter a program, such as the network, a file or the command line. This page lists every source, what it watches and how to switch it on.
What a source is
A source is a place where data from outside enters your program. When a source is on, EyalSec marks the data arriving through it as untrusted (taint) and follows it. If that data reaches a risky operation, you get an event. A source that is off marks nothing.
Every source is off until you switch it on, so a new machine reports nothing until you choose at least one. Start with the source that matches how your program receives input: socket for a network service, stdin for a filter or pipe, file for something that reads uploaded or downloaded files.
Names you see on screen
Each source is shown under a plain name, with its short technical name beside it in smaller type. The technical name is the one that is stored, used in flags, rules and the API, and shown on the events list, so both always refer to the same source.
| Plain name | Technical name |
|---|---|
| Network input | socket |
| Files read | file |
| Standard input | stdin |
| Files other users can change | foreign |
| Environment variables | env |
| Command-line arguments | argv |
| Test marker | make_vuln |
| Predictable random numbers | weak_random (weak-random in a rule) |
| Credentials in source code | hardcoded |
| Data read from a database | db_rows (db in a rule) |
| Database identity check | identity |
| Follow into the browser | handoff |
On the events list the same sources read as the start of a sentence, for example "Data from a network connection reached a system command" for socket. See Events.
Switching a source on
The usual way to switch a source on is the dashboard: its control in the Taint sources part of the Configure window for one machine, or of the Filters page for every machine. Each control can be On, Off or Unset (see Unset).
es-python also accepts a command-line flag and an environment variable for most sources, which is handy for a single run:
es-python --make-socket-vuln app.py
ES2_MAKE_SOCKET_VULN=1 es-python app.py
A dashboard On or Off always wins over a flag. A flag only counts while the dashboard control is Unset at both the machine and the global level. The full list of flags is on the es-python command line.
A change to a source takes effect the next time the program starts. See When a change reaches the machine.
Which products use which sources
Each product shows only the sources it can use, so the list you see depends on the machine's product. A note under the title of the Configure window or the Filters page says which controls a product does not act on yet.
| Product | Sources shown |
|---|---|
| es-python | socket, file, stdin, foreign, env, make_vuln, argv, weak_random, hardcoded, identity, db_rows, handoff |
| es-chromium | none: it does not fetch machine configuration |
On es-python, the argv, weak_random, hardcoded and identity controls take effect from the October 2026 build. A machine on an older build ignores them until it is reinstalled; until then, use the flags described under each of them below.
Source names in rules
A source's name in the Taint sources list is not always the name you pick in a rule's Source field. Two sources are spelled differently, and a rule is accepted only in the rule spelling.
| In Taint sources | In a rule's Source |
|---|---|
weak_random (underscore) |
weak-random (hyphen) |
db_rows |
db |
Every other source uses the same word in both places. The Source dropdown in the rules table already uses the rule spelling, so this matters mostly when you use the API.
A rule's Source list also offers a few names that have no control of their own, because they are switched on by another source. See Sources that only appear in rules.
Network input (socket)
socket marks data your program receives from another computer: anything read from a network connection, including the plain text read out of an encrypted (TLS) connection. It is the source most network services need.
On es-python, the data returned by the socket receive calls (recv, recvfrom, recv_into, recvmsg) and by the TLS socket read methods is marked.
- Switch on: the socket control, or
--make-socket-vuln/ES2_MAKE_SOCKET_VULN=1. - Origins you will see:
socket:fd,socket:connected,socket:unbound; TLS reads showssl:fd, and a memory-mapped socket showsmmap-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) # untrusted, origin socket:connected
Limits. When socket is the only source switched on, data read with a raw os.read() on a socket is not marked; switching on any other source as well restores it. Older socket-only machines can use this source and no other.
Files read (file)
file marks the contents of files your program opens and reads from disk. Use it for programs that process uploaded, downloaded or user-supplied files.
On es-python, the bytes returned by open(path).read(), os.read(), os.pread() and memory-mapping a file are marked, when they come from a regular file.
- Switch on: the file control, or
--make-file-vuln/ES2_MAKE_FILE_VULN=1. - Origins you will see:
fd:posix_read,fd:posix_pread,fileio.read(candidate),fileio.readall(candidate); a memory-mapped file showsmmap-file:fd.
ES2_MAKE_FILE_VULN=1 es-python -c 'print(open("/etc/hostname").read())'
# the file contents are untrusted, origin fileio.readall(candidate)
Limits. Checking each read adds some cost to programs that read many files.
Standard input (stdin)
stdin marks data fed to your program on standard input: text piped into it or typed at the keyboard. Use it for command-line tools and filters that read their input this way.
On es-python, the string returned by input() and reads from sys.stdin are marked.
- Switch on: the stdin control, or
--make-stdin-vuln/ES2_MAKE_STDIN_VULN=1. - Origins you will see:
stdin:input(frominput()) andstdin(from reads onsys.stdin).
name = input("name? ") # untrusted, origin stdin:input
blob = sys.stdin.buffer.read() # untrusted, origin stdin
Files other users can change (foreign)
foreign watches files that another user on the machine can write to. An attacker who can edit such a file controls what your program reads from it, and can run their own code inside your program if it loads the file as code. It does two things:
- Data read from those files. When your program reads a file another user can write, for example with
open(path).read(), the data is marked untrusted under foreign and followed to the operations it reaches, like any other source. A file only you can write is not marked; that is what file is for. - Code loaded from those files. When your program loads code from such a file, it reports the loading itself, even while the program is starting and even if no data flows anywhere. On es-python this covers importing a module (source, cached bytecode or a native extension module), running the main script,
compile,execorevalof a file's contents, starting such a file as a program (subprocess,os.system,os.exec*,os.posix_spawn*) and loading it as a library withctypes.
A file counts as one another user can change when someone other than you and root owns it, when anyone can write it, when its group can write it and the group has other members, or when an access control list (setfacl) lets another user or a group with other members write it. A file in a directory another user can write counts too, unless the directory is sticky, like /tmp. Files owned by root are trusted unless anyone can write them.
- Switch on: the foreign control, or
--make-foreign-vuln/ES2_MAKE_FOREIGN_VULN=1. It is not included in--make-everything-vuln. - Events you will see: a data flow whose origin is the file's path, and for a code load
foreign-code:followed by the file's path, once per file per process, so an import loop does not flood you. Under a Raise rule the load is stopped, every time it is tried. The Block privilege escalation template turns this source on for you. - Side effect: on es-python, switching foreign on also switches socket on, unless you set socket to Off.
- Shadowed modules: es-python also reports a standard-library module that was replaced by a file of the same name somewhere else (event
shadow import, rule sourceshadow-import).
To trust some locations on purpose, such as a shared tools directory your team maintains, list their absolute path prefixes in ES2_FOREIGN_CODE_ALLOW (this exempts code loads only), separated by colons. Nothing is exempt automatically.
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
Environment variables (env)
env marks the values of environment variables your program reads. Whoever starts a process controls its environment, so a value read from it is untrusted when something else builds that environment from user input.
On es-python, values read from os.environ or os.getenv() are marked, and so are the command-line arguments in sys.argv while the argv control is Unset (see argv). Only the value is marked, never the variable name.
- Switch on: the env control, or
--make-env-vuln/ES2_MAKE_ENV_VULN=1. - Origins you will see:
envfor environment values andargvfor command-line arguments.
import os, sys
token = os.environ["API_TOKEN"] # untrusted, origin env
target = sys.argv[1] # untrusted, origin argv
Command-line arguments (argv)
argv marks the command-line arguments a program was started with. They are attacker-controlled when another program builds the command line from user input, and fully trusted when a person typed them.
On es-python, arguments follow the env source while the argv control is Unset, so switching on env tracks them too. Set argv to On or Off to decide them on their own (October 2026 build and later).
Test marker (make_vuln)
make_vuln lets your own code mark a value as untrusted, by calling the make_vuln() builtin on it. It is mainly a testing tool: mark a value, pass it through your code, and see which risky operations it reaches.
import os
payload = make_vuln("hello; id")
os.system("echo " + payload) # event: untrusted data reached os system
- Switch on: set the make_vuln control to On. There is no flag: while it is Unset or Off,
make_vuln()does nothing and returns its argument unchanged. - Custom numbers:
make_vuln(value, 42)tags the value with your own number, so a rule can target it. Choose Custom number… in a rule's Source field and enter the number. See the rule Source field.
Predictable random numbers (weak_random)
weak_random marks values from a random number generator that is not meant for security. Such values are fine for shuffling or jitter, but predictable to an attacker when used as a session token or a password reset link. The event is reported where the value is used, not where it was generated.
On es-python this covers random.getrandbits (which the rest of the random module uses) and uuid1. uuid4 is not included, because it uses the operating system's secure generator.
- Switch on: the weak_random control (October 2026 build and later), or
--make-weakrandom-vuln/ES2_MAKE_WEAKRANDOM_VULN=1, or--make-everything-vuln. - In a rule: spell it
weak-random(see Source names in rules).
Credentials in source code (hardcoded)
hardcoded finds credentials, API keys and encryption keys written directly into your source code, and reports where they are used. It is the one source with a steady cost even when it finds nothing, so leave it off unless you are looking for this.
- Switch on: the hardcoded control (October 2026 build and later), or
--make-secret-vuln/ES2_MAKE_SECRET_VULN=1, or--make-everything-vuln.
Data read from a database (db_rows)
db_rows marks values your program reads back out of a database. Data your own application stored is still attacker data: it is how stored cross-site scripting and second-order SQL injection reach a risky operation, long after the request that planted them.
It marks a lot of data on a busy application, which is why it is a source of its own rather than part of socket.
- Switch on: the db_rows control, or
--make-db-rows-vuln/ES2_MAKE_DB_ROWS_VULN=1, or--make-everything-vuln. - Covers on es-python: the built-in
sqlite3module and the PostgreSQL, MySQL, MariaDB and Oracle drivers that ship with es-python. - In a rule: spell it
db(see Source names in rules).
Database identity check (identity)
identity is not a source of untrusted data. It is a switch for a check of your database queries: it compares the schema with the values bound to each query and reports a username stored in one form but looked up in another, or a check-then-insert race on a column with no unique constraint.
It is shown for es-python only and is off by default. Set it to On to run the check (October 2026 build and later); it has no flag.
Follow into the browser (handoff)
handoff is not a source either. When it is on, a monitored web service records which parts of each response came from that visitor's own input, so the EyalSec browser (es-chromium) can keep following that data in the page and report reflected and stored cross-site scripting.
It marks nothing, so no event is ever attributed to it and no rule can target it. It is shown for es-python, is off by default, and has no flag: only setting it to On switches it on. Turn it on only for a machine you are actively investigating, because it adds a little information to every response the service sends.
Turn every source on at once
es-python can switch on several sources for one run with a single flag: --make-everything-vuln, or ES2_MAKE_EVERYTHING_VULN=1. It covers socket, file, stdin, env, weak_random, hardcoded and db_rows.
It does not include foreign or make_vuln; switch those on separately. Like any flag, it only applies to sources whose dashboard control is Unset: a source set to Off stays off.
es-python --make-everything-vuln app.py
In the dashboard, the equivalent is Set all sources: All On (see Set all sources). It covers the Taint sources section only, not the switches under Other.
Sources that only appear in rules
A few names appear in a rule's Source list but have no control in Taint sources, because they are switched on by another source or by a flag. You can still target them with a rule.
| Rule source | Switched on by |
|---|---|
ssl-unverified |
socket (data from a TLS peer whose certificate was not checked) |
shadow-import |
foreign (es-python) |
fuzzer |
the fuzzing flags (es-python) |
db |
db_rows |
http |
Web protection (es-python): the events its checks report |