mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 10:55:31 -06:00
fix(google): bound Vertex token response bodies
Partial prerequisite backport fromaaf5ab910c(#100483). (cherry picked from commit8a7a6acb6d)
This commit is contained in:
@@ -1108,6 +1108,55 @@ describe("google transport stream", () => {
|
||||
expect(result.content).toEqual([{ type: "text", text: "ok" }]);
|
||||
});
|
||||
|
||||
it("rejects oversized authorized_user ADC token responses", async () => {
|
||||
const tempDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-google-vertex-adc-large-"));
|
||||
const credentialsPath = path.join(tempDir, "application_default_credentials.json");
|
||||
await writeFile(
|
||||
credentialsPath,
|
||||
JSON.stringify({
|
||||
type: "authorized_user",
|
||||
client_id: "client-id",
|
||||
client_secret: "client-secret",
|
||||
refresh_token: "large-refresh-token",
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
vi.stubEnv("GOOGLE_APPLICATION_CREDENTIALS", credentialsPath);
|
||||
const tokenFetchMock = vi
|
||||
.fn()
|
||||
.mockResolvedValue(new Response("x".repeat(1024 * 1024 + 1), { status: 200 }));
|
||||
|
||||
await expect(resolveGoogleVertexAuthorizedUserHeaders(tokenFetchMock)).rejects.toThrow(
|
||||
"Google OAuth token response exceeds 1048576 bytes",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects authorized_user ADC gzip responses that expand past the limit", async () => {
|
||||
const tempDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-google-vertex-adc-bomb-"));
|
||||
const credentialsPath = path.join(tempDir, "application_default_credentials.json");
|
||||
await writeFile(
|
||||
credentialsPath,
|
||||
JSON.stringify({
|
||||
type: "authorized_user",
|
||||
client_id: "client-id",
|
||||
client_secret: "client-secret",
|
||||
refresh_token: "bomb-refresh-token",
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
vi.stubEnv("GOOGLE_APPLICATION_CREDENTIALS", credentialsPath);
|
||||
const tokenFetchMock = vi.fn().mockResolvedValue(
|
||||
new Response(gzipSync("x".repeat(1024 * 1024 + 1)), {
|
||||
status: 200,
|
||||
headers: { "content-encoding": "gzip" },
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(resolveGoogleVertexAuthorizedUserHeaders(tokenFetchMock)).rejects.toThrow(
|
||||
"Google OAuth token response exceeds 1048576 decompressed bytes",
|
||||
);
|
||||
});
|
||||
|
||||
it("refreshes authorized_user ADC from a compressed token response", async () => {
|
||||
const tempDir = await mkdtemp(path.join(os.tmpdir(), "openclaw-google-vertex-adc-gzip-"));
|
||||
const credentialsPath = path.join(tempDir, "application_default_credentials.json");
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
resolveExpiresAtMsFromDurationMs,
|
||||
resolveExpiresAtMsFromDurationSeconds,
|
||||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
type GoogleAuthorizedUserCredentials = {
|
||||
@@ -46,6 +47,7 @@ const GOOGLE_VERTEX_OAUTH_SCOPE = "https://www.googleapis.com/auth/cloud-platfor
|
||||
const GOOGLE_VERTEX_TOKEN_EXPIRY_BUFFER_MS = 60_000;
|
||||
const GOOGLE_VERTEX_DEFAULT_TOKEN_LIFETIME_SECONDS = 3600;
|
||||
const GOOGLE_VERTEX_AUTHLIB_TOKEN_CACHE_MS = 5 * 60_000;
|
||||
const GOOGLE_OAUTH_TOKEN_RESPONSE_MAX_BYTES = 1024 * 1024;
|
||||
|
||||
let cachedGoogleVertexAuthorizedUserToken: GoogleVertexAuthorizedUserToken | undefined;
|
||||
let cachedGoogleAuthClient:
|
||||
@@ -277,7 +279,10 @@ async function refreshGoogleVertexAuthorizedUserAccessToken(params: {
|
||||
async function readGoogleOauthTokenResponsePayload(
|
||||
response: Response,
|
||||
): Promise<GoogleOauthTokenResponsePayload | undefined> {
|
||||
const bytes = Buffer.from(await response.arrayBuffer());
|
||||
const bytes = await readResponseWithLimit(response, GOOGLE_OAUTH_TOKEN_RESPONSE_MAX_BYTES, {
|
||||
onOverflow: ({ maxBytes }) =>
|
||||
new Error(`Google OAuth token response exceeds ${maxBytes} bytes`),
|
||||
});
|
||||
const text = decodeGoogleOauthTokenResponseBody(bytes, response.headers.get("content-encoding"));
|
||||
if (!text.trim()) {
|
||||
return undefined;
|
||||
@@ -292,8 +297,21 @@ async function readGoogleOauthTokenResponsePayload(
|
||||
function decodeGoogleOauthTokenResponseBody(bytes: Buffer, contentEncoding: string | null): string {
|
||||
if (shouldGunzipGoogleOauthTokenResponse(bytes, contentEncoding)) {
|
||||
try {
|
||||
return gunzipSync(bytes).toString("utf8");
|
||||
} catch {
|
||||
return gunzipSync(bytes, { maxOutputLength: GOOGLE_OAUTH_TOKEN_RESPONSE_MAX_BYTES }).toString(
|
||||
"utf8",
|
||||
);
|
||||
} catch (error) {
|
||||
if (
|
||||
typeof error === "object" &&
|
||||
error !== null &&
|
||||
"code" in error &&
|
||||
error.code === "ERR_BUFFER_TOO_LARGE"
|
||||
) {
|
||||
throw new Error(
|
||||
`Google OAuth token response exceeds ${GOOGLE_OAUTH_TOKEN_RESPONSE_MAX_BYTES} decompressed bytes`,
|
||||
{ cause: error },
|
||||
);
|
||||
}
|
||||
return bytes.toString("utf8");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user