Skip to content

Commit e3e884f

Browse files
content-botxsoar-botmerit-maita
authored
[Marketplace Contribution] Azure Open AI Service (#41417)
* [Marketplace Contribution] Azure Open AI Service (#40724) * "pack contribution initial commit" * Update Packs/AzureOpenAIService/Integrations/AzureOpenAIServiceFrenchDCTest/AzureOpenAIServiceFrenchDCTest.py * Update Packs/AzureOpenAIService/Integrations/AzureOpenAIServiceFrenchDCTest/AzureOpenAIServiceFrenchDCTest.yml * Update Packs/AzureOpenAIService/Integrations/AzureOpenAIServiceFrenchDCTest/AzureOpenAIServiceFrenchDCTest.py * Update Packs/AzureOpenAIService/Integrations/AzureOpenAIServiceFrenchDCTest/AzureOpenAIServiceFrenchDCTest.yml * Update Packs/AzureOpenAIService/Integrations/AzureOpenAIServiceFrenchDCTest/AzureOpenAIServiceFrenchDCTest.py * Update Packs/AzureOpenAIService/Integrations/AzureOpenAIServiceFrenchDCTest/AzureOpenAIServiceFrenchDCTest.py * Update Packs/AzureOpenAIService/Integrations/AzureOpenAIServiceFrenchDCTest/AzureOpenAIServiceFrenchDCTest.py * Update Packs/AzureOpenAIService/pack_metadata.json * Update Packs/AzureOpenAIService/pack_metadata.json * Update Packs/AzureOpenAIService/Integrations/AzureOpenAIServiceFrenchDCTest/AzureOpenAIServiceFrenchDCTest.yml * Update Packs/AzureOpenAIService/Integrations/AzureOpenAIServiceFrenchDCTest/AzureOpenAIServiceFrenchDCTest.yml * Rename AzureOpenAIServiceFrenchDCTest.py to AzureOpenAIService.py * Rename AzureOpenAIServiceFrenchDCTest.yml to AzureOpenAIService.yml * Update Packs/AzureOpenAIService/Integrations/AzureOpenAIServiceFrenchDCTest/AzureOpenAIService.yml * Add files via upload --------- Co-authored-by: merit-maita <[email protected]> * added rn * edit * edit * edit * edit * edit * edit * edit * edit * edit --------- Co-authored-by: xsoar-bot <[email protected]> Co-authored-by: merit-maita <[email protected]> Co-authored-by: merit-maita <[email protected]>
1 parent d8e789a commit e3e884f

File tree

9 files changed

+341
-0
lines changed

9 files changed

+341
-0
lines changed

Packs/AzureOpenAIService/.pack-ignore

Whitespace-only changes.

Packs/AzureOpenAIService/.secrets-ignore

Whitespace-only changes.
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
import demistomock as demisto # noqa: F401
2+
from CommonServerPython import * # noqa: F401
3+
4+
"""
5+
Cortex XSOAR Integration for Azure OpenAI
6+
"""
7+
8+
import traceback
9+
import json
10+
11+
12+
# --- CONSTANTS ---
13+
API_VERSION = "2024-02-01" # Using a stable, recent API version
14+
DEFAULT_DEPLOYMENT = "gpt-4o"
15+
16+
17+
class Client(BaseClient):
18+
"""
19+
Client class to interact with the Azure OpenAI service.
20+
Inherits from BaseClient from CommonServerPython to handle SSL verification, etc.
21+
"""
22+
23+
def __init__(self, server_url, api_key, deployment_name, instruction, verify):
24+
# Ensure the URL ends with a /
25+
base_url = server_url if server_url.endswith("/") else f"{server_url}/"
26+
super().__init__(base_url=base_url, verify=verify)
27+
self.api_key = api_key
28+
self.deployment_name = deployment_name
29+
self.instruction = instruction
30+
31+
# FIX: Added 'require_json' parameter to conditionally request JSON format.
32+
def send_message(self, message: str, require_json: bool = False) -> dict:
33+
"""
34+
Sends a message to the Azure OpenAI 'chat completions' endpoint.
35+
36+
:param message: The user message to send.
37+
:param require_json: If True, requests the response in JSON object format.
38+
:return: The full JSON response from the API.
39+
"""
40+
full_url_path = f"openai/deployments/{self.deployment_name}/chat/completions?api-version={API_VERSION}"
41+
headers = {"Content-Type": "application/json", "api-key": self.api_key}
42+
payload = {
43+
"messages": [{"role": "system", "content": self.instruction}, {"role": "user", "content": message}],
44+
"max_tokens": 2000,
45+
"temperature": 0.5,
46+
"top_p": 0.95,
47+
"frequency_penalty": 0,
48+
"presence_penalty": 0,
49+
"stop": None,
50+
"stream": False,
51+
}
52+
53+
# FIX: Only add the 'response_format' parameter when it's explicitly required.
54+
if require_json:
55+
payload["response_format"] = {"type": "json_object"}
56+
57+
# The _http_request method handles the HTTP request
58+
response = self._http_request(
59+
method="POST", url_suffix=full_url_path, headers=headers, json_data=payload, resp_type="json"
60+
)
61+
return response
62+
63+
64+
# --- COMMAND FUNCTIONS ---
65+
66+
67+
def test_module(client: Client) -> str:
68+
"""
69+
Tests API connectivity by sending a simple message.
70+
"""
71+
try:
72+
# Use a simple instruction for the test.
73+
client.instruction = "You are a helpful assistant. Respond with 'ok' if you are working."
74+
# FIX: Call send_message without requiring a JSON response.
75+
client.send_message("This is a connection test.", require_json=False)
76+
return "ok"
77+
except DemistoException as e:
78+
if "401" in str(e):
79+
return "Authentication Error: Check your API Key."
80+
elif "404" in str(e):
81+
return "Connection Error: Check the Endpoint URL and Deployment Name."
82+
else:
83+
# Provide the specific error message from the API for easier debugging.
84+
return f"An API error occurred: {str(e)}"
85+
86+
87+
def send_message_command(client: Client, args: dict) -> CommandResults:
88+
"""
89+
Executes the command to send a message and process the structured response.
90+
"""
91+
message = args.get("message", "")
92+
93+
demisto.debug(f"Sending message to Azure OpenAI: '{message}'")
94+
# FIX: Call send_message *with* the requirement for a JSON response.
95+
raw_response = client.send_message(message, require_json=True)
96+
97+
# Extract the response content
98+
raw_answer = ""
99+
if raw_response.get("choices") and isinstance(raw_response["choices"], list) and len(raw_response["choices"]) > 0:
100+
first_choice = raw_response["choices"][0]
101+
if first_choice.get("message") and first_choice.get("message").get("content"):
102+
raw_answer = first_choice["message"]["content"]
103+
104+
if not raw_answer:
105+
raise DemistoException("Could not get a valid response from the API.", res=raw_response)
106+
107+
# Logic to handle the structured JSON response
108+
try:
109+
# Attempt to parse the response as JSON
110+
parsed_data = json.loads(raw_answer)
111+
112+
# Map the parsed data to the desired output keys
113+
outputs = {
114+
"IncidentAIVerdict": parsed_data.get("IncidentAIVerdict"),
115+
"AISummary": parsed_data.get("AISummary"),
116+
"Justification": parsed_data.get("Justification"),
117+
"ConfidenceScore": parsed_data.get("ConfidenceScore"),
118+
"EmailHeaderAIAnalysis": parsed_data.get("EmailHeaderAIAnalysis"),
119+
"EmailAISummary": parsed_data.get("EmailAISummary"),
120+
"EmailAIVerdict": parsed_data.get("EmailAIVerdict"),
121+
"Answer": parsed_data,
122+
}
123+
124+
# Create a formatted, human-readable output
125+
readable_output = "## 🤖 AI Analysis\n\n"
126+
readable_output += f"**Verdict:** {outputs.get('IncidentAIVerdict', 'N/A')}\n"
127+
readable_output += f"**Confidence:** {outputs.get('ConfidenceScore', 'N/A')}\n"
128+
readable_output += f"**Summary:**\n---\n{outputs.get('AISummary', 'N/A')}\n\n"
129+
readable_output += f"**Justification:**\n---\n{outputs.get('Justification', 'N/A')}\n\n"
130+
readable_output += "### Detailed Email Analysis\n"
131+
readable_output += f"**Email Verdict:** {outputs.get('EmailAIVerdict', 'N/A')}\n"
132+
readable_output += f"**Email Summary:**\n---\n{outputs.get('EmailAISummary', 'N/A')}\n\n"
133+
readable_output += f"**Header Analysis:**\n---\n{outputs.get('EmailHeaderAIAnalysis', 'N/A')}\n"
134+
135+
# Update the incident's custom fields
136+
custom_fields_to_set = {
137+
"incidentaiverdict": outputs.get("IncidentAIVerdict"),
138+
"aisummary": outputs.get("AISummary"),
139+
"justification": outputs.get("Justification"),
140+
"confidencescore": outputs.get("ConfidenceScore"),
141+
"emailheaderaianalysis": outputs.get("EmailHeaderAIAnalysis"),
142+
"emailaisummary": outputs.get("EmailAISummary"),
143+
"emailaiverdict": outputs.get("EmailAIVerdict"),
144+
}
145+
filtered_custom_fields = {k: v for k, v in custom_fields_to_set.items() if v is not None}
146+
if filtered_custom_fields:
147+
try:
148+
demisto.executeCommand("setIncident", {"customFields": filtered_custom_fields})
149+
except Exception as e:
150+
demisto.debug(f"Could not set incident fields. Error: {e}")
151+
152+
except json.JSONDecodeError:
153+
demisto.debug("AI response was not valid JSON. Treating as raw text.")
154+
outputs = {"Answer": raw_answer}
155+
readable_output = f"## Azure OpenAI Response (Raw Text)\n\n**Response:**\n---\n{raw_answer}\n"
156+
try:
157+
demisto.executeCommand("setIncident", {"customFields": {"aianswer": raw_answer}})
158+
except Exception as e:
159+
demisto.debug(f"Could not set the 'aianswer' incident field. Error: {e}")
160+
161+
return CommandResults(
162+
readable_output=readable_output,
163+
outputs_prefix="AzureOpenAI.Analysis",
164+
outputs_key_field="IncidentAIVerdict",
165+
outputs=outputs,
166+
raw_response=raw_response,
167+
)
168+
169+
170+
# --- MAIN FUNCTION ---
171+
172+
173+
def main() -> None:
174+
"""
175+
Main function, parses parameters and executes commands.
176+
"""
177+
params = demisto.params()
178+
server_url = params.get("url")
179+
180+
api_key_details = params.get("credentials").get("password")
181+
api_key = api_key_details.get("password") if isinstance(api_key_details, dict) else api_key_details
182+
183+
instruction = params.get("instruction")
184+
if not instruction or "{" not in instruction:
185+
demisto.debug("Instruction does not seem to request JSON. Using a default prompt for email analysis.")
186+
instruction = """
187+
You are an expert cybersecurity analyst. Analyze the provided data.
188+
Your response MUST be a single, valid JSON object. Do NOT provide ANY text outside of the JSON object.
189+
The JSON format must be as follows:
190+
{
191+
"IncidentAIVerdict": "string (Malicious, Suspicious, Benign, Informational)",
192+
"AISummary": "string (A 2-3 sentence global summary of the incident.)",
193+
"Justification": "string (The primary reason for the verdict, based on the strongest evidence.)",
194+
"ConfidenceScore": "integer (A confidence score from 0 to 100 for the verdict.)",
195+
"EmailHeaderAIAnalysis": "string (A detailed analysis of the email headers, including SPF/DKIM/DMARC and routing.)",
196+
"EmailAISummary": "string (A summary of the analysis of the email body, URLs, and attachments.)",
197+
"EmailAIVerdict": "string (Phishing, Malware, Spam, BEC, Safe)"
198+
}
199+
"""
200+
201+
deployment_name = params.get("deployment_name") or DEFAULT_DEPLOYMENT
202+
verify_certificate = not params.get("insecure", False)
203+
204+
command = demisto.command()
205+
args = demisto.args()
206+
demisto.debug(f"Command being executed is {command}")
207+
208+
try:
209+
client = Client(
210+
server_url=server_url,
211+
api_key=api_key,
212+
deployment_name=deployment_name,
213+
instruction=instruction,
214+
verify=verify_certificate,
215+
)
216+
217+
if command == "test-module":
218+
return_results(test_module(client))
219+
elif command == "azure-openai-send-message":
220+
return_results(send_message_command(client, args))
221+
else:
222+
raise NotImplementedError(f"Command '{command}' is not implemented.")
223+
224+
except Exception as e:
225+
demisto.error(traceback.format_exc())
226+
return_error(f"Failed to execute {command} command. Error: {str(e)}")
227+
228+
229+
# --- ENTRY POINT ---
230+
231+
if __name__ in ("__main__", "__builtin__", "builtins"):
232+
main()
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
category: Utilities
2+
sectionOrder:
3+
- Connect
4+
- Collect
5+
commonfields:
6+
id: Azure Open AI Service
7+
version: -1
8+
configuration:
9+
- display: Azure Open AI URL
10+
name: url
11+
required: true
12+
section: Connect
13+
type: 0
14+
- defaultvalue: "Role and Goal: You are a Senior Security Engineer with expertise in threat analysis, incident response, and email security protocols. Your objective is to conduct a thorough analysis of the provided email data to identify threats, determine the risk level, and provide actionable recommendations. If there is an Email key in the context, it means that the following email has been flagged as suspicious by an automated system or a user. You must perform a deep-dive analysis to confirm its nature and provide a security report. You are a senior cybersecurity analyst and an expert in threat intelligence. Your mission is to analyze the provided data (such as IP addresses, URLs, file hashes, or email subjects) and classify each item according to its threat level. Use the following severity scale: Low: The item is confirmed to be benign. There is no evidence of malicious activity. Medium: The item is suspicious. It may be associated with unwanted or potentially harmful activity, but direct malicious intent is not confirmed. Further investigation may be required. High: The item is confirmed to be malicious. It is actively associated with threats such as malware, phishing, or command and control (C2) servers. Critical: The item is confirmed to be malicious and poses an immediate and severe threat to the organization. This includes active ransomware, advanced persistent threat (APT) activity, or data exfiltration. Your response MUST be a single, valid JSON object. Do NOT provide ANY text outside of the JSON object. The JSON format must be as follows: { \"IncidentAIVerdict\": \"string (Malicious, Suspicious, Benign, Informational)\", \"AISummary\": \"string (A 2-3 sentence global summary of the incident.)\", \t \"AIDetaiedSummary\": \"string (All details of this incidents that are usefull. Use Cortex XSOAR Markdown format to improve the readiness and mark important and less important items using colors and bold structure)\", \"Justification\": \"string (The primary reason for the verdict, based on the strongest evidence.)\", \"ConfidenceScore\": \"integer (A confidence score from 0 to 100 for the IncidentAIVerdict)\", \"EmailHeaderAIAnalysis\": \"string (A detailed analysis of the email headers, including SPF/DKIM/DMARC, routing and more depending on the context data)\", \"EmailAISummary\": \"string (A summary of the analysis of the email body, URLs, and attachments.)\", \"EmailAIVerdict\": \"string (Phishing, Malware, Spam, BEC, Safe, Other)\" }"
15+
display: Instruction
16+
name: instruction
17+
required: true
18+
type: 0
19+
section: Connect
20+
- displaypassword: API Key
21+
additionalinfo: The API Key to use for connection
22+
name: credentials
23+
required: true
24+
hiddenusername: true
25+
type: 9
26+
section: Connect
27+
- defaultvalue: gpt-4o
28+
display: Model
29+
name: deployment_name
30+
required: false
31+
type: 0
32+
section: Connect
33+
description: Azure Open AI ServiceAI.
34+
display: Azure Open AI Service
35+
name: Azure Open AI Service
36+
script:
37+
commands:
38+
- arguments:
39+
- description: The message you would like to send.
40+
name: message
41+
required: true
42+
description: Send message to Azure Open AI.
43+
name: azure-openai-send-message
44+
dockerimage: demisto/python3:3.12.11.4284848
45+
runonce: false
46+
script: ''
47+
subtype: python3
48+
type: python
49+
fromversion: 6.0.0
50+
tests:
51+
- No tests (auto formatted)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Create First you Azure Open AI environment at https://ai.azure.com
6.09 KB
Loading
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
Azure Open AI ServiceAI
2+
3+
## Configure Azure Open AI Service - French DC Test in Cortex
4+
5+
| **Parameter** | **Required** |
6+
| --- | --- |
7+
| Azure Open AI URL | True |
8+
| Instruction | True |
9+
| API Key | True |
10+
| Model | False |
11+
12+
## Commands
13+
14+
You can execute these commands from the CLI, as part of an automation, or in a playbook.
15+
After you successfully execute a command, a DBot message appears in the War Room with the command details.
16+
17+
### azure-openai-send-message
18+
19+
***
20+
Send message to Azure Open AI
21+
22+
#### Base Command
23+
24+
`azure-openai-send-message`
25+
26+
#### Input
27+
28+
| **Argument Name** | **Description** | **Required** |
29+
| --- | --- | --- |
30+
| message | The message you would like to send. | Optional |
31+
32+
#### Context Output
33+
34+
There is no context output for this command.

Packs/AzureOpenAIService/README.md

Whitespace-only changes.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "Azure Open AI Service",
3+
"description": "The primary purpose of this Cortex XSOAR integration is to bridge the gap between human-level security analysis and automated incident response by leveraging the power of Azure OpenAI. It effectively transforms a large language model, like GPT-4o, into a consistent, on-demand cybersecurity analyst that integrates directly into automated workflows.\n\nUnlike a simple chatbot, this integration is built for reliability and automation. Its core function is to send unstructured security data (such as email headers, logs, or observables) to the AI and compel it to return its analysis in a strictly defined, structured JSON format. This is achieved through a combination of specific API parameters and a detailed system prompt that instructs the AI on its role, the analysis to perform, and the exact schema to use for its response.\n\nOnce the structured JSON is received, the integration performs two key actions:\n\nAutomated Incident Enrichment: It parses the JSON and uses the data to automatically populate custom fields within the XSOAR incident. This enriches the incident with an AI-generated verdict, a summary, a justification, and a confidence score, all without manual intervention.\n\nEnhanced Playbook Automation: By providing predictable, machine-readable output, the integration allows playbooks to make intelligent, data-driven decisions. For example, a playbook can automatically escalate a high-confidence \"Malicious\" incident or close a \"Benign\" one, significantly accelerating the response lifecycle.\n\nThe integration also includes robust error handling to ensure it functions reliably in a production environment. In essence, it operationalizes artificial intelligence for security operations, turning expert knowledge into a scalable, automated resource that enhances both the speed and quality of incident response.",
4+
"support": "community",
5+
"currentVersion": "1.0.0",
6+
"author": "Jeremy Garcia",
7+
"url": "",
8+
"email": "[email protected]",
9+
"created": "2025-07-25T14:23:23Z",
10+
"categories": [
11+
"Data Enrichment & Threat Intelligence"
12+
],
13+
"tags": [],
14+
"useCases": [],
15+
"keywords": [],
16+
"marketplaces": [
17+
"xsoar",
18+
"marketplacev2"
19+
],
20+
"githubUser": [
21+
"unlik3s"
22+
]
23+
}

0 commit comments

Comments
 (0)