Building Detection Rules With Sigma
mukul975/Anthropic-Cybersecurity-Skills
Builds vendor-agnostic detection rules using the Sigma rule format for threat detection across SIEM platforms including Splunk, Elastic, and Microsoft Sentinel.
Turn a published threat-intelligence article into a tested threat-hunting campaign.
$ npx skills add SCStelz/security-investigator --skill threat-intel-campaign -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install SCStelz/security-investigator threat-intel-campaign --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.github/skills/threat-intel-campaign .claude/skills/threat-intel-campaign && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "threat-intel-campaign" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/threat-intel-campaign into .claude/skills/threat-intel-campaign/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "threat-intel-campaign", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/SCStelz/security-investigator/tree/main/.github/skills/threat-intel-campaignType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add SCStelz/security-investigator --skill threat-intel-campaign -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install SCStelz/security-investigator threat-intel-campaign --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.github/skills/threat-intel-campaign .agents/skills/threat-intel-campaign && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "threat-intel-campaign" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/threat-intel-campaign into .agents/skills/threat-intel-campaign/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "threat-intel-campaign", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add SCStelz/security-investigator --skill threat-intel-campaign -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install SCStelz/security-investigator threat-intel-campaign --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.github/skills/threat-intel-campaign .cursor/skills/threat-intel-campaign && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "threat-intel-campaign" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/threat-intel-campaign into .cursor/skills/threat-intel-campaign/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "threat-intel-campaign", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/SCStelz/security-investigator.git --path .github/skills/threat-intel-campaign--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add SCStelz/security-investigator --skill threat-intel-campaign -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install SCStelz/security-investigator threat-intel-campaign --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.github/skills/threat-intel-campaign .gemini/skills/threat-intel-campaign && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "threat-intel-campaign" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/threat-intel-campaign into .gemini/skills/threat-intel-campaign/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "threat-intel-campaign", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install SCStelz/security-investigator threat-intel-campaignInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add SCStelz/security-investigator --skill threat-intel-campaign -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .github/skills && cp -r skills-src/.github/skills/threat-intel-campaign .github/skills/threat-intel-campaign && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "threat-intel-campaign" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/threat-intel-campaign into .github/skills/threat-intel-campaign/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "threat-intel-campaign", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add SCStelz/security-investigator --skill threat-intel-campaign -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install SCStelz/security-investigator threat-intel-campaign --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/SCStelz/security-investigator.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.github/skills/threat-intel-campaign .opencode/skills/threat-intel-campaign && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "threat-intel-campaign" agent skill from https://github.com/SCStelz/security-investigator/tree/main/.github/skills/threat-intel-campaign into .opencode/skills/threat-intel-campaign/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "threat-intel-campaign", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
threat-intel-campaignTurn a published threat-intelligence article into a tested threat-hunting campaign.
Threat Intel Campaign is an agent skill from SCStelz/security-investigator. Turn a published threat-intelligence article into a tested threat-hunting campaign. Reads a platform-agnostic RSS/Atom feed (feedurl is a parameter — nothing vendor-specific is hardcoded), triages articles from a recent window, applies a huntability relevance gate to decide whether an article warrants a campaign, then writes/tests/tunes KQL hunts and publishes them as a campaign file under queries/threat-intelligence/YYYY-MM/. Also supports a single-article mode (pass an article URL directly). Side-effect-free…
Its SKILL.md is about 6.9k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Security, covering OSINT, Security operations and Commit messages. It works with Microsoft Sentinel. The repository describes itself as: Automated security investigation tool using Microsoft MCP Servers, GitHub Copilot, Python Modules and custom copilot-instructions. The licence is MIT.
7 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 51e1385. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
pythongitghpipFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
microsoft.comw3.orgFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
GITHUB_TOKENFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Threat Intel Campaign loads about 6.9k tokens when it runs. Until then it costs about 217 tokens; SKILL.md has 2,749 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from SCStelz/security-investigator at commit 51e1385, republished under its MIT licence (© SCStelz). 2,749 words, ~6,856 tokens.
.claude/skills/threat-intel-campaign/SKILL.md (or your agent's skills folder).This skill converts published threat-intelligence reporting into tested, tuned, publish-ready threat-hunting campaigns that land in queries/threat-intelligence/YYYY-MM/. It exists to be driven either:
It does the authoring (parse → triage → relevance gate → write → test → tune → publish files → regenerate manifest/TOCs). It deliberately does NOT create branches, commits, or pull requests. That orchestration — and the per-article PR isolation — belongs to the calling workflow. This keeps the skill reusable and free of git side effects when a human runs it.
What this skill produces:
| Output | Description |
|---|---|
| Campaign file(s) | queries/threat-intelligence/YYYY-MM/<slug>.md in the standard campaign format |
| Regenerated artifacts | .github/manifests/discovery-manifest.yaml + per-file Quick Reference TOCs |
| Structured result | A JSON array (one entry per article) the calling automation consumes to drive per-article PRs |
| Human summary | A readable per-article decision log |
| In-chat hunt findings summary | A per-article report of what the test runs actually surfaced — real hits, false positives to tune, and follow-up actions. Emitted to chat/run output only; never written to a tracked file. This is where concrete findings live, keeping the committed campaign file PII-free. |
No git side effects. This skill NEVER runs git commit, git push, gh pr create, or any branch operation. It writes files and regenerates the manifest/TOCs only. Publishing (branch + commit + PR per article) is the calling automation's job. If a human is running this interactively, leave the files in the working tree for them to review.
feed_url is a parameter — nothing vendor-specific is hardcoded. The skill handles any RSS 2.0 or Atom feed. The Microsoft Threat Intelligence feed is just one value a caller may pass; do not assume it.
Advanced Hunting (≤30d) is the primary test/tune engine. Write and validate every query against RunAdvancedHuntingQuery within a 30-day window. Fall back to the Sentinel Data Lake (query_lake, >30d) only when you need additional supporting evidence that AH's 30-day cap cannot provide (e.g., confirming a rare IOC's longer-term absence/presence). Follow the Tool Selection Rule and timestamp-adaptation guidance in copilot-instructions.md.
⛔ Evidence-based "tested" claims only. A query may be described as tested in the campaign file only if it was actually executed. If a query could not be run (table absent, AH safety filter, telemetry gap), say so explicitly and mark cd_ready: false with honest adaptation_notes. Never imply validation that did not happen. Follow the Evidence-Based Analysis rule in copilot-instructions.md.
⛔ Committed output is PII-free — but published IOCs are NOT PII. Test/tune runs against the live tenant, but campaign files are version-controlled. NEVER paste real tenant entities (your UPNs, hostnames, IPs, workspace/tenant GUIDs, app names) into a campaign file. The article's published IOCs (hashes, domains, URLs, certs, filenames) are the opposite — they are public, shareable, and MUST be included verbatim in the IOC Reference table and the IOC-sweep queries (this is how the committed companion files do it). Do not placeholder or omit a published IOC. Concrete tenant findings from test runs belong in the In-Chat Hunt Findings Summary, not the file. Perform a PII sanity-check before finalizing each file.
⛔ Every IOC must trace to the article. Never invent one. Copy IOCs from the article's "Indicators of compromise" table (and any inline-cited indicators) exactly. Before finalizing, re-open the article's IOC section and confirm each hash/domain/URL/filename in your file appears there character-for-character. Hallucinated or mis-transcribed IOCs are a critical evidence-integrity failure — they produce false detections and erode trust. If an indicator only appears in narrative prose (not the IOC table), label it as such.
Reference, don't reinvent. Use the kql-query-authoring skill's discipline for query construction (schema validation via kql-search MCP, table pitfalls, TimeGenerated vs Timestamp), and the detection-authoring skill's CD Metadata Contract for the <!-- cd-metadata --> block on every query. Read those SKILL.md files when authoring.
Workspace selection. Follow the SENTINEL WORKSPACE SELECTION rule in copilot-instructions.md. In unattended runs the caller will specify the workspace; if exactly one exists, auto-select and state it.
Read config.json for workspace ID, tenant, and Azure MCP parameters before querying.
Quiet runs are a success, not a failure. If nothing in the window qualifies, that is a valid, expected outcome. Emit an empty/"skipped"-only result set and stop — do not lower the bar to manufacture a campaign.
| Dependency | Used for |
|---|---|
kql-search MCP (GITHUB_TOKEN set) | Schema validation, table discovery, community query examples |
Sentinel Triage MCP (RunAdvancedHuntingQuery) | Primary query testing/tuning (≤30d) |
Sentinel Data Lake MCP (query_lake) | Supporting evidence only (>30d) |
| Microsoft Learn MCP | Grounding TTP/technique/error-code explanations |
Python 3 (stdlib xml.etree, urllib) | RSS/Atom parsing — no external dependency required |
web_fetch / web_search tools | Fetching article bodies and the feed |
.github/manifests/build_manifest.py, scripts/generate_tocs.py | Post-processing |
Feed parsing uses Python stdlib (
xml.etree.ElementTree) so it works unattended withoutpip install.feedparsermay be used if already installed, but never assume it.
| Parameter | Default | Description |
|---|---|---|
feed_url | (required in feed mode) | Any RSS 2.0 / Atom feed URL |
article_url | (none) | A single article to process directly (single-article mode) |
lookback_hours | 24 | How far back to consider feed entries (by published date) |
max_campaigns | 3 | Cap on campaigns produced per run (bounds tenant query load + review burden) |
workspace_id | from config.json | Sentinel workspace for testing |
min_queries / max_queries | 4 / 9 | Soft bounds on queries per campaign |
A. Single-article mode — caller passes article_url (the classic "read this article and write hunts" prompt).
→ Skip Phase 1 (feed) and the time filter. Still run Phase 2 (dedup) and Phase 3 (relevance gate) unless the human explicitly says "build it regardless". Then Phases 4–6 for that one article.
B. Feed mode — caller passes feed_url (+ optional lookback_hours).
→ Full Phase 0–6 across all qualifying entries in the window, capped at max_campaigns.
config.json (workspace ID, tenant, subscription, Azure MCP params).Fetch feed_url and parse entries with Python stdlib so it works for both RSS and Atom:
import sys, urllib.request, datetime as dt
import xml.etree.ElementTree as ET
from email.utils import parsedate_to_datetime
feed_url = sys.argv[1]
lookback_hours = int(sys.argv[2]) if len(sys.argv) > 2 else 24
cutoff = dt.datetime.now(dt.timezone.utc) - dt.timedelta(hours=lookback_hours)
raw = urllib.request.urlopen(urllib.request.Request(feed_url, headers={"User-Agent": "ti-campaign/1.0"}), timeout=30).read()
root = ET.fromstring(raw)
ATOM = "{http://www.w3.org/2005/Atom}"
def text(el, *tags):
for t in tags:
for tag in (t, ATOM + t):
f = el.find(tag)
if f is not None and f.text:
return f.text.strip()
return None
def parse_date(s):
if not s: return None
try: return parsedate_to_datetime(s) # RSS pubDate
except Exception:
try: return dt.datetime.fromisoformat(s.replace("Z", "+00:00")) # Atom ISO
except Exception: return None
entries = list(root.iter("item")) or list(root.iter(ATOM + "entry")) # RSS first, else Atom
for e in entries:
title = text(e, "title")
link = text(e, "link")
if not link:
a = e.find(ATOM + "link")
link = a.get("href") if a is not None else None
pub = parse_date(text(e, "pubDate", "published", "updated"))
if pub and pub >= cutoff:
print(f"{pub.isoformat()}\t{title}\t{link}")Run it with the powershell tool (python script.py <feed_url> <lookback_hours>). Collect (published, title, link) for entries inside the window. If the feed only exposes summaries, you'll fetch full bodies in Phase 3.
For each candidate URL, check whether it's already been turned into a campaign:
grep for the article URL (and a normalized form without trailing slash / query string) across queries/threat-intelligence/**.decision: "skipped", reason: "already published", and drop it from the work list.For each remaining candidate, fetch the full article body (web_fetch) and apply the Huntability Rubric. Produce a decision (campaign / skipped) with a one-line reason. Rank campaign candidates by huntability confidence and keep the top max_campaigns.
See Writing / Testing / Tuning Queries. Output: a set of validated queries, each with an honest cd-metadata block and tuning notes.
Write queries/threat-intelligence/YYYY-MM/<slug>.md in the exact Campaign File Format. YYYY-MM = the article's publication month. <slug> = short, kebab/underscore, descriptive (e.g., soho_router_dns_hijacking). Do NOT hand-write the Quick Reference TOC — generate_tocs.py creates it.
python .github/manifests/build_manifest.py (regenerate + validate; fix any error-level warnings on your new file).python scripts/generate_tocs.py (insert the Quick Reference TOC).This is the judgment step: does this article warrant a hunting campaign? Decide with explicit gates, not vibes. Cite the evidence from the article for each gate.
| Gate | PASS criteria | FAIL examples |
|---|---|---|
| G1 — Huntable behavior | Article describes specific, observable attacker behaviors mappable to ≥1 ATT&CK technique (process exec, persistence mechanism, C2 pattern, auth abuse, mailbox manipulation, registry/file artifacts, etc.) | "Threat actor targeted sector X" with no technique detail; pure attribution/geopolitics |
| G2 — Telemetry coverage | ≥1 behavior or IOC maps to a table we ingest (Device*, Email*, Identity*, Signin*/EntraId*, Cloud*, Audit*, OfficeActivity, network/DNS) | Behaviors only observable in telemetry we don't collect (e.g., physical, OT-only with no connector, third-party logs not onboarded) |
| Signal | Effect |
|---|---|
| Concrete IOCs (hashes, domains, IPs, filenames, command lines, registry keys, user-agents) | ↑↑ strong — enables direct-match hunts |
| Multiple distinct mappable TTPs (richer attack chain) | ↑ |
| Named ATT&CK technique IDs in the article | ↑ |
| Novel TTP not already covered by an existing campaign/query | ↑ |
| Overlaps heavily with an existing campaign | ↓ (consider extending the existing file instead of a new one) |
BUILD if G1 PASS and G2 PASS and (concrete IOCs present OR ≥2 distinct mappable TTPs). Otherwise SKIP with a specific reason (which gate failed / which auto-skip category).
Record the rubric outcome in the structured result reason field (e.g., "BUILD: 4 IOCs + 6 mappable TTPs (endpoint, identity)" or "SKIP: product announcement, G1 fail").
For each qualifying article:
Extract the TTPs and IOCs. Map each TTP to ATT&CK technique IDs (use Microsoft Learn MCP to confirm technique semantics). Build the IOC table (hashes, domains, IPs, filenames, etc.).
Pick detection surfaces. Map TTPs → tables. Prefer XDR-native tables for AH testing. Check the discovery manifest + grep queries/** first — if an existing query file already covers a TTP, reuse/adapt its pattern and cite it as a companion rather than duplicating.
Author each query following the kql-query-authoring discipline:
get_table_schema) before writing.copilot-instructions.md (e.g., TimeGenerated vs Timestamp, dynamic-field parse_json, IpAddress casing).project a useful, PII-light column set; order by/summarize to bound output.Test in Advanced Hunting (≤30d). Run every query via RunAdvancedHuntingQuery. Apply the Step-5 zero-result sanity check from copilot-instructions.md — a 0-row result must be verified correct (e.g., a direct-IOC sweep returning 0 in a clean environment is the desired outcome; a 0 from a broken filter is not). As you test, record the findings for each query — row count, whether hits look like true/false positives, and any notable entities — so you can build the In-Chat Hunt Findings Summary in Phase 6. These raw findings stay in chat; they do NOT go into the campaign file.
Tune. If a query is noisy, add targeted exclusions (trusted publishers, known service accounts, expected automation) and document them in Tuning Notes — generically, never with live tenant identifiers. Re-run after tuning.
Supporting evidence via Data Lake (>30d) — only if needed. If 30 days is insufficient to characterize prevalence/absence of a rare IOC, run a scoped query_lake (adapt Timestamp→TimeGenerated for Sentinel/LA tables). Use this for evidence, not as the primary engine.
CD metadata. Attach a <!-- cd-metadata --> block to every query per the detection-authoring CD Metadata Contract. Set cd_ready: true only for high-fidelity, low-noise queries that actually validated cleanly; otherwise cd_ready: false with adaptation_notes explaining what's needed.
IOC freshness note. IOC-match queries (hashes/domains) rot. Note that operators rotate IOCs and recommend periodic refresh from current MS TI / VirusTotal / a TI indicator table.
Match the existing files in queries/threat-intelligence/YYYY-MM/ exactly. Structure:
# <Threat / Actor / Campaign> — Threat Hunts
**Created:** YYYY-MM-DD
**Platform:** Microsoft Defender XDR | Microsoft Sentinel | Both
**Tables:** <exact KQL table names, comma-separated>
**Keywords:** <attack techniques, actor names, tooling, artifacts, field names>
**MITRE:** <technique/tactic IDs, comma-separated>
**Domains:** <threat-pulse domain tags: incidents|identity|spn|endpoint|email|admin|cloud|exposure>
**Timeframe:** Last N days (configurable)
**Source:** [<Article title> (<date>)](<article_url>)
---
## Threat Overview
<2–4 sentence synopsis grounded in the article. Include actor attribution if stated.>
### TTP Summary
| Capability | TTP |
|---|---|
| ... | ... |
### ⚠️ Hunt Pitfalls
| Pitfall | Mitigation |
|---|---|
| ... | ... |
---
## IOC Reference
<Table of published IOCs (hashes/domains/IPs/filenames). Note they rot; recommend refresh.>
---
## Query 1: <Title>
**Purpose:** <what it detects, and what a clean result looks like>
**Severity:** <Low|Medium|High>
**MITRE:** <technique IDs>
<!-- cd-metadata
cd_ready: true|false
cd_table: <PrimaryTable>
cd_frequency: NRT|Hourly|...
cd_severity: <Low|Medium|High>
cd_mitre: ["T...."]
cd_entities: ["device","file","account",...]
cd_adaptation_notes: "<honest notes>"
-->
` ` `kql
<tested query>
` ` `
**Expected results:** <what to expect; 0-row interpretation if a direct IOC sweep>
---
## Query 2: ...
...
---
## General Tuning Notes
1. IOC refresh ...
2. Telemetry gaps ...
3. CD-readiness summary ...
---
## References
- Microsoft Threat Intelligence — [<title>](<url>)
- MITRE ATT&CK — [<technique/actor>](<attack url>)
- Companion files: [`queries/<domain>/<file>.md`](...)Header field requirements (enforced by build_manifest.py): Tables, Keywords, MITRE, and Domains are mandatory. Domains values must come from the valid set (incidents, identity, spn, endpoint, email, admin, cloud, exposure). A missing Domains is an error-level manifest warning.
Do NOT pre-write a ## Quick Reference — Query Index section — generate_tocs.py inserts it. Pre-creating it breaks the strip-and-reinsert logic.
At the end of every run, emit a JSON array (one object per article considered) so the calling automation can isolate per-article PRs. Print it in a fenced ```json block:
[
{
"article_title": "SOHO router compromise leads to DNS hijacking...",
"article_url": "https://www.microsoft.com/en-us/security/blog/2026/04/07/...",
"published": "2026-04-07T00:00:00Z",
"decision": "campaign",
"reason": "BUILD: 6 mappable TTPs + IOCs (endpoint, identity)",
"file_path": "queries/threat-intelligence/2026-04/dns_hijacking_soho_compromise.md",
"queries_written": 9,
"queries_tested": 9,
"queries_cd_ready": 4,
"domains": ["endpoint", "identity"]
},
{
"article_title": "Microsoft named a Leader in ...",
"article_url": "https://www.microsoft.com/en-us/security/blog/2026/04/05/...",
"published": "2026-04-05T00:00:00Z",
"decision": "skipped",
"reason": "SKIP: analyst-recognition post, G1 fail",
"file_path": null
}
]decision ∈ campaign | skipped. For skipped, file_path is null. Follow the JSON block with a short human-readable summary (counts, what was built, what was skipped and why).
After the structured JSON, emit a per-article findings summary that reports what the test runs actually surfaced in the tenant. This is the counterpart to the PII-free campaign file: the file is the reusable, sanitized hunt definition; this summary is the investigation result of running those hunts right now.
Where it goes: chat / run output only. Never write it to a tracked file (not the campaign file, not any queries/** or docs/** file). For unattended runs, the calling automation decides where to route it (e.g., PR description, notification, ticket) per its own data-handling policy — the skill just emits it.
PII posture: Unlike the committed campaign file, this summary may include the concrete entities an analyst needs to act (device names, UPNs, IPs, file hashes, sender addresses, message IDs) — it is investigation output to an operator who already has tenant access, the same as any other investigation skill's chat output. Do not redact what's needed for triage; do not persist it to the repo.
Skip when nothing actionable: If every query returned a verified-clean 0 (e.g., all IOC sweeps clean in a tenant where the IOCs predate the AH window), say so in one line per query rather than padding. The value is in the hits and the FPs, not in restating "0 rows" decoratively.
## 🔎 Hunt Findings — <Article Title> (<run date>)
**Workspace:** <name> **Lookback:** <window> **Queries run:** <n>
| # | Query | Rows | Assessment | Action |
|---|-------|------|------------|--------|
| 1 | AI-brand display-name spoof | 0 | ✅ Clean (tuned; no spoofed-domain phish in window) | None |
| 4 | Fake-AI installer download | 3 | 🟠 2 likely FP (sanctioned vendor), 1 to review | Verify host on DEVICE-X; see below |
| 7 | Endpoint IOC sweep | 0 | ✅ Clean — IOCs predate 30d AH window | Re-run in Data Lake (90d) for retrospective |
### 🔴 True / suspected positives
- **Q4 — DEVICE-X / user@contoso.com:** downloaded `seedance_setup_x64.exe` from `hxxp://…` at <time>. Not a sanctioned vendor host. **Follow-up:** isolate/triage device, pivot Q5 (execution) + Q7 (C2).
### 🟠 False positives to tune
- **Q4 — 2 rows:** `<vendor>` installer from `downloads.<vendor>.com` — legitimate. **Tuning:** add `downloads.<vendor>.com` to the trusted-host list (reflected generically in the file's Tuning Notes, not as a literal tenant value).
### ⚠️ Follow-up actions
- [ ] Re-run Q3/Q7 IOC sweeps in Sentinel Data Lake (>30d) for retrospective coverage.
- [ ] Confirm DEVICE-X download disposition with endpoint team.
- [ ] If positives confirmed, consider promoting Q1/Q4/Q5 to custom detections (see detection-authoring skill).Closing the loop with the campaign file: when a finding reveals a tuning need (e.g., a legitimate host triggering FPs), capture the generic fix in the campaign file's Tuning Notes / adaptation_notes (e.g., "exclude sanctioned vendor download hosts") — never the literal tenant value. The findings summary names the specific host; the file describes the class of exclusion.
| Pitfall | Mitigation |
|---|---|
| Feed exposes only summaries, not full TTPs | Always web_fetch the full article body before the relevance gate and query authoring |
Atom vs RSS schema differences (<entry>/<published> vs <item>/<pubDate>) | The Phase-1 parser handles both; never hardcode one shape |
| Treating a marketing/recognition post as huntable | Apply the auto-skip categories; G1 must find real behavior |
| Claiming a query is "tested" when it errored or hit the AH safety filter | Only mark tested if it ran and returned a sane result; otherwise cd_ready: false + notes |
| Pasting live tenant entities (from test runs) into the committed file | Campaign files are PII-free; test data informs tuning notes only |
| Placeholdering or omitting an article's published IOCs | Published IOCs are public, not PII — transcribe them verbatim into the IOC Reference table AND the IOC-sweep queries. Never ship <HASH1> placeholders or "see table" stand-ins. |
| Inventing / mis-transcribing an IOC | Every hash/domain/URL/filename must appear character-for-character in the article's IOC table. Re-verify against the source before finalizing; a hallucinated IOC is a critical evidence-integrity failure. |
| Using Data Lake as the primary engine | AH ≤30d is primary; Data Lake >30d for supporting evidence only |
| Hand-writing the Quick Reference TOC | Let generate_tocs.py generate it |
| Forgetting to regenerate the manifest | Always run build_manifest.py after writing files; resolve error-level warnings |
| Duplicating an existing campaign/query | Grep first; extend or cite companions instead of duplicating |
| Performing git operations | Never — publishing is the automation's responsibility |
| Putting real findings/entities in the committed file, OR omitting them from chat | Two separate outputs: campaign file = PII-free reusable hunt; In-Chat Hunt Findings Summary = real hits/FPs/follow-ups (chat only). Don't merge them. |
Before emitting results, confirm:
decision + evidence-based reasonDomains valid)cd-metadata block with an honest cd_ready valuebuild_manifest.py runs clean (no error-level warnings on the new file)generate_tocs.py has inserted the Quick Reference TOC© SCStelz, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .github/skills/threat-intel-campaign of SCStelz/security-investigator.
Open the folder on GitHubat commit 51e1385
Threat Intel Campaign next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Threat Intel Campaign this skillSCStelz/security-investigator | 249 | — | ~6.9k | Automated safety check: Pass | MIT | |
| Building Detection Rules With Sigmamukul975/Anthropic-Cybersecurity-Skills | 34k | — | ~2.7k | Automated safety check: Pass | Apache-2.0 | |
| Unified Secops Platformvinayaklatthe/microsoft-security-skills | 175 | — | ~2.1k | Automated safety check: Pass | MIT | |
| Enrich Iocdandye/ai-runbooks | 127 | — | ~702 | Automated safety check: Pass | Apache-2.0 | |
| Detecting Azure Service Principal Abusemukul975/Anthropic-Cybersecurity-Skills | 34k | — | ~2.1k | Automated safety check: Pass | Apache-2.0 | |
| Implementing Stix Taxii Feed Integrationmukul975/Anthropic-Cybersecurity-Skills | 34k | — | ~2.7k | Automated safety check: Pass | Apache-2.0 |
mukul975/Anthropic-Cybersecurity-Skills
Builds vendor-agnostic detection rules using the Sigma rule format for threat detection across SIEM platforms including Splunk, Elastic, and Microsoft Sentinel.
vinayaklatthe/microsoft-security-skills
Guidance for the Microsoft unified security operations platform that brings Microsoft Sentinel, Microsoft Defender XDR, Security Copilot, Threat Intelligence, and Microsoft Security Exposure…
dandye/ai-runbooks
Enrich an IOC (IP, domain, hash, URL) with threat intelligence.
mukul975/Anthropic-Cybersecurity-Skills
Detect Azure service principal abuse in Microsoft Entra ID using KQL detection queries (Sentinel/Splunk) against Azure AD Audit and Sign-in Logs, covering added credentials, privileged role…
mukul975/Anthropic-Cybersecurity-Skills
Implements a STIX 2.1/TAXII 2.1 threat-intelligence feed consumer and producer in Python, covering TAXII server discovery, collection polling, parsing STIX bundles with the stix2 library, and…
mukul975/Anthropic-Cybersecurity-Skills
Queries Azure Monitor activity logs and sign-in logs via azure-monitor-query to detect suspicious administrative operations, impossible travel, privilege escalation, and resource modifications.
SCStelz/security-investigator
A skill your agent uses when asked to investigate Conditional Access policy changes, sign-in failures related to CA policies (error codes 53000, 50074, 530032), or suspected policy…
SCStelz/security-investigator
Weekly review of an investigation tenant-context memory file against the most recent SOC scan reports (e.g.
SCStelz/security-investigator
A skill your agent uses when asked to create heatmaps, visualize patterns over time, show activity grids, or display aggregated data in a matrix format.
SCStelz/security-investigator
Report/investigate RUNTIME ACTIVITY of AI agents (Agent 365 / Copilot Studio / M365 Copilot / Work IQ) — agents used, tools/connectors, channels, tokens, prompt/reply content, and Prompt Shield…
SCStelz/security-investigator
Audit or report on AI agent security posture across Copilot Studio, Microsoft 365 Copilot, Microsoft Foundry, and third-party agents.
SCStelz/security-investigator
Audit Entra ID app registration and service principal security posture.
Works with
Categories
Turn a published threat-intelligence article into a tested threat-hunting campaign. Threat Intel Campaign is an agent skill from SCStelz/security-investigator. Turn a published threat-intelligence article into a tested threat-hunting campaign.
Threat Intel Campaign fits situations like: keywords: threat intel campaign; ingest threat intelligence; write hunts from this article; threat intelligence blog.
Run `npx skills add SCStelz/security-investigator --skill threat-intel-campaign -a claude-code`. Or copy the skill folder (.github/skills/threat-intel-campaign in SCStelz/security-investigator) into .claude/skills/threat-intel-campaign in your project. Claude Code loads it when a task matches its description.
Run `npx skills add SCStelz/security-investigator --skill threat-intel-campaign -a codex`. Or copy the skill folder (.github/skills/threat-intel-campaign in SCStelz/security-investigator) into .agents/skills/threat-intel-campaign in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add SCStelz/security-investigator --skill threat-intel-campaign -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/threat-intel-campaign, .gemini/skills/threat-intel-campaign, .github/skills/threat-intel-campaign and .opencode/skills/threat-intel-campaign in your project.
Going by SKILL.md and its folder, Threat Intel Campaign needs the command-line tools its instructions call (python, git, gh and pip) and credentials named GITHUB_TOKEN. Our summary lists: Python 3; A credential in GITHUB_TOKEN.
SKILL.md names 2 domains. In commands or code: microsoft.com and w3.org; the agent is likely to contact these when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Threat Intel Campaign is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.9k tokens (SKILL.md is roughly 27k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Threat Intel Campaign: Building Detection Rules With Sigma (mukul975/Anthropic-Cybersecurity-Skills, 34k stars), Unified Secops Platform (vinayaklatthe/microsoft-security-skills, 175 stars), Enrich Ioc (dandye/ai-runbooks, 127 stars) and Detecting Azure Service Principal Abuse (mukul975/Anthropic-Cybersecurity-Skills, 34k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
SCStelz (a GitHub user) maintains it in SCStelz/security-investigator, which has 249 GitHub stars. The repository holds 22 skills in this directory. The repository was last updated on October 8, 2026.
Source: SCStelz/security-investigator on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.