Skip to content

Commit 1e20808

Browse files
Merge branch 'release/3.6.1'
2 parents a4de7ba + 51ca3d8 commit 1e20808

File tree

7 files changed

+154
-51
lines changed

7 files changed

+154
-51
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Changelog
22

3+
## [3.6.0](https://github.com/TheHive-Project/Cortex-Analyzers/tree/3.6.0) (2025-10-06)
4+
5+
[Full Changelog](https://github.com/TheHive-Project/Cortex-Analyzers/compare/3.5.26...3.6.0)
6+
7+
**Merged pull requests:**
8+
9+
- Update requirements.txt [\#1385](https://github.com/TheHive-Project/Cortex-Analyzers/pull/1385) ([vpiserchia](https://github.com/vpiserchia))
10+
- Add Slack responder to sync created channels into task [\#1384](https://github.com/TheHive-Project/Cortex-Analyzers/pull/1384) ([nusantara-self](https://github.com/nusantara-self))
11+
- CI - Add trivy scans & manual full rebuild [\#1383](https://github.com/TheHive-Project/Cortex-Analyzers/pull/1383) ([nusantara-self](https://github.com/nusantara-self))
12+
- Update Velociraptor Responder [\#986](https://github.com/TheHive-Project/Cortex-Analyzers/pull/986) ([weslambert](https://github.com/weslambert))
13+
314
## [3.5.26](https://github.com/TheHive-Project/Cortex-Analyzers/tree/3.5.26) (2025-09-09)
415

516
[Full Changelog](https://github.com/TheHive-Project/Cortex-Analyzers/compare/3.5.25...3.5.26)
@@ -9,6 +20,8 @@
920
- Analyzer Capa - Fixes [\#1382](https://github.com/TheHive-Project/Cortex-Analyzers/pull/1382) ([nusantara-self](https://github.com/nusantara-self))
1021
- Proofpoint - Fix a typo in folder and neurons name [\#1381](https://github.com/TheHive-Project/Cortex-Analyzers/pull/1381) ([nusantara-self](https://github.com/nusantara-self))
1122
- CI - Build Improvements [\#1380](https://github.com/TheHive-Project/Cortex-Analyzers/pull/1380) ([nusantara-self](https://github.com/nusantara-self))
23+
- Add CIRCL AIL Onion-Lookup Analyzer [\#1379](https://github.com/TheHive-Project/Cortex-Analyzers/pull/1379) ([nusantara-self](https://github.com/nusantara-self))
24+
- Neurons description improvements [\#1378](https://github.com/TheHive-Project/Cortex-Analyzers/pull/1378) ([nusantara-self](https://github.com/nusantara-self))
1225

1326
## [3.5.25](https://github.com/TheHive-Project/Cortex-Analyzers/tree/3.5.25) (2025-08-25)
1427

responders/Slack/README.md

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@ This directory contains two Slack responders for TheHive integration:
1818
- Invites default participants by email
1919
- Sets channel visibility (private or public)
2020
- Posts case summary and/or case description (optional)
21+
- **Automatically tags the case** with `slack:<channel_name>` for easy tracking
2122

22-
### Slack_SyncChannel
23-
- Retrieves all conversation history from channels with format `#case-CASEID`
23+
### Slack_SyncChannel
24+
- **Syncs all Slack channels tagged with `slack:` prefix** on the case
25+
- Retrieves all conversation history from tagged channels
26+
- **Fallback**: If no tags found, uses default format `#case-CASEID`
2427
- Creates TheHive tasks in "Communication" category with individual task logs for each message
2528
- Downloads and attaches file attachments (images, documents) to task logs
2629
- Chronologically ordered messages with timestamps and usernames
2730
- Prevents duplicate syncing by tracking message timestamps
2831
- Converts Slack user IDs to readable usernames for better readability
32+
- **Multi-channel support**: Syncs multiple channels if multiple `slack:` tags exist
2933

3034
## Preview
3135

@@ -85,5 +89,26 @@ This directory contains two Slack responders for TheHive integration:
8589

8690
## 2. Enable and configure the Responders
8791

88-
Log into your Cortex instance, go to Organization > Responders and enable the desired JIRA responders with the appropriate configuration & API keys.
92+
Log into your Cortex instance, go to Organization > Responders and enable the desired Slack responders with the appropriate configuration & API keys.
8993

94+
---
95+
96+
## Privacy & Security Considerations
97+
98+
### Channel Tagging
99+
When `Slack_CreateChannel` runs, it automatically adds a `slack:<channel_name>` tag to the case. This tag:
100+
- Makes it easy to identify which Slack channel(s) are associated with a case
101+
- Enables `Slack_SyncChannel` to reliably find channels without reconstructing names
102+
- Can be manually added to sync additional channels (see warning below)
103+
104+
### Multi-Channel Syncing
105+
`Slack_SyncChannel` will sync **all** channels that have `slack:` tags on the case:
106+
- Each channel creates its own separate task in TheHive
107+
- Partial failures are handled gracefully (some channels may sync, others may fail)
108+
- Failed channels are reported in the responder output
109+
110+
### Access Control Warning ⚠️
111+
- The bot can only read channels it has been **invited to**
112+
- Syncing brings Slack conversations into TheHive: ensure case permissions align with channel access
113+
- Private Slack channels synced to non-private TheHive cases may expose sensitive information
114+
- Consider your organization's data governance policies before syncing channels

responders/Slack/slack.py

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def run(self):
6969
.lower()
7070
)
7171
channel_name = channel_name[:80]
72+
self.channel_name = channel_name
7273

7374
headers = {
7475
"Authorization": f"Bearer {self.slack_token}",
@@ -192,49 +193,72 @@ def format_tlp(level):
192193
case_id = self.get_param("data.caseId")
193194
case_unique_id = self.get_param("data.id")
194195

195-
# Channel name format: case-CASEID
196-
channel_name = (
197-
f"{self.channel_prefix}{case_id}".replace(" ", "-")
198-
.replace(".", "")
199-
.replace(",", "")
200-
.lower()
201-
)
202-
channel_name = channel_name[:80]
196+
# Collect all channel names from tags
197+
channel_names = []
198+
case_tags = self.get_param("data.tags", [])
199+
for tag in case_tags:
200+
if tag.startswith("slack:"):
201+
channel_names.append(tag[6:]) # Remove "slack:" prefix
202+
203+
# Fallback to reconstructing channel name if no tags found
204+
if not channel_names:
205+
channel_name = (
206+
f"{self.channel_prefix}{case_id}".replace(" ", "-")
207+
.replace(".", "")
208+
.replace(",", "")
209+
.lower()
210+
)
211+
channel_name = channel_name[:80]
212+
channel_names.append(channel_name)
203213

204214
headers = {
205215
"Authorization": f"Bearer {self.slack_token}",
206216
"Content-Type": "application/json",
207217
}
208218

209-
# Find the channel
210-
channel_id = self.find_existing_channel(channel_name, headers)
211-
if not channel_id:
212-
self.error(
213-
f"Channel '{channel_name}' not found. Create the channel first."
214-
)
219+
# Sync all channels
220+
synced_channels = []
221+
errors = []
215222

216-
# Get channel conversation history
217-
conversation_data = self.get_channel_conversations(channel_id, headers)
223+
for channel_name in channel_names:
224+
try:
225+
# Find the channel
226+
channel_id = self.find_existing_channel(channel_name, headers)
227+
if not channel_id:
228+
errors.append(f"Channel '{channel_name}' not found")
229+
continue
230+
231+
# Get channel conversation history
232+
conversation_data = self.get_channel_conversations(channel_id, headers)
233+
234+
# Create or update TheHive task with conversation data
235+
task_id, action = self.create_or_update_thehive_task(
236+
case_unique_id, channel_name, conversation_data
237+
)
218238

219-
# Create or update TheHive task with conversation data
220-
task_id, action = self.create_or_update_thehive_task(
221-
case_unique_id, channel_name, conversation_data
222-
)
239+
synced_channels.append({
240+
"channel_name": channel_name,
241+
"channel_id": channel_id,
242+
"task_id": task_id,
243+
"action": action
244+
})
245+
246+
except Exception as e:
247+
errors.append(f"Error syncing '{channel_name}': {str(e)}")
248+
249+
# Build report
250+
if not synced_channels and errors:
251+
self.error(f"Failed to sync channels: {', '.join(errors)}")
223252

224-
# Include debug info in the main report
225253
report_data = {
226-
"channel_name": channel_name,
227-
"channel_id": channel_id,
228-
"task_id": task_id,
229-
"action": action,
230-
"message": f"Slack channel `{channel_name}` conversation {action} in TheHive task {task_id}.",
254+
"synced_channels": synced_channels,
255+
"total_synced": len(synced_channels),
256+
"message": f"Synced {len(synced_channels)} channel(s)"
231257
}
232-
233-
# Add debug info if we have it
234-
if hasattr(self, '_last_debug_info'):
235-
report_data["debug_info"] = self._last_debug_info
236-
delattr(self, '_last_debug_info')
237-
258+
259+
if errors:
260+
report_data["errors"] = errors
261+
238262
self.report(report_data)
239263

240264
def get_channel_conversations(self, channel_id, headers):
@@ -705,6 +729,17 @@ def create_or_update_thehive_task(self, case_id, channel_name, conversations):
705729

706730
except Exception as e:
707731
self.error(f"Failed to create task: {str(e)}")
732+
733+
def operations(self, raw):
734+
artifacts = []
735+
# AddTagToArtifact ({ "type": "AddTagToArtifact", "tag": "tag to add" }): add a tag to the artifact related to the object
736+
# AddTagToCase ({ "type": "AddTagToCase", "tag": "tag to add" }): add a tag to the case related to the object
737+
# MarkAlertAsRead: mark the alert related to the object as read
738+
# AddCustomFields ({"name": "key", "value": "value", "tpe": "type"): add a custom field to the case related to the object
739+
if self.service == "createchannel":
740+
if hasattr(self, 'channel_name'):
741+
artifacts.append(self.build_operation("AddTagToCase", tag=f"slack:{self.channel_name}"))
742+
return artifacts
708743

709744

710745
if __name__ == "__main__":

responders/Velociraptor/README.md

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,21 @@
22
This responder can be used to run a flow for a Velociraptor artifact. This could include gathering data, or performing initial response, as the artifact (or artifact "pack") could encompass any number of actions. The responder can be run on an observable type of `ip`, `fqdn`, or `other`, and will look for a matching client via the Velociraptor server. If a client match is found for the last seen IP, or the hostname, the responder will kick off the flow, the results will be returned, and the client ID will be added as a tag to the case and the observable.
33

44
#### Requirements
5-
The following options are required in the Velociraptor Responder configuration:
6-
7-
- `velociraptor_client_config`: The path to the Velociraptor API client config.
8-
(See the following for generating an API client config: https://www.velocidex.com/docs/user-interface/api/, and ensure the appropriate ACLs are granted to the API user).
9-
- `velociraptor_artifact`: The name artifact you which to collect (as you would see it in the Velociraptor GUI).
10-
- `upload_flow_results`: Upload flow results to TheHive case (bool).
11-
- `thehive_url`: URL of your TheHive installation (e.g. 'http://127.0.0.1:9000').
12-
- `thehive_apikey`: TheHive API key used to add flow results/file(s) to a case.
5+
The following options are required in the Velociraptor Responder configuration:
6+
7+
**API Client Configuration** (choose one):
8+
- `velociraptor_client_config`: The path to the Velociraptor API client config file.
9+
- `velociraptor_client_config_content_base64`: Base64-encoded API client config (recommended for SaaS/containerized deployments).
10+
11+
To generate an API client config, see: https://www.velocidex.com/docs/user-interface/api/
12+
Ensure the appropriate ACLs are granted to the API user.
13+
14+
For SaaS deployments, encode your API client config:
15+
```bash
16+
cat api_client.yaml | base64
17+
```
18+
Then paste the output into the `velociraptor_client_config_content_base64` parameter.
19+
20+
**Other Parameters**:
21+
- `velociraptor_artifact`: The name of the artifact you wish to collect (as you would see it in the Velociraptor GUI). **Required**.
22+
- `query_max_duration`: Max query duration in seconds (default: 600). **Optional**.
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
cortexutils
22
cryptography
33
grpcio-tools
4-
pyvelociraptor
5-
thehive4py~=1.8.1
4+
pyvelociraptor

responders/Velociraptor/velociraptor_flow.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,14 @@
1414
"description": "Path to API client config file",
1515
"type": "string",
1616
"multi": false,
17-
"required": true,
18-
"defaultValue": ""
17+
"required": false
18+
},
19+
{
20+
"name": "velociraptor_client_config_content_base64",
21+
"description": "Base64-encoded API client config file (YAML format). Recommended for SaaS/containerized deployments to preserve multiline YAML structure. Generate with: cat api_client.yaml | base64",
22+
"type": "string",
23+
"multi": false,
24+
"required": false
1925
},
2026
{
2127
"name": "velociraptor_artifact",

responders/Velociraptor/velociraptor_flow.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
import os
77
import time
88
import yaml
9-
from thehive4py.api import TheHiveApi
10-
from thehive4py.models import Case, CaseObservable
9+
import base64
1110
import pyvelociraptor
1211
from pyvelociraptor import api_pb2
1312
from pyvelociraptor import api_pb2_grpc
@@ -16,8 +15,24 @@
1615
class Velociraptor(Responder):
1716
def __init__(self):
1817
Responder.__init__(self)
19-
self.configpath = self.get_param('config.velociraptor_client_config', None, "File path missing!")
20-
self.config = yaml.load(open(self.configpath).read(), Loader=yaml.FullLoader)
18+
self.configpath = self.get_param('config.velociraptor_client_config', None)
19+
self.config_content_base64 = self.get_param('config.velociraptor_client_config_content_base64', None)
20+
21+
# Validate that at least one config parameter is provided
22+
if not self.configpath and not self.config_content_base64:
23+
self.error("Either velociraptor_client_config, or velociraptor_client_config_content_base64 must be provided!")
24+
25+
# Load config from content or file
26+
if self.config_content_base64:
27+
# Decode base64 content (preserves newlines)
28+
try:
29+
decoded_content = base64.b64decode(self.config_content_base64).decode('utf-8')
30+
self.config = yaml.load(decoded_content, Loader=yaml.FullLoader)
31+
except Exception as e:
32+
self.error(f"Failed to decode base64 config: {str(e)}")
33+
else:
34+
self.config = yaml.load(open(self.configpath).read(), Loader=yaml.FullLoader)
35+
2136
self.artifact = self.get_param('config.velociraptor_artifact', None, 'Artifact missing!')
2237
self.artifact_args = self.get_param('config.velociraptor_artifact_args', None)
2338
self.observable_type = self.get_param('data.dataType', None, "Data type is empty")

0 commit comments

Comments
 (0)