{"record":{"id":"c1fd53b5c9610a47","repo":"passbolt/passbolt_api","slug":"failed-extended-user-control-invalid-ip-address","errorCode":null,"errorMessage":"Failed extended user control. Invalid IP Address.","messagePattern":"Failed extended user control\\. Invalid IP Address\\.","errorType":"exception","errorClass":"InternalErrorException","httpStatus":500,"severity":"error","filePath":"src/Utility/ExtendedUserAccessControl.php","lineNumber":62,"sourceCode":"     * UserAccessControl constructor.\n     *\n     * @param string $roleName The role name\n     * @param string|null $userId the user uuid\n     * @param string|null $username the user email\n     * @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    }","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/src/Utility/ExtendedUserAccessControl.php#L44-L80","documentation":"The ExtendedUserAccessControl constructor validates the optional $userIp argument with CakePHP's Validation::ip() and throws an InternalErrorException when it is not a valid IPv4/IPv6 address. This immutable value object wraps user identity plus request metadata, so an invalid IP means the caller passed missing, empty, or malformed input (Validation::ip() also rejects null). The exception is thrown before the object is ever constructed, so no partially-initialized instance leaks.","triggerScenarios":"Calling new ExtendedUserAccessControl($role, $userId, $username, $userIp, $userAgent) with $userIp = null, an empty string, a hostname, an IPv6 with bad syntax, or an IP read from a spoofed/absent header like Client-Ip or X-Forwarded-For that contains garbage or a comma-separated list.","commonSituations":"Running behind a reverse proxy or load balancer where REMOTE_ADDR is empty or the forwarded header is missing/malformed; CLI commands (recovery, emails queue) constructing the object without request context so no IP exists; integration tests sending requests without REMOTE_ADDR; proxies appending multiple IPs ('1.2.3.4, 10.0.0.1') which fails Validation::ip().","solutions":["Ensure a valid IP string is passed: read it from $request->clientIp() (or getServerParams()['REMOTE_ADDR']) and default to a safe value like '127.0.0.1' when the request has no IP (CLI context).","If behind a proxy, configure passbolt to trust the proxy and pass the first entry of X-Forwarded-For, trimmed and validated before constructing the object.","Guard before construction: if ($userIp === null || !Validation::ip($userIp)) { $userIp = '127.0.0.1'; } for CLI or header-less contexts.","Catch InternalErrorException around construction in controllers/services and convert it to a 400-level response instead of a 500."],"exampleFix":"// before\n$uac = new ExtendedUserAccessControl(\n    Role::USER,\n    $user->id,\n    $user->username,\n    $_SERVER['REMOTE_ADDR'] ?? null,\n    $_SERVER['HTTP_USER_AGENT'] ?? null\n);\n// after\n$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';\n$ip = trim(explode(',', $ip)[0]);\nif (!Validation::ip($ip)) {\n    $ip = $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';\n}\n$uac = new ExtendedUserAccessControl(\n    Role::USER,\n    $user->id,\n    $user->username,\n    $ip,\n    $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'\n);","handlingStrategy":"validation","validationCode":"use Cake\\Validation\\Validation;\n$ip = trim(explode(',', (string)($request->getHeaderLine('X-Forwarded-For') ?: $_SERVER['REMOTE_ADDR'] ?? '')))[0] ?? '';\nif (!Validation::ip($ip)) {\n    $ip = '127.0.0.1'; // safe fallback for CLI/header-less requests\n}\n","typeGuard":"/** @param mixed $ip @phpstan-assert non-empty-string $ip */\nfunction isValidIp(mixed $ip): bool\n{\n    return is_string($ip) && \\Cake\\Validation\\Validation::ip($ip);\n}\n","tryCatchPattern":"try {\n    $uac = new ExtendedUserAccessControl($role, $userId, $username, $ip, $ua);\n} catch (\\Cake\\Http\\Exception\\InternalErrorException $e) {\n    // log $e->getMessage(), fall back to a 4xx client error response\n}\n","preventionTips":["Always source the IP via $request->clientIp() rather than raw headers.","Default to 127.0.0.1 in CLI/background contexts where no request IP exists.","Trim and take the first element of comma-separated X-Forwarded-For values.","Unit-test the constructor with null, empty, and proxy-style IP inputs."],"tags":["php","validation","ip-address","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"}