@@ -2,6 +2,7 @@ import { randomBytes, randomUUID } from "node:crypto";
22import { resolveExpiresAtMsFromDurationOrEpoch } from "openclaw/plugin-sdk/number-runtime" ;
33import { generatePkceVerifierChallenge , toFormUrlEncoded } from "openclaw/plugin-sdk/provider-auth" ;
44import { ensureGlobalUndiciEnvProxyDispatcher } from "openclaw/plugin-sdk/runtime-env" ;
5+ import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime" ;
56
67export type MiniMaxRegion = "cn" | "global" ;
78
@@ -28,6 +29,7 @@ function getOAuthEndpoints(region: MiniMaxRegion) {
2829 tokenEndpoint : `${ config . baseUrl } /oauth/token` ,
2930 clientId : config . clientId ,
3031 baseUrl : config . baseUrl ,
32+ hostname : new URL ( config . baseUrl ) . hostname ,
3133 } ;
3234}
3335
@@ -78,39 +80,47 @@ async function requestOAuthCode(params: {
7880 region : MiniMaxRegion ;
7981} ) : Promise < MiniMaxOAuthAuthorization > {
8082 const endpoints = getOAuthEndpoints ( params . region ) ;
81- const response = await fetch ( endpoints . codeEndpoint , {
82- method : "POST" ,
83- headers : {
84- "Content-Type" : "application/x-www-form-urlencoded" ,
85- Accept : "application/json" ,
86- "x-request-id" : randomUUID ( ) ,
83+ const { response, release } = await fetchWithSsrFGuard ( {
84+ url : endpoints . codeEndpoint ,
85+ init : {
86+ method : "POST" ,
87+ headers : {
88+ "Content-Type" : "application/x-www-form-urlencoded" ,
89+ Accept : "application/json" ,
90+ "x-request-id" : randomUUID ( ) ,
91+ } ,
92+ body : toFormUrlEncoded ( {
93+ response_type : "code" ,
94+ client_id : endpoints . clientId ,
95+ scope : MINIMAX_OAUTH_SCOPE ,
96+ code_challenge : params . challenge ,
97+ code_challenge_method : "S256" ,
98+ state : params . state ,
99+ } ) ,
87100 } ,
88- body : toFormUrlEncoded ( {
89- response_type : "code" ,
90- client_id : endpoints . clientId ,
91- scope : MINIMAX_OAUTH_SCOPE ,
92- code_challenge : params . challenge ,
93- code_challenge_method : "S256" ,
94- state : params . state ,
95- } ) ,
101+ policy : { allowedHostnames : [ endpoints . hostname ] } ,
102+ auditContext : "minimax.oauth.code" ,
96103 } ) ;
104+ try {
105+ if ( ! response . ok ) {
106+ const text = await response . text ( ) ;
107+ throw new Error ( `MiniMax OAuth authorization failed: ${ text || response . statusText } ` ) ;
108+ }
97109
98- if ( ! response . ok ) {
99- const text = await response . text ( ) ;
100- throw new Error ( `MiniMax OAuth authorization failed: ${ text || response . statusText } ` ) ;
101- }
102-
103- const payload = ( await response . json ( ) ) as MiniMaxOAuthAuthorization & { error ?: string } ;
104- if ( ! payload . user_code || ! payload . verification_uri ) {
105- throw new Error (
106- payload . error ??
107- "MiniMax OAuth authorization returned an incomplete payload (missing user_code or verification_uri)." ,
108- ) ;
109- }
110- if ( payload . state !== params . state ) {
111- throw new Error ( "MiniMax OAuth state mismatch: possible CSRF attack or session corruption." ) ;
110+ const payload = ( await response . json ( ) ) as MiniMaxOAuthAuthorization & { error ?: string } ;
111+ if ( ! payload . user_code || ! payload . verification_uri ) {
112+ throw new Error (
113+ payload . error ??
114+ "MiniMax OAuth authorization returned an incomplete payload (missing user_code or verification_uri)." ,
115+ ) ;
116+ }
117+ if ( payload . state !== params . state ) {
118+ throw new Error ( "MiniMax OAuth state mismatch: possible CSRF attack or session corruption." ) ;
119+ }
120+ return payload ;
121+ } finally {
122+ await release ( ) ;
112123 }
113- return payload ;
114124}
115125
116126async function pollOAuthToken ( params : {
@@ -119,20 +129,32 @@ async function pollOAuthToken(params: {
119129 region : MiniMaxRegion ;
120130} ) : Promise < TokenResult > {
121131 const endpoints = getOAuthEndpoints ( params . region ) ;
122- const response = await fetch ( endpoints . tokenEndpoint , {
123- method : "POST" ,
124- headers : {
125- "Content-Type" : "application/x-www-form-urlencoded" ,
126- Accept : "application/json" ,
132+ const { response, release } = await fetchWithSsrFGuard ( {
133+ url : endpoints . tokenEndpoint ,
134+ init : {
135+ method : "POST" ,
136+ headers : {
137+ "Content-Type" : "application/x-www-form-urlencoded" ,
138+ Accept : "application/json" ,
139+ } ,
140+ body : toFormUrlEncoded ( {
141+ grant_type : MINIMAX_OAUTH_GRANT_TYPE ,
142+ client_id : endpoints . clientId ,
143+ user_code : params . userCode ,
144+ code_verifier : params . verifier ,
145+ } ) ,
127146 } ,
128- body : toFormUrlEncoded ( {
129- grant_type : MINIMAX_OAUTH_GRANT_TYPE ,
130- client_id : endpoints . clientId ,
131- user_code : params . userCode ,
132- code_verifier : params . verifier ,
133- } ) ,
147+ policy : { allowedHostnames : [ endpoints . hostname ] } ,
148+ auditContext : "minimax.oauth.token" ,
134149 } ) ;
150+ try {
151+ return await parseMiniMaxOAuthTokenResponse ( response ) ;
152+ } finally {
153+ await release ( ) ;
154+ }
155+ }
135156
157+ async function parseMiniMaxOAuthTokenResponse ( response : Response ) : Promise < TokenResult > {
136158 const text = await response . text ( ) ;
137159 let payload :
138160 | {
0 commit comments