An unauthenticated attacker can POST to the public /graphql endpoint of nl-portal-backend-libraries and invoke two form-definition resolvers that forward a caller-supplied URL directly to the privileged Objecten-API HTTP client, which automatically attaches the configured bearer token to every outbound request. While the SSRF is constrained to the configured Objecten-API host — extensive URL-parser differential testing found no bypass — practical token capture is possible against any deployment where the attacker controls a listener at a different port or path on that same host (multi-tenant environments, shared infrastructure). With 873 downstream dependents, an OpenSSF Scorecard of 5.7/10, and 11 prior CVEs in the package family, upgrade nl.nl-portal:* to 3.0.4.RELEASE and nl-portal-frontend-libraries to v3.0.3 immediately; the fix removes the vulnerable resolvers entirely and is a breaking change requiring coordinated backend and frontend upgrades.
What is the risk?
Medium. Exploitation requires no authentication and trivial tooling — a single GraphQL POST — but practical impact is bounded by the same-host SSRF constraint. No public exploit code exists, CISA has not listed this in KEV, and no EPSS data is available at time of analysis. The elevated concern is structural: the OpenSSF score of 5.7 and 11 historical CVEs in the same package indicate persistent security hygiene gaps, and the affected system processes Dutch government citizen data (Objecten-API objects include BSN-linked task records and zaken). Deployments in shared-cloud or co-hosted environments face elevated risk where the configured Objecten-API host shares infrastructure with attacker-accessible services on alternate ports.
How does the attack unfold?
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| Ray | maven | >= 1.1.0, < 3.0.4 | 3.0.4 |
Do you use Ray? You're affected.
How severe is it?
What is the attack surface?
What should I do?
6 steps-
Upgrade nl.nl-portal:* (all modules) to 3.0.4.RELEASE or later — the fix removes getFormDefinitionByObjectenApiUrl and getFormDefinitionById entirely and replaces URL-based lookup with UUID-based retrieval.
-
Simultaneously upgrade nl-portal-frontend-libraries to v3.0.3+ — mandatory, not optional, as removed GraphQL queries are a breaking API contract change.
-
If immediate patching is blocked, apply a WAF/reverse-proxy rule blocking POST /graphql requests containing 'getFormDefinitionByObjectenApiUrl' or 'getFormDefinitionById' as interim control.
-
Rotate the Objecten-API token in all environments regardless of confirmed exploitation; treat it as potentially compromised.
-
Audit /graphql access logs for POST requests with resolver names and non-standard URL argument values predating the patch.
-
For the ZakenApiService path traversal defense gap, verify that getZaakDetails remains gated by CommonGroundAuthentication after the upgrade.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is CVE-2026-55414?
An unauthenticated attacker can POST to the public /graphql endpoint of nl-portal-backend-libraries and invoke two form-definition resolvers that forward a caller-supplied URL directly to the privileged Objecten-API HTTP client, which automatically attaches the configured bearer token to every outbound request. While the SSRF is constrained to the configured Objecten-API host — extensive URL-parser differential testing found no bypass — practical token capture is possible against any deployment where the attacker controls a listener at a different port or path on that same host (multi-tenant environments, shared infrastructure). With 873 downstream dependents, an OpenSSF Scorecard of 5.7/10, and 11 prior CVEs in the package family, upgrade nl.nl-portal:* to 3.0.4.RELEASE and nl-portal-frontend-libraries to v3.0.3 immediately; the fix removes the vulnerable resolvers entirely and is a breaking change requiring coordinated backend and frontend upgrades.
Is CVE-2026-55414 actively exploited?
No confirmed active exploitation of CVE-2026-55414 has been reported, but organizations should still patch proactively.
How to fix CVE-2026-55414?
1. Upgrade nl.nl-portal:* (all modules) to 3.0.4.RELEASE or later — the fix removes getFormDefinitionByObjectenApiUrl and getFormDefinitionById entirely and replaces URL-based lookup with UUID-based retrieval. 2. Simultaneously upgrade nl-portal-frontend-libraries to v3.0.3+ — mandatory, not optional, as removed GraphQL queries are a breaking API contract change. 3. If immediate patching is blocked, apply a WAF/reverse-proxy rule blocking POST /graphql requests containing 'getFormDefinitionByObjectenApiUrl' or 'getFormDefinitionById' as interim control. 4. Rotate the Objecten-API token in all environments regardless of confirmed exploitation; treat it as potentially compromised. 5. Audit /graphql access logs for POST requests with resolver names and non-standard URL argument values predating the patch. 6. For the ZakenApiService path traversal defense gap, verify that getZaakDetails remains gated by CommonGroundAuthentication after the upgrade.
What systems are affected by CVE-2026-55414?
This vulnerability affects the following AI/ML architecture patterns: government citizen portals, GraphQL API backends with unauthenticated public resolvers, Common Ground API integrations, multi-service internal API meshes with shared-token authentication.
What is the CVSS score for CVE-2026-55414?
CVE-2026-55414 has a CVSS v3.1 base score of 5.3 (MEDIUM).
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0049 Exploit Public-Facing Application AML.T0055 Unsecured Credentials AML.T0106 Exploitation for Credential Access Compliance Controls Affected
What are the technical details?
Original Advisory
## Summary The public GraphQL resolvers `getFormDefinitionByObjectenApiUrl(url)` and the deprecated `getFormDefinitionById(id)` fetch a caller-supplied URL using the **privileged Objecten-API token**. Because the `/graphql` endpoint is `permitAll()` and these resolvers do not declare a `CommonGroundAuthentication` parameter, an **unauthenticated** caller can make the backend issue an outbound request carrying `Authorization: Token <objecten-api-token>` to a **caller-influenced URL on the configured Objecten-API host**. This is a constrained (same-host) server-side request forgery combined with missing authorization. Reported responsibly and confirmed in a local lab build against the project's own WebFlux security stack. No production system was accessed. ## Affected - `nl.nl-portal:form` (the public resolver / entry point) together with `nl.nl-portal:objectenapi` (where the host guard lives). - First shipped in **1.1.0.RELEASE** (2023-10-31); the vulnerable code was introduced on 2023-08-12 (commit `b2f87ca`) and is present in every release since (1.1.x, 1.2.5, 1.3.0, the 3.0.x line, and 3.1.0 / `next-minor`, HEAD `45abcd2`). Fixed in 3.0.4.RELEASE (see Fix below). ## Data flow (confirmed in source) 1. `form/.../graphql/FormDefinitionQuery.kt` — `@QueryMapping getFormDefinitionByObjectenApiUrl(@Argument url)`, **no** `CommonGroundAuthentication` parameter (same for `getFormDefinitionById`). 2. → `form/.../service/ObjectsApiFormDefinitionService.kt` — passes the URL through unvalidated. 3. → `zgw/objectenapi/.../service/ObjectenApiService.kt` `getObjectByUrl(url)` — the only guard is host equality (`URI.create(url).host == objectsApiClientConfig.url.host`); **no scheme/port/path check**. 4. → `zgw/objectenapi/.../client/ObjectsApiClient.kt` `getObjectByUrl(url)` via `webClientWithoutBaseUrl()`, which attaches the default header `Authorization: Token <token>` to the fully caller-supplied URL. **Reachability:** `/graphql` is `permitAll()` (`core/.../security/OauthSecurityAutoConfiguration.kt`). Authentication is only enforced on resolvers that declare a `CommonGroundAuthentication` parameter; these do not, and there is no `@PreAuthorize`/instrumentation safety net. The project's own `GraphQLEndpointAuthorizationIT` lists `getFormDefinitionByObjectenApiUrl` as an intentionally public operation — so the unauthenticated reachability is by design; the defect is that an intentionally-public resolver forwards a privileged token to a caller-influenced URL. **Secondary (defense-in-depth):** `zgw/zaken-api/.../service/ZakenApiService.kt` `getZaakDetails` calls `objectsApiClient.getObjectByUrl` **directly**, bypassing the service-level host guard. It is currently only reachable via the authenticated `ZaakQuery.zaakdetails` field resolver with server-derived URLs, so it is not an unauthenticated vector today — but it shows why the guard belongs in the client. ## Proof of concept (lab, against the real WebFlux stack) - An unauthenticated `POST /graphql` calling `getFormDefinitionByObjectenApiUrl(url: ...)` executes without authentication. - With the configured Objecten-API host pointed at a mock server, an outbound request to a **caller-chosen port/path on that host** carried `Authorization: Token <configured-token>` — confirming the token is attached to caller-influenced URLs. ## Impact and severity — important limitations Assessed as **Medium** because two code-level facts constrain practical impact: 1. **No cross-host SSRF / token exfiltration in standard deployments.** The token only travels to the *configured* Objecten-API host. Exfiltration requires an attacker-controlled listener at that host (a different port/path routing elsewhere) — generally not the case in managed deployments. A range of URL-parser bypass payloads was tested (userinfo `@`, `%2f`/`%00`/`%09`, backslash, `#`/`?`, double-host, trailing-dot, IDN/Unicode full-stop, fraction-slash, IPv6); **no parser differential** was found between the `java.net.URI`-based guard and the Spring/Netty URI builder used by WebClient — every payload either kept the request on the configured host or was rejected (fail-closed). The lab token-leak PoC works only because the configured host there is `localhost`; this does not generalize to production. 2. **Arbitrary PII object read is blocked by typed deserialization.** The response is deserialized into `ObjectsApiObject<ObjectsApiFormIoFormDefinition>`, whose envelope fields and `data.formDefinition` are all non-nullable Kotlin properties (Jackson `KotlinModule` registered). An object without a top-level `data.formDefinition` (e.g. taken/berichten/zaakdetails) fails to deserialize (`DecodingException`) and returns no data. The resolver can therefore only return objects shaped like a form definition — and form definitions are intentionally public (loaded pre-login). **Escalation conditions** that would raise severity toward High: - the Objecten-API host shares infrastructure with an attacker-controllable endpoint (other port/path), enabling capture of the privileged token; or - a URL-parser differential is later found that escapes the host guard. ## Remediation - Move the host validation out of `ObjectenApiService.getObjectByUrl` and into `ObjectsApiClient.getObjectByUrl` so the direct caller `ZakenApiService.getZaakDetails` is covered too, and tighten it from host-only to **scheme + host + port + path-prefix**. Preferably, do not accept a full URL at all: validate/extract the object UUID and rebuild the URL from the fixed configured base (reuse the existing `ObjectsApiClient.getObjectById` pattern, `/api/v2/objects/{uuid}`). - Separately decide whether `getFormDefinitionByObjectenApiUrl` / `getFormDefinitionById` should remain unauthenticated. They are currently intentionally public (forms load before login); for a stricter posture, add a `CommonGroundAuthentication` parameter as in the other resolvers — noting this breaks pre-login form loading. ## Credit Reported responsibly by **Ray Sabee** (https://whitehatsecurity.nl), independent security researcher — GitHub [@raysabee](https://github.com/raysabee). ## Fix Fixed in **3.0.4.RELEASE** (commit `39ad80f`, PR #700, "rework form module"): - The unauthenticated resolvers `getFormDefinitionByObjectenApiUrl` and the deprecated `getFormDefinitionById` were **removed** from both `FormDefinitionQuery` and the GraphQL schema. - `getFormDefinitionByName` now requires a `CommonGroundAuthentication` parameter (no longer public). - The URL-based service method `findObjectsApiFormDefinitionByUrl(url)` was removed and replaced by `getObjectsApiFormDefinitionById(objectId: UUID)`, which fetches by UUID via the fixed `/api/v2/objects/{uuid}` path (no caller-supplied URL, so no SSRF) and validates the object type against the configured form-definition object type. - Form definitions are now retrieved through the new authenticated query `getFormDefinitionByTaskId(taskId)` in `nl.nl-portal:taak`, which authorizes the caller against the task (`CommonGroundAuthentication`, BSN/KVK match, else `401`) and derives the form-definition UUID from the task's own server-side data, not from caller input. - No resolver feeds caller-controlled input into `ObjectenApiService.getObjectByUrl` anymore. The `objectenapi` module itself was not changed; the fix lives entirely in `nl.nl-portal:form` and the new `nl.nl-portal:taak` query. ## Upgrade instructions - **Backend:** upgrade `nl.nl-portal:*` to **3.0.4** (or later). - **Frontend:** upgrade `nl-portal-frontend-libraries` to **v3.0.3** (or later). This is required: the removed GraphQL queries (`getFormDefinitionByObjectenApiUrl`, `getFormDefinitionById`) and the now-authenticated `getFormDefinitionByName` are a breaking change. Frontend v3.0.3 uses the new authenticated `getFormDefinitionByTaskId` / `getFormDefinitionByName` queries.
Exploitation Scenario
An attacker discovers a public-facing nl-portal citizen portal and confirms /graphql accepts unauthenticated POSTs. They craft a request calling getFormDefinitionByObjectenApiUrl with a URL pointing to a non-standard path or port on the configured Objecten-API host — for example, http://objecten-api.internal:9090/metrics. The backend validates only host equality (passes), then issues an outbound GET carrying Authorization: Token <configured-token> to the attacker-specified path. In a shared-tenant or misconfigured cloud environment where the attacker controls a service at an alternate port on the Objecten-API host, the token arrives at the attacker's listener. Armed with the privileged token, the attacker directly queries the Objecten-API for citizen task records, case files, and PII-bearing objects — entirely outside the portal's typed deserialization boundary that limited the GraphQL response.
Weaknesses (CWE)
CWE-862 — Missing Authorization: The product does not perform an authorization check when an actor attempts to access a resource or perform an action.
- [Architecture and Design] Divide the product into anonymous, normal, privileged, and administrative areas. Reduce the attack surface by carefully mapping roles with data and functionality. Use role-based access control (RBAC) [REF-229] to enforce the roles at the appropriate boundaries. Note that this approach may not protect against horizontal authorization, i.e., it will not protect a user from attacking others with the same role.
- [Architecture and Design] Ensure that access control checks are performed related to the business logic. These checks may be different than the access control checks that are applied to more generic resources such as files, connections, processes, memory, and database records. For example, a database may restrict access for medical records to a specific database user, but each record might only be intended to be accessible to the patient and the patient's doctor [REF-7].
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N References
Timeline
Related Vulnerabilities
CVE-2023-6019 9.8 Ray: unauthenticated RCE via dashboard command injection
Same package: ray CVE-2023-48022 9.8 Ray: unauthenticated RCE via job submission API
Same package: ray CVE-2023-6021 9.3 Ray: LFI allows unauthenticated file read
Same package: ray CVE-2023-6020 9.3 Ray: unauthenticated LFI exposes entire filesystem
Same package: ray CVE-2026-32981 7.5 Ray Dashboard: unauthenticated path traversal file read
Same package: ray