[Cases] Migrate attachment client/service layer to unified-only requests - #290961
Conversation
Collapses the client/service/route/workflow layer onto unified-only attachment requests (AttachmentRequestV2 / BulkCreateAttachmentsRequestV2), adds BulkCreateUnifiedAttachmentsRequestRt / BulkGetUnifiedAttachmentsResponseRt, and removes the now-unused BulkGetAttachmentsResponseRtV2 and legacy decode path. Co-authored-by: Cursor <cursoragent@cursor.com>
Split attachment/v2.ts into: - v2.ts: unified-only types (BulkCreateUnifiedAttachmentsRequest, etc.) - v2_union.ts: v1-legacy union types (AttachmentRequestV2, etc.), exposed as the attachmentApiV2Union namespace. Also migrate base_response_actions_client.ts to the unified-only client type, matching cases_connector_executor.ts/add_alerts.ts.
szwarckonrad
left a comment
There was a problem hiding this comment.
Defend Workflows single type name change LGTM
| // validation re-runs through the per-type Zod schemas. | ||
| return createAttachments({ | ||
| attachments: attachments as unknown as attachmentApiV2.BulkCreateAttachmentsRequestV2, | ||
| attachments: attachments as unknown as attachmentApiV2Union.BulkCreateAttachmentsRequestV2, |
There was a problem hiding this comment.
still using union because actual routes accept both right now, #290934 updates the route to be unified only
radtke-vale
left a comment
There was a problem hiding this comment.
LGTM 🚀 Very nice refactoring! I tried REST API locally + attaching events/files. All looks good.
| isUnifiedOnlyAttachment(attachment) | ||
| ); | ||
| // The client accepts unified payloads only; convert the mixed wire body here. | ||
| const attachments = rawAttachments.map((attachment) => |
There was a problem hiding this comment.
My understanding is that it's one of the main changes - shifting the v1 conversion one level up to the route level 👍 So all internal requests expect unified, but for the end users we'll make conversion on the route level
…91173) ## Summary `validateCreateCommentRequest` blocks alerts and events from being attached to a closed case. The event check matched by legacy type only (`AttachmentType.event`, i.e. `'event'`), so unified events (`security.event`) never triggered the guard and were silently accepted on closed cases. Alerts already used the unified-aware `isAlertAttachmentType` predicate. This aligns events to the same pattern via `isEventAttachmentType`, which matches both `'event'` (legacy) and `'security.event'` (unified). Also removes the now-dead `getAttachmentsByType` helper (its only call site was the buggy event check). **Found by** @radtke-vale, validated on staging in [this review comment](#290961 (comment)) on #290961. ## Before / after (validated on staging) Before: ``` POST kbn:/api/cases/{caseId}/comments { "type": "security.event", "owner": "securitySolution", "attachmentId": "...", "metadata": { "index": "..." } } ``` → `200`, attachment created on a closed case. After: ```json { "statusCode": 400, "error": "Bad Request", "message": "Event cannot be attached to a closed case" } ``` ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/docs/extend/kibana/contributing/workflow/how-we-use-github#release-notes) - [x] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. Made with Cursor
- extract_observables.test.ts: fix stale legacyAlertAttachment reference left over from merge conflict resolution (branch only defines alertAttachment, a unified-only fixture) - case_with_comments.test.ts: remove duplicate closed-case-unified-event test; the merged-in top-level unifiedEventComment fixture already covers this case in the pre-existing 'throws if trying to add an event or alert to a closed case' test
💛 Build succeeded, but was flaky
Failed CI Steps
Metrics [docs]Module Count
Page load bundle
Test Failures
History
|
…ck (#291173) (#291204) # Backport This will backport the following commits from `main` to `9.5`: - [[Cases] Fix unified event attachments bypassing closed-case check (#291173)](#291173) <!--- Backport version: 12.0.4 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"christineweng","email":"18648970+christineweng@users.noreply.github.com"},"sourceCommit":{"committedDate":"2026-09-15T15:55:18Z","message":"[Cases] Fix unified event attachments bypassing closed-case check (#291173)\n\n## Summary\n\n`validateCreateCommentRequest` blocks alerts and events from being\nattached to a closed case. The event check matched by legacy type only\n(`AttachmentType.event`, i.e. `'event'`), so unified events\n(`security.event`) never triggered the guard and were silently accepted\non closed cases.\n\nAlerts already used the unified-aware `isAlertAttachmentType` predicate.\nThis aligns events to the same pattern via `isEventAttachmentType`,\nwhich matches both `'event'` (legacy) and `'security.event'` (unified).\n\nAlso removes the now-dead `getAttachmentsByType` helper (its only call\nsite was the buggy event check).\n\n**Found by** @radtke-vale, validated on staging in [this review\ncomment](https://github.com/elastic/kibana/pull/290961#discussion_r4014165733)\non #290961.\n\n## Before / after (validated on staging)\n\nBefore:\n```\nPOST kbn:/api/cases/{caseId}/comments\n{\n \"type\": \"security.event\",\n \"owner\": \"securitySolution\",\n \"attachmentId\": \"...\",\n \"metadata\": { \"index\": \"...\" }\n}\n```\n→ `200`, attachment created on a closed case.\n\nAfter:\n```json\n{\n \"statusCode\": 400,\n \"error\": \"Bad Request\",\n \"message\": \"Event cannot be attached to a closed case\"\n}\n```\n\n### Checklist\n\n- [ ] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n- [ ]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas added for features that require explanation or tutorials\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [ ] If a plugin configuration key changed, check if it needs to be\nallowlisted in the cloud and added to the [docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n- [ ] This was checked for breaking HTTP API changes, and any breaking\nchanges have been approved by the breaking-change committee. The\n`release_note:breaking` label should be applied in these situations.\n- [ ] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed\n- [x] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/docs/extend/kibana/contributing/workflow/how-we-use-github#release-notes)\n- [x] Review the [backport\nguidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)\nand apply applicable `backport:*` labels.\n\nMade with Cursor","sha":"0b5aba9fe5761d0fd2fc2ce576e6d36146231465","branchLabelMapping":{"^v9.6.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","backport:version","v9.6.0","v9.5.5","Team:Lantern"],"title":"[Cases] Fix unified event attachments bypassing closed-case check","number":291173,"url":"https://github.com/elastic/kibana/pull/291173","mergeCommit":{"message":"[Cases] Fix unified event attachments bypassing closed-case check (#291173)\n\n## Summary\n\n`validateCreateCommentRequest` blocks alerts and events from being\nattached to a closed case. The event check matched by legacy type only\n(`AttachmentType.event`, i.e. `'event'`), so unified events\n(`security.event`) never triggered the guard and were silently accepted\non closed cases.\n\nAlerts already used the unified-aware `isAlertAttachmentType` predicate.\nThis aligns events to the same pattern via `isEventAttachmentType`,\nwhich matches both `'event'` (legacy) and `'security.event'` (unified).\n\nAlso removes the now-dead `getAttachmentsByType` helper (its only call\nsite was the buggy event check).\n\n**Found by** @radtke-vale, validated on staging in [this review\ncomment](https://github.com/elastic/kibana/pull/290961#discussion_r4014165733)\non #290961.\n\n## Before / after (validated on staging)\n\nBefore:\n```\nPOST kbn:/api/cases/{caseId}/comments\n{\n \"type\": \"security.event\",\n \"owner\": \"securitySolution\",\n \"attachmentId\": \"...\",\n \"metadata\": { \"index\": \"...\" }\n}\n```\n→ `200`, attachment created on a closed case.\n\nAfter:\n```json\n{\n \"statusCode\": 400,\n \"error\": \"Bad Request\",\n \"message\": \"Event cannot be attached to a closed case\"\n}\n```\n\n### Checklist\n\n- [ ] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n- [ ]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas added for features that require explanation or tutorials\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [ ] If a plugin configuration key changed, check if it needs to be\nallowlisted in the cloud and added to the [docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n- [ ] This was checked for breaking HTTP API changes, and any breaking\nchanges have been approved by the breaking-change committee. The\n`release_note:breaking` label should be applied in these situations.\n- [ ] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed\n- [x] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/docs/extend/kibana/contributing/workflow/how-we-use-github#release-notes)\n- [x] Review the [backport\nguidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)\nand apply applicable `backport:*` labels.\n\nMade with Cursor","sha":"0b5aba9fe5761d0fd2fc2ce576e6d36146231465"}},"sourceBranch":"main","suggestedTargetBranches":["9.5"],"targetPullRequestStates":[{"branch":"main","label":"v9.6.0","branchLabelMappingKey":"^v9.6.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/291173","number":291173,"mergeCommit":{"message":"[Cases] Fix unified event attachments bypassing closed-case check (#291173)\n\n## Summary\n\n`validateCreateCommentRequest` blocks alerts and events from being\nattached to a closed case. The event check matched by legacy type only\n(`AttachmentType.event`, i.e. `'event'`), so unified events\n(`security.event`) never triggered the guard and were silently accepted\non closed cases.\n\nAlerts already used the unified-aware `isAlertAttachmentType` predicate.\nThis aligns events to the same pattern via `isEventAttachmentType`,\nwhich matches both `'event'` (legacy) and `'security.event'` (unified).\n\nAlso removes the now-dead `getAttachmentsByType` helper (its only call\nsite was the buggy event check).\n\n**Found by** @radtke-vale, validated on staging in [this review\ncomment](https://github.com/elastic/kibana/pull/290961#discussion_r4014165733)\non #290961.\n\n## Before / after (validated on staging)\n\nBefore:\n```\nPOST kbn:/api/cases/{caseId}/comments\n{\n \"type\": \"security.event\",\n \"owner\": \"securitySolution\",\n \"attachmentId\": \"...\",\n \"metadata\": { \"index\": \"...\" }\n}\n```\n→ `200`, attachment created on a closed case.\n\nAfter:\n```json\n{\n \"statusCode\": 400,\n \"error\": \"Bad Request\",\n \"message\": \"Event cannot be attached to a closed case\"\n}\n```\n\n### Checklist\n\n- [ ] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n- [ ]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas added for features that require explanation or tutorials\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [ ] If a plugin configuration key changed, check if it needs to be\nallowlisted in the cloud and added to the [docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\n- [ ] This was checked for breaking HTTP API changes, and any breaking\nchanges have been approved by the breaking-change committee. The\n`release_note:breaking` label should be applied in these situations.\n- [ ] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed\n- [x] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/docs/extend/kibana/contributing/workflow/how-we-use-github#release-notes)\n- [x] Review the [backport\nguidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)\nand apply applicable `backport:*` labels.\n\nMade with Cursor","sha":"0b5aba9fe5761d0fd2fc2ce576e6d36146231465"}},{"branch":"9.5","label":"v9.5.5","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Summary
Context:
During cases attachment v2 work, all the v2 types (such as
AttachmentRequestV2) are unions of v1 and v2 for backward compatibility. After #287729, all public routes perform v1 -> v2 conversion before sending to cases client, hence union shape is no longer necessary.This PR migrates the client/service layer to accept unified-only attachment payloads internally.
Major changes
....V2(v1+v2 union) type with...Unified(v2 only)common/types/api/attachment/v2.tsinto:v2.tsandv2_union.tsvalidateRegisteredAttachmentstovalidateUnifiedAttachmentsNo wire-shape changes in this PR. Follow-up PR #290934 removes the legacy wire shape from the bulk-create route.
Made with Cursor
Checklist
release_note:breakinglabel should be applied in these situations.release_note:*label is applied per the guidelinesbackport:*labels.