Skip to the content.

EvoScientist/EvoScientist — security scan

Repository: EvoScientist/EvoScientist Commit scanned: 10c032450e0e Scan date: 2026-07-28 Disclosure status: disclosed — focused issue filed upstream

Summary

Severity Count
Critical 0
High 16
Medium 23
Low 0
Info 0

Total findings: 39 (1 real after curation — and no tool ranked it)

EvoScientist (4.4k★, Apache-2.0) is a self-evolving AI scientist: a multi-agent research system built on deepagents that plans experiments, searches literature, writes and debugs code, analyses data, and drafts papers. It is explicitly human-on-the-loop rather than human-in-the-loop, and it reaches the user through ten chat channels (Feishu, WeChat/WeCom, Slack, Discord, Telegram, DingTalk, QQ, Signal, email, iMessage).

39 findings on a 4.9 MB Python codebase is an unusually low count, and the dependency picture is the cleanest this series has seen: Trivy reports zero vulnerabilities on uv.lock, pip-audit 0 across 148 packages, Dependabot is wired, and the Dockerfile is multi-stage and drops to a non-root USER evosci. Every one of the 39 scanner findings is a false positive. The one real finding was found by hand, in the gap between two files.

Top findings

1. Inbound chat webhooks skip signature verification on an attacker-chosen branch — scanner-silent

Filed upstream as #392. The approval half of this chain — sub-agent shell execution never being gated — is already tracked by the maintainers in #387; this report is only the ingress half.

2. What the 39 scanner findings actually were

Patterns observed

The interesting thing about scanning a self-evolving agent is that the obvious question turns out to be the wrong one. I expected the finding to live in the evolution loop — an agent that writes its own skills and installs them is a prompt-injection-to-persistence story waiting to happen. It doesn’t, because that loop is the most carefully built part of the codebase: autoskills go through a proposal → review → approval lifecycle, skill names are validated against a strict regex, and skill frontmatter is checked against a key allowlist. The code interpreter is a QuickJS sandbox, not in-process Python exec, and its tool allowlist explicitly excludes shell execute with the reason written down in the module docstring: “would bypass HumanInTheLoopMiddleware approval.” That is an advertised boundary that is actually enforced — the opposite of the Agently case, where a component named PythonSandbox wasn’t one.

So the real finding was in the plumbing beside it, which is becoming the pattern: docetl built a code-operator engine and left an unauthenticated /fs router next to it; here the sandboxing, the approval middleware, and the skill-proposal lifecycle are all thought through, and the front door is a webhook handler that verifies signatures only if the caller includes the field that triggers verification. Defenses fail at their seams. The tell in both cases is the same: the correct pattern exists in the same file — WeChat’s _handle_verify (the GET handshake) checks the signature properly, and verify_signature itself is a faithful implementation of the WeChat spec. The knowledge is there; one branch just doesn’t reach it.

A note on 0.0.0.0, because this series keeps meeting it and the reading is not constant. On dimos the scattered 0.0.0.0 literals were fine — the listeners that mattered defaulted to 127.0.0.1, and the ones binding wide were teleop surfaces that need LAN reach. Here the inverse holds: a webhook receiver must be reachable by Tencent’s servers, so “it’s only exposed if you deploy it” isn’t a mitigation, it’s the deployment. When a listener’s whole purpose is to accept unsolicited POSTs from the public internet, the signature check is the entire security boundary, and a conditional around it is a conditional around authentication.

Worth crediting separately: the honesty of the --tunnel flag on EvoSci deploy, whose own --help text reads “no auth — anyone with the URL can drive the agent; trusted use only.” Saying that plainly is worth more than a sandbox that overpromises.

Notes on the tool

Disclosure timeline

Reproduce

git clone https://github.com/EvoScientist/EvoScientist /tmp/scan-target
python scanner/run_scan.py --repo /tmp/scan-target \
  --reports-dir ./reports/evoscientist-evoscientist --min-severity medium