Skip to the content.

future-agi/future-agi — security scan

Repository: future-agi/future-agi Commit scanned: 5b84ef4a7666 Scan date: 2026-09-01 Disclosure status: withheld — one real finding, reported privately to security@futureagi.com per the project’s SECURITY.md (which forbids public vulnerability issues). Described here at class level only until it is remediated.

Summary

Severity Count
Critical 26
High 476
Medium 722
Low 0
Info 3

Total findings: 1227 raw / 1224 above the medium floor — the largest raw count in this series to date, and (as usual on a large, well-built codebase) one real finding after curation, which no scanner rule represented and which is withheld here pending a private fix.

Future AGI (1.9k★, Apache-2.0 + a separate EE license) is an end-to-end LLM and AI-agent observability and evaluation platform — trace ingestion over OTLP, a ClickHouse-backed store of prompts, completions, tokens and costs, an evaluation engine, a simulation runner, a Go AI gateway (agentcc-gateway, an OpenAI/Anthropic/Gemini-compatible proxy with per-org keys, rotation and RBAC), and a Django backend behind a React frontend. The asset at risk here is everyone’s captured model traffic — the prompts and outputs an operator has routed through the platform to observe them.

It is, and this shapes the whole write-up, a carefully built system with a real security policy: a named disclosure email, a severity/SLA table, a safe-harbor clause, and an explicit in-scope list that names the gateway. The one real finding is not a team that doesn’t think about isolation — it is a single service whose network boundary diverges from the consistent, commented rule applied to every service beside it.

What this write-up does not contain

Future AGI’s SECURITY.md asks reporters to disclose privately and not open a public issue, with acknowledgement within 24 hours and coordinated disclosure 7–90 days after a patch. That is exactly the norm this series honours: the finding was sent to security@futureagi.com with a full dossier and a reproduction, and this page withholds the file, the service and the mechanism until a fix is available. What follows is the class of the finding and the method that found it — enough to be an honest record, not enough to be a weaponised one.

The private report also carries the thing that makes a withheld post honest: the maintainers get the concrete detail immediately, and the public series only says something real was found and responsibly routed.

The class of the finding

One service in a multi-service deployment is published on all network interfaces while every service beside it — including every datastore — is correctly bound to loopback, and the production overlay inherits the exposure unchanged. The exposed service is the most dangerous one in the file to leave open, and reaching it hands an unauthenticated caller a path to the trace store the platform exists to protect.

Three properties made it reportable rather than arguable, and each is a pattern this series has converged on:

The scope discipline that kept it to one finding

Two nearby things looked like the same class and were dropped after the same kind of check:

The difference between “reportable” and “noise” here was entirely which service and which deployment path — the same discrimination the claude-tap and open-wearables write-ups turned on.

The two hypotheses that returned NO — and why publishing that matters

Most of the manual effort on this scan went into two auth surfaces that came back clean, and saying so is part of the record:

A probe that can only ever say “yes” is not a probe. These two said no, and a scan series that only ever reports the yes is selling something.

Patterns observed

1227 findings, and the shape of the noise is by now familiar. The top buckets: 100 mutable-action-tag GitHub-Actions warnings (the perennial band-flooder — one coherent “pin your actions by SHA” recommendation rendered as a hundred rows); 94 SQLAlchemy raw-query + 44 formatted-SQL-query hits, which resolve to the #1 recurring identifier FP on close reading — the ClickHouse query builders interpolate validated bucketing function names and column identifiers (regex-checked keys, an allowlisted time_bucket map with a safe default) while binding every value through %(param)s; 52 non-literal-regexp and 40 Go no-direct-write-to-responsewriter XSS-audit hits on internal tooling. I read the four taint-tracked SQL findings (the higher-signal tainted-sql-string / query-set-extra rules) individually: two were audit-log description f-strings, one was the allowlisted bucket function, and the value paths were all bound. Nothing in the SQL cluster survived.

The dependency tier is a coverage story, not a finding story — and it repeats a known gotcha. The scanner emitted No supported Python dependency manifest found, because this is a monorepo and the manifests live under futureagi/ (a requirements.txt, a pyproject.toml, a uv.lock), not at the root the dependency scanner walks. So the 26 “critical” and much of the “high” tier — ChromaDB RCE, LiteLLM auth-bypass, Authlib JWK-injection, Django SQLi, langchain serialization RCE — come from Trivy parsing lockfiles it did find (the Go go.sum, the frontend package-lock.json, the gateway modules), and each wants the version-match → reachable → mitigated gate before it means anything. Several are Proxy-only or transport-only CVEs in libraries this repo uses as clients, the same reachability split that hollowed out the critical tier on notte. None was the finding; the finding was structural and came from reading the deployment, not the dependency graph. This is the monorepo root-only coverage gap again — the top backlog item it keeps voting for.

Gitleaks was quiet in the right way. Three secret hits, all inert: one in a .env.production.example, one a pricing-table constant in Go, one a fixture in a frontend __tests__ file — and the repo ships a real .gitleaks.toml that allowlists exactly those tiers. Honour the baseline; the maintainers already curated their own secret surface.

The credit is substantial, and naming it is the point of a strict-norm post. Every datastore is loopback-bound with an explaining comment. The production overlay’s ${VAR:?...} guards refuse to boot on dev secrets. The gateway admin plane fails closed on an unset token with constant-time compares across all 24 handlers. The license verifier pins its algorithm and reads keys from a local map. nsjail is a real sandbox built into the shipped image, not a stub. The one real finding is a single service’s network binding diverging from a rule the project otherwise applies consistently and documents — which is exactly why it was findable, and exactly why it is worth one clean private report rather than a grouped issue.

Notes on the tool

Disclosure timeline

The private email is a manual step this pipeline does not take automatically; the dossier was drafted and staged on 2026-09-01 and sent by the operator on 2026-09-08.

Reproduce

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