top of page

Automating Response #SOAR #EDR - Building an Interactive Slack App (Bonus Section)

Jv Cyberguard

Part 5(Optional): Bonus Section. Project enhancements: Build out a Slack custom app

As mentioned previously, a significant room for improvement in the lab completed thus far is that it still does not completely mirror a real word automated response use case since we still have to having to manually visit the Tines Page Action (User Prompt). This is not the standard enterprise workflow for automation-driven SOAR processes. In most enterprise environments, the goal is to remove manual steps where possible and allow analysts to take action directly from their primary communication tool (Slack, Teams, Email, etc.) without needing to open Tines manually.

 

One approach to this is using Slack interactive buttons. Honestly, I went down this rabbit hole to build this out and it took me hours and lots of documentation and ChatGPT. I am going to give you a super high level overview of what I did and let you go down that rabbit hole on your own. (Update:I ended up demonstrating almost everything)

 

 

Let me preface by saying when I first began writing this bonus section I did not want to deviate too far away from our working solution, so I tried to append the link to the Page Action (User Prompt) to "Hack-tool-Detected" message in slack.

 

So you may be wondering why did I not just do that. Can't we just access the page directly by sending the URL in the slack message. Well, I tried but quickly learned the following.

 


 So I tried exactly that. Essentially Page Action URLs in Tines are dynamically generated when input is received, so there was no way to attach it to the Slack message. I then asked myself, what if we:

 

  1. Create a page action that has no input. That way the URL is constant.

  2. Include that URL in the Slack message.

  3. And then have that page action feed into the triggers for yes or no.

 

It's a patch job but it was supposed to serves its purpose. However, while in theory it did work. The "static" URL did change after some time, so that was in no way sustainable.

 

So of the potential solutions, I decided to approach this by using a Slack Interactive app with a message built with the Block Kit UI Framework. It allows us to create interactive buttons in Slack to allow analysts to take action directly from their primary communication tool in the Alerts channel without needing to open Tines manually.

 

We are going to change out our workflow here.


Due to other deliverables. I honestly can't go step by step with screenshots. You will have to do some additional research here.

 

The outcome we're try to achieve is this. Essentially, containing the user prompt in Slack.


 

We are going to break this down into 3 stages.

 

  1. Build an interactive slack app. This app will have access to a range of APIs that provide the access we need to read and right data from inside Slack.

  2. We are going to Setup a slack action in Tines that will allow us to send the message and the interactive buttons over to Alerts channel.

  3. Then we are going to Test.


Trust me it's not as crazy as it sounds. Sign up here https://api.slack.com/apps


I already signed up and signed in. I don't remember the steps exactly you need to complete, but use your same Slack account and workspace that you used for the lab. You want to get to this screen. This should be the dashboard you have access to after logging in to the Slack apps portal.


Then pick the same workspace that you've already created.


We are going to side step here and drag another webhook in Tines to our story board.


Copy the Url and paste it in the custom app. The webhook receives the event the User chose, which is to "Isolate" or Ignore" and we are setting the Request URL so the Slack App which we call Isolation App knows where to send it.


We next set the permission for the app. Navigate to OAuth & permissions. Scroll down to the section scopes > Add an OAuth Scope.


In the menu navigate to Install app and then install app and click Allow.


You should now have an OAuth token. Save that to your notepad  for now. The app should also be in your Slack workspace.

Finally, add the Isolation app to the channel. In the message field in the alerts channel type the following.


Next step. We are going to Setup a slack action in Tines that will allow us to send the message and the interactive buttons over to Alerts channel.  We will use the Block Kit UI framework to build this interactivity in our messages. Block Kit is essentially using JSON as a tool to create interactivity in our messages to enhance our workflow in Slack. You can learn about it from the docs below and also play around with the Block kit building tool kit.

 

 

In the second documentation navigate to the hyperlink to Block Kit Builder where you can design the message UI and then just copy the JSON for it.


Here is a snapshot of the Block Kit Builder.


However, I am going to provide you the Block we use to achieve this lab in a moment. I leveraged the docs, the builder and Ai to get it done. Feel free to do the same.

 

Once you drag the Slack template on to the board. Search for Block and then we are going to send a message that accepts the block kit builder JSON.


Delete all the code that was in there.


Paste the below code in. Before you do, here’s a simple breakdown of what each part of this JSON in Block Kit format does in Slack:

  1. Channel

    • "channel": "#alerts" tells Slack to post this message in the #alerts channel.

  2. Blocks Array

    • This is the core of Slack’s Block Kit layout. Each object in the array represents a different “block” of content in the final Slack message.

  3. Header Block

    • "type": "header" displays a large, bold heading at the top of the message.

    • In this case, it shows the detection category from LimaCharlie (e.g., “Hack-Tool-Detected-Lazagne.exe”).

  4. Section Block

    • "type": "section" defines a block of text (or fields) and can include an image accessory.

    • The "fields" array contains multiple mrkdwn entries (Markdown text) to list important details: Time, Computer Name, Source IP, Username, File Path, etc.

    • The "accessory" is an image (the Tines logo), which appears on the right side of this block.

  5. Actions Block

    • "type": "actions" groups interactive elements (in this case, two buttons: “Isolate” and “Ignore”).

    • Each button has:

      • "action_id" and "value" (to identify which button was clicked).

      • "url" referencing <<PROMPT("isolate_button")>> or <<PROMPT("ignore_button")>>. This means Tines will generate a link for each button so users can be taken to a Tines page or prompt when they click.


In short, this JSON creates a Slack message that shows the LimaCharlie detection details, includes an image, and offers two buttons for the analyst to either “Isolate” or “Ignore” the detected threat—all without leaving Slack.


{

  "channel": "#alerts",

  "blocks": [

    {

      "type": "header",

      "text": {

        "type": "plain_text",

        "text": "<<retrieve_lima_charlie_detections.body.cat>>"

      }

    },

    {

      "type": "section",

      "fields": [

        {

          "type": "mrkdwn",

          "text": "*Time:*\n<<retrieve_lima_charlie_detections.body.gen_time>>"

        },

        {

          "type": "mrkdwn",

          "text": "*Computer Name:*\n<<retrieve_lima_charlie_detections.body.detect.routing.hostname>>"

        },

        {

          "type": "mrkdwn",

          "text": "*Source IP:*\n<<retrieve_lima_charlie_detections.body.detect.routing.int_ip>>"

        },

        {

          "type": "mrkdwn",

          "text": "*Username:*\n<<retrieve_lima_charlie_detections.body.detect.event.USER_NAME>>"

        },

        {

          "type": "mrkdwn",

          "text": "*File Path:*\n<<retrieve_lima_charlie_detections.body.detect.event.FILE_PATH>>"

        },

        {

          "type": "mrkdwn",

          "text": "*Command Line:*\n<<retrieve_lima_charlie_detections.body.detect.event.COMMAND_LINE>>"

        },

        {

          "type": "mrkdwn",

          "text": "*Sensor ID:*\n<<retrieve_lima_charlie_detections.body.detect.routing.sid>>"

        },

        {

          "type": "mrkdwn",

          "text": "*Detection Link:*\n<<retrieve_lima_charlie_detections.body.link>>"

        }

      ],

      "accessory": {

        "type": "image",

        "alt_text": "cute cat"

      }

    },

    {

      "type": "actions",

      "elements": [

        {

          "type": "button",

          "text": {

            "type": "plain_text",

            "text": "Isolate"

          },

          "action_id": "isolate",

          "value": "isolate",

          "url": "<<PROMPT(\"isolate_button\")>>"

        },

        {

          "type": "button",

          "text": {

            "type": "plain_text",

            "text": "Ignore"

          },

          "action_id": "ignore",

          "value": "ignore",

          "url": "<<PROMPT(\"ignore_button\")>>"

        }

      ]

    }

  ]

}


Remember the OAuth token we copied and saved to our notepad. Well in Tines, we now have to create a  new Slack Credential.


Once you're in credentials click new.


 

Click on Slack and this time choose use my own slack app.


Paste the OAuth token from the Isolation app in here.


Go back to our story (playbook).

 

Select the Slack send a block message we are working on and change the connection from Slack to the Slack 1 credential.


Next connect the webhook that retrieve LimaCharlie Detections to it.


Rerun/ "Re-emit" the last event from the webhook.


Let's check to see if we got a message in Slack? Oh our interactive message didn't work. We only got the message from the original slack action.


I see what's wrong.

 

We forgot to paste into our new slack action the alerts channel ID. Go ahead and copy from the old slack action and  paste that info in to our new one.


Now test it again.


Voila


If you try to click Isolate or ignore it redirects you to a generic thank you for your submission page. I got that field from the initial contents of block message before we deleted it and put the custom one.


In this case, I clicked Isolate. This is where the custom app we created comes in. Remember that we created a subsequent webhook and gave Slack the webhook URL as the request URL. That way we can receive the user's choice and now adjust our Trigger action "User says yes" and "Trigger action "User says no". To trigger based on the JSON returned from Slack which contains the action the user took on the message within our Slack custom app.

 

Do you notice the webhook now has a notification?Click on the webhook then event


The Slack Custom Interactive App (Isolation App) sent the user action back to the webhook using the request URL we specified in slack in our custom app.


If we scroll down, inside the payload what we need is the action id value.

 

We need to be able to parse the payload so that we can use that as our trigger condition, effectively replacing our page action.


Then customize output.


We are going to use the json_parse function to parse this out.

 

The function `JSON_PARSE` is designed to take a JSON string as input and convert it into a structured format that can be used programmatically (e.g., accessing specific properties or fields). In other words, this function converts the raw JSON data into an object that Tines can reference and manipulate. Once it’s parsed, you can use dot notation or array indexing to dig deeper into specific parts of the JSON (like accessing a value for a particular key or action ID). So yes, it is effectively a function to parse JSON and make the data accessible in a structured way for further processing in your Tines story

 

 "=JSON_PARSE(OUTPUT.body.payload)" is a function call that takes the raw JSON string (OUTPUT.body.payload) and converts it into a JSON object. This lets you access nested fields using dot notation (for example, JSON_PARSE(OUTPUT.body.payload).actions[0].value).


Test the Slack message again and choose isolate again in the message in the alerts channel, it will replay the JSON just received and use our function to parse it.


Go to webhook events. We see the new event and everything is parsed. It's a good way down but we find the action _id in the object inside the array actions.


We want to connect the webhook to a Trigger. I think we are  at a good point to remove the page action and those connections. Connect the webhook to the triggers instead.


Move the webhook over to the center. Maybe rename it to, "Retrieve User's decision: Isolated/ Ignore. Then connect it to the triggers.


This is going to break our workflow temporarily because our rules in our triggers still look for the user's decision from the Page Action (user prompt) values.

 

We need the new events in JSON to trickle down. That way we can change the JSON path to look for the value containing the user's decision: Isolate or Ignore from the webhook.

Re-emit/re-run the last event in the slack action by testing.


Use one of the most recent successful events.


Then go in slack and click isolate.


We should be able to find the value we are look for now. Here's the easy way to do this.

Open the webhook events. Copy the key for the field we are interested inside the JSON.


Paste it in notepad.

Click on the trigger user says no > rule > delete the entire thing > paste the json path to the key value pair for action_id.  Do you notice the trigger now picks up the value for the action id? We can adjust our User says yes condition to state that if this key equals to isolate then.. It will fire.



Do the same thing for the Trigger action "user says no". Hint: it's the exact same JSON.


Let's test. From the slack action again. Let's go.


Click Isolate in Slack.



I checked and no isolation occurred. So down here it seems we have an issue. Although I didn't mentioned it, given that we're at the testing for isolation phase I would hope you checked using ping and also checked the Lima Charlie interface.


Taking a second to re-assess...I'm noticing a problem. The retrieve Lima Charlie Detections event isn't listed in the data received. It is not making its way down the playbook because the webhook isn't connected to it. Therefore, it isn't able to retrieve the sensor id to perform isolation. We will need to pass the data along as metadata.


Looking at the data that is received from the Retrieve user's decision isolate/ignore webhook. I think we can use the value key to pass the sensor id and the hostname to  Slack inside the button. That way when the it comes back we will be able to reference the paths to those values.


Something like this

"value":"<<retrieve_lima_charlie_detections.body.detect.routing.sid>>|<<retrieve_lima_charlie_detections.body.detect.routing.hostname>>"

 

To do this, Let's edit the Slack Action's block message to contain the variables for sensor id and hostname, using the format above. We can always manipulate the string using split later.


Scroll down to the action section of the block.

For isolate and ignore we will pass on both variables for the hostname and the sensor id separated by a pipe. Format matters here, be careful.



"<<retrieve_lima_charlie_detections.body.detect.routing.sid>>|<<retrieve_lima_charlie_detections.body.detect.routing.hostname>>"


 

Now test > On slack click isolate > then we can check the webhook events to see how it came in.


We see that we are getting both pieces of data.


Let's go to the Isolate sensor action and try to figure out a way to pull the data relevant for that action - sensor id.

 

So we are able to find the value through the below object path. We now need to manipulate the value to only retrieve the sensor id which is to the left of the pipe.

 

retrieve_user_s_decision_isolate_ignore.actions[0].value


Using a split function we can do this. SPLIT(string, delimiter) returns an array (a list of values), where each element is a substring from the original string that was split based on the specified delimiter

The function we will use is below SPLIT(retrieve_user_s_decision_isolate_ignore.actions[0].value, "|")[0]


Copy and paste this path in the Get isolation status action, URL path as well.


Then for the last action on this side of the tree we need to send a Slack message with the computer name saying that it was isolated. So we will copy and paste the JSON path to the value again, but this time change the zero (0) to one (1) so that we can get the hostname part of the value. We essentially split the values and now are able to pull them from an array.

 

The result is null because the data didn't make it this far down.


Its null because we have to re-emit the most recent webhook with the data adjust so it could be split properly. Before we re-emit, start a continuous ping and make sure the device is not isolated currently.


It now populates and our device is also likely isolated. Check Limacharlie.


It's also isolated in Limacharlie.


We also get confirmation via Tines that the device was successfully isolated.


Copy the split function we used to retrieve the hostname from the value to the Slack message action that is triggered if we decide to not isolate.


It will be null until we re-emit the slack message action that triggers the block kit message to be sent to slack. And this time we'd have to click ignore.


We clicked ignore and it says the computer has not been isolated.


We get the message!


So this is officially the end of the bonus section and also the entire lab.

 

Conclusion: Automating Incident Response with Tines & Slack


Thanks for sticking with me through this, if you made it this far you are a real one. We built a hands-on automation workflow integrating LimaCharlie (EDR), Tines (SOAR), and Slack to improve security response. We started by using a Tines Page Action to prompt analysts for decisions, but later optimized the process by leveraging Slack interactive buttons for direct response.


This project introduced key EDR (Endpoint Detection & Response) concepts, showing how detections are generated and how we can automate actions like host isolation. We also explored string manipulation techniques, such as using SPLIT() to extract key data like sensor_id and hostname, ensuring smooth automation.


So what are our key takeaways:

Understanding EDR detections and SOAR automation

Optimizing response actions from Tines Pages to Slack buttons

String parsing and data transformation for automation workflows

 

Where to go from here?

While this workflow effectively automates security response, there are several areas for improvement and scalability. One key enhancement would be adding richer context to detections, such as correlating alerts with threat intelligence sources to prioritize incidents. We could also integrate case management tools like Jira or ServiceNow to track analyst decisions and automate ticketing. Another improvement would be enhancing logging and auditing, ensuring every action taken—whether isolation or ignore—is recorded for compliance and forensic analysis. For scalability, this solution could be expanded to handle multiple security tools, aggregating detections from different EDRs or SIEMs into a unified response pipeline. Additionally, role-based access control (RBAC) could be implemented, ensuring only authorized analysts can trigger isolation. By refining these areas, we can make this workflow even more robust, scalable, and enterprise-ready.

 
 
 

Comments


©2025 by The SOC spot

bottom of page