{"record":{"id":"4549dac162ab5326","repo":"passbolt/passbolt_api","slug":"failed-extended-user-control-invalid-user-agent","errorCode":null,"errorMessage":"Failed extended user control. Invalid user agent.","messagePattern":"Failed extended user control\\. Invalid user agent\\.","errorType":"exception","errorClass":"InternalErrorException","httpStatus":500,"severity":"error","filePath":"src/Utility/ExtendedUserAccessControl.php","lineNumber":67,"sourceCode":"     * @param string|null $userIp the user ip\n     * @param string|null $userAgent the user agent\n     */\n    public function __construct(\n        string $roleName,\n        ?string $userId = null,\n        ?string $username = null,\n        ?string $userIp = null,\n        ?string $userAgent = null\n    ) {\n        parent::__construct($roleName, $userId, $username);\n\n        if (!Validation::ip($userIp)) {\n            throw new InternalErrorException('Failed extended user control. Invalid IP Address.');\n        }\n        $this->userIp = $userIp;\n\n        if (!UserAgentValidation::isValid($userAgent)) {\n            throw new InternalErrorException('Failed extended user control. Invalid user agent.');\n        }\n        $this->userAgent = $userAgent;\n    }\n\n    /**\n     * Get the user ip address\n     *\n     * @return string\n     */\n    public function getUserIp(): string\n    {\n        return $this->userIp;\n    }\n\n    /**\n     * Get the user agent\n     *\n     * @return string","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/src/Utility/ExtendedUserAccessControl.php#L49-L85","documentation":"The ExtendedUserAccessControl constructor validates the optional $userAgent argument with App\\Utility\\Validation\\UserAgentValidation::isValid() and throws an InternalErrorException when the string is empty or fails the user-agent format rules. User agents are stored/logged for security events, so blank or junk values are rejected at construction time. Like the IP check, this happens before the object exists.","triggerScenarios":"Constructing ExtendedUserAccessControl with $userAgent = null or '' because the HTTP request carried no User-Agent header (bots, curl without -A, CLI jobs), or a user agent string exceeding the expected length or containing stripped/encoded characters that UserAgentValidation::isValid() rejects.","commonSituations":"API clients or scripts calling passbolt endpoints with curl/requests without setting a User-Agent header; health checks and load balancer probes omitting the header; background jobs constructing the UAC object outside a web request; extremely long or binary-garbage UA strings from malicious clients.","solutions":["Pass a fallback user agent when the header is missing: $request->getHeaderLine('User-Agent') ?: 'unknown' before constructing the object.","In CLI/background jobs, pass a descriptive literal such as 'passbolt-cli' or 'passbolt-recovery' as $userAgent.","Check the UserAgentValidation rules and ensure the string does not exceed the configured max length or contain control characters; sanitize the header value first.","If a legitimate client is rejected, inspect what UserAgentValidation::isValid() requires (non-empty, printable, length-bounded) and fix the client to send a conformant User-Agent header."],"exampleFix":"// before\n$uac = new ExtendedUserAccessControl(\n    Role::USER,\n    $user->id,\n    $user->username,\n    $ip,\n    $request->getHeaderLine('User-Agent') // may be ''\n);\n// after\n$userAgent = $request->getHeaderLine('User-Agent');\nif (!UserAgentValidation::isValid($userAgent)) {\n    $userAgent = 'unknown';\n}\n$uac = new ExtendedUserAccessControl(\n    Role::USER,\n    $user->id,\n    $user->username,\n    $ip,\n    $userAgent\n);","handlingStrategy":"validation","validationCode":"use App\\Utility\\Validation\\UserAgentValidation;\n$ua = $request->getHeaderLine('User-Agent');\nif (!UserAgentValidation::isValid($ua)) {\n    $ua = 'unknown';\n}\n","typeGuard":"/** @param mixed $ua @phpstan-assert non-empty-string $ua */\nfunction isValidUserAgent(mixed $ua): bool\n{\n    return is_string($ua) && UserAgentValidation::isValid($ua);\n}\n","tryCatchPattern":"try {\n    $uac = new ExtendedUserAccessControl($role, $userId, $username, $ip, $ua);\n} catch (\\Cake\\Http\\Exception\\InternalErrorException $e) {\n    // log and retry construction with 'unknown' user agent\n}\n","preventionTips":["Always pass a non-empty fallback user agent when the header is absent.","In CLI jobs, pass a literal identifier like 'passbolt-cli' instead of null.","Strip control characters and cap the UA length before constructing the object.","Keep curl/API clients configured with an explicit User-Agent header."],"tags":["php","validation","user-agent","constructor"],"backgroundTag":"invalid-argument-value","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}