Build an Automated SOC Analyst Agent
Deploy an AI agent that triages security alerts and maps IOCs to MITRE ATT&CK.
Introduction
30 Second Summary
SOC analysts spend hours triaging repetitive alerts that follow predictable patterns. What if an AI agent could handle that first pass automatically, flagging only the incidents that truly need human eyes?
In this project, you will build an automated SOC analyst powered by Hermes Agent and Anthropic's Claude. It monitors an alert inbox on your Linux server, extracts and enriches Indicators of Compromise, maps findings to MITRE ATT&CK, and generates structured investigation reports on a schedule.
What You'll Build
A fully autonomous triage agent running on your Linux server that processes security alerts unattended and produces actionable investigation reports.
By the end of this project, you'll have:
- A configured Hermes Agent instance with a custom SOC analyst persona that shapes every interaction.
- A custom IOC triage skill with DNS/WHOIS enrichment and MITRE ATT&CK technique mapping.
- An automated cron job that triages new alerts every 2 hours and saves structured Markdown reports.
- Secret Mission: Real-time alert delivery to your Telegram via the messaging gateway.
Are there any prerequisites?
This project assumes familiarity with SIEM alert triage, IOC types (IPs, domains, hashes), and the MITRE ATT&CK framework. You will also need a Linux server with shell access and an Anthropic API key from console.anthropic.com.
Install Hermes Agent and Configure Anthropic
Before your agent can triage a single alert, it needs a brain and a voice. The brain is Hermes Agent, the open-source AI agent framework from Nous Research.
The voice is Anthropic's claude-sonnet-4-6 model, which will power the reasoning behind every triage decision. In this step, you will install Hermes and wire it up to Anthropic so the two can talk.
In this step, get ready to:
- Install Hermes Agent on your Linux server.
- Configure Anthropic as the LLM provider with the claude-sonnet-4-6 model.
- Verify a working chat session.
Install Hermes Agent on your Linux server
Hermes Agent is a self-improving AI agent with persistent memory, a skills system, and built-in scheduling. The official installer handles all dependencies (Python, Node, ripgrep, ffmpeg) automatically.
- Check whether Hermes is already installed by running this command:
hermes --version
✔️ I see version v2026.6.5 or higher
Hermes Agent is already installed and up to date. Skip ahead to the next substep to configure Anthropic.
ⓧ I see an older version
You have a previous version of Hermes installed. Upgrade to the latest release so you have all the features this project uses.
- Update Hermes Agent by running this command:
hermes update
- Confirm the upgrade succeeded by running:
hermes --version
You should see v2026.6.5 or higher in the output.
ⓧ Command not found
Hermes is not installed yet. Follow the steps below to install it fresh.
- Install Hermes Agent by running the official one-line installer:
curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash
What does this command do?
The curl command downloads the install script from the official Hermes website. Piping it to bash executes the script immediately.
The installer detects your system, installs all required dependencies (Python 3.11+, Node.js, ripgrep, ffmpeg), and places the hermes binary on your PATH.
- Re-source your shell so the new hermes command is available:
source ~/.bashrc
- Verify the installation by running the built-in diagnostic:
hermes doctor
You should see all checks pass with no errors reported.
Seeing errors from hermes doctor?
If you see a Python version error, the installer may have been unable to upgrade your system Python. Install Python 3.11+ manually and rerun the installer.
If hermes is still not found after sourcing, try opening a fresh terminal session.
help me troubleshoot Hermes Agent installation errors on Linux
Configure Anthropic as your LLM provider
Hermes Agent supports multiple LLM providers. You will connect it to Anthropic so every agent session uses claude-sonnet-4-6 for reasoning.
- Launch the interactive model selector by running:
hermes model
- Select Anthropic from the provider list.
- When prompted, paste your ANTHROPIC_API_KEY.
- Choose claude-sonnet-4-6 as the model.
Where does this configuration go?
The model selector writes your provider and model choice to ~/.hermes/config.yaml. Your API key is stored separately in ~/.hermes/.env to keep credentials isolated from configuration.
- Confirm your configuration by checking the config file:
cat ~/.hermes/config.yaml
You should see the model section with Anthropic as the provider and claude-sonnet-4-6 as the default.
✔️ Awesome, I've got everything!
Great. Your Anthropic configuration is locked in.
ⓧ I'd like to double check the full code
Your ~/.hermes/config.yaml should contain at minimum:
model:
provider: "anthropic"
default: "claude-sonnet-4-6"
Config file looks different?
Your config.yaml may contain additional keys beyond the model section. That is normal. As long as the model block shows anthropic and claude-sonnet-4-6, you are set.
If the model section is missing, rerun hermes model and make sure you complete all the prompts before exiting.
help me configure Anthropic as my Hermes Agent LLM provider
Verify a working chat session
The final check is a live conversation. If Hermes can reach Anthropic and return a coherent answer, your setup is complete.
- Start an interactive Hermes session by running:
hermes
- Type a test question: What MITRE ATT&CK technique covers credential dumping? and press Enter.
- Confirm you get a coherent response mentioning MITRE ATT&CK techniques (such as T1003).
- Type /quit to exit the session.
What just happened?
Hermes read your config.yaml, loaded the Anthropic provider, sent your question to claude-sonnet-4-6, and streamed the response back to your terminal.
This same pipeline will power every automated triage run you build in the next steps.
Getting an authentication error?
Double-check that your ANTHROPIC_API_KEY is correct by viewing ~/.hermes/.env. The key should start with sk-ant-.
If the key looks correct but you still see errors, verify you have active billing on your Anthropic account at console.anthropic.com.
help me fix Anthropic authentication errors in Hermes Agent
Your agent has a working brain. Next up, you will give it a purpose by writing a SOC analyst identity that shapes every response.
Configure the SOC Analyst Identity
Your Hermes Agent is installed and connected to Anthropic. You confirmed it responds to security-related prompts.
Right now Hermes is using its default generalist persona. For automated SOC triage, you need it to think and respond like a dedicated security analyst with specific expertise in IOC analysis and MITRE ATT&CK mapping.
In this step, get ready to:
- Replace the default SOUL.md with a SOC analyst persona.
- Configure approval settings for automated triage operations.
Write the SOUL.md persona
The SOUL.md file is the identity system in Hermes Agent. It occupies the first slot in the system prompt and completely replaces the built-in default personality.
By writing a SOC analyst persona here, every interaction with Hermes will be shaped by security operations expertise.
- Open ~/.hermes/SOUL.md for editing by running:
nano ~/.hermes/SOUL.md
- Delete any existing content in the file and replace it with the following SOC analyst persona:
You are an advanced Security Operations Center (SOC) Analyst and Cyber Threat Intelligence (CTI) specialist. Your primary mission is to triage security alerts, extract and enrich Indicators of Compromise (IOCs), map adversary techniques to the MITRE ATT&CK framework, and produce actionable investigation reports.
## Core Expertise
- SIEM alert triage and prioritization
- IOC extraction: IP addresses, domains, file hashes (MD5, SHA-1, SHA-256), URLs, email addresses
- IOC enrichment via DNS resolution, WHOIS lookups, and reputation checks
- MITRE ATT&CK technique identification and mapping
- Structured investigation report generation
- False positive assessment and confidence scoring
## Operational Principles
1. Always extract ALL indicators from raw alert data before beginning analysis
2. Enrich every IOC with available tools before making severity judgments
3. Map findings to specific MITRE ATT&CK techniques with tactic context
4. Assign a severity rating (Critical/High/Medium/Low/Informational) with justification
5. Provide concrete next steps for human analysts
6. When uncertain about a finding, state your confidence level explicitly
7. Never fabricate enrichment data. If a lookup fails, report it as inconclusive
## Output Standards
- Use structured markdown for all reports
- Include timestamps in UTC
- Cite the specific ATT&CK technique IDs (e.g., T1071.001)
- Separate confirmed findings from hypotheses
What does this persona define?
- Core Expertise lists the specific security skills the agent draws on during triage. This scopes its responses to IOC extraction, enrichment, and ATT&CK mapping.
- Operational Principles establishes a strict analysis workflow. The agent always extracts all indicators before judging severity, and never fabricates enrichment data.
- Output Standards ensures every report uses structured markdown, UTC timestamps, and specific technique IDs like T1071.001.
- Save the file by pressing Ctrl+O, then Enter, then Ctrl+X to exit nano.
You won't see a visible change yet. You will verify the persona is active after configuring approval settings in the next substep.
File didn't save?
- Make sure you pressed Ctrl+O (the letter O, not zero) followed by Enter to confirm the filename before exiting.
- If nano shows "Permission denied", check that ~/.hermes/ exists. Hermes creates this directory during installation.
Why can't I save my SOUL.md file in ~/.hermes/?
✔️ Awesome, I've got everything!
Great. Make sure you saved ~/.hermes/SOUL.md before continuing.
ⓧ I'd like to double check the full code
Here is the complete SOUL.md file for comparison:
You are an advanced Security Operations Center (SOC) Analyst and Cyber Threat Intelligence (CTI) specialist. Your primary mission is to triage security alerts, extract and enrich Indicators of Compromise (IOCs), map adversary techniques to the MITRE ATT&CK framework, and produce actionable investigation reports.
## Core Expertise
- SIEM alert triage and prioritization
- IOC extraction: IP addresses, domains, file hashes (MD5, SHA-1, SHA-256), URLs, email addresses
- IOC enrichment via DNS resolution, WHOIS lookups, and reputation checks
- MITRE ATT&CK technique identification and mapping
- Structured investigation report generation
- False positive assessment and confidence scoring
## Operational Principles
1. Always extract ALL indicators from raw alert data before beginning analysis
2. Enrich every IOC with available tools before making severity judgments
3. Map findings to specific MITRE ATT&CK techniques with tactic context
4. Assign a severity rating (Critical/High/Medium/Low/Informational) with justification
5. Provide concrete next steps for human analysts
6. When uncertain about a finding, state your confidence level explicitly
7. Never fabricate enrichment data. If a lookup fails, report it as inconclusive
## Output Standards
- Use structured markdown for all reports
- Include timestamps in UTC
- Cite the specific ATT&CK technique IDs (e.g., T1071.001)
- Separate confirmed findings from hypotheses
Configure security-appropriate approval settings
By default, Hermes prompts for manual approval before executing every command. For a SOC analyst that runs automated triage, you need low-risk operations (file reads, DNS lookups) to proceed automatically while genuinely dangerous commands still require approval.
- Set the approval mode to smart by running:
hermes config set approvals.mode smart
What does smart approval mode do?
The smart mode lets Hermes automatically approve low-risk operations like reading files and running DNS lookups. Commands that could modify your system or delete data still require your explicit approval.
The hermes config set command automatically routes this setting to ~/.hermes/config.yaml. Your config file now contains both the model provider and the approval mode.
- Verify the persona is active by starting a new Hermes session:
hermes
- Type the following message and press Enter:
Who are you and what do you specialize in?
Hermes should respond as a SOC analyst with CTI expertise. It should mention alert triage, IOC enrichment, MITRE ATT&CK mapping, and investigation reports.
- Type /quit to exit the session.
Still seeing the default Hermes persona?
- Confirm ~/.hermes/SOUL.md exists and is not empty. Run cat ~/.hermes/SOUL.md to check its contents.
- Make sure you started a fresh session. The SOUL.md file is loaded when a new session begins, not mid-conversation.
- Check for typos in the file path. The file must be at exactly ~/.hermes/SOUL.md (case-sensitive).
Why isn't my Hermes agent responding with the SOC analyst persona I configured?
Your agent now has a dedicated SOC analyst identity and security-appropriate approval settings. Next up, you will build a custom triage skill that gives the agent a structured workflow for extracting and enriching IOCs from alert data.
Build the IOC Triage Skill
Your Hermes Agent has a SOC analyst persona, but it does not yet know how to triage an alert.
Hermes uses a skills system to give the agent structured workflows it can follow. In this step, you will create a custom soc-triage skill that teaches Hermes exactly how to extract IOCs, enrich them, and produce a MITRE ATT&CK-mapped investigation report.
In this step, get ready to:
- Create the skill directory structure inside ~/.hermes/skills/.
- Write the triage workflow, enrichment script, and report template.
- Verify the skill loads as a slash command.
Create the skill directory structure
Hermes skills live in ~/.hermes/skills/{category}/{skill-name}/ with optional subdirectories for scripts, templates, and references.
- Create the full directory tree by running these commands:
mkdir -p ~/.hermes/skills/cybersecurity/soc-triage/scripts
mkdir -p ~/.hermes/skills/cybersecurity/soc-triage/templates
What does this create?
This creates a nested folder path: skills/ is the top-level skill directory, cybersecurity/ is the category, and soc-triage/ is your skill name.
The scripts/ subdirectory will hold your IOC enrichment helper. The templates/ subdirectory will hold the investigation report structure.
- Confirm the directories exist by running:
ls ~/.hermes/skills/cybersecurity/soc-triage/
You should see scripts and templates listed in the output.
Not seeing the directories?
Make sure you used the -p flag with mkdir. Without it, the command fails if parent directories do not exist yet.
help me troubleshoot my directory creation
Write the SKILL.md and supporting files
Every Hermes skill needs a SKILL.md file. This is the instruction set Hermes follows when the skill is invoked.
- Create the main skill file by opening it in your editor:
nano ~/.hermes/skills/cybersecurity/soc-triage/SKILL.md
- Paste the following content into the file:
---
name: soc-triage
description: Automated SOC alert triage with IOC extraction, enrichment, MITRE ATT&CK mapping, and investigation report generation
version: 1.0.0
metadata:
hermes:
tags: [security, soc, triage, ioc, mitre]
category: cybersecurity
requires_toolsets: [terminal]
---
# SOC Alert Triage
## When to Use
- When a security alert needs triage and investigation
- When raw log data or SIEM alerts contain potential IOCs
- When you need to enrich indicators and assess threat severity
- When invoked via /soc-triage or scheduled cron automation
## Procedure
1. **Ingest Alert Data**
- Read the alert file (JSON, plain text, or log format)
- Identify the alert type, source, and timestamp
- Note any pre-assigned severity or category from the SIEM
2. **Extract IOCs**
- Scan all text fields for IP addresses (IPv4 and IPv6)
- Extract domain names and URLs
- Identify file hashes (MD5: 32 hex chars, SHA-1: 40 hex chars, SHA-256: 64 hex chars)
- Extract email addresses if present
- Deduplicate all extracted indicators
3. **Enrich IOCs**
- Run the enrichment script for batch processing:
```bash
python3 ~/.hermes/skills/cybersecurity/soc-triage/scripts/enrich-iocs.py <ioc1> <ioc2> ...
```
- For IP addresses: DNS resolution (forward/reverse), WHOIS lookup, geolocation from WHOIS
- For domains: DNS A/MX records, WHOIS registration data, registrar and creation date
- For hashes: Note them for manual lookup (VirusTotal, etc.)
- Record all enrichment results including failures
4. **MITRE ATT&CK Mapping**
- Based on the alert context and enriched IOCs, identify applicable techniques
- Map to specific technique IDs (e.g., T1071.001 for Web Protocols under C2)
- Include the tactic category (Initial Access, Execution, Persistence, etc.)
- Assess confidence level for each mapping (High/Medium/Low)
5. **Severity Assessment**
- Consider: IOC reputation, alert context, potential impact, confidence level
- Assign overall severity: Critical / High / Medium / Low / Informational
- Justify the rating with specific evidence
6. **Generate Investigation Report**
- Use the template at templates/investigation-report.md as the structure
- Save the report to the specified output directory
- Include a filename with timestamp: `triage-YYYYMMDD-HHMMSS.md`
## Pitfalls
- Do not fabricate enrichment data if a lookup fails. Report "lookup failed" or "inconclusive"
- Private IP ranges (10.x, 172.16-31.x, 192.168.x) are internal. Do not attempt external enrichment on them
- A single IOC match does not confirm compromise. Correlate multiple indicators before assigning High/Critical
- WHOIS rate limiting may cause failures on bulk lookups. Process in batches with delays if needed
## Verification
- The investigation report contains all extracted IOCs with enrichment status
- At least one MITRE ATT&CK technique is mapped with justification
- A severity rating is assigned with supporting evidence
- Concrete next steps are provided for the human analyst
What does this skill file do?
The YAML frontmatter at the top registers the skill with Hermes. The requires_toolsets: [terminal] line ensures the skill only activates when terminal access is available.
The body follows the standard skill format: When to Use, Procedure, Pitfalls, and Verification. Hermes reads these sections to understand the workflow, avoid common mistakes, and confirm its output quality.
- Save and exit nano by pressing Ctrl+O, then Enter, then Ctrl+X.
Next, you will create the enrichment helper script. This is a Python tool that the agent calls to perform DNS and WHOIS lookups on extracted IOCs.
- Create the enrichment script by running:
nano ~/.hermes/skills/cybersecurity/soc-triage/scripts/enrich-iocs.py
- Paste the following Python code into the file:
#!/usr/bin/env python3
"""IOC Enrichment Helper for SOC Triage Skill.
Accepts IOCs as command-line arguments, performs DNS resolution
and WHOIS lookups, and outputs structured JSON results.
Usage:
python3 enrich-iocs.py 192.168.1.100 evil.example.com d41d8cd98f00b204e9800998ecf8427e
"""
import json
import socket
import subprocess
import sys
import re
from datetime import datetime, timezone
def classify_ioc(indicator):
"""Classify an IOC by type."""
ip_v4_pattern = re.compile(
r'^(\d{1,3}\.){3}\d{1,3}$'
)
hash_md5 = re.compile(r'^[a-fA-F0-9]{32}$')
hash_sha1 = re.compile(r'^[a-fA-F0-9]{40}$')
hash_sha256 = re.compile(r'^[a-fA-F0-9]{64}$')
domain_pattern = re.compile(
r'^[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+$'
)
if ip_v4_pattern.match(indicator):
return "ipv4"
elif hash_sha256.match(indicator):
return "sha256"
elif hash_sha1.match(indicator):
return "sha1"
elif hash_md5.match(indicator):
return "md5"
elif domain_pattern.match(indicator):
return "domain"
else:
return "unknown"
def is_private_ip(ip):
"""Check if an IP address is in a private range."""
octets = ip.split('.')
if len(octets) != 4:
return False
first = int(octets[0])
second = int(octets[1])
if first == 10:
return True
if first == 172 and 16 <= second <= 31:
return True
if first == 192 and second == 168:
return True
if first == 127:
return True
return False
def dns_resolve(indicator, ioc_type):
"""Perform DNS resolution for IPs and domains."""
results = {"forward": None, "reverse": None, "error": None}
try:
if ioc_type == "ipv4":
try:
hostname, _, _ = socket.gethostbyaddr(indicator)
results["reverse"] = hostname
except socket.herror:
results["reverse"] = "No PTR record"
elif ioc_type == "domain":
try:
ips = socket.getaddrinfo(indicator, None, socket.AF_INET)
results["forward"] = list(set(addr[4][0] for addr in ips))
except socket.gaierror:
results["forward"] = "Resolution failed"
except Exception as e:
results["error"] = str(e)
return results
def whois_lookup(indicator):
"""Perform WHOIS lookup via system whois command."""
result = {"raw_excerpt": None, "error": None}
try:
proc = subprocess.run(
["whois", indicator],
capture_output=True,
text=True,
timeout=10
)
if proc.returncode == 0 and proc.stdout:
lines = proc.stdout.strip().split('\n')
relevant_lines = []
keywords = [
'OrgName', 'Organization', 'org-name',
'NetRange', 'inetnum', 'CIDR',
'Country', 'country',
'Registrar', 'registrar',
'Creation Date', 'created',
'Updated Date', 'last-modified',
'Registrant', 'descr'
]
for line in lines:
if any(kw.lower() in line.lower() for kw in keywords):
relevant_lines.append(line.strip())
result["raw_excerpt"] = relevant_lines[:15]
else:
result["error"] = proc.stderr.strip() or "No output"
except subprocess.TimeoutExpired:
result["error"] = "WHOIS lookup timed out"
except FileNotFoundError:
result["error"] = "whois command not found. Install with: sudo apt install whois"
except Exception as e:
result["error"] = str(e)
return result
def enrich_ioc(indicator):
"""Enrich a single IOC with available data."""
ioc_type = classify_ioc(indicator)
enrichment = {
"indicator": indicator,
"type": ioc_type,
"timestamp": datetime.now(timezone.utc).isoformat(),
"dns": None,
"whois": None,
"notes": []
}
if ioc_type == "ipv4":
if is_private_ip(indicator):
enrichment["notes"].append("Private/internal IP address - no external enrichment performed")
return enrichment
enrichment["dns"] = dns_resolve(indicator, ioc_type)
enrichment["whois"] = whois_lookup(indicator)
elif ioc_type == "domain":
enrichment["dns"] = dns_resolve(indicator, ioc_type)
enrichment["whois"] = whois_lookup(indicator)
elif ioc_type in ("md5", "sha1", "sha256"):
enrichment["notes"].append(
f"Hash detected ({ioc_type}). Manual lookup recommended: "
f"https://www.virustotal.com/gui/search/{indicator}"
)
else:
enrichment["notes"].append("Unrecognized IOC type. No automated enrichment available.")
return enrichment
def main():
if len(sys.argv) < 2:
print(json.dumps({"error": "No IOCs provided. Usage: enrich-iocs.py <ioc1> [ioc2] ..."}))
sys.exit(1)
indicators = sys.argv[1:]
results = {
"enrichment_run": {
"timestamp": datetime.now(timezone.utc).isoformat(),
"total_iocs": len(indicators),
"results": []
}
}
for indicator in indicators:
enrichment = enrich_ioc(indicator.strip())
results["enrichment_run"]["results"].append(enrichment)
print(json.dumps(results, indent=2))
if __name__ == "__main__":
main()
What does this script do?
- classify_ioc() uses regex patterns to determine whether an indicator is an IPv4 address, domain, MD5, SHA-1, or SHA-256 hash.
- is_private_ip() checks for RFC 1918 ranges (10.x, 172.16-31.x, 192.168.x) so the script skips external lookups on internal addresses.
- dns_resolve() performs forward lookups for domains and reverse PTR lookups for IPs using Python's socket library.
- whois_lookup() calls the system whois command and extracts relevant fields like organization, country, and registration dates.
- The main() function processes all IOCs from the command line and outputs structured JSON that Hermes can parse.
- Save and exit nano by pressing Ctrl+O, then Enter, then Ctrl+X.
Finally, create the investigation report template. This gives Hermes a consistent structure for its output.
- Create the template file by running:
nano ~/.hermes/skills/cybersecurity/soc-triage/templates/investigation-report.md
- Paste the following template into the file:
# Investigation Report
## Summary
| Field | Value |
|-------|-------|
| Report ID | triage-{timestamp} |
| Generated | {datetime_utc} |
| Alert Source | {source} |
| Overall Severity | {severity} |
| Confidence | {confidence} |
## Alert Details
{alert_description}
## Extracted IOCs
| Indicator | Type | Enrichment Status |
|-----------|------|-------------------|
| {ioc} | {type} | {status} |
## Enrichment Results
### IP Addresses
{ip_enrichment_details}
### Domains
{domain_enrichment_details}
### File Hashes
{hash_details}
## MITRE ATT&CK Mapping
| Technique ID | Technique Name | Tactic | Confidence |
|-------------|----------------|--------|------------|
| {technique_id} | {name} | {tactic} | {confidence} |
## Analysis
{analysis_narrative}
## Severity Justification
{severity_justification}
## Recommended Next Steps
1. {next_step_1}
2. {next_step_2}
3. {next_step_3}
## Appendix: Raw Enrichment Data
{raw_json_output}
Why use a template?
Without a template, the agent would produce inconsistent report formats across different triage runs. The template enforces a standard structure with sections for IOC tables, ATT&CK mappings, severity justification, and next steps.
Hermes fills in the curly-brace placeholders with real data during each triage session. This keeps reports readable and comparable over time.
- Save and exit nano by pressing Ctrl+O, then Enter, then Ctrl+X.
The enrichment script needs to be executable so Hermes can run it directly from the terminal.
- Make the script executable by running:
chmod +x ~/.hermes/skills/cybersecurity/soc-triage/scripts/enrich-iocs.py
- Verify all files are in place by running:
ls ~/.hermes/skills/cybersecurity/soc-triage/
ls ~/.hermes/skills/cybersecurity/soc-triage/scripts/
ls ~/.hermes/skills/cybersecurity/soc-triage/templates/
You should see SKILL.md in the main directory, enrich-iocs.py in scripts/, and investigation-report.md in templates/.
Missing a file?
If any file is missing, re-run the nano command for that file and paste the content again. Make sure you pressed Ctrl+O to write before exiting.
help me figure out why my skill files are missing
✔️ Awesome, I've got everything!
Great. Make sure you saved all three files before continuing.
ⓧ I'd like to double check the full code
Here are the three files that should exist in your skill directory.
~/.hermes/skills/cybersecurity/soc-triage/SKILL.md:
---
name: soc-triage
description: Automated SOC alert triage with IOC extraction, enrichment, MITRE ATT&CK mapping, and investigation report generation
version: 1.0.0
metadata:
hermes:
tags: [security, soc, triage, ioc, mitre]
category: cybersecurity
requires_toolsets: [terminal]
---
# SOC Alert Triage
## When to Use
- When a security alert needs triage and investigation
- When raw log data or SIEM alerts contain potential IOCs
- When you need to enrich indicators and assess threat severity
- When invoked via /soc-triage or scheduled cron automation
## Procedure
1. **Ingest Alert Data**
- Read the alert file (JSON, plain text, or log format)
- Identify the alert type, source, and timestamp
- Note any pre-assigned severity or category from the SIEM
2. **Extract IOCs**
- Scan all text fields for IP addresses (IPv4 and IPv6)
- Extract domain names and URLs
- Identify file hashes (MD5: 32 hex chars, SHA-1: 40 hex chars, SHA-256: 64 hex chars)
- Extract email addresses if present
- Deduplicate all extracted indicators
3. **Enrich IOCs**
- Run the enrichment script for batch processing:
```bash
python3 ~/.hermes/skills/cybersecurity/soc-triage/scripts/enrich-iocs.py <ioc1> <ioc2> ...
```
- For IP addresses: DNS resolution (forward/reverse), WHOIS lookup, geolocation from WHOIS
- For domains: DNS A/MX records, WHOIS registration data, registrar and creation date
- For hashes: Note them for manual lookup (VirusTotal, etc.)
- Record all enrichment results including failures
4. **MITRE ATT&CK Mapping**
- Based on the alert context and enriched IOCs, identify applicable techniques
- Map to specific technique IDs (e.g., T1071.001 for Web Protocols under C2)
- Include the tactic category (Initial Access, Execution, Persistence, etc.)
- Assess confidence level for each mapping (High/Medium/Low)
5. **Severity Assessment**
- Consider: IOC reputation, alert context, potential impact, confidence level
- Assign overall severity: Critical / High / Medium / Low / Informational
- Justify the rating with specific evidence
6. **Generate Investigation Report**
- Use the template at templates/investigation-report.md as the structure
- Save the report to the specified output directory
- Include a filename with timestamp: `triage-YYYYMMDD-HHMMSS.md`
## Pitfalls
- Do not fabricate enrichment data if a lookup fails. Report "lookup failed" or "inconclusive"
- Private IP ranges (10.x, 172.16-31.x, 192.168.x) are internal. Do not attempt external enrichment on them
- A single IOC match does not confirm compromise. Correlate multiple indicators before assigning High/Critical
- WHOIS rate limiting may cause failures on bulk lookups. Process in batches with delays if needed
## Verification
- The investigation report contains all extracted IOCs with enrichment status
- At least one MITRE ATT&CK technique is mapped with justification
- A severity rating is assigned with supporting evidence
- Concrete next steps are provided for the human analyst
~/.hermes/skills/cybersecurity/soc-triage/scripts/enrich-iocs.py:
#!/usr/bin/env python3
"""IOC Enrichment Helper for SOC Triage Skill.
Accepts IOCs as command-line arguments, performs DNS resolution
and WHOIS lookups, and outputs structured JSON results.
Usage:
python3 enrich-iocs.py 192.168.1.100 evil.example.com d41d8cd98f00b204e9800998ecf8427e
"""
import json
import socket
import subprocess
import sys
import re
from datetime import datetime, timezone
def classify_ioc(indicator):
"""Classify an IOC by type."""
ip_v4_pattern = re.compile(
r'^(\d{1,3}\.){3}\d{1,3}$'
)
hash_md5 = re.compile(r'^[a-fA-F0-9]{32}$')
hash_sha1 = re.compile(r'^[a-fA-F0-9]{40}$')
hash_sha256 = re.compile(r'^[a-fA-F0-9]{64}$')
domain_pattern = re.compile(
r'^[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+$'
)
if ip_v4_pattern.match(indicator):
return "ipv4"
elif hash_sha256.match(indicator):
return "sha256"
elif hash_sha1.match(indicator):
return "sha1"
elif hash_md5.match(indicator):
return "md5"
elif domain_pattern.match(indicator):
return "domain"
else:
return "unknown"
def is_private_ip(ip):
"""Check if an IP address is in a private range."""
octets = ip.split('.')
if len(octets) != 4:
return False
first = int(octets[0])
second = int(octets[1])
if first == 10:
return True
if first == 172 and 16 <= second <= 31:
return True
if first == 192 and second == 168:
return True
if first == 127:
return True
return False
def dns_resolve(indicator, ioc_type):
"""Perform DNS resolution for IPs and domains."""
results = {"forward": None, "reverse": None, "error": None}
try:
if ioc_type == "ipv4":
try:
hostname, _, _ = socket.gethostbyaddr(indicator)
results["reverse"] = hostname
except socket.herror:
results["reverse"] = "No PTR record"
elif ioc_type == "domain":
try:
ips = socket.getaddrinfo(indicator, None, socket.AF_INET)
results["forward"] = list(set(addr[4][0] for addr in ips))
except socket.gaierror:
results["forward"] = "Resolution failed"
except Exception as e:
results["error"] = str(e)
return results
def whois_lookup(indicator):
"""Perform WHOIS lookup via system whois command."""
result = {"raw_excerpt": None, "error": None}
try:
proc = subprocess.run(
["whois", indicator],
capture_output=True,
text=True,
timeout=10
)
if proc.returncode == 0 and proc.stdout:
lines = proc.stdout.strip().split('\n')
relevant_lines = []
keywords = [
'OrgName', 'Organization', 'org-name',
'NetRange', 'inetnum', 'CIDR',
'Country', 'country',
'Registrar', 'registrar',
'Creation Date', 'created',
'Updated Date', 'last-modified',
'Registrant', 'descr'
]
for line in lines:
if any(kw.lower() in line.lower() for kw in keywords):
relevant_lines.append(line.strip())
result["raw_excerpt"] = relevant_lines[:15]
else:
result["error"] = proc.stderr.strip() or "No output"
except subprocess.TimeoutExpired:
result["error"] = "WHOIS lookup timed out"
except FileNotFoundError:
result["error"] = "whois command not found. Install with: sudo apt install whois"
except Exception as e:
result["error"] = str(e)
return result
def enrich_ioc(indicator):
"""Enrich a single IOC with available data."""
ioc_type = classify_ioc(indicator)
enrichment = {
"indicator": indicator,
"type": ioc_type,
"timestamp": datetime.now(timezone.utc).isoformat(),
"dns": None,
"whois": None,
"notes": []
}
if ioc_type == "ipv4":
if is_private_ip(indicator):
enrichment["notes"].append("Private/internal IP address - no external enrichment performed")
return enrichment
enrichment["dns"] = dns_resolve(indicator, ioc_type)
enrichment["whois"] = whois_lookup(indicator)
elif ioc_type == "domain":
enrichment["dns"] = dns_resolve(indicator, ioc_type)
enrichment["whois"] = whois_lookup(indicator)
elif ioc_type in ("md5", "sha1", "sha256"):
enrichment["notes"].append(
f"Hash detected ({ioc_type}). Manual lookup recommended: "
f"https://www.virustotal.com/gui/search/{indicator}"
)
else:
enrichment["notes"].append("Unrecognized IOC type. No automated enrichment available.")
return enrichment
def main():
if len(sys.argv) < 2:
print(json.dumps({"error": "No IOCs provided. Usage: enrich-iocs.py <ioc1> [ioc2] ..."}))
sys.exit(1)
indicators = sys.argv[1:]
results = {
"enrichment_run": {
"timestamp": datetime.now(timezone.utc).isoformat(),
"total_iocs": len(indicators),
"results": []
}
}
for indicator in indicators:
enrichment = enrich_ioc(indicator.strip())
results["enrichment_run"]["results"].append(enrichment)
print(json.dumps(results, indent=2))
if __name__ == "__main__":
main()
~/.hermes/skills/cybersecurity/soc-triage/templates/investigation-report.md:
# Investigation Report
## Summary
| Field | Value |
|-------|-------|
| Report ID | triage-{timestamp} |
| Generated | {datetime_utc} |
| Alert Source | {source} |
| Overall Severity | {severity} |
| Confidence | {confidence} |
## Alert Details
{alert_description}
## Extracted IOCs
| Indicator | Type | Enrichment Status |
|-----------|------|-------------------|
| {ioc} | {type} | {status} |
## Enrichment Results
### IP Addresses
{ip_enrichment_details}
### Domains
{domain_enrichment_details}
### File Hashes
{hash_details}
## MITRE ATT&CK Mapping
| Technique ID | Technique Name | Tactic | Confidence |
|-------------|----------------|--------|------------|
| {technique_id} | {name} | {tactic} | {confidence} |
## Analysis
{analysis_narrative}
## Severity Justification
{severity_justification}
## Recommended Next Steps
1. {next_step_1}
2. {next_step_2}
3. {next_step_3}
## Appendix: Raw Enrichment Data
{raw_json_output}
Verify the skill is loaded
Hermes automatically registers every SKILL.md it finds in the skills directory as a slash command. You can confirm the skill is available by invoking it in a chat session.
- Start a new Hermes session by running:
hermes
- In the chat, type:
/soc-triage
Hermes should load the skill and confirm it is ready to triage alerts.
- Type /quit to exit the session.
You can also verify the skill appears in the agent's skill list by running:
hermes chat --toolsets skills -q "What skills do you have?"
You should see soc-triage listed in the output.
Skill not loading?
Check that your SKILL.md file is in the correct location: ~/.hermes/skills/cybersecurity/soc-triage/SKILL.md. The file name must be exactly SKILL.md (uppercase).
Also verify the YAML frontmatter at the top of the file starts with --- and ends with ---. Missing frontmatter delimiters will prevent Hermes from recognizing the skill.
help me debug why my soc-triage skill is not loading
Your custom skill is ready. Next up, you will feed it a real alert and watch Hermes run a full interactive triage from start to finish.
Test Interactive Triage
Your SOC analyst skill is fully built and loaded. Now it's time to put the entire triage pipeline to the test with a realistic alert before you automate it.
In this step, you'll feed Hermes a simulated SIEM alert containing multiple IOC types and watch it work through the full workflow: extraction, enrichment, MITRE ATT&CK mapping, and report generation.
In this step, get ready to:
- Create a sample alert file simulating suspicious outbound connections to C2 infrastructure.
- Run a full interactive triage session using the /soc-triage skill.
- Review the generated investigation report for completeness.
Create a sample alert file
Before Hermes can triage anything, it needs alert data to work with. You'll create a directory structure that mimics how a SIEM would drop alert files for processing.
- Create the alerts inbox directory by running this command:
mkdir -p ~/alerts/inbox
- Confirm the directory exists by running:
ls ~/alerts/
- You should see inbox listed in the output.
Now you'll write a sample alert that simulates a Network IDS detecting suspicious outbound connections. This alert contains multiple IOC types for Hermes to extract: IP addresses, a domain, and file hashes.
- Create the sample alert file by running this command:
cat > ~/alerts/inbox/sample-alert.json << 'EOF'
{
"alert_id": "SIEM-2026-0613-001",
"timestamp": "2026-06-13T08:23:41Z",
"source": "Network IDS",
"severity": "medium",
"rule_name": "Suspicious Outbound Connection to Known C2 Infrastructure",
"description": "Host 10.0.1.45 initiated repeated HTTPS connections to external IP 203.0.113.42 on port 443. Traffic pattern matches known beacon interval (60s +/- jitter). DNS query for cdn-update.malwaredomain.example.com resolved to 203.0.113.42 prior to connection.",
"source_ip": "10.0.1.45",
"destination_ip": "203.0.113.42",
"destination_port": 443,
"protocol": "TCP/TLS",
"dns_queries": [
"cdn-update.malwaredomain.example.com"
],
"file_hashes_observed": [
"e99a18c428cb38d5f260853678922e03",
"a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"
],
"user": "jsmith",
"hostname": "WKS-FINANCE-012",
"bytes_out": 2048,
"bytes_in": 512,
"connection_count": 47,
"time_span_minutes": 48
}
EOF
What's in this alert?
This simulates a real SIEM alert for a potential command-and-control (C2) beacon. The alert contains five IOCs for Hermes to extract:
- 10.0.1.45 (internal source IP, private range).
- 203.0.113.42 (external destination IP, enrichable via DNS/WHOIS).
- cdn-update.malwaredomain.example.com (suspicious domain).
- Two file hashes (MD5 and SHA-1) observed on the host.
- Verify the file was created by running:
ls ~/alerts/inbox/
- You should see sample-alert.json in the output.
File not appearing?
- Make sure you pressed Enter after the final EOF line. The heredoc won't close until it sees EOF alone on a line.
- Check that you're looking in the right directory. The file lives at ~/alerts/inbox/sample-alert.json, not ~/alerts/sample-alert.json.
Help me troubleshoot my heredoc file creation.
Run an interactive triage session
With alert data ready, you can now launch Hermes and invoke the triage skill. The agent will follow the full procedure defined in your SKILL.md: reading the alert, extracting IOCs, running the enrichment script, mapping to MITRE ATT&CK, and generating a report.
- Start a Hermes chat session by running:
hermes
- Once the chat session opens, type the following triage command and press Enter:
/soc-triage Triage the alert at ~/alerts/inbox/sample-alert.json. Extract all IOCs, enrich them, map to MITRE ATT&CK, and generate an investigation report saved to ~/alerts/reports/.
What happens next?
Hermes loads the soc-triage skill, which tells it exactly how to process the alert. You'll see it work through each step in real time:
- Reading and parsing the JSON alert file.
- Extracting all IOCs (IPs, domains, hashes).
- Running enrich-iocs.py against the external indicators.
- Mapping findings to MITRE ATT&CK technique IDs.
- Saving a structured report to ~/alerts/reports/.
Since approvals are set to smart mode, low-risk operations like file reads and DNS lookups proceed automatically. You may see Hermes request approval for the WHOIS subprocess call.
- If Hermes asks for approval to run a command, review it and approve.
- Wait for the triage to complete. Hermes will confirm when the investigation report has been saved.
- Type /quit to exit the chat session.
Hermes not running the enrichment script?
- Confirm the script is executable by running ls -l ~/.hermes/skills/cybersecurity/soc-triage/scripts/enrich-iocs.py in a separate terminal. You should see -rwxr at the start of the permissions.
- If the whois command is not installed, the enrichment script will report the error gracefully. Install it with sudo apt install whois if you want full WHOIS results.
Help me debug why Hermes isn't using my enrichment script.
Review the output
The investigation report is the final deliverable of the triage pipeline. It should contain every component your SKILL.md procedure specifies.
- List the generated reports by running:
ls ~/alerts/reports/
- You should see a file named something like triage-20260613-HHMMSS.md (the timestamp will reflect when you ran the triage).
- Read the full report by running:
cat ~/alerts/reports/*.md
What should the report contain?
A complete investigation report includes all of these sections:
- Extracted IOCs listing all indicators found: 203.0.113.42, cdn-update.malwaredomain.example.com, and both file hashes.
- Enrichment results with DNS resolution and WHOIS data for the external IP and domain. The internal IP 10.0.1.45 should be flagged as private with no external enrichment.
- MITRE ATT&CK mappings with specific technique IDs (e.g., T1071.001 for Application Layer Protocol under Command and Control).
- Severity assessment with justification based on the beacon pattern and C2 indicators.
- Recommended next steps for human analysts to investigate further.
No report file in ~/alerts/reports/?
- Make sure you included ~/alerts/reports/ as the save location in your triage prompt. Hermes needs an explicit output path.
- If the triage completed but the report is elsewhere, check the Hermes session output for the exact file path it used.
- Re-run the triage in a new hermes session with the same /soc-triage prompt. The skill is designed to create the output directory if it doesn't exist.
Help me figure out where my triage report was saved.
Your triage pipeline works end to end. Next up, you'll schedule this entire workflow to run automatically on a cron timer so new alerts get triaged without any human intervention.
Automate with Scheduled Cron
Your SOC analyst can triage alerts on demand, but a human still has to type the command every time a new alert arrives. In a real SOC, alerts land around the clock. The analyst needs to wake up on its own, check for new work, and only bother you when something matters.
Hermes has a built-in cron scheduler powered by a background gateway daemon. Once the gateway is running, you can schedule jobs that execute in fresh agent sessions on a timer. In this step, you will install the gateway, create a triage job that runs every two hours, and verify it processes alerts autonomously.
In this step, get ready to:
- Install and start the Hermes gateway service.
- Create an automated cron job that triages new alerts every 2 hours.
- Verify the job is registered and trigger a manual test run.
Install and start the gateway service
The cron scheduler depends on the gateway daemon running in the background. The gateway ticks the scheduler every 60 seconds, checking for due jobs and spinning up isolated agent sessions to execute them.
- Install the gateway as a persistent user systemd service by running this command:
hermes gateway install
What does this command do?
This registers the Hermes gateway as a user-level systemd service. It starts automatically when your server boots, so cron jobs keep running even after you log out.
- Verify the gateway and scheduler are running by running this command:
hermes cron status
You should see output confirming the gateway is active and the scheduler is ticking.
Gateway not starting?
Make sure your Linux server has systemd available for user services. WSL2 users may need to enable systemd in their /etc/wsl.conf file.
help me troubleshoot the Hermes gateway not starting on my Linux server
Create the automated triage cron job
Cron jobs in Hermes run in completely fresh agent sessions. The prompt you provide must be self-contained, telling the agent everything it needs to complete the task without any prior context.
- Create the automated triage job by running this command:
hermes cron create "every 2h" \
"Check ~/alerts/inbox/ for any .json files. For each new alert file, run a full triage: extract IOCs, enrich them using the enrichment script, map to MITRE ATT&CK, and save an investigation report to ~/alerts/reports/ with a timestamp in the filename. After processing, move the alert file to ~/alerts/processed/. If the inbox is empty, respond with [SILENT]." \
--skill soc-triage \
--workdir ~ \
--name "soc-auto-triage"
What does this command do?
- The first argument sets the schedule to every 2 hours.
- The quoted string is the full prompt the agent receives in each fresh session. It describes the entire workflow: check inbox, triage, save report, move processed file.
- --skill soc-triage pre-loads your custom skill so the agent knows the triage procedure.
- --workdir ~ sets the working directory for the session.
- --name "soc-auto-triage" gives the job a human-readable name you can reference later.
What is the [SILENT] directive?
If the agent's final response starts with [SILENT], delivery is suppressed entirely. The output still gets saved to ~/.hermes/cron/output/ for audit, but you are not notified. This means an empty inbox costs nothing and generates no noise.
Seeing a skill not found error?
Double check that the skill name matches exactly. The --skill flag uses the name field from your SKILL.md frontmatter, which is soc-triage.
help me fix a skill not found error when creating a Hermes cron job
Verify the cron job is registered
- List all registered cron jobs by running:
hermes cron list
You should see soc-auto-triage listed with a schedule of every 2h and a future next_run_at timestamp displayed.
- Trigger an immediate test run to confirm the job works end-to-end:
hermes cron run soc-auto-triage
What should I see?
Hermes spins up a fresh agent session, loads the soc-triage skill, finds your sample-alert.json in the inbox, runs the full triage workflow, saves a new report to ~/alerts/reports/, and moves the processed alert to ~/alerts/processed/.
- Confirm the alert was processed by checking both directories:
ls ~/alerts/inbox/
ls ~/alerts/processed/
ls ~/alerts/reports/
The inbox should be empty. You should see sample-alert.json in ~/alerts/processed/ and a new timestamped report in ~/alerts/reports/.
Alert still in the inbox after the run?
The cron job runs in a fresh session. If the agent did not move the file, check that the ~/alerts/processed/ directory exists. The agent creates it if missing, but permission issues could block it. Run mkdir -p ~/alerts/processed manually and re-run the job.
help me debug why my Hermes cron job is not moving processed alerts
Your SOC analyst now runs autonomously. Every two hours it wakes up, checks for new alerts, triages anything it finds, and goes silent when there is nothing to do. In the Secret Mission, you will connect Telegram so critical findings get pushed directly to your phone instead of waiting in a file on the server.
Secret mission
Wire Up Telegram for Real-Time Alerts
Your automated triage agent saves investigation reports to disk. But what if critical findings could reach your phone the moment they are generated? In this secret mission, you will connect a Telegram bot so Hermes pushes triage summaries directly to your chat.
Clean Up Your Resources
Clean Up Your Resources
Your automated SOC analyst is now fully operational with scheduled triage and Telegram delivery. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Cost warning
The cron job runs every 2 hours. Each triage invocation costs approximately $0.01-0.05 in Anthropic API tokens depending on alert complexity. With the [SILENT] directive, empty inboxes do not invoke the LLM and cost nothing.
If you leave the agent running with alerts flowing in, budget approximately $0.50-2.00/month depending on alert volume. Pause or delete below if you want to avoid ongoing charges.
Resources you used:
- Hermes Agent installation at ~/.hermes/ (free, local).
- Gateway service running as a user systemd service (free, local).
- Cron job soc-auto-triage scheduled every 2 hours.
- Alert directories at ~/alerts/ (inbox, reports, processed).
- Anthropic API usage (pay-per-token, approximately $0.01-0.05 per triage run).
Keep everything running
No action needed. Choose this if you are still actively building or want the SOC analyst to keep triaging alerts on schedule.
- The gateway service and cron job will continue running.
- With the [SILENT] directive, empty inboxes cost nothing because the LLM is never invoked.
- New alert files dropped into ~/alerts/inbox/ will be automatically triaged on the next scheduled run.
- Budget approximately $0.50-2.00/month for a 2-hour interval depending on alert volume.
Pause - I'll come back to this later
Shut down running processes to stop API costs while keeping your configuration and skill files intact.
- Pause the cron job by running:
hermes cron pause soc-auto-triage
- Stop the gateway service by running:
systemctl --user stop hermes-gateway
- Your ~/.hermes/ directory, skills, SOUL.md, and alert files all remain on disk.
- To resume later, run hermes gateway install and hermes cron resume soc-auto-triage.
Delete - I don't want to use this again
Remove all project resources and start fresh. This cannot be undone.
- Remove the cron job by running:
hermes cron remove soc-auto-triage
- Uninstall the gateway service by running:
hermes gateway uninstall
- Remove the alert directories by running:
rm -rf ~/alerts
- Remove the entire Hermes installation (config, skills, memories, everything) by running:
rm -rf ~/.hermes
- This removes your SOUL.md, the soc-triage skill, all enrichment scripts, investigation reports, and your Anthropic API key from the local environment.
- Your Anthropic API key still exists on your Anthropic account. Revoke it at console.anthropic.com if you no longer need it.
Nice Work!
Nice Work!
You just built a fully autonomous SOC triage agent that monitors your alert inbox, enriches IOCs, maps findings to MITRE ATT&CK, and generates structured investigation reports on a schedule.
You've learned how to:
- Configured Hermes Agent with a custom SOC analyst persona via SOUL.md and connected it to Anthropic as the LLM provider.
- Authored a custom IOC triage skill with a Python enrichment script that performs DNS resolution and WHOIS lookups, and maps findings to MITRE ATT&CK techniques.
- Deployed an automated cron job powered by the Hermes gateway that triages new alerts unattended every 2 hours and produces structured investigation reports.
- Secret Mission: Connected a Telegram bot for real-time push delivery of triage findings directly to your phone.
Ready to quiz yourself?