Human-in-the-Loop Response Automation¶
This tutorial walks through building an end-to-end workflow that detects a credential dumping tool, asks a SOC analyst for approval before isolating the host, and executes the response via a Python playbook. It demonstrates how D&R rules, the Feedback extension, and Playbooks work together to implement human-in-the-loop security automation.
What You Will Build¶
NEW_PROCESS event (mimikatz.exe)
|
v
D&R rule detects it
|
v
ext-feedback sends approval
request to Slack channel
(suppressed per-host per-hour)
|
v
Analyst clicks Approve or Deny
(auto-denied after 10 minutes)
|
v
Playbook receives the response
|
+---+---+
| |
Approved Denied
| |
Isolate Tag host
the host for monitoring
Why human-in-the-loop? Automated isolation is effective but disruptive. A sysadmin running a legitimate tool on a server, or a red team engagement, can trigger credential-tool detections. Asking a human before isolating avoids costly false-positive disruptions while still ensuring fast response when the threat is real.
Prerequisites¶
Subscribe to the following extensions in the LimaCharlie marketplace:
- Feedback (
ext-feedback) -- delivers approval requests and collects responses - Playbook (
ext-playbook) -- executes Python playbooks
You also need a Slack Tailored Output configured with a slack_api_token and slack_channel. See Feedback Slack Setup for details on creating a Slack App with interactivity enabled.
No Slack?
You can use any channel type (Telegram, Microsoft Teams, email, or web). Replace the channel type in Step 1 accordingly. The D&R rule, playbook, and response flow are identical regardless of channel.
Step 1: Create a Feedback Channel¶
Add a Slack channel to the Feedback extension config. This tells ext-feedback where to deliver approval requests.
Verify the channel was created:
Step 2: Write the D&R Rule¶
This rule detects credential dumping tools and asks for human approval before taking action. The response is routed to a playbook that handles isolation or monitoring.
Detection¶
The detection matches processes whose file path ends with known credential dumping tool names:
op: or
rules:
- op: ends with
event: NEW_PROCESS
path: event/FILE_PATH
value: mimikatz.exe
case sensitive: false
- op: ends with
event: NEW_PROCESS
path: event/FILE_PATH
value: procdump.exe
case sensitive: false
- op: ends with
event: NEW_PROCESS
path: event/FILE_PATH
value: pypykatz.exe
case sensitive: false
Note
This is a simplified detection for illustration. Production rules would include additional context (e.g., command-line arguments targeting lsass, hash lookups, or behavioral patterns).
Response¶
The response has two actions: report the detection, and request approval via ext-feedback.
- action: report
name: cred-tool-detected - {{ .routing.hostname }}
- action: extension request
extension name: ext-feedback
extension action: request_simple_approval
extension request:
channel: '{{ "soc-approvals" }}'
question: '{{ "Credential dumping tool detected on " }}{{ .routing.hostname }}{{ " (process: " }}{{ base .event.FILE_PATH }}{{ "). Isolate this host?" }}'
feedback_destination: '{{ "playbook" }}'
playbook_name: '{{ "handle-isolation-decision" }}'
approved_content:
sid: routing.sid
hostname: routing.hostname
file_path: event.FILE_PATH
action: '{{ "isolate" }}'
denied_content:
sid: routing.sid
hostname: routing.hostname
file_path: event.FILE_PATH
action: '{{ "monitor" }}'
timeout_seconds: '{{ 600 }}'
timeout_choice: '{{ "denied" }}'
suppression:
max_count: 1
period: 1h
is_global: false
keys:
- 'cred-tool-isolation'
- '{{ .routing.hostname }}'
Key design decisions:
- Suppression prevents the same host from flooding the Slack channel. Keyed on hostname, it ensures at most one approval request per host per hour. Without this, a noisy process restarting repeatedly would generate dozens of Slack messages.
- Timeout auto-denies after 10 minutes. The playbook still runs on timeout (with
responder: "timeout"), so the host gets tagged for monitoring even if no one is watching Slack. approved_content/denied_contentcarry the sensor ID and hostname through the human decision point, so the playbook has everything it needs to act without additional API calls to look up the sensor.
Step 3: Create the Playbook¶
Create a playbook named handle-isolation-decision in the Playbooks section (Automation > Playbooks in the web UI), or via Infrastructure as Code.
This playbook receives the feedback response and either isolates the host or tags it for monitoring.
Playbook Code¶
from limacharlie.Sensor import Sensor
def playbook(sdk, data):
if not sdk:
return {"error": "credentials required"}
content = data.get("content", {})
choice = data.get("choice")
responder = data.get("responder")
action = content.get("action")
sid = content.get("sid")
hostname = content.get("hostname", "unknown")
file_path = content.get("file_path", "unknown")
if not sid:
return {"error": "no sensor ID in response content"}
sensor = Sensor(sdk, sid)
if action == "isolate":
# Isolate the host from the network.
sensor.isolateNetwork()
# Generate a detection so this shows up in the Detections view.
return {
"data": {
"action": "isolated",
"hostname": hostname,
"approved_by": responder,
},
"detection": {
"summary": f"Host {hostname} isolated after cred-tool detection (approved by {responder})",
"sid": sid,
"hostname": hostname,
"file_path": file_path,
},
"cat": "Credential-Tool-Host-Isolated",
}
else:
# Tag the host for enhanced monitoring instead of isolating.
sensor.tag("cred-tool-monitor", ttl=86400)
return {
"data": {
"action": "monitoring",
"hostname": hostname,
"decided_by": responder,
"reason": "denied" if choice == "denied" else "timeout",
},
}
What the Playbook Receives¶
When ext-feedback dispatches to a playbook, the data parameter contains:
| Field | Description |
|---|---|
request_id |
UUID of the feedback request |
question |
The original question text |
choice |
approved or denied |
responder |
Username of the person who responded, or "timeout" |
content |
The JSON from approved_content or denied_content (whichever matches the choice) |
The content field is where the D&R rule's context (sensor ID, hostname, file path) arrives. This is why the approved_content and denied_content in the D&R rule include sid and hostname -- they travel through the feedback system and arrive intact in the playbook.
Infrastructure as Code¶
To manage the playbook via git-sync:
hives:
playbook:
handle-isolation-decision:
data:
python: |-
from limacharlie.Sensor import Sensor
def playbook(sdk, data):
if not sdk:
return {"error": "credentials required"}
content = data.get("content", {})
choice = data.get("choice")
responder = data.get("responder")
action = content.get("action")
sid = content.get("sid")
hostname = content.get("hostname", "unknown")
file_path = content.get("file_path", "unknown")
if not sid:
return {"error": "no sensor ID in response content"}
sensor = Sensor(sdk, sid)
if action == "isolate":
sensor.isolateNetwork()
return {
"data": {
"action": "isolated",
"hostname": hostname,
"approved_by": responder,
},
"detection": {
"summary": f"Host {hostname} isolated after cred-tool detection (approved by {responder})",
"sid": sid,
"hostname": hostname,
"file_path": file_path,
},
"cat": "Credential-Tool-Host-Isolated",
}
else:
sensor.tag("cred-tool-monitor", ttl=86400)
return {
"data": {
"action": "monitoring",
"hostname": hostname,
"decided_by": responder,
"reason": "denied" if choice == "denied" else "timeout",
},
}
usr_mtd:
enabled: true
expiry: 0
tags: []
comment: "Handles isolation decisions from ext-feedback approval requests"
Warning
The playbook needs an API key with sensor.set permissions (for isolation and tagging). When triggering the playbook from a D&R rule, pass credentials via the credentials field, or configure the playbook extension with a default API key.
Step 4: Test the Workflow¶
You can test the full flow without waiting for a real detection by sending a feedback request directly from the CLI:
limacharlie feedback request-approval \
--channel soc-approvals \
--question "TEST: Credential dumping tool detected on workstation-42. Isolate this host?" \
--destination playbook --playbook handle-isolation-decision \
--approved-content '{"sid": "YOUR_SENSOR_SID", "hostname": "workstation-42", "file_path": "C:\\tools\\mimikatz.exe", "action": "isolate"}' \
--denied-content '{"sid": "YOUR_SENSOR_SID", "hostname": "workstation-42", "file_path": "C:\\tools\\mimikatz.exe", "action": "monitor"}' \
--timeout 120 --timeout-choice denied
Replace YOUR_SENSOR_SID with a real sensor ID from your organization. After running this command:
- A message appears in your Slack channel with Approve and Deny buttons
- Click Approve -- the
handle-isolation-decisionplaybook runs and isolates the sensor - Click Deny -- the playbook tags the sensor with
cred-tool-monitorfor 24 hours
If you want to test without affecting a real sensor, use the web channel instead to get a URL you can open in a browser:
limacharlie feedback channel add --name test-web --type web
limacharlie feedback request-approval \
--channel test-web \
--question "TEST: Isolate workstation-42?" \
--destination playbook --playbook handle-isolation-decision \
--approved-content '{"sid": "test-sid", "hostname": "workstation-42", "file_path": "mimikatz.exe", "action": "isolate"}' \
--denied-content '{"sid": "test-sid", "hostname": "workstation-42", "file_path": "mimikatz.exe", "action": "monitor"}'
The CLI returns a url you can open in your browser to respond.
How It All Fits Together¶
- A
NEW_PROCESSevent fires when mimikatz.exe runs on an endpoint - The D&R rule matches and sends a
request_simple_approvalto ext-feedback - Suppression checks whether this host already has a pending request (keyed on hostname, 1-hour window) -- if so, the action is skipped
- ext-feedback delivers the question to Slack with Approve/Deny buttons
- A SOC analyst clicks Approve (or the 10-minute timeout fires and auto-denies)
- ext-feedback routes the response through the webhook adapter and D&R pipeline
- The
handle-isolation-decisionplaybook runs with the analyst's choice and the event context - The playbook isolates the host (if approved) or tags it for monitoring (if denied/timed out)
Extending the Pattern¶
Escalation chain: If the timeout fires, have the playbook send a second feedback request to a different channel (e.g., management-approvals) with a shorter timeout before auto-isolating.
Multi-step workflow: Chain multiple feedback requests. For example, after isolation approval, ask "Run memory forensics on this host?" with the response going to a second playbook that triggers Velociraptor or a Dumper collection.
Audit trail: Set the feedback destination to case instead of playbook to log every approval decision as a case note, creating a reviewable audit trail.