- name
- cti-expert
- description
- Cyber threat intelligence and OSINT analysis toolkit. Runs structured investigations and delivers analyst-grade intelligence products with sourced, trust-scored findings. Use for OSINT and CTI cases, digital-footprint and exposure review, domain/subdomain/DNS/certificate recon, web-infrastructure pivoting (favicon hashes, tracker IDs, TLS certs, phishing-kit fingerprinting, campaign clustering), username/email/phone enumeration, breach and infostealer-log triage, image forensics, geolocation, crypto-wallet and IBAN/bank-account tracing, darknet search, M365/Azure and SaaS tenant recon, China/Sinophone recon (ICP filings, PRC corporate registries, Baidu/FOFA/Quake/ZoomEye), vulnerability and ransomware lookup, threat modeling, PII redaction, and structured reporting. Commands include /case, /sweep, /query, /webpivot, /username, /phone, /email-deep, /breach-deep, /icp, /cn-corp, /iban, /stealer-log, /exposure, /threat-model, /report, /brief, /redact, /apikeys.
- version
- 2.12
- author
- Hieu Ngo - chongluadao.vn
# CTI Expert
Cyber threat intelligence and open-source intelligence skill. Turns Claude into a trained CTI/OSINT analyst. Generates precision search queries, interprets public data, builds case timelines, and delivers structured intelligence products — no API keys, no paid subscriptions.
> **Runs anywhere.** Works in **Claude Code** (Desktop & CLI) and in **OpenAI Codex / ChatGPT** and other `AGENTS.md`-aware agents — see [`AGENTS.md`](AGENTS.md) for the cross-agent runtime contract. Throughout this file, **`$SKILL_DIR`** = the directory containing this `SKILL.md` (Claude Code: `~/.claude/skills/cti-expert`; Codex/manual clone: the repo you are working in). Resolve it by locating `SKILL.md` — never hard-assume `~/.claude`. Detect the OS once (Windows/macOS/Linux) and prefer **uv** for all Python — see §13 Tool Auto-Install Policy.
Collection method: `agent-browser` when available (JavaScript-heavy sites, infinite-scroll, screenshot evidence), with automatic fallback to web search / web fetch / direct URL fetch. Tool limitations are logged as collection gaps — never as case blockers.
---
## 1. Quick Start
```bash
# Full autonomous case — runs every applicable technique
/case target.com
# Guided flow for first-time investigators
/flow person
# Summary of what's been found so far
/brief
```
Append `--yolo` to any command to skip all interactive prompts and confirmations. The analyst makes every decision autonomously.
---
## 2. AEAD Case Lifecycle
Every investigation follows four phases:
| Phase | What Happens |
|-------|-------------|
| **Acquire** | Collect raw data — `/sweep`, `/query`, `/username`, `/phone`, `/email-deep`, `/breach-deep`, `/subdomain`, `/webpivot` + `/icp` (domain/URL targets), `/dork-sweep` · `/docleak` · `/github-osint`/`/secrets`, `/cn-corp` · `/iban` · `/hash-id` on discovery |
| **Enrich** | **Recursive pivot loop** — the [pivot orchestration engine](engine/pivot-orchestration.md) tracks candidate identifiers hop-by-hop; `/cti` executes each matching enrichment leg and re-enters **verified owner-linked** yields as seeds. Registrant email→`/email-deep` (not a second `/breach-deep` or `/intelx` on the same selector), username→`/username`, name→`/dork-sweep`+`/docleak`, apex→`intelx phonebook`+`/secrets`+`/github-osint` when keyed and allowed. The deterministic pipeline covers infrastructure; the slash-command agent runs the remaining identity/exposure legs and closes each frontier lead with evidence. Never treat a generated query or shared infrastructure as an operator link. Acquire↔Enrich iterate until the applicable frontier is addressed. |
| **Assess** | Score and verify — `/exposure`, `/threat-model`, `/validate`, `/coverage`, `/verify-finding`. Judgments carry **likelihood terms**, coverage gets the **5W1H pass**, attributions get an **ACH matrix** ([`handbook/analytic-standards.md`](handbook/analytic-standards.md)). **If the case has not converged** (frontier still open after the pivot loop + deterministic pipeline) and posture is active, `/case` **auto-escalates to the `/harness` deepening loop** — keyless-first (the CLI's own model), egress hard-gated on hostile infra; `--no-harness` opts out |
| **Deliver** | Package output — `/report`, `/brief`, `/render`, `/workspace save` — **first ASKS whether to import more evidence from manual investigation** (merged into the report JSON before anything is built), **always auto-saves the base data bundle (.md + .json + .csv + IOC bundle: .stix.json/.txt/.csv/.jsonl), then ASKS which presentation report to render — (a) PDF · (b) DOCX · (c) HTML · (d) all** (both prompts skipped under `--yolo`/guided-auto, which default to HTML). When `CHONGLUADAO_API_KEY` is set, the IOC bundle also attaches CLD's **STIX + MISP indicator feed** as companion artifacts (`cld_api.py feed stix2\|misp --raw` → loadable bundle, not merged into the case graph). **Deep-layer persist (automatic, ZERO extra egress):** when `/backend` is live, `/case` **reuses the pivots it already collected** — never re-fetches — to persist the versioned case at `$SKILL_DIR/intel_engine/cases/<CASE-ID>/` and correlate it cross-case; see the auto-chain note below. See [`connectors/chongluadao-api.md`](connectors/chongluadao-api.md) |
Run `/progress` at any point to see which phase you're in and what's pending.
> **Intake before Acquire (`/cti` driver).** `intel.py cti <target>` scopes the case **before any
> collection** (Quarry included): at a terminal it asks the **target class** and **purpose** (the
> `case_scope` fields — classes from `WebPivot/references/intake.json`) and persists the answers to
> `cases/<case>/scope.json`; a case that already has a stated scope is not re-asked. Scripted runs
> (no TTY, `--yes`, `INTEL_NO_PROMPT`, or nested inside a harness phase) never block on input:
> pass `--class <class>` / `--purpose <purpose>`, otherwise the run proceeds on the default
> (`unknown` / `triage`) and prints `[scope] DEFAULTED …`. The summary JSON carries `target_class`.
> **`/case` and web-infra pivoting.** For a **domain or URL** target, `/case` includes
> web-infrastructure pivoting (`/webpivot`) in the Acquire phase. It runs **keyless by default**
> (crt.sh + passive DNS + anonymous urlscan) and **upgrades automatically when premium keys are
> set** via `/apikeys` (Shodan/Censys/FOFA/Hunter.how/DNSLytics/SecurityTrails/urlscan-PRO/WhoisXML). With keys
> the pipeline also: reads the **urlscan Pro hostname lifecycle** (pre-registration NS/A eras on the
> timeline, verdict rows in Appendix B), runs the **MO-neighbour pivot** on the estate's non-CDN origin
> (co-tenants WHOIS-verified; only a registrant join-key ever seeds, same-MO personas render as a
> rung-10 *Related personas* table — `--mask-personas` to aggregate), **measures entitlement**
> (`meta.capability.plans`, per-case `capability_plans.json`; Censys' search is its own probe), and
> fires SecurityTrails DNS-history + DSL reverse-WHOIS, DNSLytics reverse-IP (co-tenancy-filtered),
> a once-per-case Censys cert search, Shodan cert/JARM search and IntelX (loop: `--full` only). Every
> metered leg is `--free-only`/`no_spend`-gated; IntelX selectors, WhoisXML/SecurityTrails reverse-WHOIS terms,
> MO-neighbour origins, DNSLytics reverse-IP and the Censys cert search are bought once per CASE (on-disk memo),
> while per-host legs (urlscan lifecycle, SecurityTrails subdomains/history, Shodan search) stay per host under
> per-case caps. Because
> `/webpivot` can fetch the target directly, for hostile infrastructure it prefers passive capture
> (urlscan/Wayback) — see [`techniques/web-pivot.md`](techniques/web-pivot.md). It is **not** run for
> username/phone/person targets.
>
> **Historical origin-IP sweep (automatic, offline join).** Every A/AAAA record any source ever saw
> for a collected host or subdomain — CIRCL/HackerTarget passive DNS, SecurityTrails history,
> Validin, urlscan's hostname index, Webamon servers, passive-SSL cert→IP, keyless urlscan scans — is
> merged into one dated `origin_history` block on the domain pivot and classified: **CDN / WAF /
> DDoS-protection edge** (CIDR index *plus* the ASN / org the source named: Cloudflare, Akamai,
> Imperva, Sucuri …), **parking**, **bogon**, **ambiguous** (DDoS-Guard / StackPath — they sell both
> proxying and servers), or **origin** (customer Google Cloud VMs included). The loop and the harness
> then collect each **origin** and **ambiguous** IP as an IPPivot seed (ASN + abuse contact,
> ports/banners, PTR, self-hosted MX, passive-SSL certificates, co-hosted domains — keyless
> reverse-IP under `--free-only`). Rails: an address last seen **before the current registration**
> is the previous owner's (lead, never collected); the **current co-tenants** of a
> `historical`/`undated` origin or of an `ambiguous` network are held as dated leads, never seeds or
> KB edges; a recent origin's co-tenants pass the usual shared-hosting guard and stay
> neighbour-rung. The box's own IP lookup is checked again after collection, so an edge or
> DDoS-Guard network the history could not name still holds its tenants. On a **cloud/VPS** network
> (Google Cloud, DigitalOcean, OVH, Hetzner, Vultr …) up to **10** co-tenants are queued and linked
> automatically — counted the same way by the frontier and the KB, with the reverse index's own total
> so a truncated list never hides a big shared host; above that **every** domain goes to analyst
> approval (harness: `proposal.json` / `case_approve`; loop: an *APPROVAL REQUIRED* gap and
> `/approve-cohost`) and nothing is collected or linked until approved. Approval is **per domain**
> (a shared-platform tenant such as `kit.pages.dev` counts on its own, never the whole platform) and
> is the evidence: exactly the approved sites are seeded, linked, and may bind the cluster with the
> case host past both the shared-hosting bound and the clustering cap of 8 domains per indicator;
> automatically linked sites keep that cap. Every unapproved site on the box stays a held lead,
> never automatic. Every held origin-sweep lead lists all its domains. Without the CDN range
> cache an address is `undetermined` and never collected.
> Caps and the edge/ambiguous/parking lists live in
> `intel_engine/WebPivot/references/origin_history.json`; the assessment renders a *Historical
> Origin IPs* table (with tenancy overlap for shared boxes), the house report a *Historical origin
> hosting* table, and `/origin-history <case>` prints the dated history on demand.
>
> **⭐ ChongLuaDao is the first-party premium upgrade.** With `CHONGLUADAO_API_KEY` set
> (`/apikeys set chongluadao <KEY>`), Acquire/Enrich fold CLD's own datasets into `/scam-check`,
> `/threat-check`, `/phone`, `/breach-deep`, `/email-deep`, `/vuln-check` and `/impersonate`, and
> `/cld <target>` is the direct entry point. Your client connects **only to ChongLuaDao, never
> to the target** (provable from `scripts/cld/cld_api.py`); for URL/AI/IP checks CLD does any
> target fetch server-side — the safe first-touch verdict on a live scam funnel before a direct
> pivot. Full catalog + AEAD placement: [`connectors/chongluadao-api.md`](connectors/chongluadao-api.md).
>
> **Archive IOC harvest runs by default too.** For domain/URL targets the Acquire phase also runs
> `wayback_harvest.py <domain> --indicators` (add `--urlscan` when `URLSCAN_API_KEY` is set),
> harvesting **emails, phones, crypto wallets, tracking/verification IDs, SaaS-operator IDs, and
> socials from the *entire* Wayback history** — not just the live page — with first-seen/last-seen
> per selector. It writes case-schema `indicators[]` to `<case>/raw/harvest.indicators.json`, which
> merge into the case and flow into the **auto-saved IOC bundle** at Deliver. This is the step that
> recovers selectors a network later scrubbed — across the whole snapshot corpus, not just the live page.
> Passive by construction — only web.archive.org (+ urlscan.io if keyed), never the target.
>
> **The five v2.6 commands are in the pipeline too — no flags.** `/icp` runs for every
> domain/URL/org target (and an IP's resolved hostname); `/cn-corp`, `/iban` and `/hash-id`
> fire the moment a company name/USCC, payment detail, or hash appears — and all three feed
> their yields **back into the recursive pivot loop** as new seeds, so an ICP licence serial or
> a reused bank account expands the graph like any other node. `/redact` is the exception: it
> is **opt-in** (`--redact`), because a redacted report is a weaker artifact and that should
> always be a deliberate choice. Full trigger table: §Technique Activation Matrix.
> Narrow with `--no-cn`.
> **Two layers, one skill: broad collector → deep pipeline.** cti-expert is the **broad
> collector** — the wide net of Acquire/Enrich commands (`/webpivot`, `/sweep`, `/subdomain`,
> `/icp`, `/username`, `/email-deep`, `/breach-deep`, …) that pull artifacts from anywhere. The
> **`intel_engine` engine is now vendored in-repo under `intel_engine/`** (`intel_engine/harness/`,
> `intel_engine/tools/`, `intel_engine/WebPivot/`, `intel_engine/IntelGraph|IntelReport|BinaryPivot|IntelAnalysis/`)
> and supplies the **pipeline chains + deeper pivoting logic**: a persistent knowledge base (`intel_engine/knowledge/`), versioned cases
> (`cases/`), cross-case correlation, calibrated assessment, and rendering.
>
> **The chain (automatic for `/case`, and it NEVER re-fetches):** broad collection (cti-expert)
> already ran `pivot_extract` per host during Acquire. When `/backend` resolves to Tier 1/2 **and**
> the run produced ≥1 host seed (domain/URL/IP), `/case` hands what it already collected to the
> deterministic pipeline in **reuse mode** (`--no-collect`) — one command, a complete case dir, and
> nothing touches the target again:
>
> 1. Write each host's already-collected pivot JSON to `$SKILL_DIR/intel_engine/cases/<CASE-ID>/raw/<host>.json`
> and the host list to an absolute `<CASE-ID>-seeds.txt`, both **anchored at the engine root**
> (`ROOT=$SKILL_DIR/intel_engine`) — never a CWD-relative path.
> 2. `intel.py pipeline open <CASE-ID> <abs-seeds.txt> --no-collect` — `--no-collect` skips the live
> fetch and runs the *rest of the existing pipeline* over the raw you just wrote: ingest →
> prior-overlap (`/recall`) → risk (`/risk`) → shared cluster seeds → `clusters.json` →
> `case_graph.json` → ICD-203 `assessment.md`. Every step is a KB/local read: zero egress, zero
> metered calls.
> 3. In a full `/cti` (not `--quick`), run `intel.py enrich <CASE-ID>` next. It reads stored
> raw pivots, performs matching third-party identity/OSINT lookups, checkpoints results
> in `cases/<CASE-ID>/enrichment/` (including `index.json`), and prints recorded credit
> units per leg. `--free-only`/`no_spend` leave metered leads OPEN for a later full run;
> passive posture forbids direct target contact, not third-party metered lookups. This
> phase has third-party egress and can spend credits, unlike the reuse-mode handoff.
>
> **The persisted case lands at `$SKILL_DIR/intel_engine/cases/<CASE-ID>/` — `raw/`, `shared.txt`,
> `clusters.json`, `case_graph.json`, `assessment.md` — NOT the current working directory**, and it
> is a COMPLETE case that `intel.py pipeline status <CASE-ID>` accepts (not the partial dir a hand-rolled
> op sequence would leave). `<CASE-ID>` is the same id as the report filenames (mint
> `CASE-YYYYMMDD-NN` when none is given). Its cluster/risk/assessment fold back into the auto-saved
> report. Tier 3 (stateless), or a person/username/phone target with no host seeds → the handoff is
> **skipped silently** and the tier noted; broad collection + report are unaffected.
>
> **A collecting run never starts without the analyst's go-ahead.** `/case`'s handoff stays
> `--no-collect`, and `/cti`'s default engine — the cross-type harness loop (`intel.py cti
> <target>`) — collects live. So **confirm before launching it**: target, case id, rounds,
> spend posture. `intel.py pipeline open` without `--no-collect` re-fetches every seed directly
عرض على GitHub