A personal, CLI-based SOC investigation assistant for Microsoft Azure Sentinel. MySec uses a LangGraph multi-agent system backed by Azure OpenAI to let you investigate incidents, enrich entities, and query Sentinel data through a natural-language conversation — no portal pivoting, no manual KQL.
MySec runs as a full-screen terminal application. The supervisor agent ("MySec") directly investigates incidents end-to-end — pulling incidents, extracting entities, dispatching to specialist agents, following new leads, and synthesising everything into a consolidated markdown report.
MySec > investigate incident 1234
MySec will automatically:
- Pull the incident from
SecurityIncidentand all linked alerts fromSecurityAlert - Extract typed entities (IPs, user accounts, hostnames, URLs, file hashes) from alert evidence
- State an investigation plan based on incident type, severity, and discovered entities
- Dispatch each entity type to the appropriate specialist agent
- Analyse returned data, follow any new leads with further agent calls
- Return a full consolidated investigation summary
| Agent | Role | Data Sources |
|---|---|---|
| MySec (Supervisor) | Incident triage, investigation coordination, synthesis | SecurityIncident, SecurityAlert + all specialist agents |
| Threat | IOC enrichment — IPs, domains, URLs, file hashes | VirusTotal, AbuseIPDB, AlienVault OTX |
| Identity | User activity — sign-ins, audit events, risk data | SigninLogs, AuditLogs, AADRiskyUsers, AADUserRiskEvents |
| Endpoint | Device activity — processes, logons, network, files | DeviceProcessEvents, DeviceLogonEvents, DeviceNetworkEvents, DeviceFileEvents |
| Network | Traffic, DNS, firewall activity | CommonSecurityLog, HUNT_Bluecat, AzureNetworkAnalytics_CL |
| Office | Microsoft 365 activity — SharePoint, Exchange, Teams | OfficeActivity, CloudAppEvents |
| Web | Internet proxy activity via Zscaler | HUNT_Zscaler |
| Azure | Azure control-plane and resource diagnostic activity | AzureActivity, AzureDiagnostics, StorageBlobLogs |
| KQL | Natural-language to KQL — executes ad-hoc queries and self-corrects on errors | Any Log Analytics table |
| Hunter | Proactive threat hunting — hypothesis-driven, MITRE ATT&CK aligned, writes hunt reports | All workspace tables |
| NVD-CVE | CVE research — CVSS scoring, exploitability assessment, patch prioritisation | LLM knowledge + nvd-cve skill |
| Qualys | Qualys vulnerability inventory for a specific host | QualysHostDetectionV3_CL |
All specialist agents are goal-driven and run autonomously until their goal is satisfied, all tools have exhausted retries, or a configurable max-iteration cap is hit.
Data source note — HUNT_ tables: Tables prefixed
HUNT_are Azure Data Explorer (ADX) tables accessed through the Sentinel API as workspace functions, not native Log Analytics tables. Some require function-call syntax (HUNT_Bluecat(),HUNT_Zscaler()); all requirecolumn_ifexists()guards since their column schemas vary by workspace.
- Python 3.11+
- Azure OpenAI deployment
- Azure Log Analytics workspace (Sentinel)
- Azure Service Principal with
Log Analytics Readeron the workspace - API keys for VirusTotal, AbuseIPDB, and AlienVault OTX (optional — agents degrade gracefully if absent)
git clone <repo-url>
cd MySec
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS/Linux
pip install -r requirements.txtCopy .env.example to .env and fill in your values (never commit the populated file):
# Azure OpenAI
AZURE_OPENAI_API_KEY=
AZURE_OPENAI_ENDPOINT=
AZURE_OPENAI_DEPLOYMENT=
AZURE_OPENAI_API_VERSION=2024-02-01
# Azure Service Principal (Log Analytics Reader on the Sentinel workspace)
AZURE_TENANT_ID=
AZURE_CLIENT_ID=
AZURE_CLIENT_SECRET=
AZURE_WORKSPACE_ID=
# Threat Intelligence (optional — agents note when keys are absent)
VIRUSTOTAL_API_KEY=
ABUSEIPDB_API_KEY=
ALIENVAULT_OTX_API_KEY=Edit config/app.yaml to adjust defaults:
lookback_days: 30 # KQL lookback window
row_limit: 500 # Max rows per query
max_retries: 3 # Retries on transient API errors
log_level: INFO # DEBUG for full LLM/tool traces# Interactive TUI
python main.py
# Single-shot investigation (exits after response)
python main.py investigate 1234Running python main.py without arguments launches the full-screen Textual TUI — a two-panel layout with the conversation on the left and a live agent/tool status panel on the right.
Type any natural-language request in the input box and press Enter. Example prompts:
investigate incident 1234
what did user john.doe@contoso.com do in the last 7 days?
get threat intel for 185.220.101.45
show me Azure activity for resource group rg-production
what web sites did jane.smith@contoso.com visit yesterday?
hunt for password spray activity over the last 7 days
show me all failed sign-ins from outside Australia in the last 24 hours
research CVE-2024-21413 with nvd-cve
get the Qualys vulnerability report for host WIN-PROD-01
| Key | Action |
|---|---|
Enter |
Submit prompt |
/ |
Open command menu |
↑ / ↓ (command menu open) |
Navigate command suggestions |
↑ / ↓ (command menu closed) |
Recall previous / next submitted input (history navigation) |
Escape |
Close command menu |
Ctrl+L |
Clear conversation log |
Ctrl+C |
Quit |
python main.py investigate 1234
python main.py what did user john.doe@contoso.com do in the last 7 daysPrints the response and exits. The report is saved to reports/.
Type / in the input box to open a searchable command menu. Arrow keys navigate the list; Enter selects.
| Command | Description |
|---|---|
/ListAgents |
Display all registered agents with their tools and purpose |
/ListTools |
Display all tool modules and the @tool functions they export |
/ListSkills |
Display all available skills with name, description, and tags |
| Command | Description |
|---|---|
/NewAgent <goal description> |
Have the supervisor create a new sub-agent, its companion skill, register it, and hot-reload |
/NewTool <goal description> |
Have the supervisor create a new tool module and wire it to agents |
/NewSkill <goal description> |
Have the supervisor create a new skill |
The supervisor follows a strict 8-step workflow (defined in skills/scaffold/SKILL.md) including mandatory validation before hot-reload. New components are live immediately — no restart required.
| Command | Description |
|---|---|
/RemoveAgent <name> |
Remove an agent directory, deregister it, and strip it from all agent configs |
/RemoveTool <name> |
Remove a tool module and strip its functions from all agent configs |
/RemoveSkill <name> |
Remove a skill directory |
| Command | Description |
|---|---|
/verify agent <name> |
Health-check an agent — structure, config, tool resolution, skill, registry, import |
/verify skill <name> |
Health-check a skill — frontmatter, body, skill_store availability |
/verify tool <name> |
Health-check a tool module — import, docstrings, credential pattern, agent wiring |
/verify agent all |
Health-check every registered agent, summary table |
/verify skill all |
Health-check every skill in skills/, summary table |
/verify tool all |
Health-check every tool module in tools/, summary table |
/verify all |
Health-check all agents, skills, and tools in one summary table |
Each check reports ✓ Pass or ✗ Fail with a concrete fix instruction. The bulk "all" commands show one row per component; components with errors are listed at the bottom with the exact individual command to see full details.
| Command | Description |
|---|---|
/clear |
Clear the conversation log and reset conversation history |
/help |
Show all available commands |
/quit |
Exit MySec |
MySec includes a built-in scaffolding system that lets the supervisor create, validate, wire, and hot-reload new agents, tools, and skills without any manual file editing or restarts.
- The user issues
/NewAgent <goal>in the TUI. - The TUI dispatches a structured prompt to the supervisor.
- The supervisor calls scaffold tools in order:
scaffold_read_template("agent")— reads the canonical templatescaffold_write_skill(name, content)— creates the companion skill firstscaffold_write_agent(name, agent_py, config_yaml)— writes both filesscaffold_validate_agent(name)— mandatory structural check before proceedingscaffold_register_agent(name)— adds toagents/_registry.pyscaffold_update_agent_config(...)— wires to any other agents the user specifiesscaffold_hotreload(summary)— triggers live reload; new agent is available immediately
scaffold_validate_agent catches every known failure mode before hot-reload:
| Check | What it prevents |
|---|---|
@tool decorator present |
Class-based implementations |
async def <name>_agent exact match |
Wrong function name, missing _agent suffix |
load_agent_config / resolve_tools / create_react_agent present |
Incomplete template use |
| No class definitions | Object-oriented misuse of the template |
system_prompt non-empty and calls load_skill() |
Agents that ignore their domain knowledge |
config.yaml has name, max_iterations, tools |
Missing required config fields |
skills/<name>/SKILL.md exists |
Missing companion skill |
SKILL.md has opening and closing --- |
Broken frontmatter that prevents skill loading |
If validation fails, the supervisor rewrites the affected file and retries — scaffold_hotreload is never called until scaffold_validate_agent returns valid: true.
Scaffold tools enforce strict path guards:
- Writes are only permitted inside
agents/,tools/, andskills/ agents/mysec/(the supervisor) is write-protected- Template directories are write-protected
scaffold_tools.pyandskill_tools.pycannot be modified by the scaffold system- Credentials are always read from
.env— the supervisor never generates or hardcodes values
After any scaffold write operation, the TUI automatically:
- Re-scans all
tools/*.pymodules and rebuilds the tool registry - Re-scans
skills/and reinitialises the skill store - Reloads
agents/_registry.pyand re-imports all registered agents - Rebuilds the supervisor with the updated tool and agent lists
No restart is required. The ↺ System reloaded message confirms the reload completed.
MySec/
├── main.py # Entry point — CLI args, TUI launch, single-shot mode
├── config/
│ └── app.yaml # Global settings (lookback_days, row_limit, log_level, etc.)
├── agents/
│ ├── _registry.py # COMMITTED — list of registered agent names (dynamic loader)
│ ├── _template/ # COMMITTED — boilerplate for creating new agents
│ │ ├── agent.py
│ │ └── config.yaml
│ ├── mysec/ # local only — supervisor
│ ├── threat/ # local only — Threat Intelligence
│ ├── identity/ # local only — Identity Investigation
│ ├── endpoint/ # local only — Endpoint Investigation
│ ├── network/ # local only — Network Investigation
│ ├── office/ # local only — Office 365 Investigation
│ ├── web/ # local only — Web Proxy Investigation
│ ├── azure/ # local only — Azure Resource Investigation
│ ├── kql/ # local only — Ad-hoc KQL
│ ├── hunter/ # local only — Proactive threat hunting
│ ├── nvd-cve/ # local only — CVE / vulnerability research
│ └── qualys/ # local only — Qualys endpoint vulnerability report
├── tools/
│ ├── _template_tools.py # COMMITTED — boilerplate for creating new tools
│ ├── skill_tools.py # COMMITTED — list_skills, load_skill (skill infrastructure)
│ ├── scaffold_tools.py # local only — scaffold system tools (write/validate/remove/reload)
│ └── *.py # local only — workspace-specific tool implementations
├── core/
│ ├── log_analytics.py # Async KQL client (service principal auth, retry)
│ ├── llm.py # AzureChatOpenAI factory
│ ├── config_loader.py # YAML config loader and validation
│ ├── tool_loader.py # Dynamic tool registry — scans tools/*.py at startup and on reload
│ ├── skill_store.py # SkillStore — scans skills/, serves content on demand
│ ├── callbacks.py # AgentTraceCallback — logs prompts, tool calls, tool results
│ ├── tui.py # Textual TUI — MySecApp, AgentPanel, command handling
│ ├── status_panel.py # Rich Live status panel (single-shot mode)
│ ├── memory.py # Self-learning KQL pattern store
│ ├── report.py # Session report writer
│ ├── console.py # Shared Rich Console and severity colour helpers
│ ├── state.py # LangGraph TypedDict state
│ └── agent_loop.py # Goal-driven loop helper
├── skills/ # Agent skill library — domain knowledge loaded on demand
│ ├── _template/ # COMMITTED — boilerplate for creating new skills
│ │ └── SKILL.md
│ ├── scaffold/ # local only — scaffold workflow guide (used by supervisor)
│ └── */SKILL.md # local only — workspace-specific domain knowledge
├── logs/
│ ├── app.log # JSON structured log (cleared on startup)
│ └── conversation.log # Human-readable transcript (cleared on startup)
├── memory/
│ └── patterns.json # Self-learning KQL pattern store (auto-created)
├── reports/ # Investigation reports — one .md per run, kept permanently
├── scripts/
│ └── sync_gitignore.py # Toggles gitignore exclusions based on commit_implementations
└── .env # Secrets — never commit
MySec uses a lazy-loading skill system to keep LLM context lean. Domain knowledge — detection thresholds, indicator lists, KQL templates, schema references — lives in skills/<name>/SKILL.md files and is loaded only when an agent determines it is relevant.
Every agent has two skill tools available:
| Tool | Description |
|---|---|
list_skills(query) |
Returns skill names and one-line descriptions. Lightweight — no full content. |
load_skill(name) |
Returns the full SKILL.md content for a specific skill. |
Each SKILL.md has YAML frontmatter (between --- delimiters) and a markdown body:
---
name: identity-anomaly-detection
description: Azure AD anomaly detection criteria, thresholds, and risk indicators
tags:
- identity
- azure-ad
---
# Identity Anomaly Detection
...full knowledge content...| Skill | Purpose |
|---|---|
identity-anomaly-detection |
Azure AD sign-in anomaly criteria, MFA codes, risk event types |
endpoint-indicators |
LOLBin list, suspicious process patterns, lateral movement indicators |
network-analysis |
RFC1918 classification, traffic summary fields, DNS tunneling indicators |
office365-risk-indicators |
High-risk O365 operation types, bulk download thresholds |
azure-risk-operations |
High-risk ARM operations, Key Vault access patterns, anti-forensics indicators |
threat-intel-verdict |
IOC verdict synthesis rules, source weighting, per-IOC report format |
sentinel-schema |
All Sentinel table schemas and key columns |
kql-techniques |
KQL idioms, column safety, self-correction patterns |
nvd-cve |
CVSS v3.1 scoring, severity bands, exploitability flags, vulnerability types, report format (used by nvd-cve agent) |
qualys |
QualysHostDetectionV3_CL schema, KQL templates, severity thresholds, output format (used by qualys agent) |
scaffold |
Step-by-step workflow for creating and removing agents, tools, and skills (used by supervisor) |
hunt-credential-access |
Credential access KQL templates (T1078, T1110, T1556) |
hunt-persistence |
Persistence KQL templates (T1098, T1136, T1137, T1053) |
hunt-lateral-movement |
Lateral movement KQL templates (T1021, T1550, T1570) |
hunt-execution |
Execution KQL templates (T1059, T1047, T1218) |
hunt-defense-evasion |
Defense evasion KQL templates (T1562, T1070) |
hunt-command-and-control |
C2 KQL templates (T1071, T1568, T1571) |
hunt-exfiltration |
Exfiltration KQL templates (T1567, T1048, T1537) |
hunt-discovery |
Discovery KQL templates (T1087, T1526) |
hunt-initial-access |
Initial access KQL templates (T1566, T1133) |
hunt-outcome-criteria |
Campaign outcome criteria (No / Emerging / Full Campaign) |
hunt-data-sources |
Available Sentinel tables for hunting, workspace naming conventions |
Agents are discovered at startup and on every hot-reload via agents/_registry.py:
AGENTS = [
"threat", "identity", "endpoint", "network",
"office", "web", "azure", "kql", "hunter",
"nvd-cve", "qualys",
]Each name must match the directory under agents/ and the exported @tool function
(<name>_agent, with hyphens converted to underscores). The supervisor receives all
registered agents as tools automatically — no changes to agents/mysec/ are needed
when adding a new agent.
Agents with hyphenated directory names (e.g. qualys) are loaded via
file-path import and mapped to the underscore function name (endpoint_vuln_report_agent).
core/tool_loader.py scans all tools/*.py files at startup (and after every hot-reload),
importing each module and collecting BaseTool instances. Agent config.yaml files list
tool names instead of imports:
tools:
- execute_kql
- get_kql_patterns
- list_skills
- load_skillresolve_tools(config.tools) is called inside every agent's ainvoke loop — new tool
files are picked up without any import changes in agent.py.
Each agent's behaviour is controlled entirely by its config.yaml. System prompts contain only workflow steps and rules — all domain knowledge is in skills, loaded on demand.
name: identity
max_iterations: 10
tools:
- get_signin_logs
- get_noninteractive_signins
- get_audit_logs
- get_risky_users
- get_user_risk_events
- list_skills
- load_skill
system_prompt: |
You are the Identity Investigation agent...
Before analysing results, call load_skill("identity-anomaly-detection")...Edit system_prompt to tune analysis behaviour. Edit or add SKILL.md files to update domain knowledge — no agent code changes required.
The Hunter agent performs hypothesis-driven threat hunts against Sentinel data, aligned to MITRE ATT&CK. Invoke it via any natural-language hunt request:
hunt for credential access activity over the last 7 days
hunt for lateral movement using T1021
look for living-off-the-land techniques on endpoints this week
The Hunter agent will:
- Clarify the hypothesis and map it to MITRE ATT&CK techniques
- Call
list_skills(query="hunt")to discover available hunt template skills - Load the relevant skill for KQL templates
- Check memory for prior working queries on each table
- Execute queries, self-correcting on column or syntax errors (up to 5 attempts per query)
- Assess evidence against three outcomes: No Campaign, Emerging Campaign, or Full Campaign Identified
- Write a structured hunt report to
reports/ - Return a concise summary with findings, IOCs, and recommended actions
Zero-result queries are still reported — they confirm coverage, not absence of a hunt.
The KQL agent converts any natural-language data request into a KQL query, executes it, and returns formatted results. It self-corrects on errors (wrong column names, absent tables, timeouts) for up to five attempts.
show all sign-ins from IP 185.220.101.45 in the last 30 days
how many failed authentications per user in the last 7 days?
list all Key Vault access events for vault kv-prod yesterday
Results include the row count, time range, and the final KQL query for reuse.
The NVD-CVE agent researches CVEs referenced in incidents or requested directly:
research CVE-2024-21413
what is the severity of the PrintNightmare vulnerability?
is there an active exploit for CVE-2023-44487?
The agent returns a structured report covering CVSS v3.1 score and vector, exploitability (public exploit, CISA KEV status, active exploitation), affected products, patch SLA based on severity, and remediation guidance.
The Qualys agent queries Qualys scan data from QualysHostDetectionV3_CL:
get the vulnerability report for 10.1.2.3
show me all critical vulnerabilities on host WIN-PROD-01
Returns a summary of vulnerabilities by severity band, with long-standing open issues (open > 30 days) flagged for escalation.
After each session, MySec writes successful KQL queries (those that returned >0 rows) to memory/patterns.json. On subsequent runs, agents consult this store to prefer query patterns that have worked before for the same table and entity type.
memory/patterns.json is plain JSON and fully editable — delete stale entries directly if needed.
| File | Contents | Cleared on startup |
|---|---|---|
logs/app.log |
JSON structured log — agent invocations, tool calls, LLM requests, errors | Yes |
logs/conversation.log |
Plain-text transcript of the full analyst ↔ MySec conversation | Yes |
reports/<timestamp>_investigation.md |
Markdown report of the session — one file per run, kept permanently | No |
Use the TUI slash commands — the scaffold system handles all file creation, wiring, validation, and hot-reload automatically.
The supervisor will:
- Read the agent template and scaffold skill
- Create a companion skill with domain knowledge, KQL templates, and output format
- Write
agents/<name>/agent.pyandconfig.yamlfrom the template - Run
scaffold_validate_agent— fix and retry until all checks pass - Register the agent in
_registry.py - Ask which specialist agents should be able to call this new agent
- Hot-reload — the agent is live immediately
The supervisor will create tools/<name>_tools.py, wire the tool names into the relevant agent configs, and hot-reload.
The supervisor will create skills/<name>/SKILL.md with proper frontmatter and domain content.
If creating components outside the TUI, follow the templates in agents/_template/, tools/_template_tools.py, and skills/_template/SKILL.md. Run /verify agent|skill|tool <name> after creation to confirm correctness.
By default only templates and shared infrastructure are committed. The commit_implementations flag in config/app.yaml controls whether agent, skill, and tool implementations are also tracked.
# false (default) — only templates committed; implementations are gitignored
# true — agents/, skills/, tools/ implementations are tracked
commit_implementations: falseApply the change after editing:
python scripts/sync_gitignore.py| Path | Default | Reason |
|---|---|---|
agents/_registry.py |
Committed | Agent discovery list |
agents/_template/ |
Committed | Boilerplate for new agents |
agents/*/ (all others) |
Gitignored | Workspace-specific |
tools/_template_tools.py |
Committed | Boilerplate for new tool modules |
tools/skill_tools.py |
Committed | Skill system infrastructure |
tools/*.py (all others) |
Gitignored | Workspace-specific |
skills/_template/ |
Committed | Boilerplate for new skills |
skills/*/ (all others) |
Gitignored | Workspace-specific |
core/tool_loader.py |
Committed | Dynamic tool registry |
logs/, reports/, memory/ |
Always gitignored | Runtime output — may contain incident / PII data |
.env |
Always gitignored | Secrets — never commit |
- All credentials are read from environment variables — never hard-coded.
- All user-supplied values (incident IDs, UPNs, IPs) are sanitised before inclusion in KQL strings.
- Log Analytics queries go through the
azure-monitor-querySDK with service principal auth. - Scaffold tools enforce write restrictions — only
agents/,tools/, andskills/are writable; the supervisor agent and all templates are protected. .gitignoreexcludes all secrets, runtime output, and workspace-specific implementations by default.