Skip to content

fix(import): apply SSRF request-filtering agent to import fetch#3565

Open
ChristianTeroerde wants to merge 2 commits into
teableio:developfrom
ChristianTeroerde:fix/import-ssrf-guard
Open

fix(import): apply SSRF request-filtering agent to import fetch#3565
ChristianTeroerde wants to merge 2 commits into
teableio:developfrom
ChristianTeroerde:fix/import-ssrf-guard

Conversation

@ChristianTeroerde

Copy link
Copy Markdown

What

Importer.getFile() (the import "analyze" flow behind GET /api/import/analyze) fetches a user-supplied attachmentUrl with raw node-fetch and no SSRF protection. The attachments path already routes its outbound fetches through getSsrfSafeAgents() (request-filtering-agent), but this import path does not — so an authenticated caller can make the server request arbitrary internal / link-local hosts, and node-fetch will follow redirects to them.

Fix

  • Add getSsrfSafeFetchAgent() to utils/ssrf-guard.ts. node-fetch's agent option takes a (parsedUrl) => Agent factory (unlike axios' httpAgent/httpsAgent), so it returns the http or https request-filtering agent per URL.
  • Pass it to the import fetch: fetch(url, { agent: getSsrfSafeFetchAgent() }).
  • Honors the existing TEABLE_SSRF_PROTECTION_DISABLED env toggle; no behavior change for URLs that resolve to public hosts.

This mirrors the protection the attachments path already applies, including redirect-based bypasses (e.g. http://evil.comhttp://169.254.169.254).

Scope

Two files, +26/−1, no public API change:

  • apps/nestjs-backend/src/utils/ssrf-guard.ts (new getSsrfSafeFetchAgent() helper)
  • apps/nestjs-backend/src/features/import/open-api/import.class.ts (use it in getFile())

Importer.getFile() fetched a user-supplied attachmentUrl (via
GET /api/import/analyze) with raw node-fetch and no SSRF protection,
while the attachments path already routes through getSsrfSafeAgents()
(request-filtering-agent). Add getSsrfSafeFetchAgent() for node-fetch's
(parsedUrl) => Agent factory and use it in the import fetch so
attacker-supplied import URLs cannot reach private/link-local hosts
(including via redirects). Honors TEABLE_SSRF_PROTECTION_DISABLED.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Jul 11, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8be690375b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +249 to +251
const { body: stream, headers } = await fetch(url, {
agent: getSsrfSafeFetchAgent(),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep first-party import attachment URLs fetchable

When the UI uploads an import file with the default local storage provider, notify() returns a relative /api/attachments/read/... presigned URL for cookie-authenticated requests; this method then rewrites it to http://localhost:${process.env.PORT}... before fetching it. Adding the request-filtering agent here makes those normal uploaded-file imports resolve to loopback and get rejected as a private address, so /api/import/analyze and the subsequent import flow fail for the default local-storage path unless SSRF protection is disabled. The SSRF guard needs to exempt trusted first-party attachment URLs or read local attachments without going through a blocked localhost fetch.

Useful? React with 👍 / 👎.

… SSRF guard

Review feedback: the request-filtering agent rejected the loopback address,
so default local-storage imports (relative /api/attachments/read/... URLs that
getFile() rewrites to http://localhost:PORT) would fail unless SSRF protection
is disabled.

Only apply the SSRF filter to externally-supplied absolute URLs. A relative
reference is trusted as first-party only when its rewrite resolves to a genuine
localhost host, so the loopback fetch bypasses the filter and default imports
keep working. A crafted value such as "@169.254.169.254" resolves to a
non-localhost host and stays filtered, so the guard is not bypassable through
the rewrite branch.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants