Files
Investidor-V2/tools/portal-service-followup-bootstrap-mfa-qa.php
T
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

23 lines
1.9 KiB
PHP

<?php
// Guarded QA-only security check. Authenticated actor without recent MFA cannot confirm or replay.
$app = (static fn ($p) => require $p)(__DIR__.'/portal-service-followup-runtime.php');
$db = Illuminate\Support\Facades\DB::connection();
$row = $db->table('portal_security_approvals')->where('kind','bootstrap')->where('requested_by',15)->where('subject_user_id',16)->first();
throw_unless($row && $row->consumed_at, RuntimeException::class);
$installation = app(App\Services\PortalProfileInstallation::class);
$second = App\Models\User::findOrFail(16);
$session = app('session')->driver();$session->start();$session->forget(['portal.mfa_actor','portal.mfa_at']);
$pairBefore = $db->table('portal_superuser_pair')->first();
$auditBefore = $db->table('audit_events')->count();
$denied = false;
try {
$installation->confirmBootstrap($second,$row->id,['hash'=>$installation->bootstrapPreview($second,$row->id)['hash'],
'confirm'=>true,'idempotency_key'=>(string)Illuminate\Support\Str::uuid(),'reason'=>'SIMULAÇÃO QA: uma sessão sem MFA recente nunca confirma nem recupera a operação protegida.']);
} catch (Symfony\Component\HttpKernel\Exception\HttpException $error) {
$denied = $error->getStatusCode() === 403;
}
throw_unless($denied && $pairBefore == $db->table('portal_superuser_pair')->first() && $auditBefore === $db->table('audit_events')->count(), RuntimeException::class,'Missing MFA must deny before side effects');
$proof=['result'=>'passed','scope'=>'Service security check without recent MFA; browser normal-MFA ceremony is proved separately','status'=>403,'pair_unchanged'=>true,'audit_unchanged'=>true,'active_writes'=>0];
file_put_contents(dirname(__DIR__).'/storage/app/private/investmoz-v2/portal-audit/service-followup-bootstrap-mfa-negative-qa.json',json_encode($proof,JSON_PRETTY_PRINT|JSON_THROW_ON_ERROR));
echo json_encode($proof,JSON_THROW_ON_ERROR).PHP_EOL;