Run your test suite under es-python in GitHub Actions. Every injection flow your tests reach is summarised on the run's page, and the job fails at the severity you choose.
Your tests already drive your code through its real paths. With this action they also tell you when untrusted data on one of those paths reached a SQL query, a shell command, a deserializer, a file path or a dynamic import. Keep the test runner, the linters and the scanners you use today; this adds one step next to them.
For: teams with an EyalSec account that includes es-python, on Linux runners (x86_64 or aarch64), Python 3.9 to 3.14.
- On your EyalSec dashboard, open Settings > API access and create a full-access key (API keys).
- In your repository, add it as an Actions secret named
EYALSEC_API_KEY(Settings > Secrets and variables > Actions). - Add the step after your dependencies are installed:
name: tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-24.04
# One run at a time per machine: a second install would stop the first
# run's es-python from reporting.
concurrency:
group: es-python-${{ github.repository }}
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- run: pip install -r requirements.txt pytest
- uses: EyalSec/es-python-action@v1
with:
api-key: ${{ secrets.EYALSEC_API_KEY }}
run: es-python -m pytestWherever you would type python, the command types es-python. It uses the
packages installed for the runner's own Python of the same version, which is
why the step comes after pip install.
After the command, the step log prints the check, and the run's summary page gets a table like this one, from this repository's own end-to-end test, whose deliberately vulnerable example joins text received over a network connection into a shell command:
Reported by this run Critical High Medium Low Info 7 3 0 0 0 4
Severity Operation Event Occurrences, all runs critical new subprocess shell22518624 1 critical new shell injection22518612 1 critical new os spawn22518599 1
new means no earlier run had reached that finding; seen means one had. The value that reached the operation is never printed in the log or the summary, because CI pages are often public. Follow the link at the bottom of the summary to the dashboard for the whole flow: where the data came in, the call path, and the line that used it.
| Input | Default | Meaning |
|---|---|---|
api-key |
required | Your EyalSec API key, from a secret. Full access, because installing is a write. |
run |
required | The command to run under es-python, in bash. Several lines run as one script that stops at the first failing line. |
machine |
ci-<repository name> |
The EyalSec machine this job reports under, created on the first run. |
url |
https://eyalsec.com |
Your EyalSec address. |
python-version |
the runner's python3 |
The Python version, fixed when the machine is created. |
distro, arch |
read from the runner | The runner's target as EyalSec names it, such as ubuntu_24_04 and x86_64. See supported targets. |
fail-on |
high |
critical, high, medium, low, info, or never to report without failing. |
new-only |
false |
Fail only on findings no earlier run had reached. |
sources |
socket,file |
The taint sources to switch on for the command, comma-separated, or dashboard to use only the machine's Configure settings. |
wait |
10 |
Seconds to wait for the run's events to arrive before checking. |
working-directory |
. |
Where the command runs. |
A source set here is a default for the run: an explicit On or Off for the same source on your dashboard still wins.
| Output | Meaning |
|---|---|
result |
pass, fail (findings at the threshold), error (the action could not do its job), or command-failed. |
total |
Every event the run reached, at any severity. |
findings |
Those at or above fail-on. |
new-findings |
Of findings, the ones no earlier run had reached. |
events-url |
The dashboard's events list for this machine. |
The step fails when your command fails (its own exit status is kept), when
findings reach fail-on, or when the action cannot install or check. To keep
the job going and decide yourself, set continue-on-error: true on the step
and read result.
- One run per machine at a time. Each run reinstalls the machine with a
fresh one-time install secret, so no machine credential is ever cached, and
a second job installing the same machine stops the first one's reporting.
Use
concurrencyas above, or give each job its ownmachine. - Matrix builds need one machine per leg, for example
machine: ci-myrepo-py${{ matrix.python }}. A machine's Python version is fixed when it is created, and each name counts once against your machine limit however often it runs. - Pull requests from forks get no secrets, so the step stops with
"api-key is empty". To run those tests without es-python, add
if: github.event.pull_request.head.repo.fork != trueto this step and a plain test step with the opposite condition. - Accepting a finding. A finding keeps failing the job on every run that reaches it, until the flow is fixed or you add a Hide rule for it. Hidden and suppressed events never fail a build.
- The API key acts as you. Keep it in the secret store, and regenerate it
if a log or a fork ever exposed it. The action passes it to
curlthrough a private file, never on a command line, and masks it in the log. - Events are kept like any other: they count toward your event quota and appear on the Events page under the CI machine's name.
The same flow without this action, and for GitLab CI, is in
Run es-python in CI.
lib/es-python-ci.sh is the CI driver described there, and it can be run on
its own.
Book a live demo and we run it on your own code with you. There is no self-serve plan and no trial; pricing is metered by machines and events (eyalsec.com/pricing).
- Questions and problems: support@eyalsec.com, or an issue here. Never paste an API key or an install command into an issue.
- A vulnerability in EyalSec itself: security@eyalsec.com, under our disclosure policy.
More about es-python: EyalSec/es-python.
MIT for the contents of this repository. es-python itself is licensed with your EyalSec account.
Python is a trademark of the Python Software Foundation. EyalSec is not affiliated with or endorsed by the PSF. GitHub and GitHub Actions are trademarks of GitHub, Inc.