'abc']) !== bg_auth_session_fingerprint(['password_hash'=>'def']), 'Credential change invalidates session fingerprint'); bg_test(!bg_validate_user_session(), 'Absent session is rejected before querying the database'); $_SERVER['REMOTE_ADDR'] = '198.51.100.197'; bg_test(bg_login_rate_limit(), 'New login client has available attempts'); for ($attempt = 0; $attempt < 10; ++$attempt) bg_login_rate_limit(false); bg_test(!bg_login_rate_limit(), 'Ten failed logins trigger 15-minute lockout'); bg_login_rate_limit(true); bg_test(bg_login_rate_limit(), 'Successful login clears failure counter'); unlink(bg_data_file('login-failures-' . hash('sha256', '198.51.100.197') . '.json')); bg_test(strpos(realpath(dirname(__DIR__) . '/webui'), bg_data_dir() . '/') !== 0, 'Private data directory is outside the web root'); $dir = sys_get_temp_dir() . '/bastionguard-config-test-' . bin2hex(random_bytes(6)); if (!mkdir($dir, 0700)) throw new RuntimeException('mkdir failed'); $path = $dir . '/config.json'; $r = bg_config_direct_write(['path'=>$path, 'type'=>'json','scope'=>'user'], '{"a":1}', false); bg_test($r['code'] === 0 && file_get_contents($path) === '{"a":1}', 'Atomic JSON config save'); $r = bg_config_direct_write(['path'=>$path, 'type'=>'json','scope'=>'user'], '{invalid}', false); bg_test($r['code'] === 2 && file_get_contents($path) === '{"a":1}', 'Invalid JSON cannot overwrite valid configuration'); bg_test(!glob($dir . '/.bg-config-*'), 'No leftover temporary config file'); unlink($path); rmdir($dir); echo "webui regressions: OK\n";