fix(ai): address Alibaba image audit findings

This commit is contained in:
Aiden Cline
2026-07-19 16:17:09 +00:00
committed by 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴
parent 07ce99dc9e
commit 0baabcdc4b
6 changed files with 149 additions and 23 deletions
+19 -17
View File
@@ -25,7 +25,7 @@ export const PATH = "/services/aigc/multimodal-generation/generation"
export type Family = "qwen" | "wan"
export interface QwenImageOptions extends Record<string, unknown> {
export interface QwenImageOptions {
readonly negativePrompt?: string
readonly promptExtend?: boolean
readonly watermark?: boolean
@@ -36,14 +36,14 @@ export interface WanColor {
readonly ratio: string
}
export interface WanImageOptions extends Record<string, unknown> {
export interface WanImageOptions {
readonly resolution?: "1K" | "2K" | "4K"
readonly thinkingMode?: boolean
readonly colorPalette?: ReadonlyArray<WanColor>
readonly watermark?: boolean
}
export interface AlibabaImageOptions extends Record<string, unknown> {
export interface AlibabaImageOptions {
readonly qwen?: QwenImageOptions
readonly wan?: WanImageOptions
}
@@ -240,7 +240,8 @@ const expiration = (url: string) => {
if (!URL.canParse(url)) return undefined
const value = new URL(url).searchParams.get("Expires")
if (value === null || !Number.isFinite(Number(value))) return undefined
return new Date(Number(value) * 1000).toISOString()
const date = new Date(Number(value) * 1000)
return Number.isNaN(date.getTime()) ? undefined : date.toISOString()
}
export const model = (input: ModelInput) => {
@@ -297,20 +298,21 @@ export const model = (input: ModelInput) => {
return yield* invalidOutput("Alibaba Images returned no images", { requestId: decoded.request_id })
return new ImageResponse({
images: urls.map(
(url) =>
new GeneratedImage({
mediaType: "image/png",
data: url,
providerMetadata: {
alibaba: {
modelId: request.model.id,
family: input.family,
expiresAt: expiration(url),
},
images: urls.map((url) => {
const expiresAt = expiration(url)
return new GeneratedImage({
mediaType: "image/png",
data: url,
expiresAt,
providerMetadata: {
alibaba: {
modelId: request.model.id,
family: input.family,
expiresAt,
},
}),
),
},
})
}),
usage:
decoded.usage === undefined
? undefined
+7
View File
@@ -2,6 +2,7 @@ import { Config } from "effect"
import { Auth } from "../src/route"
import type { ModelFactory } from "../src/route/auth-options"
import * as OpenAIChat from "../src/protocols/openai-chat"
import * as Alibaba from "../src/providers/alibaba"
import * as AmazonBedrock from "../src/providers/amazon-bedrock"
import * as Anthropic from "../src/providers/anthropic"
import * as AnthropicCompatible from "../src/providers/anthropic-compatible"
@@ -250,3 +251,9 @@ Cloudflare.CloudflareWorkersAI.configure({ accountId: "account", apiKey: "cf-key
GitHubCopilot.configure({ baseURL: "https://copilot.test", apiKey: "copilot-key" }).model("gpt-4.1")
// @ts-expect-error GitHub Copilot model selectors only accept model ids.
GitHubCopilot.configure({ baseURL: "https://copilot.test", apiKey: "copilot-key" }).model("gpt-4.1", {})
Alibaba.configure({ image: { providerOptions: { qwen: { promptExtend: true } } } }).image("qwen-image-2.0")
// @ts-expect-error Alibaba Qwen options are closed and reject unimplemented parameters.
Alibaba.configure({ image: { providerOptions: { qwen: { style: "photorealistic" } } } })
// @ts-expect-error Alibaba Wan options are closed and reject unimplemented parameters.
Alibaba.configure({ image: { providerOptions: { wan: { parameters: { watermark: true } } } } })
@@ -3,7 +3,7 @@
"metadata": {
"tags": ["prefix:alibaba-images", "provider:alibaba", "protocol:alibaba-images"],
"name": "alibaba-images/generates-with-qwen-image-2-0",
"recordedAt": "2026-07-19T16:05:19.642Z"
"recordedAt": "2026-07-19T16:15:36.863Z"
},
"interactions": [
{
@@ -21,7 +21,7 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"output\":{\"choices\":[{\"finish_reason\":\"stop\",\"message\":{\"content\":[{\"image\":\"https://dashscope-463f.oss-accelerate.aliyuncs.com/7d/7b/20260720/991d10e3/fe425a64-dc6f-4c67-a4fa-a06810984ba9.png?Expires=1785082919&OSSAccessKeyId=fixture&Signature=fixture\"}],\"role\":\"assistant\"}}]},\"usage\":{\"height\":512,\"image_count\":1,\"width\":512},\"request_id\":\"484490f4-9881-90eb-8d52-d270d2395920\"}"
"body": "{\"output\":{\"choices\":[{\"finish_reason\":\"stop\",\"message\":{\"content\":[{\"image\":\"https://dashscope-463f.oss-accelerate.aliyuncs.com/7d/04/20260720/991d10e3/93325a03-aa95-4dd3-a395-8d7246b5e72c.png?Expires=1785083536&OSSAccessKeyId=%5BREDACTED%5D&Signature=%5BREDACTED%5D\"}],\"role\":\"assistant\"}}]},\"usage\":{\"height\":512,\"image_count\":1,\"width\":512},\"request_id\":\"9f94d544-393b-9d6f-b005-4d0ff21f6fdf\"}"
}
}
]
@@ -3,7 +3,7 @@
"metadata": {
"tags": ["prefix:alibaba-images", "provider:alibaba", "protocol:alibaba-images"],
"name": "alibaba-images/generates-with-wan-2-7",
"recordedAt": "2026-07-19T16:05:23.617Z"
"recordedAt": "2026-07-19T16:15:40.689Z"
},
"interactions": [
{
@@ -21,7 +21,7 @@
"headers": {
"content-type": "application/json"
},
"body": "{\"output\":{\"choices\":[{\"finish_reason\":\"stop\",\"message\":{\"content\":[{\"image\":\"https://dashscope-463f.oss-accelerate.aliyuncs.com/1d/ae/20260720/10739b93/1784477122_595381b5.png?Expires=1784563522&OSSAccessKeyId=fixture&Signature=fixture\",\"type\":\"image\"}],\"role\":\"assistant\"}}],\"finished\":true},\"usage\":{\"image_count\":1,\"input_tokens\":31,\"output_tokens\":2,\"size\":\"1024*1024\",\"total_tokens\":33},\"request_id\":\"ec5cacb5-c7b0-9e46-8459-49003a3816c3\"}"
"body": "{\"output\":{\"choices\":[{\"finish_reason\":\"stop\",\"message\":{\"content\":[{\"image\":\"https://dashscope-463f.oss-accelerate.aliyuncs.com/1d/0b/20260720/10739b93/1784477739_553aee1e.png?Expires=1784564140&OSSAccessKeyId=%5BREDACTED%5D&Signature=%5BREDACTED%5D\",\"type\":\"image\"}],\"role\":\"assistant\"}}],\"finished\":true},\"usage\":{\"image_count\":1,\"input_tokens\":31,\"output_tokens\":2,\"size\":\"1024*1024\",\"total_tokens\":33},\"request_id\":\"3eb93323-2d8f-9aa5-b010-82dd8fab80ea\"}"
}
}
]
@@ -1,5 +1,5 @@
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { describe, expect, test } from "bun:test"
import { Effect, Option, Schema } from "effect"
import { Image } from "../../src"
import { Alibaba } from "../../src/providers"
import { recordedTests } from "../recorded-test"
@@ -8,11 +8,77 @@ const alibaba = Alibaba.configure({
apiKey: process.env.DASHSCOPE_API_KEY ?? "fixture",
})
const SIGNED_QUERY_CREDENTIALS = new Set(
[
"AccessKeyId",
"AWSAccessKeyId",
"GoogleAccessId",
"OSSAccessKeyId",
"Signature",
"SecurityToken",
"X-Amz-Credential",
"X-Amz-Security-Token",
"X-Amz-Signature",
"X-Goog-Credential",
"X-Goog-Signature",
"X-OSS-Security-Token",
].map((name) => name.replace(/[^a-z0-9]/gi, "").toLowerCase()),
)
const redactSignedUrl = (value: string) => {
if (!URL.canParse(value)) return value
const url = new URL(value)
if (url.protocol !== "http:" && url.protocol !== "https:") return value
url.searchParams.forEach((_, name) => {
if (SIGNED_QUERY_CREDENTIALS.has(name.replace(/[^a-z0-9]/gi, "").toLowerCase()))
url.searchParams.set(name, "[REDACTED]")
})
return url.toString()
}
const redactSignedUrls = (body: string) =>
Option.match(Schema.decodeUnknownOption(Schema.UnknownFromJsonString)(body), {
onNone: () => body,
onSome: (value) =>
JSON.stringify(
(function redact(input: unknown): unknown {
if (typeof input === "string") return redactSignedUrl(input)
if (Array.isArray(input)) return input.map(redact)
if (input === null || typeof input !== "object") return input
return Object.fromEntries(Object.entries(input).map(([key, child]) => [key, redact(child)]))
})(value),
),
})
const recorded = recordedTests({
prefix: "alibaba-images",
provider: "alibaba",
protocol: "alibaba-images",
requires: ["DASHSCOPE_API_KEY"],
options: { redact: { body: redactSignedUrls } },
})
test("recorder redacts signed URL credentials in nested JSON strings repeatably", () => {
const body = JSON.stringify({
output: {
image:
"https://example.oss.aliyuncs.com/image.png?Expires=1893456000&OSSAccessKeyId=access&Signature=signature&x-oss-security-token=token",
},
mirrors: ["https://storage.googleapis.com/image.png?X-Goog-Credential=credential&X-Goog-Signature=signature"],
})
const once = redactSignedUrls(body)
const twice = redactSignedUrls(once)
const parsed = JSON.parse(once)
const oss = new URL(parsed.output.image)
const gcs = new URL(parsed.mirrors[0])
expect(oss.searchParams.get("Expires")).toBe("1893456000")
expect(oss.searchParams.get("OSSAccessKeyId")).toBe("[REDACTED]")
expect(oss.searchParams.get("Signature")).toBe("[REDACTED]")
expect(oss.searchParams.get("x-oss-security-token")).toBe("[REDACTED]")
expect(gcs.searchParams.get("X-Goog-Credential")).toBe("[REDACTED]")
expect(gcs.searchParams.get("X-Goog-Signature")).toBe("[REDACTED]")
expect(twice).toBe(once)
})
describe("Alibaba Images recorded", () => {
@@ -50,6 +50,7 @@ describe("Alibaba Images", () => {
})
expect(result.image?.data).toContain("dashscope-result-intl")
expect(result.image?.expiresAt).toBe("2030-01-01T00:00:00.000Z")
expect(result.image?.providerMetadata).toEqual({
alibaba: {
modelId: "qwen-image-2.0-pro",
@@ -193,4 +194,54 @@ describe("Alibaba Images", () => {
),
),
)
it.effect("rejects parameters overlays owned by the protocol", () =>
Image.generate({
model: Alibaba.configure({ apiKey: "test" }).image("qwen-image-2.0"),
prompt: "A robot",
http: { body: { parameters: { watermark: true } } },
}).pipe(
Effect.flip,
Effect.tap((error) =>
Effect.sync(() => {
expect(error.reason._tag).toBe("InvalidRequest")
expect(error.message).toContain("http.body cannot overlay protocol-owned field(s): parameters")
}),
),
Effect.provide(
ImageClient.layer.pipe(
Layer.provide(
dynamicResponse(() => Effect.die("reserved body validation must happen before the request is sent")),
),
),
),
),
)
it.effect("ignores finite expiration values outside the Date range", () =>
Effect.gen(function* () {
const result = yield* Image.generate({
model: Alibaba.configure({ apiKey: "test" }).image("qwen-image-2.0"),
prompt: "A robot",
})
expect(result.image?.expiresAt).toBeUndefined()
expect(result.image?.providerMetadata?.alibaba?.expiresAt).toBeUndefined()
}).pipe(
Effect.provide(
ImageClient.layer.pipe(
Layer.provide(
dynamicResponse((input) => {
const payload = response("qwen")
payload.output.choices[0].message.content[0].image =
"https://dashscope-result-intl.oss-cn-singapore.aliyuncs.com/result.png?Expires=9007199254740991"
return Effect.succeed(
input.respond(JSON.stringify(payload), { headers: { "content-type": "application/json" } }),
)
}),
),
),
),
),
)
})