[master] fix: reject null pathOperator in v4 flow HTTP selectors#17264
Merged
Conversation
Contributor
Author
|
Cherry-pick of 611dfe1 has failed: To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally |
6830b9e to
13e9085
Compare
… read The write and validation paths now reject a null pathOperator with a 400 instead of NPE-ing and partially persisting corrupt data, on the management API and the automation/CRD endpoint, for API-level and plan flows. The read path coerces a stored null to the model default STARTS_WITH so already-corrupt records load, delete, and self-heal on next save instead of returning 500. Why: a v4 HTTP flow selector with a null pathOperator caused a 500 NPE in FlowMapper instead of a 400 validation error, and left the API un-readable/un-deletable/un-updatable once persisted.
13e9085 to
b3f5109
Compare
|
carlos-andres-osorio
approved these changes
Jun 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



This is an automatic copy of pull request #17260 done by Mergify.
Issue
APIM-14252
Why
Creating or updating a v4 HTTP Proxy API with a flow whose HTTP selector has a null
pathOperatorreturned a 500 NPE instead of a 400 validation error. Worse, the write path partially committed before crashing: on POST the API was persisted with an empty flow list, and on PUT an existing API had its name/listeners/endpoints overwritten and its flows wiped before the failure. Any record already stored with a nullpathOperatorthen became unreadable, undeletable, and unupdatable because the read mapper also NPE-d.path: nullwas already correctly rejected by a@NotNullconstraint;pathOperator: nullhad no equivalent guard.What changed
FlowValidationDomainService— adds a pre-persist check that rejects any HTTP selector with a nullpathOperator, throwingInvalidFlowException(400) on the management API create/update path for API-level and plan flows.ApiValidationServiceImpl— routes the v4 HTTP API update path throughFlowValidationDomainService(it previously used the deprecatedFlowValidationServiceImpl, which had no null-pathOperatorguard), so aPUTwith a nullpathOperatoris rejected before persistence instead of partially overwriting the existing API.ValidateApiCRDDomainService— applies the same null-pathOperatorrejection on the automation/CRD import path.InvalidFlowException— newmissingPathOperator(flowName)factory carrying the offending flow name.FlowMapper— write side now throwsInvalidDataException(400) when mapping a nullpathOperatorto the repository model, as a defensive backstop; read side coerces a stored null to the model defaultSTARTS_WITHso already-corrupt records load, delete, and self-heal on next save instead of returning 500.Affected endpoints
The fix touches both the write/validation path (now returns 400) and the read path (now self-heals corrupt records). The same code is reached from several endpoints, not just
POST /apis:flowchart LR subgraph WRITE["Write / validation — now 400 (was 500 + partial persist)"] direction TB POSTAPI["POST /management/v2/.../apis"] PUTAPI["PUT /management/v2/.../apis/{apiId}"] POSTPLAN["POST /management/v2/.../apis/{apiId}/plans"] PUTPLAN["PUT /management/v2/.../apis/{apiId}/plans/{planId}"] CRD["PUT /automation/.../apis (CRD import)"] end subgraph READ["Read / export — now coerces null to STARTS_WITH (was 500)"] direction TB GETAPI["GET /management/v2/.../apis & /apis/{apiId}"] EXPORT["GET /management/v2/.../apis/{apiId}/_export/definition"] GETPLANS["GET /management/v2/.../apis/{apiId}/plans"] DELAPI["DELETE /management/v2/.../apis/{apiId}"] GETCRD["GET /automation/.../apis/{hrid}"] end FVDS["FlowValidationDomainService\n.checkHttpSelectorsPathOperator()"] VCRD["ValidateApiCRDDomainService\n.checkApiFlowsPathOperator()"] MAPW["FlowMapper.toRepository()\n(write — throws InvalidDataException)"] MAPR["FlowMapper.toDefinition()\n(read — defaults to STARTS_WITH)"] POSTAPI --> FVDS PUTAPI --> FVDS POSTPLAN --> FVDS PUTPLAN --> FVDS CRD --> VCRD FVDS --> MAPW VCRD --> MAPW GETAPI --> MAPR EXPORT --> MAPR GETPLANS --> MAPR DELAPI --> MAPR GETCRD --> MAPRFlowValidationDomainServicePOST/PUT/apis,POST/PUT/apis/{apiId}/planspathOperatorwith 400ValidateApiCRDDomainServicePUT /automation/.../apis(CRD import)pathOperatorwith 400FlowMapperwrite sideFlowMapperread sideGET/DELETE/apis,_export/definition,GET /apis/{apiId}/plans, automationGETSTARTS_WITH; corrupt records load/delete/self-healUser-facing impact
pathOperatornow returns400with a validation error instead of500, on the management API (create/update for both APIs and plans) and the automation/CRD endpoint. No more partial persistence of corrupt API records.pathOperatorare now readable, listable, exportable and deletable again (the operator defaults toSTARTS_WITHon read) and self-heal on the next save.How to test
The fix affects every endpoint in the diagram above — verify a representative write and read endpoint.
Write path (validation): create a v4 HTTP Proxy API with a flow HTTP selector that has
pathOperator: null:Expect a
400withflows[].selectors[].pathOperator is required. Repeat withPUTagainst an existing API and with a plan create/update (POST/PUT /apis/{apiId}/plans) — same result, and the existing API/plan is left untouched. The automationPUT /automation/.../apisimport returns 400 as well.Read path (self-heal): for any API already persisted with a null
pathOperator(e.g. created before this fix),GET /apis/{apiId},_export/definition, andDELETEnow succeed instead of returning 500; the selector reads back asSTARTS_WITH.Known issues / follow-up
CRD plan-flow null
pathOperatoris validated late, not up-front. The up-front CRD guard (ValidateApiCRDDomainService.checkApiFlowsPathOperator) only inspects API-levelspec.flows. A nullpathOperatorin a plan flow is caught downstream byCreatePlanDomainServiceat persistence time, which has two consequences specific to the CRD import endpoint (PUT /apis/_import/crd):dryRun=false) returns400, but the API document is committed before plan creation runs, so a flow-less orphan API (no plans) is left behind — i.e. rejected, but not "nothing persisted." The orphan is clean (no null is stored), reads/deletes normally, and the next reconcile with a corrected spec completes. This is the pre-existing non-transactional CRD partial-commit; this PR removes the 500/data-corruption, not the atomicity gap.dryRun=true) returns200(false-positive) for a plan-flow null, because dry-run runs only the up-front API-level check and skips the persistence-time plan validation. An API-level null is still correctly rejected at dry-run (400).The plain management
POST/PUT /apisandPOST/PUT /apis/{apiId}/planspaths reject a nullpathOperatorwith400, leave nothing persisted, and leave any existing API/plan untouched (verified). The gap above is specific to the CRD import/dry-run path and is tracked as follow-up.Cherry-pick of 611dfe1 has failed:
To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally