Skip to content

Image generation: add fal provider#49454

Merged
vincentkoc merged 1 commit into
mainfrom
vincentkoc-code/midjourney-skill
Mar 18, 2026
Merged

Image generation: add fal provider#49454
vincentkoc merged 1 commit into
mainfrom
vincentkoc-code/midjourney-skill

Conversation

@vincentkoc

Copy link
Copy Markdown
Member

Summary

  • add bundled fal provider support for core image_generate
  • wire provider auth via FAL_KEY and default onboarding to fal/fal-ai/flux/dev
  • support text-to-image plus single-image edit flows via FLUX image-to-image

Testing

  • pnpm exec oxfmt --check src/image-generation/providers/fal.ts src/image-generation/providers/fal.test.ts extensions/fal/index.ts extensions/fal/onboard.ts
  • local Vitest / repo-wide typecheck were not conclusive in this fresh worktree after install

@aisle-research-bot

aisle-research-bot Bot commented Mar 18, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 3 potential security issue(s) in this PR:

# Severity Title
1 🟠 High fal image provider allows API key exfiltration / SSRF via unvalidated configurable baseUrl
2 🟡 Medium SSRF via unvalidated image URL download in fal image provider
3 🟡 Medium fal image provider allows unbounded image dimensions/count leading to resource & cost exhaustion

1. 🟠 fal image provider allows API key exfiltration / SSRF via unvalidated configurable baseUrl

Property Value
Severity High
CWE CWE-918
Location src/image-generation/providers/fal.ts:23-159

Description

The fal image generation provider constructs request URLs from cfg.models.providers.fal.baseUrl and sends the fal API key in the Authorization header without validating the scheme/host or applying SSRF protections.

Impact if an attacker can influence req.cfg (e.g., by modifying runtime config / models.json, or via any config-write surface):

  • Credential exfiltration: requests (including Authorization: Key ...) can be redirected to an attacker-controlled origin by setting models.providers.fal.baseUrl.
  • SSRF: baseUrl can be set to internal/private network targets (e.g., http://127.0.0.1:..., http://169.254.169.254/...) and the server will make authenticated requests there.
  • Redirect handling: because this uses raw fetch(), redirects are handled by the runtime fetch implementation; headers like Authorization may be forwarded depending on implementation, increasing exfil risk.

Vulnerable code:

const direct = cfg?.models?.providers?.fal?.baseUrl?.trim();
return (direct || DEFAULT_FAL_BASE_URL).replace(/\/+$/u, "");
...
await fetch(`${resolveFalBaseUrl(req.cfg)}/${model}`, {
  headers: { Authorization: `Key ${auth.apiKey}` },
});

Notes on configuration flow (threat model context):

  • req.cfg is propagated from the runtime config passed to generateImage() (see src/image-generation/runtime.ts) and is typically sourced from loadConfig() in tool/agent flows (e.g., src/agents/tools/image-generate-tool.ts).
  • Config write RPCs (e.g., config.patch/config.apply) exist and are admin-scoped, but the provider should still fail closed because the base URL controls where secrets are sent.

Recommendation

Fail closed on unsafe baseUrl overrides and apply SSRF/redirect protections before sending credentials.

Suggested approach:

  1. Parse and validate the base URL:
    • require https:
    • block userinfo (username:password@​host)
    • restrict host to known fal API domains (or require an explicit allowCustomHost flag)
  2. Use the existing SSRF/redirect-safe fetch wrapper (fetchWithSsrFGuard) so that:
    • private/internal networks are blocked by default
    • cross-origin redirects drop unsafe headers like Authorization

Example patch sketch:

import { fetchWithSsrFGuard } from "../../infra/net/fetch-guard.js";

function resolveFalBaseUrlSafe(cfg: OpenClawConfig | undefined): string {
  const raw = (cfg?.models?.providers?.fal?.baseUrl ?? DEFAULT_FAL_BASE_URL).trim();
  const u = new URL(raw);
  if (u.protocol !== "https:") throw new Error("fal baseUrl must be https");
  if (u.username || u.password) throw new Error("fal baseUrl must not include userinfo");// Option A: strict allowlist
  const host = u.hostname.toLowerCase();
  if (host !== "fal.run" && !host.endsWith(".fal.run")) {
    throw new Error("fal baseUrl host is not allowed");
  }
  return u.toString().replace(/\/+$/u, "");
}// Then replace raw fetch with guarded fetch
const { response, release } = await fetchWithSsrFGuard({
  url: `${resolveFalBaseUrlSafe(req.cfg)}/${model}`,
  init: {
    method: "POST",
    headers: { Authorization: `Key ${auth.apiKey}`, "Content-Type": "application/json" },
    body: JSON.stringify(requestBody),
  },
  timeoutMs: 60_000,// Optionally: policy: { hostnameAllowlist: ["fal.run", "*.fal.run"] }
});
try {// handle response
} finally {
  await release();
}

If custom endpoints/proxies are required, gate them behind an explicit config flag (e.g., models.providers.fal.allowCustomHost: true) and still enforce HTTPS + SSRF policy.


2. 🟡 SSRF via unvalidated image URL download in fal image provider

Property Value
Severity Medium
CWE CWE-918
Location src/image-generation/providers/fal.ts:95-106

Description

The new fal image-generation provider downloads image bytes from a URL returned by the upstream API without any validation or SSRF protection.

  • payload.images[].url is treated as trusted and passed directly to fetch().
  • No allowlist of fal media hosts, no scheme restriction, and no private-network blocking is applied.
  • Node/undici fetch() follows redirects by default (redirect: "follow"), so even if a trusted host is returned, an attacker-controlled/open redirect could pivot the request to internal hosts (e.g., 169.254.169.254, localhost).

Vulnerable code:

const downloaded = await fetchImageBuffer(url);

async function fetchImageBuffer(url: string) {
  const response = await fetch(url);
  ...
}

Recommendation

Route all remote URL fetching through the existing SSRF-guarded utilities in this repo (they already implement private-network blocking + safe redirect handling with pinned DNS):

  • Prefer fetchRemoteMedia() from src/media/fetch.ts (supports SSRF guard + redirect limit + size limits).
  • Enforce https: and an allowlist for fal media domains (e.g. *.fal.media, and any other documented CDN hostnames).
  • Keep redirects limited and re-validated on each hop (the guard already does this).

Example fix (sketch):

import { fetchRemoteMedia } from "../../media/fetch.js";

const FAL_MEDIA_POLICY = {// Only allow fal media/CDN hosts; do NOT allow private network.
  hostnameAllowlist: ["*.fal.media"],
};

async function fetchImageBuffer(url: string) {
  const parsed = new URL(url);
  if (parsed.protocol !== "https:") {
    throw new Error("fal image URL must be https");
  }

  const result = await fetchRemoteMedia({
    url,
    maxBytes: 15 * 1024 * 1024,      // pick an appropriate cap
    maxRedirects: 3,
    readIdleTimeoutMs: 10_000,
    ssrfPolicy: FAL_MEDIA_POLICY,
  });

  return {
    buffer: result.buffer,
    mimeType: result.contentType ?? "image/png",
  };
}

If fal provides signed URLs or an SDK method to fetch assets safely, prefer that over arbitrary URL fetching.


3. 🟡 fal image provider allows unbounded image dimensions/count leading to resource & cost exhaustion

Property Value
Severity Medium
CWE CWE-400
Location src/image-generation/providers/fal.ts:43-58

Description

The fal image-generation provider forwards caller-controlled sizing and image-count parameters to the upstream API without enforcing the provider’s own advertised limits (supportedSizes, supportedResolutions) or applying defensive caps.

Impact:

  • Cost/DoS amplification: callers can request extremely large width/height (via size like 99999x99999) and/or very large count, potentially triggering expensive upstream work.
  • Local memory pressure: the provider also downloads generated images into memory via fetchImageBuffer() with no max-bytes limit, compounding DoS risk if large images (or many images) are returned.

Vulnerable code (size parsing accepts up to 5 digits per dimension and returns it unbounded):

const match = /^(\d{2,5})x(\d{2,5})$/iu.exec(trimmed);
...
return { width, height };

Related sink (unbounded forwarding of size/count):

  • image_size: imageSize (from req.size / req.resolution)
  • num_images: req.count ?? 1

Call chain / existing validation:

  • src/agents/tools/image-generate-tool.ts enforces count to 1-4 for the image_generate tool, but does not validate size beyond being a string.
  • src/image-generation/runtime.ts forwards size, count, and resolution directly to providers with no additional validation.
  • Therefore, untrusted tool callers can abuse size, and any plugin code calling generateImage() can abuse both size and count unless it adds its own validation.

Recommendation

Enforce strict provider-side limits before sending requests upstream, and cap downloads.

  1. Restrict size to supported values (or apply hard caps):
const SUPPORTED_SIZES = new Set(["1024x1024","1024x1536","1536x1024","1024x1792","1792x1024"]);

function resolveSafeFalImageSize(size?: string, resolution?: "1K"|"2K"|"4K") {
  const trimmed = size?.trim();
  if (trimmed && SUPPORTED_SIZES.has(trimmed)) {
    const [w,h] = trimmed.split("x").map((v) => Number.parseInt(v, 10));
    return { width: w, height: h };
  }// fall back to resolution mapping / default
  return mapResolutionToSize(resolution) ?? DEFAULT_OUTPUT_SIZE;
}
  1. Clamp count (even if the tool currently limits it) to defend other call sites:
const MAX_COUNT = 4;
const count = Math.min(Math.max(1, req.count ?? 1), MAX_COUNT);
  1. Cap image downloads from returned URLs by using existing limited-fetch helpers (e.g. fetchRemoteMedia/readResponseWithLimit) with a reasonable maximum (e.g. MAX_IMAGE_BYTES):
import { fetchRemoteMedia, MAX_IMAGE_BYTES } from "../../media/fetch.js";// or a shared helper that enforces maxBytes

This ensures attacker-controlled or unexpected upstream responses cannot exhaust memory.


Analyzed PR: #49454 at commit c546c18

Last updated on: 2026-03-18T05:26:26Z

@vincentkoc vincentkoc self-assigned this Mar 18, 2026
@openclaw-barnacle openclaw-barnacle Bot added size: M maintainer Maintainer-authored PR labels Mar 18, 2026
@vincentkoc
vincentkoc force-pushed the vincentkoc-code/midjourney-skill branch from 6dec0b9 to c546c18 Compare March 18, 2026 04:34
@greptile-apps

greptile-apps Bot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a bundled fal provider to image_generate, wiring FAL_KEY auth and defaulting onboarding to fal/fal-ai/flux/dev for both text-to-image and single-image edit (FLUX image-to-image) flows. The implementation follows the same shape as the existing Google and OpenAI image providers and the extension entry/onboard pattern matches other bundled providers.

Key findings:

  • fileExtensionForMimeType does not strip MIME type parameters (; charset=…) before extracting the extension, which can produce malformed filenames like image-1.png; charset=utf-8 if the fal CDN ever includes such parameters.
  • The downloaded.mimeType || entry.content_type fallback on line 183 is dead code — fetchImageBuffer always returns a non-empty mimeType, so entry.content_type is never consulted. This is misleading.
  • Multi-image responses are downloaded sequentially inside a for…of loop rather than with Promise.all, which serialises independent HTTP fetches unnecessarily when count > 1.

Confidence Score: 3/5

  • Safe to merge after addressing the MIME type parameter stripping bug in fileExtensionForMimeType; the other two findings are style/performance improvements.
  • The core provider logic is correct and well-tested. One logic issue (malformed filenames from unstripped MIME type parameters) can cause incorrect file extensions in production if the CDN ever includes content-type parameters. The dead fallback code and sequential downloads are lower-severity concerns. The PR is otherwise well-structured and consistent with existing providers.
  • src/image-generation/providers/fal.ts — fileExtensionForMimeType and the image download loop need attention.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/image-generation/providers/fal.ts
Line: 83-93

Comment:
**MIME type parameters not stripped from file extension**

`fileExtensionForMimeType` takes the raw `Content-Type` header value from `fetchImageBuffer`, which can include parameters separated by `;` (e.g. `image/png; charset=UTF-8`). The current slicing logic will produce a file name like `image-1.png; charset=utf-8` because it only looks for the `/` separator and doesn't account for `;`-delimited parameters.

While CDNs rarely add such parameters to image responses, the fal CDN is not guaranteed to omit them forever. Splitting on `;` before slicing would make this robust:

```suggestion
function fileExtensionForMimeType(mimeType: string | undefined): string {
  const normalized = mimeType?.toLowerCase().trim().split(";")[0]?.trim();
  if (!normalized) {
    return "png";
  }
  if (normalized.includes("jpeg")) {
    return "jpg";
  }
  const slashIndex = normalized.indexOf("/");
  return slashIndex >= 0 ? normalized.slice(slashIndex + 1) || "png" : "png";
}
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/image-generation/providers/fal.ts
Line: 178-184

Comment:
**Dead fallback to `entry.content_type`**

`fetchImageBuffer` always returns a non-empty `mimeType` — it defaults to `"image/png"` when the `Content-Type` header is absent. This means `downloaded.mimeType` is always truthy and `entry.content_type` is never reached. The fallback creates a misleading impression that there is a safety net here.

```suggestion
          fileName: `image-${imageIndex}.${fileExtensionForMimeType(downloaded.mimeType)}`,
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/image-generation/providers/fal.ts
Line: 171-185

Comment:
**Sequential image downloads for multi-image requests**

When `count > 1` and fal returns multiple image URLs, each URL is downloaded sequentially with a separate `await fetchImageBuffer(url)` inside the `for...of` loop. For a request that returns, say, 4 images, this serialises 4 independent HTTP fetches. Using `Promise.all` over the filtered URLs would download them in parallel and noticeably reduce latency for multi-image responses:

```typescript
const urls = (payload.images ?? [])
  .map((entry) => entry.url?.trim())
  .filter((url): url is string => Boolean(url));

const downloaded = await Promise.all(urls.map((url) => fetchImageBuffer(url)));

const images: GeneratedImageAsset[] = downloaded.map((dl, i) => ({
  buffer: dl.buffer,
  mimeType: dl.mimeType,
  fileName: `image-${i + 1}.${fileExtensionForMimeType(dl.mimeType)}`,
}));
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "Image generation: ad..."

Comment on lines +83 to +93
function fileExtensionForMimeType(mimeType: string | undefined): string {
const normalized = mimeType?.toLowerCase().trim();
if (!normalized) {
return "png";
}
if (normalized.includes("jpeg")) {
return "jpg";
}
const slashIndex = normalized.indexOf("/");
return slashIndex >= 0 ? normalized.slice(slashIndex + 1) || "png" : "png";
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 MIME type parameters not stripped from file extension

fileExtensionForMimeType takes the raw Content-Type header value from fetchImageBuffer, which can include parameters separated by ; (e.g. image/png; charset=UTF-8). The current slicing logic will produce a file name like image-1.png; charset=utf-8 because it only looks for the / separator and doesn't account for ;-delimited parameters.

While CDNs rarely add such parameters to image responses, the fal CDN is not guaranteed to omit them forever. Splitting on ; before slicing would make this robust:

Suggested change
function fileExtensionForMimeType(mimeType: string | undefined): string {
const normalized = mimeType?.toLowerCase().trim();
if (!normalized) {
return "png";
}
if (normalized.includes("jpeg")) {
return "jpg";
}
const slashIndex = normalized.indexOf("/");
return slashIndex >= 0 ? normalized.slice(slashIndex + 1) || "png" : "png";
}
function fileExtensionForMimeType(mimeType: string | undefined): string {
const normalized = mimeType?.toLowerCase().trim().split(";")[0]?.trim();
if (!normalized) {
return "png";
}
if (normalized.includes("jpeg")) {
return "jpg";
}
const slashIndex = normalized.indexOf("/");
return slashIndex >= 0 ? normalized.slice(slashIndex + 1) || "png" : "png";
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/image-generation/providers/fal.ts
Line: 83-93

Comment:
**MIME type parameters not stripped from file extension**

`fileExtensionForMimeType` takes the raw `Content-Type` header value from `fetchImageBuffer`, which can include parameters separated by `;` (e.g. `image/png; charset=UTF-8`). The current slicing logic will produce a file name like `image-1.png; charset=utf-8` because it only looks for the `/` separator and doesn't account for `;`-delimited parameters.

While CDNs rarely add such parameters to image responses, the fal CDN is not guaranteed to omit them forever. Splitting on `;` before slicing would make this robust:

```suggestion
function fileExtensionForMimeType(mimeType: string | undefined): string {
  const normalized = mimeType?.toLowerCase().trim().split(";")[0]?.trim();
  if (!normalized) {
    return "png";
  }
  if (normalized.includes("jpeg")) {
    return "jpg";
  }
  const slashIndex = normalized.indexOf("/");
  return slashIndex >= 0 ? normalized.slice(slashIndex + 1) || "png" : "png";
}
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +178 to +184
images.push({
buffer: downloaded.buffer,
mimeType: downloaded.mimeType,
fileName: `image-${imageIndex}.${fileExtensionForMimeType(
downloaded.mimeType || entry.content_type,
)}`,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Dead fallback to entry.content_type

fetchImageBuffer always returns a non-empty mimeType — it defaults to "image/png" when the Content-Type header is absent. This means downloaded.mimeType is always truthy and entry.content_type is never reached. The fallback creates a misleading impression that there is a safety net here.

Suggested change
images.push({
buffer: downloaded.buffer,
mimeType: downloaded.mimeType,
fileName: `image-${imageIndex}.${fileExtensionForMimeType(
downloaded.mimeType || entry.content_type,
)}`,
});
fileName: `image-${imageIndex}.${fileExtensionForMimeType(downloaded.mimeType)}`,
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/image-generation/providers/fal.ts
Line: 178-184

Comment:
**Dead fallback to `entry.content_type`**

`fetchImageBuffer` always returns a non-empty `mimeType` — it defaults to `"image/png"` when the `Content-Type` header is absent. This means `downloaded.mimeType` is always truthy and `entry.content_type` is never reached. The fallback creates a misleading impression that there is a safety net here.

```suggestion
          fileName: `image-${imageIndex}.${fileExtensionForMimeType(downloaded.mimeType)}`,
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +171 to +185
for (const entry of payload.images ?? []) {
const url = entry.url?.trim();
if (!url) {
continue;
}
const downloaded = await fetchImageBuffer(url);
imageIndex += 1;
images.push({
buffer: downloaded.buffer,
mimeType: downloaded.mimeType,
fileName: `image-${imageIndex}.${fileExtensionForMimeType(
downloaded.mimeType || entry.content_type,
)}`,
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Sequential image downloads for multi-image requests

When count > 1 and fal returns multiple image URLs, each URL is downloaded sequentially with a separate await fetchImageBuffer(url) inside the for...of loop. For a request that returns, say, 4 images, this serialises 4 independent HTTP fetches. Using Promise.all over the filtered URLs would download them in parallel and noticeably reduce latency for multi-image responses:

const urls = (payload.images ?? [])
  .map((entry) => entry.url?.trim())
  .filter((url): url is string => Boolean(url));

const downloaded = await Promise.all(urls.map((url) => fetchImageBuffer(url)));

const images: GeneratedImageAsset[] = downloaded.map((dl, i) => ({
  buffer: dl.buffer,
  mimeType: dl.mimeType,
  fileName: `image-${i + 1}.${fileExtensionForMimeType(dl.mimeType)}`,
}));
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/image-generation/providers/fal.ts
Line: 171-185

Comment:
**Sequential image downloads for multi-image requests**

When `count > 1` and fal returns multiple image URLs, each URL is downloaded sequentially with a separate `await fetchImageBuffer(url)` inside the `for...of` loop. For a request that returns, say, 4 images, this serialises 4 independent HTTP fetches. Using `Promise.all` over the filtered URLs would download them in parallel and noticeably reduce latency for multi-image responses:

```typescript
const urls = (payload.images ?? [])
  .map((entry) => entry.url?.trim())
  .filter((url): url is string => Boolean(url));

const downloaded = await Promise.all(urls.map((url) => fetchImageBuffer(url)));

const images: GeneratedImageAsset[] = downloaded.map((dl, i) => ({
  buffer: dl.buffer,
  mimeType: dl.mimeType,
  fileName: `image-${i + 1}.${fileExtensionForMimeType(dl.mimeType)}`,
}));
```

How can I resolve this? If you propose a fix, please make it concise.

@vincentkoc
vincentkoc merged commit 6710a2b into main Mar 18, 2026
23 of 38 checks passed
@vincentkoc
vincentkoc deleted the vincentkoc-code/midjourney-skill branch March 18, 2026 04:35
gumadeiras pushed a commit to gumadeiras/clawdbot that referenced this pull request Mar 18, 2026

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

Copy link
Copy Markdown
Contributor

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: 6dec0b96e3

ℹ️ 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 thread extensions/fal/index.ts
flagName: "--fal-api-key",
envVar: "FAL_KEY",
promptMessage: "Enter fal API key",
defaultModel: FAL_DEFAULT_IMAGE_MODEL_REF,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Remove text default model from fal API-key setup

Passing defaultModel: FAL_DEFAULT_IMAGE_MODEL_REF into createProviderApiKeyAuthMethod makes auth setup treat fal/fal-ai/flux/dev as the agent's default chat model (via applyPrimaryModel in src/plugins/provider-api-key-auth.ts), not just the image-generation default. In non-interactive onboarding, and in interactive flows that apply defaults, this can rewrite agents.defaults.model.primary to a provider that has no text-generation runtime, causing normal agent/model calls to fail after fal auth. Keep the image default in applyFalConfig (agents.defaults.imageGenerationModel) and do not set defaultModel here.

Useful? React with 👍 / 👎.

BunsDev pushed a commit that referenced this pull request Mar 18, 2026
fuller-stack-dev pushed a commit to fuller-stack-dev/openclaw that referenced this pull request Mar 20, 2026
fuller-stack-dev pushed a commit to fuller-stack-dev/openclaw that referenced this pull request Mar 20, 2026
dustin-olenslager pushed a commit to dustin-olenslager/ironclaw-supreme that referenced this pull request Mar 24, 2026
ralyodio pushed a commit to ralyodio/openclaw that referenced this pull request Apr 3, 2026
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
Nachx639 pushed a commit to Nachx639/clawdbot that referenced this pull request Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant