CVE-2026-53469
Description
An authenticated user can delete all customer data in migration-planner by sending an unauthenticated DELETE request to the /api/v1/sources route.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
An authenticated user can delete all customer data in migration-planner by sending an unauthenticated DELETE request to the /api/v1/sources route.
Vulnerability
A flaw exists in the migration-planner application where the DELETE /api/v1/sources route is improperly protected. This endpoint, located in internal/handlers/v1alpha1/source.go, calls sourceSrv.DeleteSources(ctx) without proper authorization checks, filtering, or confirmation. This affects all versions of migration-planner prior to the fix.
Exploitation
An attacker with authenticated access to the migration-planner SaaS platform can exploit this vulnerability by sending a single DELETE request to the /api/v1/sources endpoint. No special privileges beyond standard authentication are required, and no user interaction is necessary.
Impact
Successful exploitation allows any authenticated user to destroy all customer data, including sources, agents, and assessments, by executing an un-scoped DELETE FROM sources command against the shared PostgreSQL database. This results in a critical loss of data availability and integrity across the entire SaaS platform [3].
Mitigation
The vulnerability was addressed by removing the DELETE /api/v1/sources endpoint and its associated SourceStore.DeleteAll method [2]. The fixed version is available as of the merge of pull request #1227 [2]. A suggested short-term fix involved hard-disabling the handler to return a 401 error [3].
AI Insight generated on Jun 10, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
1db4c7857bd8fECOPROJECT-4723 | fix: Remove bulk delete sources endpoint
12 files changed · +25 −353
api/v1alpha1/openapi.yaml+0 −24 modified@@ -69,30 +69,6 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" - delete: - tags: - - source - description: delete a collection of sources - operationId: deleteSources - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - "401": - description: Unauthorized - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "500": - description: Internal error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" /api/v1/sources/{id}: get: tags:
api/v1alpha1/spec.gen.go+7 −7 modified@@ -181,13 +181,13 @@ var swaggerSpec = []string{ "viLdu1etixBRJVRLjFBTya5InqVcr5/EXtw6Jdcc/i6L5M6VZV170KvtXKsLBwEqAfiGXJEPBKjjQH34", "snDg+k97xUme5tRXWuj29Lc9/T317tK2qbxHcI46hqvJppdpEMAz30a2jPeYG1ftobAmFhJ4SEAcWMtv", "NrPY1otwy7KPZ2tpl9tNWVp1Cjs5E6TpEZ8WzNosKPEkxNIOLB1EBuCCBIvCDR8HLiQghDdIP44mLWv8", - "Rp7AYnwa9412i3HrxrFVhY9mNnIaM1dzeZ3B6CXROi4NAuSqzZxOQdLTHr0zTr8+YXqjH/DmSVOl3kJT", - "V4p1pJMfH4NwaorttWET8VrcMU1L+146Tj5uYg/Vgz/N3mkWtt0znxe3VreT7hGgNYyc30S6u8ikg/21", - "fPDr2Xp7ebjN07eRMpdtZkK1inWNoL5DYiulWyndSunGDMEGj9MamdRfn5tYbsoUfZqHv3ptoOFJFeZW", - "M2w1wwb37xrbew+HcKbsbh9Br6pAfkXQU1J/8ekY6LZlLSKbnJkvzSrEe7qdvWEj7iIendi5nf1a2WVZ", - "8mqKtFC3H7Og0T2zQF8wxxB8vHpfb8G9prckoNDTjRpJrjsA7P3lrLiIIY5nBHkKezaddvUeCKoq1KjM", - "0pmAbDX5VpOv0we4TcaTNObqTbDBCswa2g3Bs9z379YWLC/1mZqDOWJt1clWnWzYMPQRDIRfayPoz8D1", - "kXtjM/8CJfbdzK4cCGbWLwp+rgDV2kbZK86ec//l/v8HAAD//x11ePR5RQEA", + "Rp7AYnwa9412i3HrxrFVhY9mNnIaMxe1XEEljWzXTuP02+ZyfqgpttdMFYpqunRx3zMt7bp3nHzchM7V", + "gz+NrjUL2+rY58WtVfXTPWKwhpH195SRO7pUpIP9tXy269l6e9m0zeu2kbKIuZ2mY9XjGkF9h8RWSrdS", + "upXSjRmCDR6KNTKpvz43sdyUKfo0D0X12kDDkyrMrWbYaoYN7t81tvceDuFM2d0+gl5VgfyKoKek/uLT", + "MdBty1pENjkzX5pViPd0O3vDRtxFPDqxczv7tbLLsuTVFGmhbj9mQaM7X4G+YI4h+Hj1vt6Ce01vSUCh", + "pxs1klx3ANj7y1lxEUMczwjyFPZsOu3qPRBUVTRRmYgzAdlq8q0mX6fPaJuMJ2mv1RtSgxWYNbQbgme5", + "79+tLVhe6jM1B3PE2qqTrTrZsGHoIxgIv9ZG0J+B6yP3xmb+BUrsu5ldORDMrF8U/FwBqrWNslecPef+", + "y/3/DwAA//+RVUTiqUMBAA==", } // GetSwagger returns the content of the embedded swagger specification file
internal/api/client/client.gen.go+0 −118 modified@@ -225,9 +225,6 @@ type ClientInterface interface { CreatePartnerRequest(ctx context.Context, id openapi_types.UUID, body CreatePartnerRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // DeleteSources request - DeleteSources(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // ListSources request ListSources(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -850,18 +847,6 @@ func (c *Client) CreatePartnerRequest(ctx context.Context, id openapi_types.UUID return c.Client.Do(req) } -func (c *Client) DeleteSources(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteSourcesRequest(c.Server) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - func (c *Client) ListSources(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewListSourcesRequest(c.Server) if err != nil { @@ -2432,33 +2417,6 @@ func NewCreatePartnerRequestRequestWithBody(server string, id openapi_types.UUID return req, nil } -// NewDeleteSourcesRequest generates requests for DeleteSources -func NewDeleteSourcesRequest(server string) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/v1/sources") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("DELETE", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - // NewListSourcesRequest generates requests for ListSources func NewListSourcesRequest(server string) (*http.Request, error) { var err error @@ -2960,9 +2918,6 @@ type ClientWithResponsesInterface interface { CreatePartnerRequestWithResponse(ctx context.Context, id openapi_types.UUID, body CreatePartnerRequestJSONRequestBody, reqEditors ...RequestEditorFn) (*CreatePartnerRequestResponse, error) - // DeleteSourcesWithResponse request - DeleteSourcesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*DeleteSourcesResponse, error) - // ListSourcesWithResponse request ListSourcesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListSourcesResponse, error) @@ -3936,30 +3891,6 @@ func (r CreatePartnerRequestResponse) StatusCode() int { return 0 } -type DeleteSourcesResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *Status - JSON401 *Error - JSON500 *Error -} - -// Status returns HTTPResponse.Status -func (r DeleteSourcesResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r DeleteSourcesResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - type ListSourcesResponse struct { Body []byte HTTPResponse *http.Response @@ -4614,15 +4545,6 @@ func (c *ClientWithResponses) CreatePartnerRequestWithResponse(ctx context.Conte return ParseCreatePartnerRequestResponse(rsp) } -// DeleteSourcesWithResponse request returning *DeleteSourcesResponse -func (c *ClientWithResponses) DeleteSourcesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*DeleteSourcesResponse, error) { - rsp, err := c.DeleteSources(ctx, reqEditors...) - if err != nil { - return nil, err - } - return ParseDeleteSourcesResponse(rsp) -} - // ListSourcesWithResponse request returning *ListSourcesResponse func (c *ClientWithResponses) ListSourcesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListSourcesResponse, error) { rsp, err := c.ListSources(ctx, reqEditors...) @@ -6693,46 +6615,6 @@ func ParseCreatePartnerRequestResponse(rsp *http.Response) (*CreatePartnerReques return response, nil } -// ParseDeleteSourcesResponse parses an HTTP response from a DeleteSourcesWithResponse call -func ParseDeleteSourcesResponse(rsp *http.Response) (*DeleteSourcesResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &DeleteSourcesResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Status - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} - // ParseListSourcesResponse parses an HTTP response from a ListSourcesWithResponse call func ParseListSourcesResponse(rsp *http.Response) (*ListSourcesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body)
internal/api/server/server.gen.go+0 −87 modified@@ -128,9 +128,6 @@ type ServerInterface interface { // (POST /api/v1/partners/{id}/request) CreatePartnerRequest(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) - // (DELETE /api/v1/sources) - DeleteSources(w http.ResponseWriter, r *http.Request) - // (GET /api/v1/sources) ListSources(w http.ResponseWriter, r *http.Request) @@ -343,11 +340,6 @@ func (_ Unimplemented) CreatePartnerRequest(w http.ResponseWriter, r *http.Reque w.WriteHeader(http.StatusNotImplemented) } -// (DELETE /api/v1/sources) -func (_ Unimplemented) DeleteSources(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} - // (GET /api/v1/sources) func (_ Unimplemented) ListSources(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) @@ -1295,21 +1287,6 @@ func (siw *ServerInterfaceWrapper) CreatePartnerRequest(w http.ResponseWriter, r handler.ServeHTTP(w, r.WithContext(ctx)) } -// DeleteSources operation middleware -func (siw *ServerInterfaceWrapper) DeleteSources(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteSources(w, r) - })) - - for _, middleware := range siw.HandlerMiddlewares { - handler = middleware(handler) - } - - handler.ServeHTTP(w, r.WithContext(ctx)) -} - // ListSources operation middleware func (siw *ServerInterfaceWrapper) ListSources(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -1732,9 +1709,6 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/api/v1/partners/{id}/request", wrapper.CreatePartnerRequest) }) - r.Group(func(r chi.Router) { - r.Delete(options.BaseURL+"/api/v1/sources", wrapper.DeleteSources) - }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/api/v1/sources", wrapper.ListSources) }) @@ -3741,40 +3715,6 @@ func (response CreatePartnerRequest500JSONResponse) VisitCreatePartnerRequestRes return json.NewEncoder(w).Encode(response) } -type DeleteSourcesRequestObject struct { -} - -type DeleteSourcesResponseObject interface { - VisitDeleteSourcesResponse(w http.ResponseWriter) error -} - -type DeleteSources200JSONResponse Status - -func (response DeleteSources200JSONResponse) VisitDeleteSourcesResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - -type DeleteSources401JSONResponse Error - -func (response DeleteSources401JSONResponse) VisitDeleteSourcesResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(401) - - return json.NewEncoder(w).Encode(response) -} - -type DeleteSources500JSONResponse Error - -func (response DeleteSources500JSONResponse) VisitDeleteSourcesResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(500) - - return json.NewEncoder(w).Encode(response) -} - type ListSourcesRequestObject struct { } @@ -4347,9 +4287,6 @@ type StrictServerInterface interface { // (POST /api/v1/partners/{id}/request) CreatePartnerRequest(ctx context.Context, request CreatePartnerRequestRequestObject) (CreatePartnerRequestResponseObject, error) - // (DELETE /api/v1/sources) - DeleteSources(ctx context.Context, request DeleteSourcesRequestObject) (DeleteSourcesResponseObject, error) - // (GET /api/v1/sources) ListSources(ctx context.Context, request ListSourcesRequestObject) (ListSourcesResponseObject, error) @@ -5426,30 +5363,6 @@ func (sh *strictHandler) CreatePartnerRequest(w http.ResponseWriter, r *http.Req } } -// DeleteSources operation middleware -func (sh *strictHandler) DeleteSources(w http.ResponseWriter, r *http.Request) { - var request DeleteSourcesRequestObject - - handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.DeleteSources(ctx, request.(DeleteSourcesRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "DeleteSources") - } - - response, err := handler(r.Context(), w, r, request) - - if err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(DeleteSourcesResponseObject); ok { - if err := validResponse.VisitDeleteSourcesResponse(w); err != nil { - sh.options.ResponseErrorHandlerFunc(w, r, err) - } - } else if response != nil { - sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) - } -} - // ListSources operation middleware func (sh *strictHandler) ListSources(w http.ResponseWriter, r *http.Request) { var request ListSourcesRequestObject
internal/cli/delete.go+7 −12 modified@@ -75,19 +75,14 @@ func (o *DeleteOptions) Run(ctx context.Context, args []string) error { } switch kind { case SourceKind: - if id != nil { - response, err := c.DeleteSourceWithResponse(ctx, *id) - if err != nil { - return fmt.Errorf("deleting %s/%s: %w", kind, id, err) - } - fmt.Printf("%s\n", response.Status()) - } else { - response, err := c.DeleteSourcesWithResponse(ctx) - if err != nil { - return fmt.Errorf("deleting %s: %w", plural(kind), err) - } - fmt.Printf("%s\n", response.Status()) + if id == nil { + return fmt.Errorf("source ID is required: use delete source/<id>") + } + response, err := c.DeleteSourceWithResponse(ctx, *id) + if err != nil { + return fmt.Errorf("deleting %s/%s: %w", kind, id, err) } + fmt.Printf("%s\n", response.Status()) default: return fmt.Errorf("unsupported resource kind: %s", kind) }
internal/handlers/v1alpha1/source.go+0 −9 modified@@ -70,15 +70,6 @@ func (s *ServiceHandler) CreateSource(ctx context.Context, request server.Create return server.CreateSource201JSONResponse(response), nil } -// (DELETE /api/v1/sources) -func (s *ServiceHandler) DeleteSources(ctx context.Context, request server.DeleteSourcesRequestObject) (server.DeleteSourcesResponseObject, error) { - err := s.sourceSrv.DeleteSources(ctx) - if err != nil { - return server.DeleteSources500JSONResponse{}, nil - } - return server.DeleteSources200JSONResponse{}, nil -} - // (DELETE /api/v1/sources/{id}) func (s *ServiceHandler) DeleteSource(ctx context.Context, request server.DeleteSourceRequestObject) (server.DeleteSourceResponseObject, error) { source, err := s.sourceSrv.GetSource(ctx, request.Id)
internal/handlers/v1alpha1/source_test.go+0 −24 modified@@ -808,30 +808,6 @@ var _ = Describe("source handler", Ordered, func() { }) Context("delete", func() { - It("successfully deletes all the sources", func() { - firstSourceID := uuid.New() - firstAgentID := uuid.New() - tx := gormdb.Exec(fmt.Sprintf(insertSourceWithUsernameStm, firstSourceID, "admin", "admin")) - Expect(tx.Error).To(BeNil()) - tx = gormdb.Exec(fmt.Sprintf(insertAgentStm, firstAgentID, "not-connected", "status-info-1", "cred_url-1", firstSourceID)) - Expect(tx.Error).To(BeNil()) - - secondSourceID := uuid.New() - tx = gormdb.Exec(fmt.Sprintf(insertSourceWithUsernameStm, secondSourceID, "batman", "batman")) - Expect(tx.Error).To(BeNil()) - tx = gormdb.Exec(fmt.Sprintf(insertAgentStm, uuid.New(), "not-connected", "status-info-1", "cred_url-1", secondSourceID)) - Expect(tx.Error).To(BeNil()) - - srv := handlers.NewServiceHandler(service.NewSourceService(s, nil), service.NewAssessmentService(s, nil, nil), nil, service.NewSizerService(nil, s), nil, nil, nil) - _, err := srv.DeleteSources(context.TODO(), server.DeleteSourcesRequestObject{}) - Expect(err).To(BeNil()) - - count := 1 - tx = gormdb.Raw("SELECT COUNT(*) FROM SOURCES;").Scan(&count) - Expect(tx.Error).To(BeNil()) - Expect(count).To(Equal(0)) - }) - It("successfully deletes a source", func() { firstSourceID := uuid.New() firstAgentID := uuid.New()
internal/service/source.go+0 −8 modified@@ -165,14 +165,6 @@ func (s *SourceService) CreateSource(ctx context.Context, sourceForm mappers.Sou return *result, nil } -func (s *SourceService) DeleteSources(ctx context.Context) error { - if err := s.store.Source().DeleteAll(ctx); err != nil { - return err - } - - return nil -} - func (s *SourceService) DeleteSource(ctx context.Context, id uuid.UUID) error { if err := s.store.Source().Delete(ctx, id); err != nil { return err
internal/service/source_test.go+0 −24 modified@@ -249,30 +249,6 @@ var _ = Describe("source handler", Ordered, func() { }) Context("delete", func() { - It("successfully deletes all the sources", func() { - firstSourceID := uuid.New() - firstAgentID := uuid.New() - tx := gormdb.Exec(fmt.Sprintf(insertSourceWithUsernameStm, firstSourceID, "admin", "admin")) - Expect(tx.Error).To(BeNil()) - tx = gormdb.Exec(fmt.Sprintf(insertAgentStm, firstAgentID, "not-connected", "status-info-1", "cred_url-1", firstSourceID)) - Expect(tx.Error).To(BeNil()) - - secondSourceID := uuid.New() - tx = gormdb.Exec(fmt.Sprintf(insertSourceWithUsernameStm, secondSourceID, "batman", "batman")) - Expect(tx.Error).To(BeNil()) - tx = gormdb.Exec(fmt.Sprintf(insertAgentStm, uuid.New(), "not-connected", "status-info-1", "cred_url-1", secondSourceID)) - Expect(tx.Error).To(BeNil()) - - srv := service.NewSourceService(s, nil) - err := srv.DeleteSources(context.TODO()) - Expect(err).To(BeNil()) - - count := 1 - tx = gormdb.Raw("SELECT COUNT(*) FROM SOURCES;").Scan(&count) - Expect(tx.Error).To(BeNil()) - Expect(count).To(Equal(0)) - }) - It("successfully deletes a source", func() { firstSourceID := uuid.New() firstAgentID := uuid.New()
internal/store/source.go+0 −6 modified@@ -14,7 +14,6 @@ import ( type Source interface { List(ctx context.Context, filter *SourceQueryFilter) (model.SourceList, error) Create(ctx context.Context, source model.Source) (*model.Source, error) - DeleteAll(ctx context.Context) error Get(ctx context.Context, id uuid.UUID) (*model.Source, error) Delete(ctx context.Context, id uuid.UUID) error Update(ctx context.Context, source model.Source) (*model.Source, error) @@ -61,11 +60,6 @@ func (s *SourceStore) Create(ctx context.Context, source model.Source) (*model.S return &source, nil } -func (s *SourceStore) DeleteAll(ctx context.Context) error { - result := s.getDB(ctx).Unscoped().Exec("DELETE FROM sources") - return result.Error -} - func (s *SourceStore) Get(ctx context.Context, id uuid.UUID) (*model.Source, error) { source := model.Source{ID: id} result := s.getDB(ctx).Preload("Agents").Preload("ImageInfra").Preload("Labels").First(&source)
internal/store/source_test.go+0 −25 modified@@ -233,31 +233,6 @@ var _ = Describe("source store", Ordered, func() { Expect(count).To(Equal(1)) }) - It("successfully delete all sources", func() { - id := uuid.New() - agentID := uuid.New() - tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, id, "source1", "user1", "org_id_1")) - Expect(tx.Error).To(BeNil()) - tx = gormdb.Exec(fmt.Sprintf(insertAgentStm, agentID, "not-connected", "status-info-1", "cred_url-1", id)) - - Expect(tx.Error).To(BeNil()) - tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString(), "source2", "user1", "org_id_1")) - Expect(tx.Error).To(BeNil()) - - err := s.Source().DeleteAll(context.TODO()) - Expect(err).To(BeNil()) - - count := 2 - tx = gormdb.Raw("SELECT COUNT(*) FROM sources;").Scan(&count) - Expect(tx.Error).To(BeNil()) - Expect(count).To(Equal(0)) - - count = 1 - tx = gormdb.Raw("SELECT COUNT(*) FROM agents;").Scan(&count) - Expect(tx.Error).To(BeNil()) - Expect(count).To(Equal(0)) - }) - AfterEach(func() { gormdb.Exec("DELETE from agents;") gormdb.Exec("DELETE from sources;")
test/e2e/service/source.go+11 −9 modified@@ -136,25 +136,27 @@ func (s *plannerService) RemoveSource(uuid uuid.UUID) error { return err } -// RemoveSources deletes all existing sources +// RemoveSources lists all sources for the current user and deletes each one individually func (s *plannerService) RemoveSources() error { zap.S().Infof("[PlannerService] Delete sources [user: %s, organization: %s]", s.credentials.Username, s.credentials.Organization) - res, err := s.api.DeleteRequest(apiV1SourcesPath) + + sources, err := s.GetSources() if err != nil { - return err + return fmt.Errorf("failed to list sources for deletion: %v", err) } - removeSourcesRes, err := internalclient.ParseDeleteSourcesResponse(res) - if err != nil { - return fmt.Errorf("failed to parse res: %v", err) + if sources == nil { + return nil } - if res.StatusCode != http.StatusOK { - return fmt.Errorf("failed to delete sources. response status code: %d. response error: %s", res.StatusCode, string(removeSourcesRes.Body)) + for _, source := range *sources { + if err := s.RemoveSource(source.Id); err != nil { + return fmt.Errorf("failed to delete source %s: %v", source.Id, err) + } } - return err + return nil } // UpdateSource updates the inventory of a specific source
Vulnerability mechanics
Root cause
"The DELETE /api/v1/sources endpoint lacked authorization and filtering, allowing any authenticated user to delete all customer data."
Attack vector
An authenticated user can send a DELETE request to the `/api/v1/sources` route. This request bypasses necessary authorization and filtering mechanisms. The vulnerability allows for the destruction of all customer data, including sources, agents, and assessments, leading to a critical loss of availability and integrity across the entire SaaS platform [ref_id=1].
Affected code
The vulnerability existed in the DELETE /api/v1/sources route, which was wired in `openapi.yaml` and called `sourceSrv.DeleteSources(ctx)` without proper checks [ref_id=1]. The affected code was removed from `internal/api/client/client.gen.go`, `internal/api/server/server.gen.go`, `api/v1alpha1/openapi.yaml`, `internal/handlers/v1alpha1/source.go`, and related test files.
What the fix does
The patch removes the DELETE /api/v1/sources endpoint entirely from the API client and server implementations [patch_id=5486183]. The commit message indicates that this endpoint deleted all sources without proper authorization checks. The fix replaces this with individual deletion of sources via DELETE /api/v1/sources/{id}, ensuring that bulk deletion without authorization is no longer possible.
Preconditions
- authThe attacker must be an authenticated user.
Generated on Jun 10, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
0No linked articles in our index yet.