feat: bind explicitly authorized HTTP text extraction channel
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';require dirname(__DIR__) . '/vendor/topthink/framework/src/helper.php';
|
||||
use app\common\service\followupaudio\FollowupAudioProviderConfig as P;
|
||||
use app\common\service\followupaudio\FollowupAudioException as E;
|
||||
use app\common\service\followupaudio\FollowupAudioStreamTransport as T;
|
||||
new think\App();$config=new think\Config();think\Container::getInstance()->instance('config',$config);
|
||||
$n=0;$ok=static function(bool $value,string $why)use(&$n){if(!$value)throw new RuntimeException($why);$n++;};
|
||||
$reject=static function(callable $f)use($ok){try{$f();}catch(E $e){$ok(in_array($e->errorCode,['HTTPS_REQUIRED','CONFIG_INVALID'],true),'bounded configuration rejection');return;}throw new RuntimeException('Expected reject');};
|
||||
$slot=['driver'=>'asr_then_llm','label'=>'Synthetic GPT','asr'=>['protocol'=>'dify','binding_revision'=>'test-v1','base_url'=>'https://asr.invalid/v1','api_key'=>'synthetic-asr','model'=>'synthetic-asr'],'extraction'=>['protocol'=>'openai','base_url'=>'http://text.invalid/v1','api_key'=>'synthetic-key','model'=>'synthetic-llm','http_base_opt_in'=>'http://text.invalid/v1']];
|
||||
$s=['providers'=>['openai'=>$slot],'http_transport'=>'openssl_stream'];
|
||||
$p=P::resolve('openai',$s,[]);$ok(P::stageEndpointAllowed($p,'extraction'),'exact authorized text HTTP accepted');$ok(P::stageEndpointAllowed($p,'asr'),'ASR remains HTTPS');
|
||||
$ok($p['extraction']['http_policy']==='exact-text-endpoint-v1','versioned opt-in included in fingerprint input');
|
||||
foreach(['','http://other.invalid/v1','http://text.invalid:81/v1','http://text.invalid/other/v1','http://text.invalid/v1/','http://text.invalid/v1/chat/completions',true,[]]as$pin){$x=$s;$x['providers']['openai']['extraction']['http_base_opt_in']=$pin;$reject(fn()=>P::resolve('openai',$x,[]));}
|
||||
foreach(['http://text.invalid.evil/v1','http://text.invalid/v1?x=1','http://u:p@text.invalid/v1','http://text.invalid/a/../v1','http://text.invalid/%2e/v1',"http://text.invalid/v1\r\nX: 1",'ftp://text.invalid/v1']as$url){$x=$s;$x['providers']['openai']['extraction']['base_url']=$url;$x['providers']['openai']['extraction']['http_base_opt_in']=str_contains($url,'invalid.evil')?'http://text.invalid/v1':$url;$reject(fn()=>P::resolve('openai',$x,[]));}
|
||||
$x=$s;$x['providers']['openai']['asr']['base_url']='http://text.invalid/v1';$reject(fn()=>P::resolve('openai',$x,[]));
|
||||
$x['providers']['openai']['asr']['http_base_opt_in']='http://text.invalid/v1';$reject(fn()=>P::resolve('openai',$x,[]));
|
||||
$x=$s;$x['providers']['openai']['extraction']['protocol']='dify_chat';$x['providers']['openai']['extraction']['binding_revision']='test';$reject(fn()=>P::resolve('openai',$x,[]));
|
||||
$x=$s;$x['providers']['qwen']=$slot;unset($x['providers']['qwen']['extraction']['http_base_opt_in']);$reject(fn()=>P::resolve('qwen',$x,[]));
|
||||
$legacy=$slot;$legacy['extraction']['base_url']='https://text.invalid/v1';unset($legacy['extraction']['http_base_opt_in']);$old=['providers'=>['qwen'=>$legacy],'http_transport'=>'openssl_stream'];$oldHash=P::resolve('qwen',$old,[])['fingerprint'];$old['providers']['qwen']['extraction']['http_base_opt_in']='';$ok(P::resolve('qwen',$old,[])['fingerprint']===$oldHash,'empty opt-in does not change legacy hash');$old['providers']['openai']=$slot;$ok(P::resolve('qwen',$old,[])['fingerprint']===$oldHash,'new slot never rebinds old slot');$ok($p['fingerprint']!==$oldHash,'new HTTP identity differs');
|
||||
$s['providers']['openai']['preview_verified_fingerprint']=$p['fingerprint'];$ok(P::previewVerified('openai',$s,[]),'explicit pin still needs matching preview fingerprint');$s['providers']['openai']['preview_verified_fingerprint']='';$ok(!P::previewVerified('openai',$s,[]),'no readiness invented');$ok(!P::secureEndpoint('http://text.invalid/v1',false),'global TLS primitive unchanged');
|
||||
$dir=sys_get_temp_dir().'/fa-http-extraction-'.bin2hex(random_bytes(6));mkdir($dir,0700);$sock=stream_socket_server('tcp://127.0.0.1:0',$eno,$err);$port=(int)substr(strrchr(stream_socket_get_name($sock,false),':'),1);fclose($sock);
|
||||
file_put_contents($dir.'/router.php','<?php file_put_contents(getenv("LOG"),$_SERVER["REQUEST_URI"]."\n",FILE_APPEND); if(isset($_SERVER["HTTP_X_NEVER"]))exit; $v=json_decode(file_get_contents("php://input"),true); if(($v["redirect"]??false)){header("Location: /stolen/v1/chat/completions",true,302);echo "redirect";return;} header("Content-Type: application/json");echo "{\"ok\":true}";');
|
||||
$proc=proc_open([PHP_BINARY,'-n','-S','127.0.0.1:'.$port,$dir.'/router.php'],[0=>['pipe','r'],1=>['file',$dir.'/stdout','a'],2=>['file',$dir.'/stderr','a']],$pipes,null,array_merge(getenv(),['LOG'=>$dir.'/requests']));fclose($pipes[0]);
|
||||
try{
|
||||
for($i=0;$i<100;$i++){$sock=@stream_socket_client('tcp://127.0.0.1:'.$port,$eno,$err,.1);if($sock){fclose($sock);break;}usleep(20000);}
|
||||
$url='http://127.0.0.1:'.$port.'/v1/chat/completions';$spec=['url'=>$url,'stage'=>'extraction','timeout'=>3,'api_key'=>'synthetic-key','json'=>['model'=>'synthetic'],'http_extraction_url'=>$url,'extraction_protocol'=>'openai'];
|
||||
$reply=T::request($spec,fn()=>true,1024,false);$ok($reply['http_code']===200&&$reply['errno']===0&&$reply['body']==='{"ok":true}','real standalone stream child exact HTTP request without loopback bypass');
|
||||
foreach(['missing','other-url','asr','dify','multipart']as$kind){$v=$spec;if($kind==='missing')unset($v['http_extraction_url']);if($kind==='other-url')$v['http_extraction_url']=$url.'/other';if($kind==='asr')$v['stage']='asr';if($kind==='dify')$v['extraction_protocol']='dify_chat';if($kind==='multipart')$v['multipart']=[];$before=file_get_contents($dir.'/requests');$reply=T::request($v,fn()=>true,1024,false);$ok($reply['errno']===43&&file_get_contents($dir.'/requests')===$before,'child rejects ' . $kind);}
|
||||
$spec['json']['redirect']=true;$reply=T::request($spec,fn()=>true,1024,false);$ok($reply['http_code']===302&&!str_contains(file_get_contents($dir.'/requests'),'/stolen/'),'redirect never followed');
|
||||
echo 'FOLLOWUP_AUDIO_HTTP_EXTRACTION assertions='.$n.' PASS exact_pin=1 stage_isolation=1 legacy_fingerprint=1 child_transport=1 no_redirect=1'.PHP_EOL;
|
||||
}finally{proc_terminate($proc,9);proc_close($proc);foreach(glob($dir.'/*')as$f)unlink($f);rmdir($dir);}
|
||||
Reference in New Issue
Block a user