mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-22 18:25:32 -04:00
fix(ai): address Alibaba image audit findings
This commit is contained in:
committed by
𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴
parent
07ce99dc9e
commit
0baabcdc4b
@@ -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
|
||||
|
||||
@@ -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 } } } } })
|
||||
|
||||
+2
-2
@@ -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\"}"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+2
-2
@@ -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" } }),
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user