Skip to content

fix(devtools): read viewer database from server config only (VULN-11550) - #16055

Closed
gr2m wants to merge 1 commit into
mainfrom
fix-devtools-dbpath-traversal
Closed

fix(devtools): read viewer database from server config only (VULN-11550)#16055
gr2m wants to merge 1 commit into
mainfrom
fix-devtools-dbpath-traversal

Conversation

@gr2m

@gr2m gr2m commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Background

The DevTools viewer's /api/notify endpoint stored a dbPath taken from the (unauthenticated) POST body into a module-level remoteDbPath, and every API read called reloadDb(remoteDbPath), which passed it straight to fs.existsSync / fs.readFileSync with no validation. Any web page a developer visited while the viewer was running could therefore point the viewer at an arbitrary file:

  • Arbitrary file read — JSON files containing a runs array are returned verbatim via /api/runs (e.g. another project's .devtools/generations.json); other targets act as a JSON-validity / file-existence oracle.
  • DoS — a dbPath of /dev/zero or a multi-GB file hangs / OOMs the synchronous readFileSync.
  • The poisoned path is module-level, so a single POST persists until the viewer restarts.

This is VULN-11550 (ANT-2026-R88SY5T6). It is distinct from VULN-11530 (fixed in #16042), which only addressed reachability (wildcard CORS, 0.0.0.0 bind, Host/Origin allowlisting) and left this sink in place.

Summary

Determine the viewer database location from server-side configuration only — never from a network request:

  • reloadDb() no longer accepts a path argument. It reads only the configured location: the default <cwd>/.devtools/generations.json, or the new AI_SDK_DEVTOOLS_DB_PATH env var when the viewer runs in a different directory than the app.
  • /api/notify ignores any dbPath in the request body (it remains a pure "data changed → reload" signal); the module-level remoteDbPath is removed.
  • Database reads go through a shared helper that requires a regular file (statSync) under a 100 MB cap, closing the /dev/zero / huge-file DoS vector.

Common usage (viewer launched from the project root) is unchanged — <cwd>/.devtools/generations.json already resolves to the file the SDK writes.

Manual Verification

  • Grepped every call site: there is no remaining reloadDb(<network value>) and no remaining reference to remoteDbPath; the only inputs to the read are process.cwd() and the AI_SDK_DEVTOOLS_DB_PATH env var.
  • Confirmed the default configured path (<cwd>/.devtools/generations.json) is byte-identical to the DB_PATH the SDK writes to, so a viewer started in the project root behaves exactly as before.
  • Confirmed the statSync + size-cap helper rejects a non-regular path (directory/device) without throwing, so a hostile or missing path degrades to an empty DB rather than hanging.

Checklist

  • All commits are signed (PRs with unsigned commits cannot be merged)
  • Tests have been added / updated (for bug fixes / features)
  • Documentation has been added / updated (for bug fixes / features)
  • A patch changeset for relevant packages has been added (for bug fixes / features - run pnpm changeset in the project root)
  • I have reviewed this pull request (self-review)

Future Work

  • Document the new AI_SDK_DEVTOOLS_DB_PATH env var alongside AI_SDK_DEVTOOLS_PORT in the devtools docs.
  • Assess whether @ai-sdk/devtools exists on the release-v6.0 / release-v5.0 branches and backport if applicable.

Related Issues

The viewer stored a `dbPath` from the unauthenticated /api/notify POST body and
fed it straight to fs.readFileSync on every API call, so any page a developer
visited could point the viewer at an arbitrary file — arbitrary JSON read /
existence oracle, plus a synchronous hang/OOM via /dev/zero or a huge file.

reloadDb() no longer accepts a path; it reads only the server-configured
location (default `<cwd>/.devtools/generations.json`, overridable via the new
AI_SDK_DEVTOOLS_DB_PATH env var). /api/notify ignores any `dbPath` in the body.
Reads are bounded to regular files under a 100 MB cap, closing the device-file /
huge-file DoS vector.

Follow-up to #16042 (which added the Host/Origin gating for VULN-11530).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gr2m gr2m added the backport Admins only: add this label to a pull request in order to backport it to the prior version label Jun 12, 2026
@gr2m

gr2m commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator Author

Closing in favor of #16049 (same fix for VULN-11550, owned by the issue assignee).

Per the comparison: #16049 validates + confines the notify dbPath (basename generations.json under .devtools, statSync isFile, and realpathSync symlink-escape rejection) with zero behavior change for the cross-directory viewer case, and it uniquely guards against symlink escape. The only gap vs this PR is the absence of a read-size cap, which I'll graft onto #16049.

This PR took the stricter "don't accept any network path" approach (matching the report's literal suggested fix) but at the cost of requiring AI_SDK_DEVTOOLS_DB_PATH for cross-CWD viewers. We're going with the lower-risk path.

@gr2m gr2m closed this Jun 12, 2026
@gr2m

gr2m commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator Author

duplicate of #16049

gr2m added a commit that referenced this pull request Jun 12, 2026
Adds a 100 MB cap in validateRemoteDbPath so an attacker-influenced (but
otherwise valid) .devtools/generations.json cannot hang/OOM the synchronous
read. Complements the existing basename/dir, isFile, and symlink-escape
checks. Closes the last gap noted when consolidating the duplicate VULN-11550
fixes (#16055).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport Admins only: add this label to a pull request in order to backport it to the prior version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant