{"record":{"id":"de97523aa06dd548","repo":"passbolt/passbolt_api","slug":"invalid-filter-inner-message","errorCode":null,"errorMessage":"Invalid filter. {inner message}","messagePattern":"Invalid filter\\. (.+?)","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Controller/Component/QueryStringComponent.php","lineNumber":244,"sourceCode":"\n    /**\n     * Validate query items\n     *\n     * @param array $query items to validate\n     * @param array $allowedQueryItems whitelisted items\n     * @param array<callable> $filterValidators Filters validator callable\n     * @return bool true if validate\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if a validation error occurs\n     */\n    public static function validateQueryItems(array $query, array $allowedQueryItems, array $filterValidators): bool\n    {\n        foreach ($query as $key => $parameters) {\n            switch ($key) {\n                case 'filter':\n                    try {\n                        self::validateFilters($parameters, $filterValidators);\n                    } catch (CakeException $e) {\n                        throw new BadRequestException(__('Invalid filter.') . ' ' . $e->getMessage());\n                    }\n                    break;\n                case 'order':\n                    try {\n                        self::validateOrders($parameters, $allowedQueryItems);\n                    } catch (CakeException $e) {\n                        throw new BadRequestException(__('Invalid order.') . ' ' . $e->getMessage());\n                    }\n                    break;\n                case 'contain':\n                    try {\n                        self::validateContain($parameters);\n                    } catch (CakeException $e) {\n                        throw new BadRequestException(__('Invalid contain.') . ' ' . $e->getMessage());\n                    }\n                    break;\n            }\n        }","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/src/Controller/Component/QueryStringComponent.php#L226-L262","documentation":"Thrown by QueryStringComponent::validateQueryItems when validating the 'filter' query string key. The component delegates to validateFilters(); if any filter name or value is rejected, the resulting CakeException is caught and re-thrown as a Cake\\Http\\Exception\\BadRequestException prefixed with 'Invalid filter.' plus the inner reason. It signals the client sent an HTTP query parameter that does not conform to the passbolt filter whitelist or value format rules.","triggerScenarios":"GET requests with ?filter[...] where a filter name is unknown (or lacks a registered custom validator), a date filter (from/created-before/created-after) is not parseable, a boolean filter (is-active, is-deleted, etc.) is not 'true'/'false'/0/1, has-users/has-groups receive non-UUID values, or metadata_key_type is not 'user_key'/'shared_key'.","commonSituations":"Client SDKs constructing filters from user input; typos in filter names (e.g. filter[is_deleted] instead of is-deleted); sending snake_case instead of kebab-case; passing unvalidated UUIDs or raw dates from a UI; plugins that forgot to register a filter validator via getFilterValidators.","solutions":["Read the inner message after 'Invalid filter.' — it names the exact filter or value problem.","Correct the filter name to one of the supported keys: search, from, created-before, created-after, has-access, has-id, has-managers, has-users, has-groups, has-parent, is-shared-with-group, modified-after, is-active, is-admin, is-favorite, is-owned-by-me, is-shared-with-me, is-shared, is-success, is-deleted, expired, deleted, has-tag, frequency, metadata_key_type.","Fix the filter value format: UUIDs for user/resource/group filters, ISO datetimes for date filters, true/false for boolean filters, user_key|shared_key for metadata_key_type.","If it is a custom filter, register a validator callable in the controller's QueryStringComponent $filterValidators option.","Remove the filter entirely if it is not needed — unknown filters are rejected, not ignored."],"exampleFix":"// before\nGET /resources.json?filter[is_deleted]=yes&filter[foo]=1\n// after\nGET /resources.json?filter[is-deleted]=true","handlingStrategy":"validation","validationCode":"$allowedFilters = ['search','from','created-before','created-after','has-access','has-id','has-managers','has-users','has-groups','has-parent','is-shared-with-group','modified-after','is-active','is-admin','is-favorite','is-owned-by-me','is-shared-with-me','is-shared','is-success','is-deleted','expired','deleted','has-tag','frequency','metadata_key_type'];\nforeach ((array)($query['filter'] ?? []) as $name => $value) {\n    if (!in_array($name, $allowedFilters, true)) {\n        throw new InvalidArgumentException(\"Unsupported filter: $name\");\n    }\n}\n// additionally: date filters must be strtotime()-parseable, boolean filters in {true,false,0,1},\n// has-users/has-groups/has-access/has-id values must be UUIDs, metadata_key_type in {user_key,shared_key}","typeGuard":"function isUuidList(mixed $value): bool {\n    $list = is_array($value) ? $value : [$value];\n    return $list !== [] && count(array_filter($list, fn($v) => is_string($v) && preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $v))) === count($list);\n}","tryCatchPattern":"try {\n    $result = $api->get('/resources.json', ['query' => ['filter' => $filters]]);\n} catch (BadRequestException $e) {\n    if (str_starts_with($e->getMessage(), 'Invalid filter.')) {\n        $logger->warning('Rejected filters', ['filters' => $filters, 'reason' => $e->getMessage()]);\n        $result = $api->get('/resources.json'); // retry without filters\n    } else {\n        throw $e;\n    }\n}","preventionTips":["Build query strings from a whitelist map of filter names, never from raw user input keys","Use kebab-case filter names exactly as documented (is-deleted, not is_deleted)","Validate UUIDs and dates client-side before sending them as filter values","Pin your client to the server's API version so filter sets match","Catch and log 'Invalid filter.' responses in dev to catch typos early"],"tags":["http","bad-request","query-string","validation","filters"],"backgroundTag":"invalid-query-parameter","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"}