@@ -459,60 +459,46 @@ describe("ssrfPolicyFromHttpBaseUrlAllowedOrigin — SDK boundary safety", () =>
459459 ) . rejects . toThrow ( SsrFBlockedError ) ;
460460 } ) ;
461461
462- it ( "rebinding a trusted private origin to a loopback IP is rejected" , async ( ) => {
463- const baseUrl = "http://lan-llm.corp.internal:11434/v1" ;
464- const policy = ssrfPolicyFromHttpBaseUrlAllowedOrigin ( baseUrl ) ;
465- const policyForUrl = resolveSsrFPolicyForUrl ( new URL ( baseUrl ) , policy ) ;
466-
467- await expect (
468- resolvePinnedHostnameWithPolicy ( "lan-llm.corp.internal" , {
469- policy : policyForUrl ,
470- lookupFn : createLookupFn ( [ { address : "127.0.0.1" , family : 4 } ] ) ,
471- } ) ,
472- ) . rejects . toThrow ( SsrFBlockedError ) ;
473- } ) ;
474-
475- it ( "rebinding a trusted private origin to IPv6 loopback is rejected" , async ( ) => {
462+ it . each ( [
463+ [ "IPv4 loopback" , "127.0.0.1" , 4 ] ,
464+ [ "IPv6 loopback" , "::1" , 6 ] ,
465+ [ "IPv4-mapped IPv6 loopback" , "::ffff:127.0.0.1" , 6 ] ,
466+ [ "NAT64-embedded IPv4 loopback" , "64:ff9b::127.0.0.1" , 6 ] ,
467+ [ "ISATAP-embedded IPv4 loopback" , "2001:4860:1::5efe:7f00:1" , 6 ] ,
468+ ] as const ) ( "rejects a trusted private origin rebound to %s" , async ( _name , address , family ) => {
476469 const baseUrl = "http://lan-llm.corp.internal:11434/v1" ;
477470 const policy = ssrfPolicyFromHttpBaseUrlAllowedOrigin ( baseUrl ) ;
478471 const policyForUrl = resolveSsrFPolicyForUrl ( new URL ( baseUrl ) , policy ) ;
479472
480473 await expect (
481474 resolvePinnedHostnameWithPolicy ( "lan-llm.corp.internal" , {
482475 policy : policyForUrl ,
483- lookupFn : createLookupFn ( [ { address : "::1" , family : 6 } ] ) ,
476+ lookupFn : createLookupFn ( [ { address, family } ] ) ,
484477 } ) ,
485478 ) . rejects . toThrow ( SsrFBlockedError ) ;
486479 } ) ;
487480
488- it ( "explicit localhost origin resolving to 127.0.0.1 is still allowed" , async ( ) => {
489- // When the trusted origin IS localhost itself (not a rebinding), the loopback
490- // resolution must be permitted so local-dev provider setups keep working.
491- const baseUrl = "http://localhost:11434/v1" ;
492- const policy = ssrfPolicyFromHttpBaseUrlAllowedOrigin ( baseUrl ) ;
493- const policyForUrl = resolveSsrFPolicyForUrl ( new URL ( baseUrl ) , policy ) ;
494-
495- // Must resolve without throwing — loopback allowed because hostname === "localhost"
496- await expect (
497- resolvePinnedHostnameWithPolicy ( "localhost" , {
498- policy : policyForUrl ,
499- lookupFn : createLookupFn ( [ { address : "127.0.0.1" , family : 4 } ] ) ,
500- } ) ,
501- ) . resolves . toBeDefined ( ) ;
502- } ) ;
503-
504- it ( "explicit 127.0.0.1 origin resolving to loopback is still allowed" , async ( ) => {
505- // A plugin that explicitly targets 127.0.0.1 directly as its base URL
506- // must not be blocked — the operator chose loopback intentionally.
507- const baseUrl = "http://127.0.0.1:11434/v1" ;
508- const policy = ssrfPolicyFromHttpBaseUrlAllowedOrigin ( baseUrl ) ;
509- const policyForUrl = resolveSsrFPolicyForUrl ( new URL ( baseUrl ) , policy ) ;
481+ it . each ( [
482+ [ "localhost" , "127.0.0.1" , 4 ] ,
483+ [ "localhost.localdomain" , "127.0.0.1" , 4 ] ,
484+ [ "api.localhost" , "::1" , 6 ] ,
485+ [ "127.0.0.1" , "127.0.0.1" , 4 ] ,
486+ [ "[::1]" , "::1" , 6 ] ,
487+ [ "[64:ff9b::127.0.0.1]" , "64:ff9b::127.0.0.1" , 6 ] ,
488+ ] as const ) (
489+ "allows an explicit %s origin to resolve to loopback" ,
490+ async ( host , address , family ) => {
491+ const baseUrl = `http://${ host } :11434/v1` ;
492+ const policy = ssrfPolicyFromHttpBaseUrlAllowedOrigin ( baseUrl ) ;
493+ const policyForUrl = resolveSsrFPolicyForUrl ( new URL ( baseUrl ) , policy ) ;
494+ const hostname = new URL ( baseUrl ) . hostname . replace ( / ^ \[ | \] $ / g, "" ) ;
510495
511- await expect (
512- resolvePinnedHostnameWithPolicy ( "127.0.0.1" , {
513- policy : policyForUrl ,
514- lookupFn : createLookupFn ( [ { address : "127.0.0.1" , family : 4 } ] ) ,
515- } ) ,
516- ) . resolves . toBeDefined ( ) ;
517- } ) ;
496+ await expect (
497+ resolvePinnedHostnameWithPolicy ( hostname , {
498+ policy : policyForUrl ,
499+ lookupFn : createLookupFn ( [ { address, family } ] ) ,
500+ } ) ,
501+ ) . resolves . toBeDefined ( ) ;
502+ } ,
503+ ) ;
518504} ) ;
0 commit comments