Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
layout: default
title: AI PatchLab Scans
description: "97 curated security scans of open-source AI agents, MCP servers and LLM apps - 21 confirmed fixes, run local-first with Semgrep, Gitleaks, Trivy and pip-audit."
description: "97 curated security scans of open-source AI agents, MCP servers and LLM apps - 23 confirmed fixes, run local-first with Semgrep, Gitleaks, Trivy and pip-audit."
---

# AI PatchLab Scans
Expand All @@ -20,7 +20,7 @@ remediation and confidence rules to normalize the findings.

> **Want this run privately against your own codebase?** I do independent
> security review of AI agents, MCP servers, and LLM apps —
> [**work with me →**]({{ '/work-with-me' | relative_url }}). 97 scans, 21 confirmed fixes, methodology in the open.
> [**work with me →**]({{ '/work-with-me' | relative_url }}). 97 scans, 23 confirmed fixes, methodology in the open.

> **OpenAI just launched [Daybreak](https://openai.com/index/daybreak-securing-the-world/) and Patch the Planet.**
> Same remediation loop, opposite trade-off: their path is a cloud frontier model;
Expand Down Expand Up @@ -104,7 +104,7 @@ entire job.
| 2026-09-06 | [ApodexAI/FrontierAgent](scans/apodexai-frontieragent.html) | 58 | 1 real — withheld | private |
| 2026-09-05 | [doobidoo/mcp-memory-service](scans/doobidoo-mcp-memory-service.html) | 176 | 0 real | — |
| 2026-09-04 | [basicmachines-co/basic-memory](scans/basicmachines-co-basic-memory.html) | 270 | 0 real | — |
| 2026-09-03 | [samuelgursky/davinci-resolve-mcp](scans/samuelgursky-davinci-resolve-mcp.html) | 97 | 1 real — withheld | private |
| 2026-09-03 | [samuelgursky/davinci-resolve-mcp](scans/samuelgursky-davinci-resolve-mcp.html) | 97 | 1 real | **fixed** |
| 2026-09-02 | [HKUDS/OpenOPC](scans/hkuds-openopc.html) | 56 | 1 real — withheld | private |
| 2026-09-01 | [future-agi/future-agi](scans/future-agi-future-agi.html) | 1227 | 1 real — withheld | private |
| 2026-08-31 | [shy3130/tick-stock-panel](scans/shy3130-tick-stock-panel.html) | 77 | 1 real | **fixed** |
Expand All @@ -126,7 +126,7 @@ entire job.
| 2026-08-11 | [semantica-agi/Semantica](scans/semantica-agi-semantica.html) | 60 | 1 real — withheld | private |
| 2026-08-10 | [datascale-ai/OpenTalking](scans/datascale-ai-opentalking.html) | 93 | 1 real | — |
| 2026-08-09 | [NeptuneHub/AudioMuse-AI](scans/neptunehub-audiomuse-ai.html) | 265 | 1 real — withheld | private |
| 2026-08-08 | [theroyallab/tabbyAPI](scans/theroyallab-tabbyapi.html) | 18 | 2 real | |
| 2026-08-08 | [theroyallab/tabbyAPI](scans/theroyallab-tabbyapi.html) | 18 | 2 real | **fixed** |
| 2026-08-07 | [huangruiteng/loopx](scans/huangruiteng-loopx.html) | 57 | 1 real — withheld | **fixed** |
| 2026-08-06 | [nottelabs/notte](scans/nottelabs-notte.html) | 226 | 1 real — withheld | private |
| 2026-08-05 | [Vexa-ai/vexa](scans/vexa-ai-vexa.html) | 297 | 1 real — withheld | private |
Expand Down
4 changes: 2 additions & 2 deletions docs/scan-log.md

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion docs/scans/future-agi-future-agi.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,12 @@ grouped issue.
dossier + reproduction attached
- 2026-09-01 — this page published with the finding **withheld** (class only)

- 2026-09-08 — private report **delivered** by email to `security@futureagi.com`,
seven days after drafting; the delay was on the reporting side. The policy's
24-hour acknowledgement window runs from here. Detail stays withheld

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

## Reproduce

Expand Down
3 changes: 3 additions & 0 deletions docs/scans/ontos-ai-knowhere.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ forty that have one.
- 2026-08-28 — scan run; one Medium finding curated and verified by execution.
- 2026-08-28 — private disclosure drafted for the maintainers (PVR disabled; routed to the
maintainer contact per SECURITY.md's fallback). Detail withheld from this page.
- 2026-09-08 — private report delivered by email to the active maintainer's commit address,
eleven days after drafting; the delay was on the reporting side. Detail stays withheld
pending a fix or the 90-day window.

## Reproduce

Expand Down
110 changes: 97 additions & 13 deletions docs/scans/samuelgursky-davinci-resolve-mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ date: 2026-09-03
**Repository:** [samuelgursky/davinci-resolve-mcp](https://github.com/samuelgursky/davinci-resolve-mcp)
**Commit scanned:** `619d473`
**Scan date:** 2026-09-03
**Disclosure status:** reported privately — detail withheld pending a coordinated fix
**Disclosure status:** ✅ **resolved** — reported privately by email; fixed by the maintainer in
[v2.212.1](https://github.com/samuelgursky/davinci-resolve-mcp/releases/tag/v2.212.1) with a
published advisory
([GHSA-8f4v-j8rq-hj47](https://github.com/samuelgursky/davinci-resolve-mcp/security/advisories/GHSA-8f4v-j8rq-hj47),
Low, patched `2.212.1`) and a regression test, **twelve minutes after the report was read**. The
maintainer has cleared the mechanism for publication; the finding below is now in full.

## Summary

Expand All @@ -25,22 +30,69 @@ date: 2026-09-03

## Top findings

### 1. A credential-handling weakness in the opt-in networked transport — withheld
### 1. The networked transport's bearer token was written in cleartext to `logs/server.log` — fixed in v2.212.1

- **Tool:** semgrep — raised at medium, promoted by curation
- **Confidence:** high — mechanism executed against the real module
- **Status:** reported privately to the maintainer on 2026-09-03
- **Class:** CWE-532, insertion of sensitive information into a log file
- **Status:** reported privately 2026-09-03, delivered 2026-09-08, fixed the same hour

SECURITY.md asks that exploit detail not be published before a coordinated fix, and that
request is being honoured: the file, the line and the mechanism are not in this post. What
can be said without helping anyone is the scope. It affects only the opt-in networked
transport, not the default stdio mode, and only for users who did not pin their own token. It
is a local exposure — it gives nothing to a remote attacker, and needs someone who already
shares the machine or the checkout. The fix is one line, and the repository already contains
the pattern it should follow.
`src/utils/mcp_transport.py:127` logged the generated bearer token verbatim when the opt-in
networked transport (`--transport sse` or `--transport streamable-http`) was started without
`$DAVINCI_MCP_TOKEN` pinned:

This entry will be filled in once a fix ships, or dropped entirely if the maintainer would
rather it stayed private.
```python
if generated:
logger.info("Generated bearer token (set $DAVINCI_MCP_TOKEN to pin it): %s", token)
```

That logger has no handler of its own, so the record propagates to the root logger — which
`src/server.py:167-171` had already configured at import time with a
`logging.FileHandler(<project_dir>/logs/server.log)`. `run_networked` is called from that same
module, so the token that is the transport's only access control was appended to a log file.

**Why it mattered even as a local-only exposure: the same secret already had a carefully
protected copy, and the log copy was worse on both axes.** `write_transport_state` stores the
token through `src/utils/private_state.py`, which opens with an explicit `0o600`, re-chmods on
POSIX, runs `icacls /inheritance:r` on Windows, and lives under a `0700` per-user directory
whose docstring says *"never in a shared tempdir"*. `logging.FileHandler` does none of that —
default mode, `0644` under the usual umask, world-readable. And the durability was inverted:
`run_networked`'s `finally:` clears the protected copy at shutdown, while the log line is
appended forever. Every token any networked session ever generated accumulated in
`server.log` and outlived the process.

**The sibling differential was the whole argument.** The control panel's token, in the same
`server.py`, is handled against exactly this threat — *"Passed via the environment (never argv,
which `ps` would show to every local user)"* and *"The token travels in the URL fragment —
browsers never send fragments, so it stays out of every request line and log."* Two tokens,
one threat model, opposite handling. The report pointed at the rule the project had already
written down, and at the claim in `SECURITY.md` that the pidfile and the state file were the
only on-disk copies: there was a third.

**Verified by execution, not by reading.** A script imported the real module, installed the
same root-logger configuration `server.py` installs, stubbed `uvicorn.run`, and called the real
`run_networked`: token present in `server.log`, state file absent after shutdown. The POSIX
mode contrast was read from code (the run was on Windows) and the report said so.

**What limited it.** Opt-in transport only; the default stdio mode never reaches the code.
`logs/` is gitignored. A pinned token never hit the `if generated:` branch. Graded Low on a
single-user desktop, Medium on a shared host or an agent-readable checkout.

**Fix, as shipped in `9f955ab5` / v2.212.1.** The maintainer took the report's primary
suggestion — the log line now names the state file's path instead of the value — and added an
`isatty`-guarded echo of a generated token to an interactive stderr for hand-launched
operators, so a redirected stderr gets nothing. `SECURITY.md` now states the rule outright: the
pidfile and the transport state file are the only on-disk copies of either token, and neither
is ever written to `logs/server.log`. The module docstring no longer describes the token as
"logged at startup". A regression test runs the real `run_networked` against a root
`FileHandler` configured the way `server.py` configures it and asserts the token never reaches
the file — against the previous code it fails with the token found in the log, which is this
finding reproduced in the suite. The advisory's workaround section tells existing users to
treat any token in `server.log` as exposed, truncate the log, and restart.

**And the channel.** The report opened with a process note: `SECURITY.md` named a GitHub
security advisory as its first channel, but private vulnerability reporting was disabled, so
the endpoint answered `403` to an outside reporter. It is enabled now.

### Everything else — 96 findings, none of them real

Expand Down Expand Up @@ -161,7 +213,39 @@ out to be wired into twenty privileged routes as a second layer beneath the toke
- 2026-09-03 — reported privately by email to the maintainer, per SECURITY.md. GitHub private
vulnerability reporting is the first channel that policy names, but it is disabled on the
repository, so an outside reporter cannot use it; the report notes this alongside the finding.
- Detail withheld from this page pending a coordinated fix.
- 2026-09-03 — this page published with the finding withheld
- 2026-09-08 00:37 UTC — private report delivered by email to the address `SECURITY.md`
points to. The draft had sat unsent for five days; the send is a manual step in this
pipeline, and the delay was on the reporting side, not the maintainer's
- 2026-09-08 00:49 UTC — **fixed** in `9f955ab5` and released as
[v2.212.1](https://github.com/samuelgursky/davinci-resolve-mcp/releases/tag/v2.212.1),
twelve minutes after the email; regression test included
- 2026-09-08 00:55 UTC — advisory
[GHSA-8f4v-j8rq-hj47](https://github.com/samuelgursky/davinci-resolve-mcp/security/advisories/GHSA-8f4v-j8rq-hj47)
published (Low, `< 2.212.1`); private vulnerability reporting switched on for the repository
- 2026-09-08 01:16 UTC — maintainer's reply: *"The report was exact, and the reproduction
matched what I found in the code."* Mechanism cleared for publication
- 2026-09-08 — this page updated with the full detail

## Resolution

Thirty-nine minutes from the email leaving to the maintainer's reply, with a fix, a release, an
advisory and a regression test in between — the fastest resolution in the series by a wide
margin, and the clearest case yet that the slow part of coordinated disclosure was the
reporter. The report was written on 2026-09-03 and delivered on 2026-09-08; the maintainer
needed twelve minutes.

Three things about the fix are worth recording. It adopted the report's *pointer-in-the-log*
suggestion rather than the alternative the report also offered, and then solved the usability
question the report had deliberately left to the maintainer (how does a hand-launching operator
see the token?) with an `isatty` guard — a better answer than either option as written. The
regression test does not merely assert the new behaviour; it reproduces the finding against the
old code, which means the suite now carries the negative control. And the documentation was
corrected in the same release, so the `SECURITY.md` claim this post used as an oracle is true
again — the third on-disk copy is gone, and the policy now says so explicitly.

The advisory credits *"an external security researcher"*; the maintainer offered a named
credit, which is the reporter's call and has not been taken up as of this update.

## Reproduce

Expand Down
3 changes: 3 additions & 0 deletions docs/scans/sentelabsai-openexecutive.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ were 100% of the scanner's output.
vulnerability reporting) is disabled, and the policy directs reporters away from email, so
the report is being routed to the organisation's published contact address with a note
about the closed channel.
- 2026-09-08 — private report delivered by email to the organisation's published contact
address (cc the top committer), nine days after drafting; the delay was on the reporting
side. Detail stays withheld pending a fix or the 90-day window.

## Reproduce

Expand Down
54 changes: 53 additions & 1 deletion docs/scans/theroyallab-tabbyapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ date: 2026-08-08
**Repository:** [theroyallab/tabbyAPI](https://github.com/theroyallab/tabbyAPI)
**Commit scanned:** `c50f0d2b`
**Scan date:** 2026-08-08
**Disclosure status:** disclosed — [issue #448](https://github.com/theroyallab/tabbyAPI/issues/448)
**Disclosure status:** ✅ **resolved** — [issue #448](https://github.com/theroyallab/tabbyAPI/issues/448)
closed as completed on 2026-09-07 by the maintainer, 30 days after filing, via
[PR #449](https://github.com/theroyallab/tabbyAPI/pull/449) (CORS allowlist knob, credentialed
wildcard dropped) plus a maintainer follow-up commit that warns at startup when auth is disabled
and the origin list is still `["*"]`. The second item (the `image_url` server-side fetch) was
explicitly left for a separate change and is unchanged. See *Resolution* below.

## Summary

Expand Down Expand Up @@ -264,6 +269,53 @@ away.
- 2026-08-08 — CORS and SSRF primitives verified against the resolved dependency versions
- 2026-08-08 — [issue #448](https://github.com/theroyallab/tabbyAPI/issues/448) filed
- 2026-08-08 — public post (this page)
- 2026-08-14 — [PR #449](https://github.com/theroyallab/tabbyAPI/pull/449) opened by a contributor
(`Anai-Guo`), scoped to "the CORS portion of #448" and crediting the write-up and repro
- 2026-09-07 — PR #449 merged by the maintainer (`turboderp`) in `3d2f4c43`; issue #448 closed
as completed in the same second. Follow-up commit `45a9d282` the same day: startup warning
when `disable_auth` is on and `allowed_origins` still contains `"*"`, and the config help
text now says that web pages open in a browser on this machine count as local callers
- 2026-09-08 — this page updated. The `image_url` fetch (second item) is unchanged as of
`common/image_util.py` at HEAD

## Resolution

Thirty days from filing to close, the longest turnaround in the series so far, and a
resolution that lands the composite finding exactly where the differential said it had to land.

**What changed.** `endpoints/server.py` now reads its origin list from a new
`network.allowed_origins` config key and sets `allow_credentials=False`, with a comment
naming the reflection behaviour this post described. `config_sample.yml` documents the key.
The maintainer's follow-up commit adds a warning in `common/auth.py` at the moment auth is
disabled: *"With authentication disabled and `allowed_origins` left at ["*"], any website
open in a browser on this machine can send requests to this instance and read the responses,
including admin endpoints."* The line in the config help that this post quoted — *"Turn on
this option if you are ONLY connecting from localhost"* — is gone, replaced by *"web pages
open in a browser on this machine also count as local callers; restrict allowed_origins
below if you disable auth."* That sentence is the finding, restated by the project in its own
documentation.

**What did not change, and why that is consistent with this post.** The default stays
`["*"]`; the PR author kept it permissive to avoid breaking existing browser front-ends and
offered to flip it, and the maintainer chose the warning instead of the flip. The
differential in this post showed that dropping `allow_credentials` alone does not close the
`disable_auth` case — a non-credentialed cross-origin `fetch()` still reads responses under a
literal `*` — so the operator-facing fix for that deployment is the new allowlist, and the
startup warning is what now points the operator at it. That is a reasonable trade for a
project whose users run browser UIs against it, and it is an honest one: the hole is closed
for anyone who reads the warning, and named for anyone who does not.

**Left open.** The `image_url` server-side fetch has no scheme or host policy and
`disable_fetch_requests` still defaults to `False`; the PR says so explicitly and leaves it
for a separate change. `common/image_util.py` has not been touched since the scan.

**A note on who wrote the fix.** PR #449 was authored by a contributor rather than the
maintainer, generated with an AI coding tool, and scoped and worded with unusual care — the
body reproduces the reflection mechanism correctly and states the limits of its own testing.
The maintainer then wrote the part a contributor could not decide: the product-level answer
to "should the default change". That split — contributor ships the knob, maintainer decides
the policy — is the same shape as [SAG #153](zleap-ai-sag.html), where posing the product
question beat proposing the patch.

## Reproduce

Expand Down
Loading