Files
SGF/scripts/requisition-composition-security.php
2026-11-04 09:37:20 +02:00

29 lines
3.9 KiB
PHP

<?php
use App\Modules\Purchasing\InternalRequisitionQa;
use Illuminate\Support\Facades\DB;
$s=json_decode(file_get_contents(getenv('IR_COMPOSITION_STATE')),true,64,JSON_THROW_ON_ERROR);
$name=$argv[1]??'security.json';if(!preg_match('/^security(?:-[a-z0-9]+)?\.json$/D',$name)||file_exists($s['root'].'/'.$name))throw new LogicException('Completed security proof preserved');
require $s['app'].'/vendor/autoload.php';$app=require $s['app'].'/bootstrap/app.php';$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
$checks=[];$check=function($v,$name)use(&$checks){if(!$v)throw new RuntimeException($name);$checks[]=$name;};
$refuse=function(callable $fn,$name)use($check){try{$fn();}catch(LogicException $e){$check(true,$name);return;}throw new RuntimeException('Expected refusal: '.$name);};
$sql=0;DB::listen(function()use(&$sql){$sql++;});
$check(InternalRequisitionQa::mount()===null,'disabled mount returns without database connection');
$check(count(array_filter(iterator_to_array(Illuminate\Support\Facades\Route::getRoutes()),fn($r)=>str_starts_with($r->uri(),'api/internal-requisitions')))===0,'disabled provider registers no requisition routes');
config(['internal_requisitions.qa_enabled'=>true]);$app->detectEnvironment(fn()=>'production');$refuse(fn()=>InternalRequisitionQa::mount(),'production environment refused before connection');$app->detectEnvironment(fn()=>'budget-testing');
config(['database.connections.pgsql.host'=>'192.0.2.1']);DB::purge();$refuse(fn()=>InternalRequisitionQa::mount(),'non-loopback target refused before connection');
$check($sql===0,'disabled and invalid configurations executed zero SQL');
$original=config('database.connections.pgsql');
foreach(['port'=>'55432','username'=>'another_role','url'=>'pgsql://invalid','search_path'=>'another_schema']as$field=>$value){config(['database.connections.pgsql'=>array_replace($original,['host'=>'127.0.0.1',$field=>$value])]);DB::purge();$refuse(fn()=>InternalRequisitionQa::mount(),'invalid '.$field.' refused before connection');}
$check($sql===0,'all invalid connection boundaries executed zero SQL');
$pin=$s['env']['SGF_REQUISITIONS_QA_MANIFEST'];$check(InternalRequisitionQa::verifiedBundle($s['bundle'],$pin)===realpath($s['bundle']),'exact bundle verified');
$refuse(fn()=>InternalRequisitionQa::verifiedBundle($s['bundle'],str_repeat('0',64)),'wrong manifest pin refused');
$refuse(fn()=>InternalRequisitionQa::verifiedBundle($s['root'].'/absent',$pin),'missing bundle refused');
$copy=$s['root'].'/tamper-'.$name;mkdir($copy);$manifest=json_decode(file_get_contents($s['bundle'].'/manifest.json'),true,32,JSON_THROW_ON_ERROR);
foreach($manifest['files']as$file=>$hash){if(!is_dir(dirname($copy.'/'.$file)))mkdir(dirname($copy.'/'.$file),0700,true);copy($s['bundle'].'/'.$file,$copy.'/'.$file);}copy($s['bundle'].'/manifest.json',$copy.'/manifest.json');
$target=$copy.'/packages/purchasing/src/InternalRequisitions.php';file_put_contents($target,"\n// altered",FILE_APPEND);$refuse(fn()=>InternalRequisitionQa::verifiedBundle($copy,$pin),'altered core refused before execution');copy($s['bundle'].'/packages/purchasing/src/InternalRequisitions.php',$target);
unset($manifest['files']['integrations/sgf/internal-requisitions/routes.php']);file_put_contents($copy.'/manifest.json',json_encode($manifest));$refuse(fn()=>InternalRequisitionQa::verifiedBundle($copy,hash_file('sha256',$copy.'/manifest.json')),'incomplete route contract refused');
$manifest['files']['../outside.php']=str_repeat('0',64);file_put_contents($copy.'/manifest.json',json_encode($manifest));$refuse(fn()=>InternalRequisitionQa::verifiedBundle($copy,hash_file('sha256',$copy.'/manifest.json')),'path traversal contract refused');
file_put_contents($s['root'].'/'.$name,json_encode(['passed'=>true,'checks'=>$checks,'sql_queries'=>$sql,'application_commands'=>0],JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));echo count($checks)." composition guard checks passed.\n";