Tradecraft Lookup
The Tradecraft Lookup tool gives the agent a personal, user-curated catalog of trusted offensive-security knowledge URLs — HackTricks, PayloadsAllTheThings, CVE PoC repos, vendor research blogs — and lets it pull the exact page it needs mid attack chain.
Two things make it different from the agent's other reference tools:
- You curate the sources. The agent only consults sites you explicitly trusted. There is no open-web crawl at runtime.
- The catalog itself is the routing intelligence. When you add a resource, the system writes a 250-350 word summary of what it covers. That summary is what the model reads to decide whether the resource is the right place to look. No vector database, no learned router.
The catalog is configured per-user in Global Settings → Tradecraft. This page covers what the tool does at runtime, how the cache works, and how it differs from
web_searchand the FAISS Knowledge Base. For full implementation details and sequence diagrams, see README.TRADECRAFT.md.
When the agent uses it
The tool is wired into the exploitation and post-exploitation phases only. The agent is told to prefer it after query_graph and web_search when it needs a specific exploitation page or PoC, not general background.
Typical calls:
| User question | What the agent does |
|---|---|
| "Escalate from a low-privilege domain user to Domain Admin." | Picks hacktricks, narrows the sitemap to the Kerberoast page, fetches it, reads the impacket-GetUserSPNs example. |
| "Find a public PoC for CVE-2021-41773." | Picks trickest_cve, builds the deterministic path contents/2021/CVE-2021-41773.md, returns the markdown. |
| "What's a clean SSRF bypass for cloud metadata?" | Picks payloads, narrows the sitemap to the SSRF folder, fetches the bypass cheatsheet. |
| "Find a recent writeup on subdomain takeover automation." | Picks 0xpatrik, narrows the agentic-crawl sitemap to the relevant post, fetches it. |
If zero resources are enabled, the tool is removed from the registry entirely — the agent will not see it as an option.
Two phases: Verify (once) vs Agent session (every call)
The tool's design separates a slow, expensive one-time setup from a fast, cheap per-call lookup:
flowchart LR
subgraph "Verify phase (once per resource)"
V1[Detect type from homepage]
V2[Build sitemap with type-specific strategy]
V3[LLM writes 250-350 word summary]
V4[Persist sitemap + summary]
end
subgraph "Agent session (every tool call)"
A1[Agent picks resource_id]
A2[Lexical rank sitemap, top 30]
A3[Small LLM picks best page]
A4[Tier 1 HTTP fetch / cache hit]
A5[Wrap in untrusted-content envelope]
end
V1 --> V2 --> V3 --> V4
V4 -.JSON in DB.-> A2
- Verify phase runs once when you click Add Resource (or Refresh). Slow (seconds to minutes), expensive (one or many LLM calls), produces durable data.
- Agent session runs every time the agent calls the tool. Fast (sub-second on cache hit), cheap (zero or one small LLM call), reads the data verify wrote.
Resource types
The tool understands six resource types. Each one drives a different sitemap-extraction strategy and a different default cache TTL. Detection runs at verify time on the homepage HTML — there is no manual override.
| Type | Detection signal | Sitemap source | Default TTL |
|---|---|---|---|
mkdocs-wiki | <meta name="generator" content="mkdocs">, material, or mdBook signals | /sitemap.xml → mkdocs.yml → rendered nav harvest | 7 days |
gitbook | gitbook.io host, GitBook generator meta, static-2v.gitbook.com, data-rsc-router | /sitemap.xml → Tier-2 Playwright nav harvest | 7 days |
github-repo | github.com / raw.githubusercontent.com (and not a CVE repo) | GitHub Trees API recursive, filtered to .md, .txt, .rst | 7 days |
cve-poc-db | GitHub repo whose name contains cve OR a homepage with >20 CVE-IDs | Just {owner, repo, branch} — per-CVE lookups are deterministic by ID | 30 days |
sphinx-docs | *.readthedocs.io, _static/searchindex.js, or Docusaurus signals | searchindex.json / search-index.json | 14 days |
agentic-crawl | none of the above matched | Bounded LLM-driven Playwright loop | 1 day |
For the in-depth comparison of each type — what triggers the detector, how the sitemap is built, and what the agent sees at runtime — see the Resource type differences section in Global Settings.
Tool signature
tradecraft_lookup(
resource_id: str, # slug from your enabled resources catalog
query: str = "", # free-text technique/topic (used for section pick)
cve_id: str = "", # required when resource_id points to a cve-poc-db
section_path: str = "", # skip auto-pick, force a specific URL or repo path
force_refresh: bool = False, # bypass cache for this call
)
The tool's docstring is rebuilt on every project load from your enabled resources. The agent sees a catalog like:
TOOL: tradecraft_lookup
Use this tool to fetch specific exploitation tradecraft from a known
knowledge resource.
Available resources (pick the best resource_id for your query):
hacktricks (mkdocs-wiki) https://book.hacktricks.wiki
Comprehensive offensive security wiki. Covers web (XSS, SSRF,
SSTI, deserialization), Active Directory (Kerberoasting, DCSync,
golden ticket, ADCS), Linux/Windows privesc, cloud, container
escapes... [your saved summary]
payloads (github-repo) https://github.com/swisskyrepo/PayloadsAllTheThings
Payload library organized per vulnerability class. Each folder
has Intruder lists, bypass cheatsheets... [your saved summary]
trickest_cve (cve-poc-db) https://github.com/trickest/cve
CVE -> public PoC mapping. Pass cve_id="CVE-YYYY-NNNNN".
[your saved summary]
Toggling a resource off in the UI removes it from this catalog on the next agent invocation.
What happens on each call
sequenceDiagram
participant Agent as Claude (main agent)
participant Tool as tradecraft_lookup
participant Cache as sqlite + disk
participant Web as Target site
participant LLM as Claude Haiku (section picker)
Agent->>Tool: resource_id, query
Tool->>Tool: Look up resource by slug
Tool->>Tool: Lexical rank sitemap (Jaccard + substring overlap, top 30)
alt sitemap small or top-1 score >= 0.6
Tool->>Tool: Use top lexical match
else
Tool->>LLM: Pick best of 30 titles
LLM-->>Tool: index 1..30
end
Tool->>Cache: Lookup URL
alt Cache hit & fresh
Cache-->>Tool: cached markdown (~ms)
else Cache miss or stale
Tool->>Web: Tier 1 HTTP GET
alt Tier 1 thin (< 800 bytes)
Tool->>Web: Tier 2 Playwright render
end
Web-->>Tool: HTML / PDF / JSON
Tool->>Tool: Strip nav/footer, extract article, convert to markdown
Tool->>Cache: Save to disk + sqlite row
end
Tool-->>Agent: [BEGIN UNTRUSTED TRADECRAFT RESULT] envelope
Section picker
Two stages:
- Cheap lexical filter — score every sitemap entry against the query using Jaccard similarity over tokens, with a substring-overlap bonus that catches near-misses like
kerberoastingvskerberoast. Keep top 30. - One small LLM call (Claude Haiku by default, configurable via
TRADECRAFT_SECTION_PICKER_MODEL) — show the model the 30 candidates and ask "which one best answers the query?". Short-circuited when the sitemap has ≤5 entries or the top-1 lexical score is already ≥0.6.
If the LLM call fails for any reason, the tool falls back to the top-1 lexical match. The cve-poc-db type skips this stage entirely and uses the deterministic CVE-ID path.
Two-tier fetch
| Tier | Fetcher | Used for |
|---|---|---|
| 1 | Plain HTTPS via httpx, with <article> / <main> extraction and HTML→markdown conversion | 90% of static wikis, GitHub raw.githubusercontent.com blobs, Sphinx *.html pages |
| 2 | execute_playwright MCP tool (real browser render) | SPAs, JS-only shells, anti-bot challenges, and any Tier 1 response < TRADECRAFT_TIER2_THRESHOLD_BYTES (default 800) |
GitHub blob/ URLs are auto-rewritten to raw.githubusercontent.com/.../{branch}/{path} so Tier 1 hits the raw markdown directly instead of the rendered GitHub HTML page.
PDF sub-extractor
If the homepage or a fetched page comes back with Content-Type: application/pdf (or with the %PDF- magic bytes), the tool runs the PDF sub-extractor instead of HTML parsing. It extracts up to 200 pages, stores each page as page-N.md under <resource>/<sha256(url)>/, and writes a single SQLite row keyed by URL. Per-page lookups become deterministic on subsequent calls.
Output envelope
Every tool response is wrapped in an untrusted-content envelope so the agent's existing prompt-injection scrubbing applies:
[BEGIN UNTRUSTED TRADECRAFT RESULT]
resource: hacktricks
url: https://book.hacktricks.wiki/.../kerberoast.html
section_title: Kerberoast
fetched_at: 2026-04-26T14:02:11Z (cache miss, tier 1)
---
<the markdown body of the page, capped by the agent's TOOL_OUTPUT_MAX_CHARS>
Code blocks:
- bash:
impacket-GetUserSPNs domain.local/user:password ...
- powershell:
Invoke-Kerberoast | Out-File hashes.txt
[END UNTRUSTED TRADECRAFT RESULT]
No tradecraft-specific cap is applied — the agent's global TOOL_OUTPUT_MAX_CHARS setting is the single source of truth.
CVE PoC special path
The cve-poc-db type is special-cased because the repos involved hold hundreds of thousands of files organized deterministically by ID:
trickest/cve/
2021/
CVE-2021-41773.md
CVE-2021-44228.md
2022/
...
flowchart TD
A[Agent calls<br>resource_id='trickest_cve'<br>cve_id='CVE-2021-41773'] --> B[Tool sees type=cve-poc-db]
B --> C[Skip section picker entirely]
C --> D[Build path: contents/2021/CVE-2021-41773.md]
D --> E[GitHub Contents API direct hit]
E --> F{200 OK?}
F -->|yes| G[Return markdown]
F -->|no| H[Fallback: list /contents/2021<br>+ substring match]
H --> I{found?}
I -->|yes| G
I -->|no| J[Return 'PoC not found']
The tool docstring tells the agent "You MUST pass cve_id='CVE-YYYY-NNNNN'" for cve-poc-db resources, so Claude knows to provide the ID when calling.
The cache layer
All fetched pages live on disk under /app/tradecraft_cache/<resource_id>/<sha256(url)>.md, indexed by a SQLite database at /app/tradecraft_cache/index.sqlite. Each row tracks {url, fetched_at, ttl, resource_id, file_path, tier}.
flowchart LR
Q[Tool query] --> CC{Cache lookup}
CC -->|hit & fresh| H[Return cached file<br>~ms]
CC -->|miss or stale| F[Fetch live]
F --> S[Save to disk + sqlite row]
S --> R[Return content<br>~seconds]
| Mechanism | Effect |
|---|---|
| Per-URL asyncio lock | Two concurrent calls for the same URL collapse to a single fetch. |
| Type-default TTL | 7d for wikis/repos, 14d for sphinx-docs, 30d for cve-poc-db, 1d for agentic-crawl. |
| Per-resource TTL override | Set cacheTtlSec on the resource form to bypass the type default. |
| Self-healing connection | If /app/tradecraft_cache/index.sqlite is removed externally (rm -rf), the next call transparently reopens the database. |
force_refresh=True | Bypass the cache for one call (also re-saves on success). |
| UI Refresh button | Invalidates the cache for that resource and triggers re-verify. |
The cache is per-resource and per-URL. Disabling a resource does not delete cached pages; deleting the resource does.
SSRF guard
Both the verify endpoint and the at-query-time fetcher run the same URL validation:
| Check | Behavior |
|---|---|
| Scheme | Only http:// and https:// are accepted |
| Hostname | Required (no bare IPs without scheme) |
| Private / loopback / link-local / multicast / reserved | Rejected with private address blocked |
localhost, *.local, *.internal, ::1 | Rejected with private address blocked |
| DNS NXDOMAIN | Rejected with a separate DNS resolution failed for <host> message so users can tell "domain doesn't exist" from "internal IP blocked" |
The verify endpoint runs SSRF validation before building the LLM client, so a private-IP probe never wakes the LLM at all.
Comparison with other reference tools
| Tool | When to use | Source freshness | Latency | Curation |
|---|---|---|---|---|
web_search | General research, open-web context, recent news | Real-time (Tavily) + pre-ingested KB | 1-3s | Open web |
FAISS KB (inside web_search) | Pre-ingested security corpora (gtfobins, lolbas, owasp, nvd, exploitdb, nuclei) | Stale unless re-ingested | <1s | Curated at install time |
tradecraft_lookup | Specific exploitation technique, payload, or PoC from a trusted curated source | Always fresh (live fetch with cache) | <1s on cache hit, 1-5s on miss | Curated by you at the resource level |
The agent's prompt is updated to tell it: prefer tradecraft_lookup after query_graph and web_search when you need a specific exploitation page or PoC, not general background.
Lifecycle of one resource
stateDiagram-v2
[*] --> Adding: User clicks Add Resource
Adding --> Verifying: Webapp creates row,<br>calls /tradecraft/verify
Verifying --> Active: Summary + type + sitemap saved
Verifying --> ActiveDegraded: Verify failed,<br>resource saved with lastError
Active --> ToolCallable: Agent loads project
ActiveDegraded --> ToolCallable: Same, with warning chip
ToolCallable --> CacheMiss: Agent calls tool, no cache
ToolCallable --> CacheHit: Agent calls tool, cache hit
CacheMiss --> ToolCallable: Page fetched + cached
CacheHit --> ToolCallable: Page returned ~ms
Active --> Refreshing: User clicks Refresh
Refreshing --> Active: Sitemap rebuilt
Active --> Disabled: User toggles off
Disabled --> Active: User toggles on
Disabled --> [*]: User deletes
Active --> [*]: User deletes
When a resource is disabled, it is filtered out before the agent's tool docstring is built, so the agent literally cannot see it as an option. There is no risk of the agent trying to call a disabled resource.
If the agent has a stale conversation and tries to call a slug that no longer exists (e.g. you deleted the resource), the tool returns an error envelope: Resource '<slug>' not configured. The agent recovers and tries something else.
Configuration
All knobs live under the project's Project Settings → agent block, prefixed TRADECRAFT_*:
| Setting | Default | Effect |
|---|---|---|
TRADECRAFT_TOOL_ENABLED | true | Master kill-switch for the tool |
TRADECRAFT_FETCH_TIMEOUT | 30 | HTTP timeout (seconds) for Tier 1 / Tier 2 fetches |
TRADECRAFT_TIER2_THRESHOLD_BYTES | 800 | Tier-1 response size below which the tool escalates to Playwright |
TRADECRAFT_DEFAULT_TTL_SEC | 86400 | Fallback cache TTL when both type-default and per-resource override are zero |
TRADECRAFT_SECTION_PICKER_MODEL | claude-haiku-4-5-20251001 | Small model for the at-query-time "which page best answers this?" decision |
TRADECRAFT_CRAWL_MAX_PAGES | 30 | Pages visited by the agentic-crawl loop |
TRADECRAFT_CRAWL_MAX_LLM_CALLS | 20 | "Which links to follow?" Claude calls per crawl |
TRADECRAFT_CRAWL_TIME_BUDGET_SEC | 180 | Wall-clock budget per crawl |
TRADECRAFT_CRAWL_MAX_DEPTH | 3 | Max link depth from the homepage |
Per-user knobs live on each resource card (see Global Settings → Tradecraft):
| Field | Effect |
|---|---|
| GitHub Token Override | Per-resource GitHub PAT for org-private cheatsheets without changing the user-level token |
| Cache TTL seconds | Per-URL cache lifetime, 0 to inherit the type default |
| Enabled | Hide from the agent without losing the saved sitemap and summary |
Glossary
- Tradecraft — applied operator knowledge for offensive security, the "how to actually do it" between theory and tools. Originally a CIA term for spy operational skills.
- Resource — one URL you added to the catalog (e.g. HackTricks).
- Resource type — one of
mkdocs-wiki,github-repo,cve-poc-db,sphinx-docs,gitbook,agentic-crawl. Determines how the system extracts a sitemap. - Slug — server-generated stable identifier (
hacktricks,hacktricks-2, ...) the agent passes asresource_id. Stable across renames so in-flight conversations and cache rows stay valid. - Sitemap — the per-resource map of pages, stored as JSON in the database. Built once at verify time.
- Catalog — the dynamic tool description built from all enabled resources, shown to the agent at runtime.
- Verify — the one-time process of fetching, typing, sitemap-building, and summarizing a newly-added resource.
- Section picker — the at-query-time logic that narrows a sitemap to one page URL using lexical ranking + an optional small Claude call.
- Tier 1 / Tier 2 — two-step content fetch. Tier 1 is plain HTTPS; Tier 2 is Playwright. Used both at verify time and query time.
- Untrusted-content envelope — the
[BEGIN UNTRUSTED TRADECRAFT RESULT]/[END ...]wrapper around fetched content; engages the agent's existing prompt-injection scrubbing.