Skip to content

[EDR Workflows] Add runscript from Microsoft Defender for Endpoint support to Response Console#222377

Merged
tomsonpl merged 57 commits intoelastic:mainfrom
tomsonpl:mde-custom-scripts
Jun 18, 2025
Merged

[EDR Workflows] Add runscript from Microsoft Defender for Endpoint support to Response Console#222377
tomsonpl merged 57 commits intoelastic:mainfrom
tomsonpl:mde-custom-scripts

Conversation

@tomsonpl
Copy link
Contributor

@tomsonpl tomsonpl commented Jun 3, 2025

Overview

This PR adds support for custom script execution functionality in the Microsoft Defender Endpoint (MDE) response actions client, including the ability to run scripts and retrieve custom script libraries.

Previous PRs

New Features

1. Run Script Action Support

  • Method: runscript()
  • Functionality: Execute custom PowerShell and Python scripts on Microsoft Defender Endpoint agents
  • Parameters: Supports scriptName and optional args parameters
  • API Integration: Uses RUN_SCRIPT sub-action to communicate with Microsoft Defender API
  • Error Handling: Comprehensive error handling for API failures and missing machine action IDs

2. Custom Scripts Library Management

  • Method: getCustomScripts()
  • Functionality: Retrieve available custom scripts from Microsoft Defender's script library
  • API Integration: Uses GET_LIBRARY_FILES sub-action
  • Response Format: Transforms MDE API response to standardized CustomScriptsResponse format

3. Script Result Download Support

  • Methods: getFileInfo() and getFileDownload()
  • Functionality: Enables downloading of script execution results from MDE
  • Supported Actions: Currently supports runscript outputs
  • File Format: Downloads JSON streams containing script output
  • Status Tracking: Tracks file availability status (AWAITING_UPLOAD, READY)

Technical Changes

Core Implementation (ms_defender_endpoint_actions_client.ts)

  • Added runscript() method with proper parameter validation and error handling
  • Added getCustomScripts() method with response transformation
  • Enhanced processPendingActions() to handle script execution results
  • Fixed TypeScript interface compatibility issue between implementation and interface

Processing MDE completion (microsoft_defender_endpoint.ts)

  • Enhanced pending actions processor to download script execution results
  • Added special handling for runscript actions (unlike isolate/release which don't fetch results)

File Download Support (microsoft_defender_endpoint.ts)

  • Added support for downloading runscript execution results directly from Microsoft Defender Endpoint
  • Integrated new methods:
    • getFileInfo() – retrieves metadata and checks file availability status from response documents
    • getFileDownload() – streams JSON output directly from Defender’s API using readable streams
  • Implemented support for status tracking: AWAITING_UPLOAD, READY
  • Auto-generates filenames using the format: runscript-output-{machineActionId}.json

Feature Flag

  • Introduced microsoftDefenderEndpointRunScriptEnabled

UI

Screenshot 2025-06-11 at 09 28 05

Downloaded file example:

runscript-output-3887e24e-4185-493d-a139-1cf4fa6a106c.json

How to test

  • Use x-pack/solutions/security/plugins/security_solution/scripts/endpoint/run_microsoft_defender_host.js to setup MDE env and generate alert
  • Turn on the feature flag
  • use runscript command through Response Console

Closes: https://github.com/elastic/security-team/issues/12318
Closes: https://github.com/elastic/security-team/issues/12350
Closes: https://github.com/elastic/security-team/issues/12319
Closes: https://github.com/elastic/security-team/issues/12320
Closes: https://github.com/elastic/security-team/issues/12417

@tomsonpl tomsonpl self-assigned this Jun 3, 2025
@tomsonpl tomsonpl added Team:Defend Workflows “EDR Workflows” sub-team of Security Solution release_note:feature Makes this part of the condensed release notes v9.1.0 v8.19.0 labels Jun 3, 2025
@tomsonpl
Copy link
Contributor Author

tomsonpl commented Jun 3, 2025

/ci

@tomsonpl
Copy link
Contributor Author

tomsonpl commented Jun 4, 2025

/ci

@tomsonpl
Copy link
Contributor Author

tomsonpl commented Jun 4, 2025

/ci

@tomsonpl
Copy link
Contributor Author

tomsonpl commented Jun 4, 2025

/ci

@tomsonpl
Copy link
Contributor Author

tomsonpl commented Jun 4, 2025

/ci

@tomsonpl
Copy link
Contributor Author

tomsonpl commented Jun 5, 2025

/ci

@tomsonpl
Copy link
Contributor Author

tomsonpl commented Jun 5, 2025

/ci

);

additionalData = {
output: {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for reviewers:

  • this code is here as a temporary solution to help me verify if the value coming in is valid
  • it will be refactored in a following PR that will address showing this value as a downloadable link in the response console
  • possibly it won't be returned as stdout but differently - more similar to how we do it in s1

@tomsonpl tomsonpl marked this pull request as ready for review June 6, 2025 12:40
@tomsonpl tomsonpl requested a review from ashokaditya June 16, 2025 12:09
@tomsonpl
Copy link
Contributor Author

And I think this UX might be frustrating to a user where they can't easily pick another script and have to clear out the search input in the popup.

@raqueltabuyo any opinion? I think you've played with Script picker recently, should we change the behavior to what @ashokaditya is suggesting?

Copy link
Member

@ashokaditya ashokaditya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes with the schemas. the conditional schema needs a small update besides clean up of redundant schemas and type exports. Let me know if you need help with any of it.

I tested out the flow again with windows and ubuntu and everything works as expected. I'm still not seeing an OS icon for my VMs for some reason even after several hours. Not sure if there is a issue with the alert data that is being ingested via connectors.

windows
Screenshot 2025-06-17 at 11 50 50

ubuntu
Screenshot 2025-06-17 at 11 51 43

});

describe('Common validation', () => {
it('should reject when no endpoint_ids are provided', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using it.each for tests that should cover both MDE and Crowdstrike.

Comment on lines +147 to +151
// export type RunScriptActionRequestBody = Omit<BaseRunScriptActionRequest, 'parameters'> & {
// parameters:
// | MSDefenderRunScriptActionRequestBody['parameters']
// | CrowdStrikeRunScriptActionRequestBody['parameters'];
// };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should remove this before merging.

return 'At least one of Raw, HostPath, or CloudFile must be provided';
parameters: schema.oneOf([
// CrowdStrike schema
schema.conditional(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditional schema should work like a nested ternary operator

boolean condition 1
 ? do something 
 : boolean 2
   ? do else 
   : never true `. 

So RunScriptActionRequestSchema should be something like this, where the next conditional check is nested within the outer one

export const RunScriptActionRequestSchema = {
  body: schema.object({
    ...restBaseSchema,
    parameters: schema.oneOf([
      // CrowdStrike schema
      schema.conditional(
        schema.siblingRef('agent_type'),
        'crowdstrike',
        CrowdStrikeRunScriptActionRequestParamsSchema,
        schema.conditional(
          schema.siblingRef('agent_type'),
          'microsoft_defender_endpoint',
          // Microsoft Defender Endpoint schema
          MSDefenderEndpointRunScriptActionRequestParamsSchema,
          schema.never()
        )
      ),
    ]),
  }),
};

Comment on lines +138 to +143
export type MSDefenderRunScriptActionRequestBody = TypeOf<
typeof MSDefenderEndpointRunScriptActionRequestSchema.body
>;
export type CrowdStrikeRunScriptActionRequestBody = TypeOf<
typeof CrowdStrikeRunScriptActionRequestSchema.body
>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for both MS defend and Crowdstrike you just need parameter schemas. I don't see the base schema body being used anywhere. So essentially neither CrowdStrikeRunScriptActionRequestBody or MSDefenderRunScriptActionRequestBody can be removed.

You should instead export the type for MSDefenderEndpointRunScriptActionRequestParamsSchema and replace with MSDefenderRunScriptActionRequestBody['parameters'] where ever used. Same for CrowdStrikeRunScriptActionRequestBody['parameters']

},
});
}).toThrow(
'[parameters.1.scriptName]: expected value of type [string] but got [undefined]'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That error looks odd. I think it is because the parameters conditional check is not nested correctly.

parameters: {},
});
}).toThrow(
'[parameters.1.scriptName]: expected value of type [string] but got [undefined]'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. schema.conditonal should be nested. See my earlier comment.

Comment on lines +67 to +68
scriptName: NonEmptyString,
args: schema.maybe(NonEmptyString),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't use the NonEmptyString schema for MDE here cause the error thrown by it is specific to CS parameter schema.

Comment on lines +1114 to +1140
it('should accept optional comment field', () => {
expect(() => {
RunScriptActionRequestSchema.body.validate({
endpoint_ids: ['endpoint_id'],
agent_type: 'crowdstrike',
parameters: {
raw: 'Get-Process',
},
comment: 'Running process enumeration',
});
}).not.toThrow();
});

it('should accept optional case_ids field', () => {
expect(() => {
RunScriptActionRequestSchema.body.validate({
endpoint_ids: ['endpoint_id'],
agent_type: 'crowdstrike',
parameters: {
raw: 'Get-Process',
},
case_ids: ['case-id-1', 'case-id-2'],
});
}).not.toThrow();
});

it('should accept optional alert_ids field', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These three tests look agent specific cause of the parameters.raw or parameters.scriptName values. You can make a copy of each within the agent type specific scopes above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved them to CS 👍

@tomsonpl
Copy link
Contributor Author

Thanks @ashokaditya, again good comments — I applied all of them, let me know what you think 👍

Copy link
Member

@ashokaditya ashokaditya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the changes! I'm approving so this can be merged after another review. Do remember to remove the redundant tests as I mentioned.

I still think that the script picker UX needs improvement and would frustrate users with its current behaviour.

}
},
});
const getNonEmptyString = (fieldName: string) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀


// Microsoft Defender Endpoint schemas
const MSDefenderEndpointRunScriptActionRequestParamsSchema = schema.object({
export const MSDefenderEndpointRunScriptActionRequestParamsSchema = schema.object({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to rename this yet. Or rename it later when we might use a params schema for newer MDE actions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it was not renamed, just the export added, right?

endpoint_ids: ['endpoint_id'],
agent_type: 'crowdstrike' as const,
};
describe('Common fields validation', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can actually remove the entire common fields validation as it is already tested above for the base schema. Let's keep CS, MDE schema specific test in here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed 👍

Copy link
Contributor

@pmuellr pmuellr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResponseOps changes LGTM

@elasticmachine
Copy link
Contributor

elasticmachine commented Jun 18, 2025

💛 Build succeeded, but was flaky

Failed CI Steps

Metrics [docs]

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
securitySolution 9.4MB 9.4MB +3.7KB

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
securitySolution 93.3KB 93.4KB +45.0B

History

cc @tomsonpl

@tomsonpl tomsonpl enabled auto-merge (squash) June 18, 2025 09:34
@tomsonpl tomsonpl merged commit 18f606e into elastic:main Jun 18, 2025
10 checks passed
@kibanamachine
Copy link
Contributor

Starting backport for target branches: 8.19

https://github.com/elastic/kibana/actions/runs/15729292273

@kibanamachine
Copy link
Contributor

💔 All backports failed

Status Branch Result
8.19 Backport failed because of merge conflicts

Manual backport

To create the backport manually run:

node scripts/backport --pr 222377

Questions ?

Please refer to the Backport tool documentation

@tomsonpl
Copy link
Contributor Author

💚 All backports created successfully

Status Branch Result
8.19

Note: Successful backport PRs will be merged automatically after passing CI.

Questions ?

Please refer to the Backport tool documentation

tomsonpl added a commit to tomsonpl/kibana that referenced this pull request Jun 18, 2025
…pport to Response Console (elastic#222377)

(cherry picked from commit 18f606e)

# Conflicts:
#	x-pack/solutions/security/plugins/security_solution/common/endpoint/types/microsoft_defender_endpoint.ts
#	x-pack/solutions/security/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts
#	x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/crowdstrike/mocks.ts
#	x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/microsoft/defender/endpoint/mocks.ts
#	x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/microsoft/defender/endpoint/ms_defender_endpoint_actions_client.test.ts
#	x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/clients/microsoft/defender/endpoint/ms_defender_endpoint_actions_client.ts
@kibanamachine kibanamachine added the backport missing Added to PRs automatically when the are determined to be missing a backport. label Jun 20, 2025
@kibanamachine
Copy link
Contributor

Looks like this PR has a backport PR but it still hasn't been merged. Please merge it ASAP to keep the branches relatively in sync.
cc: @tomsonpl

tomsonpl added a commit that referenced this pull request Jun 23, 2025
@kibanamachine kibanamachine removed the backport missing Added to PRs automatically when the are determined to be missing a backport. label Jun 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:version Backport to applied version labels release_note:feature Makes this part of the condensed release notes Team:Defend Workflows “EDR Workflows” sub-team of Security Solution v8.19.0 v9.1.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants