@@ -2462,6 +2462,149 @@ describe("provider management validation", () => {
24622462 }
24632463 } ) ;
24642464
2465+ test ( "canonical OpenAI PATCH passes allowBenchmarkAddresses into destination resolution" , async ( ) => {
2466+ // The ordinary field-mask PATCH resolves the SAME canonical chatgpt.com destination
2467+ // POST and re-enable already admit. Without the benchmark opt-in here, a Clash/Mihomo
2468+ // fake-IP user (chatgpt.com → 198.18.0.0/15) could create the provider but could never
2469+ // patch a context overlay onto it. Loopback/RFC1918/metadata and mixed dangerous
2470+ // answers still fail closed (covered by destination-policy-resolved tests and below).
2471+ if ( existsSync ( TEST_DIR ) ) rmSync ( TEST_DIR , { recursive : true } ) ;
2472+ mkdirSync ( TEST_DIR , { recursive : true } ) ;
2473+ process . env . OPENCODEX_HOME = TEST_DIR ;
2474+ const liveConfig : OcxConfig = {
2475+ port : 0 ,
2476+ defaultProvider : "openai" ,
2477+ providers : {
2478+ openai : { ...canonicalDirect } ,
2479+ } ,
2480+ } ;
2481+ const resolvedError = spyOn ( destinationPolicy , "providerDestinationResolvedError" )
2482+ . mockResolvedValue ( null ) ;
2483+
2484+ try {
2485+ const patch = async ( name : string , body : unknown ) => {
2486+ const request = new Request ( `http://127.0.0.1/api/providers?name=${ encodeURIComponent ( name ) } ` , {
2487+ method : "PATCH" ,
2488+ headers : { "content-type" : "application/json" } ,
2489+ body : JSON . stringify ( body ) ,
2490+ } ) ;
2491+ return handleManagementAPI ( request , new URL ( request . url ) , liveConfig , {
2492+ createManagementConvergeCodex : catalogConvergenceFactory ( ) ,
2493+ } ) ;
2494+ } ;
2495+
2496+ const canonical = await patch ( "openai" , { modelContextWindows : { "gpt-5.6-luna" : 900000 } } ) ;
2497+ expect ( canonical ?. status ) . toBe ( 200 ) ;
2498+ expect ( resolvedError ) . toHaveBeenCalledWith (
2499+ "openai" ,
2500+ expect . objectContaining ( { baseUrl : canonicalDirect . baseUrl } ) ,
2501+ { allowBenchmarkAddresses : true } ,
2502+ ) ;
2503+ } finally {
2504+ resolvedError . mockRestore ( ) ;
2505+ }
2506+ } ) ;
2507+
2508+ test ( "PATCH destination benchmark exception stays scoped to the canonical openai row" , async ( ) => {
2509+ // A non-canonical openai row and any OpenAI-LOOKING custom provider must not inherit
2510+ // the fake-IP exception: their PATCHes still fail closed on benchmark answers.
2511+ if ( existsSync ( TEST_DIR ) ) rmSync ( TEST_DIR , { recursive : true } ) ;
2512+ mkdirSync ( TEST_DIR , { recursive : true } ) ;
2513+ process . env . OPENCODEX_HOME = TEST_DIR ;
2514+ const liveConfig : OcxConfig = {
2515+ port : 0 ,
2516+ defaultProvider : "openai" ,
2517+ providers : {
2518+ openai : { ...canonicalDirect } ,
2519+ mirror : {
2520+ adapter : "openai-chat" ,
2521+ baseUrl : "https://mirror.example.test/v1" ,
2522+ apiKey : "sk-secret-value" ,
2523+ } ,
2524+ "openai-proxy" : {
2525+ adapter : "openai-chat" ,
2526+ baseUrl : "https://mirror.example.test/v1" ,
2527+ apiKey : "sk-secret-value" ,
2528+ } ,
2529+ } ,
2530+ } ;
2531+ const resolvedError = spyOn ( destinationPolicy , "providerDestinationResolvedError" )
2532+ . mockResolvedValue (
2533+ "baseUrl hostname mirror.example.test resolves to a benchmark address (198.18.0.30); set allowPrivateNetwork:true only for intentionally local/self-hosted providers" ,
2534+ ) ;
2535+
2536+ try {
2537+ const patch = async ( name : string , body : unknown ) => {
2538+ const request = new Request ( `http://127.0.0.1/api/providers?name=${ encodeURIComponent ( name ) } ` , {
2539+ method : "PATCH" ,
2540+ headers : { "content-type" : "application/json" } ,
2541+ body : JSON . stringify ( body ) ,
2542+ } ) ;
2543+ return handleManagementAPI ( request , new URL ( request . url ) , liveConfig , {
2544+ createManagementConvergeCodex : catalogConvergenceFactory ( ) ,
2545+ } ) ;
2546+ } ;
2547+
2548+ const custom = await patch ( "mirror" , { defaultModel : "gpt-x" } ) ;
2549+ expect ( custom ?. status ) . toBe ( 400 ) ;
2550+ expect ( resolvedError ) . toHaveBeenCalledWith (
2551+ "mirror" ,
2552+ expect . anything ( ) ,
2553+ { allowBenchmarkAddresses : false } ,
2554+ ) ;
2555+
2556+ // Non-canonical row named "openai"-adjacent: no exception either.
2557+ const openaiProxy = await patch ( "openai-proxy" , { defaultModel : "gpt-x" } ) ;
2558+ expect ( openaiProxy ?. status ) . toBe ( 400 ) ;
2559+ expect ( resolvedError ) . toHaveBeenCalledWith (
2560+ "openai-proxy" ,
2561+ expect . anything ( ) ,
2562+ { allowBenchmarkAddresses : false } ,
2563+ ) ;
2564+ } finally {
2565+ resolvedError . mockRestore ( ) ;
2566+ }
2567+ } ) ;
2568+
2569+ test ( "canonical OpenAI PATCH still rejects non-benchmark private destination answers" , async ( ) => {
2570+ // The benchmark opt-in must not relax the rest of the SSRF guard: if the probe
2571+ // classifies the canonical destination as loopback/private/metadata, the PATCH fails.
2572+ if ( existsSync ( TEST_DIR ) ) rmSync ( TEST_DIR , { recursive : true } ) ;
2573+ mkdirSync ( TEST_DIR , { recursive : true } ) ;
2574+ process . env . OPENCODEX_HOME = TEST_DIR ;
2575+ const liveConfig : OcxConfig = {
2576+ port : 0 ,
2577+ defaultProvider : "openai" ,
2578+ providers : {
2579+ openai : { ...canonicalDirect } ,
2580+ } ,
2581+ } ;
2582+ const resolvedError = spyOn ( destinationPolicy , "providerDestinationResolvedError" )
2583+ . mockResolvedValue ( "baseUrl hostname chatgpt.com resolves to a loopback address (127.0.0.1); set allowPrivateNetwork:true only for intentionally local/self-hosted providers" ) ;
2584+
2585+ try {
2586+ const request = new Request ( "http://127.0.0.1/api/providers?name=openai" , {
2587+ method : "PATCH" ,
2588+ headers : { "content-type" : "application/json" } ,
2589+ body : JSON . stringify ( { modelContextWindows : { "gpt-5.6-luna" : 900000 } } ) ,
2590+ } ) ;
2591+ const response = await handleManagementAPI ( request , new URL ( request . url ) , liveConfig , {
2592+ createManagementConvergeCodex : catalogConvergenceFactory ( ) ,
2593+ } ) ;
2594+ expect ( response ?. status ) . toBe ( 400 ) ;
2595+ expect ( await response ?. json ( ) ) . toMatchObject ( {
2596+ error : expect . stringContaining ( "loopback address" ) ,
2597+ } ) ;
2598+ expect ( resolvedError ) . toHaveBeenCalledWith (
2599+ "openai" ,
2600+ expect . anything ( ) ,
2601+ { allowBenchmarkAddresses : true } ,
2602+ ) ;
2603+ } finally {
2604+ resolvedError . mockRestore ( ) ;
2605+ }
2606+ } ) ;
2607+
24652608 test ( "disabled-only PATCH cannot re-enable a noncanonical openai row unchanged" , async ( ) => {
24662609 if ( existsSync ( TEST_DIR ) ) rmSync ( TEST_DIR , { recursive : true } ) ;
24672610 mkdirSync ( TEST_DIR , { recursive : true } ) ;
0 commit comments