From cb2c784cfba6d162cc9ba3cc78d00f98e1ded7f8 Mon Sep 17 00:00:00 2001 From: Matt Lehman Date: Fri, 13 Feb 2026 21:46:11 -0500 Subject: [PATCH] feat(site): redesign pages UX with interactive triage demos --- README.md | 13 +- assets/logo/precursor-logo.svg | 67 +-- site/app.js | 968 +++++++++++++++++++++--------- site/data/README.md | 34 ++ site/data/firmware_triage.ndjson | 4 + site/data/firmware_triage.stderr | 65 ++ site/data/llm_claude_status.json | 5 + site/data/llm_codex_demo.json | 11 + site/data/llm_demo_prompt.txt | 345 +++++++++++ site/data/log4shell_triage.ndjson | 10 + site/data/log4shell_triage.stderr | 206 +++++++ site/data/loop_step1.stderr | 53 ++ site/data/loop_step2.stderr | 69 +++ site/data/loop_step3.stderr | 206 +++++++ site/data/loop_step4_codex.stderr | 211 +++++++ site/data/packet_triage.ndjson | 4 + site/data/packet_triage.stderr | 116 ++++ site/data/sigma_triage.ndjson | 5 + site/data/sigma_triage.stderr | 142 +++++ site/favicon.svg | 12 + site/index.html | 346 ++++++----- site/precursor-mark.svg | 30 + site/styles.css | 760 +++++++++++++---------- 23 files changed, 2880 insertions(+), 802 deletions(-) create mode 100644 site/data/README.md create mode 100644 site/data/firmware_triage.ndjson create mode 100644 site/data/firmware_triage.stderr create mode 100644 site/data/llm_claude_status.json create mode 100644 site/data/llm_codex_demo.json create mode 100644 site/data/llm_demo_prompt.txt create mode 100644 site/data/log4shell_triage.ndjson create mode 100644 site/data/log4shell_triage.stderr create mode 100644 site/data/loop_step1.stderr create mode 100644 site/data/loop_step2.stderr create mode 100644 site/data/loop_step3.stderr create mode 100644 site/data/loop_step4_codex.stderr create mode 100644 site/data/packet_triage.ndjson create mode 100644 site/data/packet_triage.stderr create mode 100644 site/data/sigma_triage.ndjson create mode 100644 site/data/sigma_triage.stderr create mode 100644 site/favicon.svg create mode 100644 site/precursor-mark.svg diff --git a/README.md b/README.md index 7cc898d..1b0d3ec 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,17 @@ # precursor

- Precursor logo + Precursor logo

-`precursor` is a CLI for **pre-protocol payload tagging + similarity clustering**. -It combines PCRE2 named-capture matching, TLSH/LZJD/FBHash similarity, optional MRSHv2 adapter mode, and JSON outputs that are easy to feed into detection engineering and LLM-assisted protocol discovery loops. -Project page: https://precursor.hashdb.io +

+ Live demos · + Scenario corpus · + Crates.io +

+ +`precursor` is a CLI for **pre-protocol payload tagging + similarity clustering** across packet payloads, logs, and raw binary fragments. +It combines PCRE2 named-capture matching, TLSH/LZJD/FBHash similarity, optional MRSHv2 adapter mode, and JSON outputs designed for fast triage loops before full parser or protocol tooling exists. ## Release 0.2.1 Highlights diff --git a/assets/logo/precursor-logo.svg b/assets/logo/precursor-logo.svg index 201ddf7..447047f 100644 --- a/assets/logo/precursor-logo.svg +++ b/assets/logo/precursor-logo.svg @@ -1,52 +1,37 @@ - + Precursor logo - A radar-inspired emblem and wordmark for the Precursor project. + Precursor wordmark with packet similarity icon. - - - + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - + + + + + + - - - PRECURSOR - - - PAYLOAD TAGGING AND SIMILARITY ANALYSIS - + + precursor + pre-protocol payload similarity triage diff --git a/site/app.js b/site/app.js index f1e7cef..ce56b1b 100644 --- a/site/app.js +++ b/site/app.js @@ -1,357 +1,751 @@ const scenarios = [ { - id: "packet-triage", + id: "packet", label: "Packet Triage", + subtitle: "mixed service payloads", title: "Pre-Protocol Packet Triage", description: - "Cluster mixed HTTP/TLS/SSH/DNS/Modbus payloads before parser commitment. Useful for scanner traffic and unknown service discovery.", + "Cluster mixed HTTP/TLS/SSH/DNS/Modbus payloads before parser commitment. Useful when traffic is noisy or service identity is ambiguous.", command: `cat samples/scenarios/pre-protocol-packet-triage/payloads.b64 \\ | precursor -p samples/scenarios/pre-protocol-packet-triage/patterns.pcre \\ - -m base64 -t -d --similarity-mode lzjd -P --protocol-hints`, - output: `stdout: similarity_hash + protocol_* fields -stderr: ---PRECURSOR_PROTOCOL_HINTS--- with top candidate clusters`, + -m base64 -t -d --similarity-mode lzjd -P --protocol-hints --stats`, + stdoutFile: "data/packet_triage.ndjson", + stderrFile: "data/packet_triage.stderr", + links: [ + { label: "scenario assets", href: "https://github.com/Obsecurus/precursor/tree/main/samples/scenarios/pre-protocol-packet-triage" }, + { label: "captured stdout", href: "data/packet_triage.ndjson" }, + { label: "captured stats", href: "data/packet_triage.stderr" }, + ], + chips: ["input: base64", "similarity: lzjd", "single-packet: on"], }, { - id: "firmware-fragments", + id: "firmware", label: "Firmware", + subtitle: "binary fragment tags", title: "Firmware Fragment Sorting", description: - "Tag likely ELF/PE/uImage/gzip fragments from arbitrary blob streams and route high-entropy unknowns for deeper reverse engineering.", + "Tag ELF/PE/uImage/gzip-style fragments from arbitrary blob streams and prioritize artifacts worth deeper reversing.", command: `cat samples/scenarios/firmware-fragment-triage/payloads.hex \\ | precursor -p samples/scenarios/firmware-fragment-triage/patterns.pcre \\ - -m hex -t --similarity-mode lzjd -P`, - output: `protocol_label typically includes firmware_binary or compressed_binary -tags include file-magic style markers`, - }, - { - id: "binary-blob", - label: "Binary", - title: "Raw-Binary Blob Mode", - description: - "Use the short -B flag to ingest arbitrary raw bytes as one payload record and tag firmware or packet fragments without UTF-8 assumptions.", - command: `printf '\\x7fELF\\x02\\x01\\x01\\x00\\x00\\x00\\x00\\x00' \\ - | precursor '(?^\\x7fELF)' -B -t --similarity-mode lzjd -P`, - output: `expected tag: elf_magic -protocol_label usually resolves to firmware_binary for ELF-like headers`, - }, - { - id: "ics-single-packet", - label: "ICS/OT", - title: "ICS Modbus Single-Packet Discovery", - description: - "Detect Modbus request/response function families from single packets where full DPI context is unavailable.", - command: `cat samples/scenarios/ics-modbus-single-packet/payloads.hex \\ - | precursor -p samples/scenarios/ics-modbus-single-packet/patterns.pcre \\ - -m hex -t -d --similarity-mode lzjd -P --protocol-hints`, - output: `cluster boosts improve confidence when payload families repeat -hint candidates can be fed into LLM-assisted rule authoring loops`, - }, - { - id: "log4shell-pcap-derived", - label: "Log4Shell", - title: "PCAP-Derived Log4Shell Probe Clustering", - description: - "Cluster evasive JNDI/LDAP probe strings derived from a public Log4Shell PCAP corpus. This is a pre-parser workflow for exploit spray discovery and rule drafting.", - command: `cat samples/scenarios/public-log4shell-pcap-derived/payloads.string \\ - | precursor -p samples/scenarios/public-log4shell-pcap-derived/patterns.pcre \\ - -m string -t -d --similarity-mode lzjd -P --protocol-hints`, - output: `expected tags include jndi_expression + obfuscation_primitive -protocol_label typically resolves to http for these request-shaped probes`, - }, - { - id: "foxit-pcap-live", - label: "PCAP Replay", - title: "Real PCAP Replay with FBHash", - description: - "Replay HTTP requests extracted from a public fox-it Log4Shell PCAP and cluster exploit staging traffic using fbhash mode.", - command: `cat samples/scenarios/public-log4shell-foxit-pcap/payloads.string \\ - | precursor -p samples/scenarios/public-log4shell-foxit-pcap/patterns.pcre \\ - -m string -t -d --similarity-mode fbhash -P --protocol-hints`, - output: `tags include urlencoded_jndi, exploit_class_path, and java_user_agent -similarity_hash values are fbhash:* and group replay families cleanly`, + -m hex -t -d --similarity-mode lzjd -P --protocol-hints --stats`, + stdoutFile: "data/firmware_triage.ndjson", + stderrFile: "data/firmware_triage.stderr", + links: [ + { label: "scenario assets", href: "https://github.com/Obsecurus/precursor/tree/main/samples/scenarios/firmware-fragment-triage" }, + { label: "captured stdout", href: "data/firmware_triage.ndjson" }, + { label: "captured stats", href: "data/firmware_triage.stderr" }, + ], + chips: ["input: hex", "blob-safe", "binary workflows"], }, { - id: "sigma-shell-triage", - label: "Sigma", + id: "sigma", + label: "Sigma Pipeline", + subtitle: "rule intent + similarity", title: "Sigma Rule to Precursor Pipeline", description: - "Load Sigma YAML directly, auto-convert keyword selectors to named captures, and score suspicious shell command streams without hand-rewriting regex files.", + "Load Sigma YAML directly, convert keyword selectors to named captures, and keep condition logic while adding similarity context.", command: `cat samples/scenarios/sigma-linux-shell-command-triage/payloads.log \\ | precursor --sigma-rule samples/scenarios/sigma-linux-shell-command-triage/sigma_rule.yml \\ - -m string -t -d --similarity-mode lzjd --protocol-hints`, - output: `tags include sigma_* captures for matched commands -output includes sigma_rule_matches and sigma_rule_ids when condition passes`, - }, - { - id: "binwalk-firmware", - label: "Firmware Blobs", - title: "Public Firmware Blob Folder Triage", - description: - "Run binary folder mode over real binwalk test artifacts and tag romfs/squashfs/cramfs/gzip magic headers in one pass.", - command: `precursor -p samples/scenarios/public-firmware-binwalk-magic/patterns.pcre \\ - -f samples/scenarios/public-firmware-binwalk-magic/blobs \\ - --input-mode binary -t -d --similarity-mode lzjd -P --protocol-hints`, - output: `expected tags: gzip_magic, romfs_magic, squashfs_magic, cramfs_magic -useful for firmware triage before full unpacking`, + -m string -t -d --similarity-mode lzjd --protocol-hints --stats`, + stdoutFile: "data/sigma_triage.ndjson", + stderrFile: "data/sigma_triage.stderr", + links: [ + { label: "scenario assets", href: "https://github.com/Obsecurus/precursor/tree/main/samples/scenarios/sigma-linux-shell-command-triage" }, + { label: "captured stdout", href: "data/sigma_triage.ndjson" }, + { label: "captured stats", href: "data/sigma_triage.stderr" }, + ], + chips: ["sigma condition", "input: string", "detection tuning"], }, { - id: "zeek-dns-log", - label: "Zeek DNS", - title: "Public Zeek DNS Log Triage", + id: "log4shell", + label: "PCAP-Derived", + subtitle: "public log4shell corpus", + title: "Public Log4Shell Probe Clustering", description: - "Extract DNS query fields from Zeek JSON logs and cluster suspicious domain families for rapid hunt pivots.", - command: `cat samples/scenarios/public-zeek-dns-log-triage/payloads.jsonl \\ - | precursor -p samples/scenarios/public-zeek-dns-log-triage/patterns.pcre \\ - -m string -j '.query' -t -d --similarity-mode lzjd --protocol-hints`, - output: `matches include possible_c2_domain and suspicious_tld tags -query extraction runs through the same JSON contract used in production pipelines`, + "Replay request payloads extracted from a public Log4Shell PCAP corpus to identify exploit-stage families and outliers quickly.", + command: `cat samples/scenarios/public-log4shell-foxit-pcap/payloads.string \\ + | precursor -p samples/scenarios/public-log4shell-foxit-pcap/patterns.pcre \\ + -m string -t -d --similarity-mode fbhash -P --protocol-hints --stats`, + stdoutFile: "data/log4shell_triage.ndjson", + stderrFile: "data/log4shell_triage.stderr", + links: [ + { label: "scenario assets", href: "https://github.com/Obsecurus/precursor/tree/main/samples/scenarios/public-log4shell-foxit-pcap" }, + { label: "captured stdout", href: "data/log4shell_triage.ndjson" }, + { label: "captured stats", href: "data/log4shell_triage.stderr" }, + ], + chips: ["input: string", "similarity: fbhash", "protocol hints"], }, ]; -const demoReels = [ +const loopSteps = [ { - id: "reel-pcap", - label: "PCAP Replay", - title: "Replay: fox-it Log4Shell PCAP -> FBHash clusters", - intervalMs: 1700, - frames: [ - `$ samples/scenarios/public-log4shell-foxit-pcap/extract_payloads.sh | head -2 -GET /test?q=%24%7B...%7D HTTP/1.1 Host: extracted.local User-Agent: python-requests/2.25.1 -GET /ExploitYEKeLeuvob.class HTTP/1.1 Host: extracted.local User-Agent: Java/1.8.0_181`, - `$ cat samples/scenarios/public-log4shell-foxit-pcap/payloads.string | precursor -p samples/scenarios/public-log4shell-foxit-pcap/patterns.pcre -m string -t -d --similarity-mode fbhash -P -{"tags":["http_method","urlencoded_jndi","ldap_scheme"],"similarity_hash":"fbhash:227:...","protocol_label":"http"}`, - `{"tags":["http_method","exploit_class_path","java_user_agent"],"similarity_hash":"fbhash:80:...","protocol_label":"http"} -... -Signal: one stage-0 exploit line + repeated class fetch family clusters`, - ], + id: "step1", + label: "Step 1", + title: "Baseline: generic HTTP method tag", + summary: "Start broad to measure corpus shape before adding specific exploit semantics.", + stderrFile: "data/loop_step1.stderr", }, { - id: "reel-firmware", - label: "Firmware", - title: "Replay: binary folder mode on real firmware blobs", - intervalMs: 1700, - frames: [ - `$ precursor -p samples/scenarios/public-firmware-binwalk-magic/patterns.pcre -f samples/scenarios/public-firmware-binwalk-magic/blobs --input-mode binary -t -d --similarity-mode lzjd -P -{"tags":["squashfs_magic"],"similarity_hash":"lzjd:128:...","protocol_label":"unknown"}`, - `{"tags":["romfs_magic"],"similarity_hash":"lzjd:128:...","protocol_label":"unknown"} -{"tags":["cramfs_magic"],"similarity_hash":"lzjd:128:...","protocol_label":"unknown"}`, - `{"tags":["gzip_magic"],"similarity_hash":"lzjd:92:...","protocol_label":"compressed_binary"} -Signal: immediate filesystem magic labeling before unpack/decompile`, - ], + id: "step2", + label: "Step 2", + title: "Targeted tag pack", + summary: "Add exploit-class, JNDI, LDAP, and Java user-agent captures to separate families.", + stderrFile: "data/loop_step2.stderr", }, { - id: "reel-sigma", - label: "Sigma", - title: "Replay: Sigma condition gating + labels", - intervalMs: 1700, - frames: [ - `$ cat samples/scenarios/sigma-linux-shell-command-triage/payloads.log | precursor --sigma-rule samples/scenarios/sigma-linux-shell-command-triage/sigma_rule.yml -m string -t -d --similarity-mode lzjd -{"tags":["sigma_..._keywords_0","sigma_..._keywords_6"],"sigma_rule_matches":["Suspicious Shell Commands"]}`, - `{"tags":["sigma_..._keywords_18"],"sigma_rule_matches":["Suspicious Shell Commands"],"sigma_rule_ids":["d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff"]}`, - `Signal: Sigma intent stays portable while Precursor adds similarity + protocol context on the same stream`, - ], + id: "step3", + label: "Step 3", + title: "Switch similarity backend", + summary: "Use FBHash mode to compare family cohesion and candidate ranking shifts.", + stderrFile: "data/loop_step3.stderr", + }, + { + id: "step4", + label: "Step 4", + title: "Codex-guided refinement", + summary: "Validate an additional encoded-JNDI pattern proposed from loop stats and output examples.", + stderrFile: "data/loop_step4_codex.stderr", }, ]; -const tabContainer = document.getElementById("scenario-tabs"); -const title = document.getElementById("scenario-title"); -const description = document.getElementById("scenario-description"); -const command = document.getElementById("scenario-command"); -const output = document.getElementById("scenario-output"); -const copyButton = document.getElementById("copy-command"); - -function renderScenario(scenarioId) { - const selected = scenarios.find((scenario) => scenario.id === scenarioId) || scenarios[0]; - title.textContent = selected.title; - description.textContent = selected.description; - command.textContent = selected.command; - output.textContent = selected.output; - tabContainer.querySelectorAll("button").forEach((button) => { - button.setAttribute("aria-selected", button.dataset.scenarioId === selected.id ? "true" : "false"); - }); -} +const heroTeasers = [ + `{"tags":["http_method"],"similarity_hash":"lzjd:128:...","protocol_label":"http","protocol_confidence":0.93}`, + `{"tags":["sigma_*"],"sigma_rule_matches":["Suspicious Shell Commands"],"similarity_hash":"lzjd:128:..."}`, + `{"tags":["gzip_magic"],"similarity_hash":"lzjd:92:...","protocol_label":"compressed_binary"}`, + `{"tags":["urlencoded_jndi","ldap_scheme"],"similarity_hash":"fbhash:227:...","protocol_label":"http"}`, +]; -scenarios.forEach((scenario, idx) => { - const button = document.createElement("button"); - button.type = "button"; - button.dataset.scenarioId = scenario.id; - button.textContent = scenario.label; - button.setAttribute("aria-selected", idx === 0 ? "true" : "false"); - button.addEventListener("click", () => renderScenario(scenario.id)); - tabContainer.appendChild(button); -}); - -copyButton.addEventListener("click", async () => { - try { - await navigator.clipboard.writeText(command.textContent || ""); - copyButton.textContent = "Copied"; - setTimeout(() => { - copyButton.textContent = "Copy"; - }, 1000); - } catch (_error) { - copyButton.textContent = "Manual copy"; - setTimeout(() => { - copyButton.textContent = "Copy"; - }, 1200); - } -}); +const scenarioTabs = [ + { id: "command", label: "Command" }, + { id: "stdout", label: "Output" }, + { id: "stats", label: "Stats" }, + { id: "insights", label: "Insights" }, +]; + +const state = { + scenarioId: scenarios[0].id, + scenarioTab: "command", + loopStepId: loopSteps[0].id, + teaserIdx: 0, + scenarioCache: new Map(), + loopCache: new Map(), +}; + +const el = { + heroMetrics: document.getElementById("hero-metrics"), + teaserCode: document.getElementById("teaser-code"), + teaserButton: document.getElementById("cycle-teaser"), + + scenarioList: document.getElementById("scenario-list"), + scenarioName: document.getElementById("scenario-name"), + scenarioDescription: document.getElementById("scenario-description"), + scenarioMeta: document.getElementById("scenario-meta"), + scenarioTabs: document.getElementById("scenario-tabs"), + scenarioPanelLabel: document.getElementById("scenario-panel-label"), + scenarioPanel: document.getElementById("scenario-panel"), + scenarioLinks: document.getElementById("scenario-links"), + copyCommand: document.getElementById("copy-command"), + + loopSteps: document.getElementById("loop-steps"), + loopStepTitle: document.getElementById("loop-step-title"), + loopStepSummary: document.getElementById("loop-step-summary"), + loopBars: document.getElementById("loop-bars"), + loopJson: document.getElementById("loop-json"), + + llmWhy: document.getElementById("llm-why"), + llmRefinements: document.getElementById("llm-refinements"), + llmPattern: document.getElementById("llm-pattern"), + llmCommand: document.getElementById("llm-command"), + llmRisk: document.getElementById("llm-risk"), + llmDeltas: document.getElementById("llm-deltas"), + llmRuntime: document.getElementById("llm-runtime"), + + installCommand: document.getElementById("install-command"), + copyInstall: document.getElementById("copy-install"), +}; + +function extractJsonObjects(text) { + const objects = []; + let depth = 0; + let start = -1; + let inString = false; + let escape = false; + + for (let i = 0; i < text.length; i += 1) { + const ch = text[i]; + + if (inString) { + if (escape) { + escape = false; + } else if (ch === "\\") { + escape = true; + } else if (ch === '"') { + inString = false; + } + continue; + } + + if (ch === '"') { + inString = true; + continue; + } -renderScenario(scenarios[0].id); + if (ch === "{") { + if (depth === 0) { + start = i; + } + depth += 1; + continue; + } -const reelTabs = document.getElementById("reel-tabs"); -const reelTitle = document.getElementById("reel-title"); -const reelFrame = document.getElementById("reel-frame"); -const reelPlay = document.getElementById("reel-play"); + if (ch === "}") { + depth -= 1; + if (depth === 0 && start >= 0) { + const candidate = text.slice(start, i + 1); + try { + objects.push(JSON.parse(candidate)); + } catch (_err) { + // ignore malformed blocks + } + start = -1; + } + } + } -let activeReel = demoReels[0]; -let activeFrame = 0; -let reelTimer = null; + return objects; +} -function stopReel() { - if (reelTimer) { - clearInterval(reelTimer); - reelTimer = null; +async function fetchText(path) { + const response = await fetch(path); + if (!response.ok) { + throw new Error(`failed to fetch ${path}`); } + return response.text(); } -function drawReelFrame() { - if (!activeReel || !reelTitle || !reelFrame) { +function renderHeroMetrics() { + const metrics = [ + { title: "4", note: "public scenario classes live on this page" }, + { title: "4", note: "similarity modes (TLSH, LZJD, FBHash, MRSHv2 adapter)" }, + { title: "JSON", note: "stable tags + similarity + protocol fields" }, + ]; + + if (!el.heroMetrics) { return; } - reelTitle.textContent = activeReel.title; - reelFrame.textContent = activeReel.frames[activeFrame] || ""; + + el.heroMetrics.innerHTML = ""; + metrics.forEach((metric) => { + const card = document.createElement("article"); + card.className = "metric"; + + const value = document.createElement("strong"); + value.textContent = metric.title; + + const note = document.createElement("small"); + note.textContent = metric.note; + + card.appendChild(value); + card.appendChild(note); + el.heroMetrics.appendChild(card); + }); } -function playReel() { - if (!activeReel) { +function renderTeaser() { + if (!el.teaserCode) { return; } - stopReel(); - activeFrame = 0; - drawReelFrame(); - reelTimer = setInterval(() => { - activeFrame = (activeFrame + 1) % activeReel.frames.length; - drawReelFrame(); - }, activeReel.intervalMs || 1600); + el.teaserCode.textContent = heroTeasers[state.teaserIdx]; } -function setActiveReel(reelId) { - const selected = demoReels.find((reel) => reel.id === reelId) || demoReels[0]; - activeReel = selected; - activeFrame = 0; - reelTabs.querySelectorAll("button").forEach((button) => { - button.setAttribute("aria-selected", button.dataset.reelId === selected.id ? "true" : "false"); - }); - drawReelFrame(); - playReel(); +function cycleTeaser() { + state.teaserIdx = (state.teaserIdx + 1) % heroTeasers.length; + renderTeaser(); } -if (reelTabs && reelTitle && reelFrame && reelPlay) { - demoReels.forEach((reel, idx) => { +function renderScenarioList() { + if (!el.scenarioList) { + return; + } + + el.scenarioList.innerHTML = ""; + scenarios.forEach((scenario) => { const button = document.createElement("button"); button.type = "button"; - button.dataset.reelId = reel.id; - button.textContent = reel.label; - button.setAttribute("aria-selected", idx === 0 ? "true" : "false"); - button.addEventListener("click", () => setActiveReel(reel.id)); - reelTabs.appendChild(button); + button.className = "scenario-button"; + button.dataset.id = scenario.id; + button.setAttribute("aria-selected", scenario.id === state.scenarioId ? "true" : "false"); + + const title = document.createElement("strong"); + title.textContent = scenario.label; + + const subtitle = document.createElement("small"); + subtitle.textContent = scenario.subtitle; + + button.appendChild(title); + button.appendChild(subtitle); + button.addEventListener("click", () => { + state.scenarioId = scenario.id; + state.scenarioTab = "command"; + renderScenarioList(); + renderScenario(); + }); + + el.scenarioList.appendChild(button); }); +} - reelPlay.addEventListener("click", () => { - playReel(); - reelPlay.textContent = "Replaying"; - setTimeout(() => { - reelPlay.textContent = "Replay"; - }, 900); +function renderScenarioTabs() { + if (!el.scenarioTabs) { + return; + } + + el.scenarioTabs.innerHTML = ""; + scenarioTabs.forEach((tab) => { + const button = document.createElement("button"); + button.type = "button"; + button.className = "tab"; + button.dataset.id = tab.id; + button.textContent = tab.label; + button.setAttribute("aria-selected", tab.id === state.scenarioTab ? "true" : "false"); + button.addEventListener("click", () => { + state.scenarioTab = tab.id; + renderScenario(); + }); + el.scenarioTabs.appendChild(button); }); +} + +function summarizeScenario(stats, hints) { + if (!stats) { + return "Stats are not available for this scenario."; + } + + const matches = (stats.Match && stats.Match.Matches) || []; + const top = matches + .slice() + .sort((a, b) => (b.Matches || 0) - (a.Matches || 0)) + .slice(0, 4) + .map((entry) => `${entry.Name}:${entry.Matches}`) + .join(", "); + + const parts = [ + `Input count: ${stats.Input?.Count ?? "n/a"}`, + `Pattern lines: ${stats.Match?.Patterns ?? "n/a"}`, + `Total matches: ${stats.Match?.TotalMatches ?? "n/a"}`, + `Similarity mode: ${stats.Environment?.SimilarityMode ?? "n/a"}`, + `Top tags: ${top || "none"}`, + ]; - setActiveReel(demoReels[0].id); + if (hints && Array.isArray(hints.Candidates)) { + parts.push(`Protocol hint candidates: ${hints.Candidates.length}`); + } + + return parts.join("\n"); } -const statsSample = { - "---PRECURSOR_STATISTICS---": "This JSON is output to STDERR so that you can parse stats separate from the primary output.", - Input: { - Count: 10, - Unique: 10, - AvgSize: "144", - MinSize: 108, - MaxSize: 387, - P95Size: 387, - TotalSize: "1.4KB", - }, - Match: { - Patterns: 5, - TotalMatches: 28, - Matches: [ - { Name: "http_method", Matches: 10 }, - { Name: "exploit_class_path", Matches: 9 }, - { Name: "urlencoded_jndi", Matches: 1 }, - ], - HashesGenerated: 10, - AvgSize: "144", - MinSize: 108, - MaxSize: 387, - P95Size: 387, - TotalSize: "1.4KB", - }, - Compare: { - Similarities: 45, - AvgDistance: "51", - MinDistance: 39, - MaxDistance: 88, - P95Distance: 88, - }, - Environment: { - SimilarityMode: "fbhash", - RegexEngine: "pcre2", - InputMode: "string", - DistanceThreshold: 100, - SinglePacketInference: true, - SigmaRulesLoaded: 0, - }, -}; +async function loadScenarioRuntime(scenario) { + if (state.scenarioCache.has(scenario.id)) { + return state.scenarioCache.get(scenario.id); + } + + const runtime = { + stdout: "", + stderr: "", + stats: null, + hints: null, + error: null, + }; + + try { + const [stdout, stderr] = await Promise.all([ + fetchText(scenario.stdoutFile), + fetchText(scenario.stderrFile), + ]); -const statsBars = document.getElementById("stats-bars"); -const statsJson = document.getElementById("stats-json"); + runtime.stdout = stdout.trim(); + runtime.stderr = stderr.trim(); -function renderStatsMode() { - if (!statsBars || !statsJson) { + const objects = extractJsonObjects(runtime.stderr); + runtime.stats = objects.find((obj) => Object.prototype.hasOwnProperty.call(obj, "---PRECURSOR_STATISTICS---")) || null; + runtime.hints = objects.find((obj) => Object.prototype.hasOwnProperty.call(obj, "---PRECURSOR_PROTOCOL_HINTS---")) || null; + } catch (error) { + runtime.error = error.message; + } + + state.scenarioCache.set(scenario.id, runtime); + return runtime; +} + +async function renderScenario() { + const scenario = scenarios.find((entry) => entry.id === state.scenarioId) || scenarios[0]; + if (!scenario) { return; } - const barMetrics = [ - { label: "Input Count", value: statsSample.Input.Count, max: 12 }, - { label: "Total Matches", value: statsSample.Match.TotalMatches, max: 40 }, - { label: "Hashes Generated", value: statsSample.Match.HashesGenerated, max: 12 }, - { label: "Pairwise Similarities", value: statsSample.Compare.Similarities, max: 50 }, - ]; - statsBars.innerHTML = ""; - barMetrics.forEach((metric) => { - const row = document.createElement("div"); - row.className = "stats-bar"; + if (el.scenarioName) { + el.scenarioName.textContent = scenario.title; + } + if (el.scenarioDescription) { + el.scenarioDescription.textContent = scenario.description; + } - const head = document.createElement("div"); - head.className = "stats-bar-head"; - const label = document.createElement("span"); - label.textContent = metric.label; - const value = document.createElement("strong"); - value.textContent = String(metric.value); - head.appendChild(label); - head.appendChild(value); - - const track = document.createElement("div"); - track.className = "stats-bar-track"; - const fill = document.createElement("div"); - fill.className = "stats-bar-fill"; - fill.style.width = `${Math.min(100, Math.round((metric.value / metric.max) * 100))}%`; - track.appendChild(fill); - - row.appendChild(head); - row.appendChild(track); - statsBars.appendChild(row); + if (el.scenarioMeta) { + el.scenarioMeta.innerHTML = ""; + scenario.chips.forEach((chip) => { + const span = document.createElement("span"); + span.className = "chip"; + span.textContent = chip; + el.scenarioMeta.appendChild(span); + }); + } + + if (el.scenarioLinks) { + el.scenarioLinks.innerHTML = scenario.links + .map((link) => `${link.label}`) + .join(" | "); + } + + renderScenarioTabs(); + const runtime = await loadScenarioRuntime(scenario); + + let label = "Command"; + let content = scenario.command; + + if (runtime.error) { + label = "Error"; + content = runtime.error; + } else if (state.scenarioTab === "stdout") { + label = "Captured stdout (.ndjson)"; + content = runtime.stdout || "No output captured."; + } else if (state.scenarioTab === "stats") { + label = "Captured --stats stderr"; + content = runtime.stats ? JSON.stringify(runtime.stats, null, 2) : runtime.stderr || "No stats available."; + } else if (state.scenarioTab === "insights") { + label = "Analyst summary"; + content = summarizeScenario(runtime.stats, runtime.hints); + } + + if (el.scenarioPanelLabel) { + el.scenarioPanelLabel.textContent = label; + } + if (el.scenarioPanel) { + el.scenarioPanel.textContent = content; + } +} + +function renderLoopSteps() { + if (!el.loopSteps) { + return; + } + + el.loopSteps.innerHTML = ""; + loopSteps.forEach((step) => { + const button = document.createElement("button"); + button.type = "button"; + button.className = "loop-step"; + button.dataset.id = step.id; + button.setAttribute("aria-selected", step.id === state.loopStepId ? "true" : "false"); + + const title = document.createElement("strong"); + title.textContent = `${step.label} - ${step.title}`; + + const subtitle = document.createElement("small"); + subtitle.textContent = step.summary; + + button.appendChild(title); + button.appendChild(subtitle); + + button.addEventListener("click", () => { + state.loopStepId = step.id; + renderLoopSteps(); + renderLoopDetail(); + }); + + el.loopSteps.appendChild(button); }); +} + +async function loadLoopStep(step) { + if (state.loopCache.has(step.id)) { + return state.loopCache.get(step.id); + } + + const data = { + stats: null, + hints: null, + error: null, + }; + + try { + const raw = await fetchText(step.stderrFile); + const blocks = extractJsonObjects(raw); + data.stats = blocks.find((obj) => Object.prototype.hasOwnProperty.call(obj, "---PRECURSOR_STATISTICS---")) || null; + data.hints = blocks.find((obj) => Object.prototype.hasOwnProperty.call(obj, "---PRECURSOR_PROTOCOL_HINTS---")) || null; + } catch (error) { + data.error = error.message; + } + + state.loopCache.set(step.id, data); + return data; +} + +function addBarMetric(container, label, value, maxValue) { + const row = document.createElement("div"); + row.className = "bar-row"; + + const head = document.createElement("div"); + head.className = "bar-head"; + const name = document.createElement("span"); + const score = document.createElement("strong"); + name.textContent = label; + score.textContent = String(value); + head.appendChild(name); + head.appendChild(score); + + const track = document.createElement("div"); + track.className = "bar-track"; + + const fill = document.createElement("div"); + fill.className = "bar-fill"; + const width = maxValue > 0 ? Math.round((value / maxValue) * 100) : 0; + fill.style.width = `${Math.min(100, width)}%`; + + track.appendChild(fill); + row.appendChild(head); + row.appendChild(track); + container.appendChild(row); +} + +async function renderLoopDetail() { + const selected = loopSteps.find((step) => step.id === state.loopStepId) || loopSteps[0]; + if (!selected) { + return; + } + + const allData = await Promise.all(loopSteps.map((step) => loadLoopStep(step))); + const currentData = allData[loopSteps.findIndex((step) => step.id === selected.id)] || {}; + + if (el.loopStepTitle) { + el.loopStepTitle.textContent = `${selected.label}: ${selected.title}`; + } + if (el.loopStepSummary) { + el.loopStepSummary.textContent = selected.summary; + } + + if (el.loopBars) { + el.loopBars.innerHTML = ""; + + if (currentData.error || !currentData.stats) { + const line = document.createElement("p"); + line.textContent = currentData.error || "No stats available."; + el.loopBars.appendChild(line); + } else { + const metrics = { + patterns: currentData.stats.Match?.Patterns || 0, + matches: currentData.stats.Match?.TotalMatches || 0, + hashes: currentData.stats.Match?.HashesGenerated || 0, + candidates: Array.isArray(currentData.hints?.Candidates) ? currentData.hints.Candidates.length : 0, + }; + + const max = { + patterns: Math.max(...allData.map((entry) => entry.stats?.Match?.Patterns || 0), 1), + matches: Math.max(...allData.map((entry) => entry.stats?.Match?.TotalMatches || 0), 1), + hashes: Math.max(...allData.map((entry) => entry.stats?.Match?.HashesGenerated || 0), 1), + candidates: Math.max(...allData.map((entry) => (entry.hints?.Candidates || []).length || 0), 1), + }; + + addBarMetric(el.loopBars, "Pattern count", metrics.patterns, max.patterns); + addBarMetric(el.loopBars, "Total matches", metrics.matches, max.matches); + addBarMetric(el.loopBars, "Hashes generated", metrics.hashes, max.hashes); + addBarMetric(el.loopBars, "Hint candidates", metrics.candidates, max.candidates); + } + } + + if (el.loopJson) { + if (currentData.stats) { + const excerpt = { + Match: { + Patterns: currentData.stats.Match?.Patterns || 0, + TotalMatches: currentData.stats.Match?.TotalMatches || 0, + Matches: currentData.stats.Match?.Matches || [], + }, + Compare: currentData.stats.Compare || {}, + Environment: currentData.stats.Environment || {}, + }; + el.loopJson.textContent = JSON.stringify(excerpt, null, 2); + } else { + el.loopJson.textContent = currentData.error || "No JSON available."; + } + } +} + +function findMatchCount(stats, name) { + const matches = stats?.Match?.Matches || []; + const item = matches.find((entry) => entry.Name === name); + return item ? item.Matches : 0; +} + +function renderLlmDeltaRow(label, value) { + const card = document.createElement("div"); + card.className = "delta"; + + const title = document.createElement("strong"); + title.textContent = label; + + const desc = document.createElement("span"); + desc.textContent = value; + + card.appendChild(title); + card.appendChild(desc); + return card; +} + +async function renderLlmSection() { + let codex = null; + let claudeStatus = null; + + try { + codex = JSON.parse(await fetchText("data/llm_codex_demo.json")); + } catch (_err) { + codex = null; + } + + try { + claudeStatus = JSON.parse(await fetchText("data/llm_claude_status.json")); + } catch (_err) { + claudeStatus = null; + } + + if (codex) { + if (el.llmWhy) { + el.llmWhy.textContent = codex.why_it_matters; + } + if (el.llmRefinements) { + el.llmRefinements.innerHTML = ""; + (codex.refinements || []).forEach((line) => { + const li = document.createElement("li"); + li.textContent = line; + el.llmRefinements.appendChild(li); + }); + } + if (el.llmPattern) { + el.llmPattern.textContent = codex.new_pattern || ""; + } + if (el.llmCommand) { + el.llmCommand.textContent = codex.next_command || ""; + } + if (el.llmRisk) { + el.llmRisk.textContent = codex.risk || ""; + } + } + + const [step3, step4] = await Promise.all([ + loadLoopStep(loopSteps.find((entry) => entry.id === "step3") || loopSteps[2]), + loadLoopStep(loopSteps.find((entry) => entry.id === "step4") || loopSteps[3]), + ]); + + if (el.llmDeltas) { + el.llmDeltas.innerHTML = ""; + + if (step3?.stats && step4?.stats) { + const patternsBefore = step3.stats.Match?.Patterns || 0; + const patternsAfter = step4.stats.Match?.Patterns || 0; + const matchesBefore = step3.stats.Match?.TotalMatches || 0; + const matchesAfter = step4.stats.Match?.TotalMatches || 0; + const newTagHits = findMatchCount(step4.stats, "jndi_remote_lookup"); + const hintBefore = (step3.hints?.Candidates || []).length; + const hintAfter = (step4.hints?.Candidates || []).length; + + el.llmDeltas.appendChild( + renderLlmDeltaRow("Pattern lines", `${patternsBefore} -> ${patternsAfter}`), + ); + el.llmDeltas.appendChild( + renderLlmDeltaRow("Total matches", `${matchesBefore} -> ${matchesAfter}`), + ); + el.llmDeltas.appendChild( + renderLlmDeltaRow("New tag hits", `jndi_remote_lookup: ${newTagHits}`), + ); + el.llmDeltas.appendChild( + renderLlmDeltaRow("Hint candidates", `${hintBefore} -> ${hintAfter}`), + ); + } else { + el.llmDeltas.appendChild(renderLlmDeltaRow("Status", "Loop comparison data unavailable")); + } + } + + if (el.llmRuntime) { + const lines = []; + lines.push("Codex CLI run: completed locally and saved to site/data/llm_codex_demo.json"); + lines.push("Validation run: cargo run against step-4 corpus output (loop_step4_codex.*)"); + if (claudeStatus) { + lines.push(`Claude CLI auth state: loggedIn=${claudeStatus.loggedIn}`); + if (!claudeStatus.loggedIn) { + lines.push("Claude demo execution is pending local CLI authentication."); + } + } + lines.push("Note: shell-level precursor binary on this machine is older than current repo features; validation used cargo run from this branch."); + el.llmRuntime.textContent = lines.join("\n"); + } +} + +async function copyText(text, button) { + if (!button) { + return; + } + try { + await navigator.clipboard.writeText(text || ""); + const original = button.textContent; + button.textContent = "Copied"; + setTimeout(() => { + button.textContent = original || "Copy"; + }, 900); + } catch (_err) { + const original = button.textContent; + button.textContent = "Manual copy"; + setTimeout(() => { + button.textContent = original || "Copy"; + }, 1200); + } +} + +function wireCopyButtons() { + if (el.copyCommand) { + el.copyCommand.addEventListener("click", async () => { + const current = scenarios.find((entry) => entry.id === state.scenarioId) || scenarios[0]; + await copyText(current?.command || "", el.copyCommand); + }); + } + + if (el.copyInstall && el.installCommand) { + el.copyInstall.addEventListener("click", async () => { + await copyText(el.installCommand.textContent || "", el.copyInstall); + }); + } +} + +async function init() { + renderHeroMetrics(); + renderTeaser(); + + if (el.teaserButton) { + el.teaserButton.addEventListener("click", () => { + cycleTeaser(); + }); + } + + setInterval(() => { + cycleTeaser(); + }, 4500); + + renderScenarioList(); + await renderScenario(); + + renderLoopSteps(); + await renderLoopDetail(); - statsJson.textContent = JSON.stringify(statsSample, null, 2); + await renderLlmSection(); + wireCopyButtons(); } -renderStatsMode(); +init(); diff --git a/site/data/README.md b/site/data/README.md new file mode 100644 index 0000000..96b1add --- /dev/null +++ b/site/data/README.md @@ -0,0 +1,34 @@ +# Site Data Provenance + +This folder contains captured output used by `site/app.js` for interactive demos on `precursor.hashdb.io`. + +## Scenario captures + +- `packet_triage.ndjson` + `packet_triage.stderr` +- `firmware_triage.ndjson` + `firmware_triage.stderr` +- `sigma_triage.ndjson` + `sigma_triage.stderr` +- `log4shell_triage.ndjson` + `log4shell_triage.stderr` + +These are generated from versioned corpora in `samples/scenarios/`. + +## Analyst loop captures + +- `loop_step1.stderr` +- `loop_step2.stderr` +- `loop_step3.stderr` +- `loop_step4_codex.stderr` + +Each file includes `--stats` JSON. Steps 3 and 4 also include protocol hint candidate JSON. + +Step 4 validates an additional encoded-JNDI tag proposed from a local Codex CLI run. + +## LLM demo artifacts + +- `llm_demo_prompt.txt`: prompt payload used for local LLM run. +- `llm_codex_demo.json`: JSON output returned by local Codex CLI for the demo prompt. +- `llm_claude_status.json`: local Claude CLI auth status at capture time. + +## Notes + +- On this machine, the globally installed `precursor` command is older than the branch features; advanced demo captures were validated with `cargo run -- ...` from this repository. +- Keep this folder small and deterministic. Remove transient files before commit. diff --git a/site/data/firmware_triage.ndjson b/site/data/firmware_triage.ndjson new file mode 100644 index 0000000..f91ea03 --- /dev/null +++ b/site/data/firmware_triage.ndjson @@ -0,0 +1,4 @@ +{"tlsh":"lzjd:128:7444f380d9e8e27d137724032cc5bc12","similarity_hash":"lzjd:128:7444f380d9e8e27d137724032cc5bc12","tags":["squashfs_magic"],"protocol_label":"unknown","protocol_abstained":true,"protocol_confidence":0.11090354888959125,"protocol_candidates":[{"protocol":"unknown","score":0.11090354888959125,"evidence":["no protocol heuristics matched","similarity cluster boost from 3 neighbors"]}],"xxh3_64_sum":"b0ce40a09e1ccb47","tlsh_similarities":{"lzjd:128:c3d965665ff17f725c8aeec96473f9fa":56,"lzjd:128:a80e56e24b7cecb0e8ef66a66fbf63eb":85,"lzjd:92:080b140fc538c915e49113fab6b29809":82}} +{"tlsh":"lzjd:128:c3d965665ff17f725c8aeec96473f9fa","similarity_hash":"lzjd:128:c3d965665ff17f725c8aeec96473f9fa","tags":["cramfs_magic"],"protocol_label":"unknown","protocol_abstained":true,"protocol_confidence":0.08788898309344878,"protocol_candidates":[{"protocol":"unknown","score":0.08788898309344878,"evidence":["no protocol heuristics matched","similarity cluster boost from 2 neighbors"]}],"xxh3_64_sum":"42807c8d890da0ac","tlsh_similarities":{"lzjd:128:a80e56e24b7cecb0e8ef66a66fbf63eb":80,"lzjd:92:080b140fc538c915e49113fab6b29809":82}} +{"tlsh":"lzjd:128:a80e56e24b7cecb0e8ef66a66fbf63eb","similarity_hash":"lzjd:128:a80e56e24b7cecb0e8ef66a66fbf63eb","tags":["romfs_magic"],"protocol_label":"unknown","protocol_abstained":true,"protocol_confidence":0.055451774444795626,"protocol_candidates":[{"protocol":"unknown","score":0.055451774444795626,"evidence":["no protocol heuristics matched","similarity cluster boost from 1 neighbors"]}],"xxh3_64_sum":"ceb02867d7ec999c","tlsh_similarities":{"lzjd:92:080b140fc538c915e49113fab6b29809":91}} +{"tlsh":"lzjd:92:080b140fc538c915e49113fab6b29809","similarity_hash":"lzjd:92:080b140fc538c915e49113fab6b29809","tags":["gzip_magic"],"protocol_label":"compressed_binary","protocol_abstained":false,"protocol_confidence":0.88,"protocol_candidates":[{"protocol":"compressed_binary","score":0.88,"evidence":["gzip magic header"]}],"xxh3_64_sum":"28c8a22844c12a80","tlsh_similarities":{}} diff --git a/site/data/firmware_triage.stderr b/site/data/firmware_triage.stderr new file mode 100644 index 0000000..1950763 --- /dev/null +++ b/site/data/firmware_triage.stderr @@ -0,0 +1,65 @@ +{ + "---PRECURSOR_STATISTICS---": "This JSON is output to STDERR so that you can parse stats separate from the primary output.", + "Input": { + "Count": 4, + "Unique": 4, + "AvgSize": "2331", + "MinSize": 109, + "MaxSize": 4096, + "P95Size": 4096, + "TotalSize": "9.11KB" + }, + "Match": { + "Patterns": 4, + "TotalMatches": 4, + "Matches": [ + { + "Name": "cramfs_magic", + "Matches": 1 + }, + { + "Name": "squashfs_magic", + "Matches": 1 + }, + { + "Name": "gzip_magic", + "Matches": 1 + }, + { + "Name": "romfs_magic", + "Matches": 1 + } + ], + "HashesGenerated": 4, + "AvgSize": "2331", + "MinSize": 109, + "MaxSize": 4096, + "P95Size": 4096, + "TotalSize": "9.11KB" + }, + "Compare": { + "Similarities": 6, + "AvgDistance": "79", + "MinDistance": 56, + "MaxDistance": 91, + "P95Distance": 85 + }, + "Environment": { + "Version": "0.2.1", + "DurationSeconds": "0.00", + "ProcessingRate": "9.11KB/ms", + "SimilarityMode": "lzjd", + "RegexEngine": "pcre2", + "InputMode": "binary", + "HashFunction": "48_1", + "DistanceThreshold": 100, + "DiffEnabled": true, + "OnlyOutputSimilar": false, + "LengthEnabled": false, + "InputJSONKey": "", + "SinglePacketInference": true, + "AbstainThreshold": 0.65, + "ProtocolTopK": 3, + "SigmaRulesLoaded": 0 + } +} diff --git a/site/data/llm_claude_status.json b/site/data/llm_claude_status.json new file mode 100644 index 0000000..338f0c0 --- /dev/null +++ b/site/data/llm_claude_status.json @@ -0,0 +1,5 @@ +{ + "loggedIn": false, + "authMethod": "none", + "apiProvider": "firstParty" +} diff --git a/site/data/llm_codex_demo.json b/site/data/llm_codex_demo.json new file mode 100644 index 0000000..355d733 --- /dev/null +++ b/site/data/llm_codex_demo.json @@ -0,0 +1,11 @@ +{ + "why_it_matters": "Signal improved from HTTP-only to exploit-aware tagging, but data still splits into a 9-sample Java/class cluster plus one encoded JNDI+LDAP outlier; a single high-confidence JNDI-remote tag will improve triage precision.", + "refinements": [ + "Promote JNDI+remote-scheme evidence to a dedicated primary tag; keep java_user_agent and exploit_class_path as context tags.", + "Use one regex that tolerates raw and URL-encoded `${jndi:` forms to avoid fragmented tagging.", + "Track cluster separation by tag-set (9 similar exploit-class requests vs 1 encoded JNDI request) to prioritize analyst review." + ], + "new_pattern": "(?i)(?(?:\\$\\{|%24%7b)[^\\r\\n]{0,64}?(?:jndi|%6a%6e%64%69)\\s*(?::|%3a)\\s*(?:ldap|ldaps|rmi|dns|iiop|nis|http|https)(?::|%3a|//|%2f%2f))", + "next_command": "cat samples/scenarios/public-log4shell-foxit-pcap/payloads.string | precursor -p samples/scenarios/public-log4shell-foxit-pcap/patterns.pcre '(?(?:\\$\\{|%24%7b)[^\\r\\n]{0,64}?(?:jndi|%6a%6e%64%69)\\s*(?::|%3a)\\s*(?:ldap|ldaps|rmi|dns|iiop|nis|http|https)(?::|%3a|//|%2f%2f))' -m string -t -d --similarity-mode fbhash -P --protocol-hints --stats", + "risk": "May still miss heavily obfuscated `${${lower:j}...}` payloads and can false-match benign encoded strings containing `jndi` plus a URI scheme." +} diff --git a/site/data/llm_demo_prompt.txt b/site/data/llm_demo_prompt.txt new file mode 100644 index 0000000..1ef258e --- /dev/null +++ b/site/data/llm_demo_prompt.txt @@ -0,0 +1,345 @@ +You are helping an analyst iterate on Precursor detections. +Read the three iteration stats snapshots and sample NDJSON lines. Then return strict JSON only with keys: +"why_it_matters", "refinements" (array of 3 strings), "new_pattern" (single PCRE named-capture regex), "next_command" (single precursor command), "risk" (string). +Keep values concise and practical. No markdown. + +ITERATION_1_STATS: +{ + "---PRECURSOR_STATISTICS---": "This JSON is output to STDERR so that you can parse stats separate from the primary output.", + "Input": { + "Count": 10, + "Unique": 10, + "AvgSize": "108", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 86, + "TotalSize": "1.06KB" + }, + "Match": { + "Patterns": 1, + "TotalMatches": 10, + "Matches": [ + { + "Name": "http_method", + "Matches": 10 + } + ], + "HashesGenerated": 10, + "AvgSize": "108", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 86, + "TotalSize": "1.06KB" + }, + "Compare": { + "Similarities": 45, + "AvgDistance": "40", + "MinDistance": 19, + "MaxDistance": 77, + "P95Distance": 76 + }, + "Environment": { + "Version": "0.2.1", + "DurationSeconds": "0.00", + "ProcessingRate": "1.06KB/ms", + "SimilarityMode": "lzjd", + "RegexEngine": "pcre2", + "InputMode": "string", + "HashFunction": "48_1", + "DistanceThreshold": 100, + "DiffEnabled": true, + "OnlyOutputSimilar": false, + "LengthEnabled": false, + "InputJSONKey": "", + "SinglePacketInference": true, + "AbstainThreshold": 0.65, + "ProtocolTopK": 3, + "SigmaRulesLoaded": 0 + } +} + +ITERATION_2_STATS: +{ + "---PRECURSOR_STATISTICS---": "This JSON is output to STDERR so that you can parse stats separate from the primary output.", + "Input": { + "Count": 10, + "Unique": 10, + "AvgSize": "108", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 86, + "TotalSize": "1.06KB" + }, + "Match": { + "Patterns": 5, + "TotalMatches": 30, + "Matches": [ + { + "Name": "exploit_class_path", + "Matches": 9 + }, + { + "Name": "ldap_scheme", + "Matches": 1 + }, + { + "Name": "java_user_agent", + "Matches": 9 + }, + { + "Name": "urlencoded_jndi", + "Matches": 1 + }, + { + "Name": "http_method", + "Matches": 10 + } + ], + "HashesGenerated": 10, + "AvgSize": "108", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 307, + "TotalSize": "3.17KB" + }, + "Compare": { + "Similarities": 45, + "AvgDistance": "40", + "MinDistance": 19, + "MaxDistance": 77, + "P95Distance": 76 + }, + "Environment": { + "Version": "0.2.1", + "DurationSeconds": "0.00", + "ProcessingRate": "1.06KB/ms", + "SimilarityMode": "lzjd", + "RegexEngine": "pcre2", + "InputMode": "string", + "HashFunction": "48_1", + "DistanceThreshold": 100, + "DiffEnabled": true, + "OnlyOutputSimilar": false, + "LengthEnabled": false, + "InputJSONKey": "", + "SinglePacketInference": true, + "AbstainThreshold": 0.65, + "ProtocolTopK": 3, + "SigmaRulesLoaded": 0 + } +} + +ITERATION_3_STATS: +{ + "---PRECURSOR_PROTOCOL_HINTS---": "Candidate payload clusters for LLM-guided protocol discovery.", + "SimilarityMode": "fbhash", + "DistanceThreshold": 100, + "Candidates": [ + { + "xxh3_64_sum": "612a4f3803dc2110", + "similarity_hash": "fbhash:227:294cb799466c30672fbc02a727755001", + "neighbor_count": 9, + "tags": [ + "http_method", + "urlencoded_jndi", + "ldap_scheme" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "312e3f63a899a72e", + "similarity_hash": "fbhash:80:de765fcd8861dc3c3b17fbe45abb4ae3", + "neighbor_count": 8, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "b4d9e2449d8ee0b6", + "similarity_hash": "fbhash:80:c8f490e698c2ba57c1f88374503b162d", + "neighbor_count": 7, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "2f77d6f6ecbfcdf1", + "similarity_hash": "fbhash:80:2d4e564aa0f5e8c207fefe1523d3af85", + "neighbor_count": 6, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "170d6ca84449255c", + "similarity_hash": "fbhash:80:74d9a54cb165463b95315476c44ce79a", + "neighbor_count": 5, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "ffd960bd567bf866", + "similarity_hash": "fbhash:80:f82eac81c88020d591f3242c2c5a1e72", + "neighbor_count": 4, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "5fc49418b456e085", + "similarity_hash": "fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1", + "neighbor_count": 3, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "45d0ebaa5f04b7a6", + "similarity_hash": "fbhash:80:819a4fd1eaab6918df7e04cf101d4090", + "neighbor_count": 2, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "8e17ec202c88d9a4", + "similarity_hash": "fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d", + "neighbor_count": 1, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "90519648a3fc0f7", + "similarity_hash": "fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978", + "neighbor_count": 0, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + } + ] +} +{ + "---PRECURSOR_STATISTICS---": "This JSON is output to STDERR so that you can parse stats separate from the primary output.", + "Input": { + "Count": 10, + "Unique": 10, + "AvgSize": "108", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 86, + "TotalSize": "1.06KB" + }, + "Match": { + "Patterns": 5, + "TotalMatches": 30, + "Matches": [ + { + "Name": "java_user_agent", + "Matches": 9 + }, + { + "Name": "http_method", + "Matches": 10 + }, + { + "Name": "urlencoded_jndi", + "Matches": 1 + }, + { + "Name": "ldap_scheme", + "Matches": 1 + }, + { + "Name": "exploit_class_path", + "Matches": 9 + } + ], + "HashesGenerated": 10, + "AvgSize": "108", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 307, + "TotalSize": "3.17KB" + }, + "Compare": { + "Similarities": 45, + "AvgDistance": "49", + "MinDistance": 39, + "MaxDistance": 88, + "P95Distance": 88 + }, + "Environment": { + "Version": "0.2.1", + "DurationSeconds": "0.00", + "ProcessingRate": "1.06KB/ms", + "SimilarityMode": "fbhash", + "RegexEngine": "pcre2", + "InputMode": "string", + "HashFunction": "48_1", + "DistanceThreshold": 100, + "DiffEnabled": true, + "OnlyOutputSimilar": false, + "LengthEnabled": false, + "InputJSONKey": "", + "SinglePacketInference": true, + "AbstainThreshold": 0.65, + "ProtocolTopK": 3, + "SigmaRulesLoaded": 0 + } +} + +ITERATION_3_SAMPLE_NDJSON: +{"tlsh":"fbhash:227:294cb799466c30672fbc02a727755001","similarity_hash":"fbhash:227:294cb799466c30672fbc02a727755001","tags":["http_method","urlencoded_jndi","ldap_scheme"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http","similarity cluster boost from 9 neighbors"]}],"xxh3_64_sum":"612a4f3803dc2110","tlsh_similarities":{"fbhash:80:de765fcd8861dc3c3b17fbe45abb4ae3":88,"fbhash:80:c8f490e698c2ba57c1f88374503b162d":88,"fbhash:80:2d4e564aa0f5e8c207fefe1523d3af85":88,"fbhash:80:74d9a54cb165463b95315476c44ce79a":88,"fbhash:80:f82eac81c88020d591f3242c2c5a1e72":88,"fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1":88,"fbhash:80:819a4fd1eaab6918df7e04cf101d4090":88,"fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d":88,"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978":88}} +{"tlsh":"fbhash:80:de765fcd8861dc3c3b17fbe45abb4ae3","similarity_hash":"fbhash:80:de765fcd8861dc3c3b17fbe45abb4ae3","tags":["http_method","exploit_class_path","java_user_agent"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http","similarity cluster boost from 8 neighbors"]}],"xxh3_64_sum":"312e3f63a899a72e","tlsh_similarities":{"fbhash:80:c8f490e698c2ba57c1f88374503b162d":39,"fbhash:80:2d4e564aa0f5e8c207fefe1523d3af85":39,"fbhash:80:74d9a54cb165463b95315476c44ce79a":39,"fbhash:80:f82eac81c88020d591f3242c2c5a1e72":39,"fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1":39,"fbhash:80:819a4fd1eaab6918df7e04cf101d4090":39,"fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d":39,"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978":39}} +{"tlsh":"fbhash:80:74d9a54cb165463b95315476c44ce79a","similarity_hash":"fbhash:80:74d9a54cb165463b95315476c44ce79a","tags":["http_method","exploit_class_path","java_user_agent"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http","similarity cluster boost from 5 neighbors"]}],"xxh3_64_sum":"170d6ca84449255c","tlsh_similarities":{"fbhash:80:f82eac81c88020d591f3242c2c5a1e72":39,"fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1":39,"fbhash:80:819a4fd1eaab6918df7e04cf101d4090":39,"fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d":39,"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978":39}} +{"tlsh":"fbhash:80:c8f490e698c2ba57c1f88374503b162d","similarity_hash":"fbhash:80:c8f490e698c2ba57c1f88374503b162d","tags":["http_method","exploit_class_path","java_user_agent"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http","similarity cluster boost from 7 neighbors"]}],"xxh3_64_sum":"b4d9e2449d8ee0b6","tlsh_similarities":{"fbhash:80:2d4e564aa0f5e8c207fefe1523d3af85":39,"fbhash:80:74d9a54cb165463b95315476c44ce79a":39,"fbhash:80:f82eac81c88020d591f3242c2c5a1e72":39,"fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1":39,"fbhash:80:819a4fd1eaab6918df7e04cf101d4090":39,"fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d":39,"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978":39}} +{"tlsh":"fbhash:80:f82eac81c88020d591f3242c2c5a1e72","similarity_hash":"fbhash:80:f82eac81c88020d591f3242c2c5a1e72","tags":["http_method","exploit_class_path","java_user_agent"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http","similarity cluster boost from 4 neighbors"]}],"xxh3_64_sum":"ffd960bd567bf866","tlsh_similarities":{"fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1":39,"fbhash:80:819a4fd1eaab6918df7e04cf101d4090":39,"fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d":39,"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978":39}} diff --git a/site/data/log4shell_triage.ndjson b/site/data/log4shell_triage.ndjson new file mode 100644 index 0000000..1ec37d7 --- /dev/null +++ b/site/data/log4shell_triage.ndjson @@ -0,0 +1,10 @@ +{"tlsh":"fbhash:227:294cb799466c30672fbc02a727755001","similarity_hash":"fbhash:227:294cb799466c30672fbc02a727755001","tags":["http_method","urlencoded_jndi","ldap_scheme"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http","similarity cluster boost from 9 neighbors"]}],"xxh3_64_sum":"612a4f3803dc2110","tlsh_similarities":{"fbhash:80:de765fcd8861dc3c3b17fbe45abb4ae3":88,"fbhash:80:c8f490e698c2ba57c1f88374503b162d":88,"fbhash:80:2d4e564aa0f5e8c207fefe1523d3af85":88,"fbhash:80:74d9a54cb165463b95315476c44ce79a":88,"fbhash:80:f82eac81c88020d591f3242c2c5a1e72":88,"fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1":88,"fbhash:80:819a4fd1eaab6918df7e04cf101d4090":88,"fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d":88,"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978":88}} +{"tlsh":"fbhash:80:de765fcd8861dc3c3b17fbe45abb4ae3","similarity_hash":"fbhash:80:de765fcd8861dc3c3b17fbe45abb4ae3","tags":["http_method","exploit_class_path","java_user_agent"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http","similarity cluster boost from 8 neighbors"]}],"xxh3_64_sum":"312e3f63a899a72e","tlsh_similarities":{"fbhash:80:c8f490e698c2ba57c1f88374503b162d":39,"fbhash:80:2d4e564aa0f5e8c207fefe1523d3af85":39,"fbhash:80:74d9a54cb165463b95315476c44ce79a":39,"fbhash:80:f82eac81c88020d591f3242c2c5a1e72":39,"fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1":39,"fbhash:80:819a4fd1eaab6918df7e04cf101d4090":39,"fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d":39,"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978":39}} +{"tlsh":"fbhash:80:74d9a54cb165463b95315476c44ce79a","similarity_hash":"fbhash:80:74d9a54cb165463b95315476c44ce79a","tags":["http_method","exploit_class_path","java_user_agent"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http","similarity cluster boost from 5 neighbors"]}],"xxh3_64_sum":"170d6ca84449255c","tlsh_similarities":{"fbhash:80:f82eac81c88020d591f3242c2c5a1e72":39,"fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1":39,"fbhash:80:819a4fd1eaab6918df7e04cf101d4090":39,"fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d":39,"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978":39}} +{"tlsh":"fbhash:80:c8f490e698c2ba57c1f88374503b162d","similarity_hash":"fbhash:80:c8f490e698c2ba57c1f88374503b162d","tags":["http_method","exploit_class_path","java_user_agent"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http","similarity cluster boost from 7 neighbors"]}],"xxh3_64_sum":"b4d9e2449d8ee0b6","tlsh_similarities":{"fbhash:80:2d4e564aa0f5e8c207fefe1523d3af85":39,"fbhash:80:74d9a54cb165463b95315476c44ce79a":39,"fbhash:80:f82eac81c88020d591f3242c2c5a1e72":39,"fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1":39,"fbhash:80:819a4fd1eaab6918df7e04cf101d4090":39,"fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d":39,"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978":39}} +{"tlsh":"fbhash:80:f82eac81c88020d591f3242c2c5a1e72","similarity_hash":"fbhash:80:f82eac81c88020d591f3242c2c5a1e72","tags":["http_method","exploit_class_path","java_user_agent"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http","similarity cluster boost from 4 neighbors"]}],"xxh3_64_sum":"ffd960bd567bf866","tlsh_similarities":{"fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1":39,"fbhash:80:819a4fd1eaab6918df7e04cf101d4090":39,"fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d":39,"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978":39}} +{"tlsh":"fbhash:80:819a4fd1eaab6918df7e04cf101d4090","similarity_hash":"fbhash:80:819a4fd1eaab6918df7e04cf101d4090","tags":["http_method","exploit_class_path","java_user_agent"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http","similarity cluster boost from 2 neighbors"]}],"xxh3_64_sum":"45d0ebaa5f04b7a6","tlsh_similarities":{"fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d":39,"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978":39}} +{"tlsh":"fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1","similarity_hash":"fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1","tags":["http_method","exploit_class_path","java_user_agent"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http","similarity cluster boost from 3 neighbors"]}],"xxh3_64_sum":"5fc49418b456e085","tlsh_similarities":{"fbhash:80:819a4fd1eaab6918df7e04cf101d4090":39,"fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d":39,"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978":39}} +{"tlsh":"fbhash:80:2d4e564aa0f5e8c207fefe1523d3af85","similarity_hash":"fbhash:80:2d4e564aa0f5e8c207fefe1523d3af85","tags":["http_method","exploit_class_path","java_user_agent"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http","similarity cluster boost from 6 neighbors"]}],"xxh3_64_sum":"2f77d6f6ecbfcdf1","tlsh_similarities":{"fbhash:80:74d9a54cb165463b95315476c44ce79a":39,"fbhash:80:f82eac81c88020d591f3242c2c5a1e72":39,"fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1":39,"fbhash:80:819a4fd1eaab6918df7e04cf101d4090":39,"fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d":39,"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978":39}} +{"tlsh":"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978","similarity_hash":"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978","tags":["http_method","exploit_class_path","java_user_agent"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http"]}],"xxh3_64_sum":"90519648a3fc0f7","tlsh_similarities":{}} +{"tlsh":"fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d","similarity_hash":"fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d","tags":["http_method","exploit_class_path","java_user_agent"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http","similarity cluster boost from 1 neighbors"]}],"xxh3_64_sum":"8e17ec202c88d9a4","tlsh_similarities":{"fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978":39}} diff --git a/site/data/log4shell_triage.stderr b/site/data/log4shell_triage.stderr new file mode 100644 index 0000000..7bad1a4 --- /dev/null +++ b/site/data/log4shell_triage.stderr @@ -0,0 +1,206 @@ +{ + "---PRECURSOR_PROTOCOL_HINTS---": "Candidate payload clusters for LLM-guided protocol discovery.", + "SimilarityMode": "fbhash", + "DistanceThreshold": 100, + "Candidates": [ + { + "xxh3_64_sum": "612a4f3803dc2110", + "similarity_hash": "fbhash:227:294cb799466c30672fbc02a727755001", + "neighbor_count": 9, + "tags": [ + "http_method", + "urlencoded_jndi", + "ldap_scheme" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "312e3f63a899a72e", + "similarity_hash": "fbhash:80:de765fcd8861dc3c3b17fbe45abb4ae3", + "neighbor_count": 8, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "b4d9e2449d8ee0b6", + "similarity_hash": "fbhash:80:c8f490e698c2ba57c1f88374503b162d", + "neighbor_count": 7, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "2f77d6f6ecbfcdf1", + "similarity_hash": "fbhash:80:2d4e564aa0f5e8c207fefe1523d3af85", + "neighbor_count": 6, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "170d6ca84449255c", + "similarity_hash": "fbhash:80:74d9a54cb165463b95315476c44ce79a", + "neighbor_count": 5, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "ffd960bd567bf866", + "similarity_hash": "fbhash:80:f82eac81c88020d591f3242c2c5a1e72", + "neighbor_count": 4, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "5fc49418b456e085", + "similarity_hash": "fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1", + "neighbor_count": 3, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "45d0ebaa5f04b7a6", + "similarity_hash": "fbhash:80:819a4fd1eaab6918df7e04cf101d4090", + "neighbor_count": 2, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "8e17ec202c88d9a4", + "similarity_hash": "fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d", + "neighbor_count": 1, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "90519648a3fc0f7", + "similarity_hash": "fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978", + "neighbor_count": 0, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + } + ] +} +{ + "---PRECURSOR_STATISTICS---": "This JSON is output to STDERR so that you can parse stats separate from the primary output.", + "Input": { + "Count": 10, + "Unique": 10, + "AvgSize": "108", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 86, + "TotalSize": "1.06KB" + }, + "Match": { + "Patterns": 5, + "TotalMatches": 30, + "Matches": [ + { + "Name": "java_user_agent", + "Matches": 9 + }, + { + "Name": "http_method", + "Matches": 10 + }, + { + "Name": "urlencoded_jndi", + "Matches": 1 + }, + { + "Name": "ldap_scheme", + "Matches": 1 + }, + { + "Name": "exploit_class_path", + "Matches": 9 + } + ], + "HashesGenerated": 10, + "AvgSize": "108", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 307, + "TotalSize": "3.17KB" + }, + "Compare": { + "Similarities": 45, + "AvgDistance": "49", + "MinDistance": 39, + "MaxDistance": 88, + "P95Distance": 88 + }, + "Environment": { + "Version": "0.2.1", + "DurationSeconds": "0.00", + "ProcessingRate": "1.06KB/ms", + "SimilarityMode": "fbhash", + "RegexEngine": "pcre2", + "InputMode": "string", + "HashFunction": "48_1", + "DistanceThreshold": 100, + "DiffEnabled": true, + "OnlyOutputSimilar": false, + "LengthEnabled": false, + "InputJSONKey": "", + "SinglePacketInference": true, + "AbstainThreshold": 0.65, + "ProtocolTopK": 3, + "SigmaRulesLoaded": 0 + } +} diff --git a/site/data/loop_step1.stderr b/site/data/loop_step1.stderr new file mode 100644 index 0000000..9e9c1bb --- /dev/null +++ b/site/data/loop_step1.stderr @@ -0,0 +1,53 @@ +{ + "---PRECURSOR_STATISTICS---": "This JSON is output to STDERR so that you can parse stats separate from the primary output.", + "Input": { + "Count": 10, + "Unique": 10, + "AvgSize": "108", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 86, + "TotalSize": "1.06KB" + }, + "Match": { + "Patterns": 1, + "TotalMatches": 10, + "Matches": [ + { + "Name": "http_method", + "Matches": 10 + } + ], + "HashesGenerated": 10, + "AvgSize": "108", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 86, + "TotalSize": "1.06KB" + }, + "Compare": { + "Similarities": 45, + "AvgDistance": "40", + "MinDistance": 19, + "MaxDistance": 77, + "P95Distance": 76 + }, + "Environment": { + "Version": "0.2.1", + "DurationSeconds": "0.00", + "ProcessingRate": "1.06KB/ms", + "SimilarityMode": "lzjd", + "RegexEngine": "pcre2", + "InputMode": "string", + "HashFunction": "48_1", + "DistanceThreshold": 100, + "DiffEnabled": true, + "OnlyOutputSimilar": false, + "LengthEnabled": false, + "InputJSONKey": "", + "SinglePacketInference": true, + "AbstainThreshold": 0.65, + "ProtocolTopK": 3, + "SigmaRulesLoaded": 0 + } +} diff --git a/site/data/loop_step2.stderr b/site/data/loop_step2.stderr new file mode 100644 index 0000000..2b7e2d8 --- /dev/null +++ b/site/data/loop_step2.stderr @@ -0,0 +1,69 @@ +{ + "---PRECURSOR_STATISTICS---": "This JSON is output to STDERR so that you can parse stats separate from the primary output.", + "Input": { + "Count": 10, + "Unique": 10, + "AvgSize": "108", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 86, + "TotalSize": "1.06KB" + }, + "Match": { + "Patterns": 5, + "TotalMatches": 30, + "Matches": [ + { + "Name": "exploit_class_path", + "Matches": 9 + }, + { + "Name": "ldap_scheme", + "Matches": 1 + }, + { + "Name": "java_user_agent", + "Matches": 9 + }, + { + "Name": "urlencoded_jndi", + "Matches": 1 + }, + { + "Name": "http_method", + "Matches": 10 + } + ], + "HashesGenerated": 10, + "AvgSize": "108", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 307, + "TotalSize": "3.17KB" + }, + "Compare": { + "Similarities": 45, + "AvgDistance": "40", + "MinDistance": 19, + "MaxDistance": 77, + "P95Distance": 76 + }, + "Environment": { + "Version": "0.2.1", + "DurationSeconds": "0.00", + "ProcessingRate": "1.06KB/ms", + "SimilarityMode": "lzjd", + "RegexEngine": "pcre2", + "InputMode": "string", + "HashFunction": "48_1", + "DistanceThreshold": 100, + "DiffEnabled": true, + "OnlyOutputSimilar": false, + "LengthEnabled": false, + "InputJSONKey": "", + "SinglePacketInference": true, + "AbstainThreshold": 0.65, + "ProtocolTopK": 3, + "SigmaRulesLoaded": 0 + } +} diff --git a/site/data/loop_step3.stderr b/site/data/loop_step3.stderr new file mode 100644 index 0000000..7bad1a4 --- /dev/null +++ b/site/data/loop_step3.stderr @@ -0,0 +1,206 @@ +{ + "---PRECURSOR_PROTOCOL_HINTS---": "Candidate payload clusters for LLM-guided protocol discovery.", + "SimilarityMode": "fbhash", + "DistanceThreshold": 100, + "Candidates": [ + { + "xxh3_64_sum": "612a4f3803dc2110", + "similarity_hash": "fbhash:227:294cb799466c30672fbc02a727755001", + "neighbor_count": 9, + "tags": [ + "http_method", + "urlencoded_jndi", + "ldap_scheme" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "312e3f63a899a72e", + "similarity_hash": "fbhash:80:de765fcd8861dc3c3b17fbe45abb4ae3", + "neighbor_count": 8, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "b4d9e2449d8ee0b6", + "similarity_hash": "fbhash:80:c8f490e698c2ba57c1f88374503b162d", + "neighbor_count": 7, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "2f77d6f6ecbfcdf1", + "similarity_hash": "fbhash:80:2d4e564aa0f5e8c207fefe1523d3af85", + "neighbor_count": 6, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "170d6ca84449255c", + "similarity_hash": "fbhash:80:74d9a54cb165463b95315476c44ce79a", + "neighbor_count": 5, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "ffd960bd567bf866", + "similarity_hash": "fbhash:80:f82eac81c88020d591f3242c2c5a1e72", + "neighbor_count": 4, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "5fc49418b456e085", + "similarity_hash": "fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1", + "neighbor_count": 3, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "45d0ebaa5f04b7a6", + "similarity_hash": "fbhash:80:819a4fd1eaab6918df7e04cf101d4090", + "neighbor_count": 2, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "8e17ec202c88d9a4", + "similarity_hash": "fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d", + "neighbor_count": 1, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "90519648a3fc0f7", + "similarity_hash": "fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978", + "neighbor_count": 0, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + } + ] +} +{ + "---PRECURSOR_STATISTICS---": "This JSON is output to STDERR so that you can parse stats separate from the primary output.", + "Input": { + "Count": 10, + "Unique": 10, + "AvgSize": "108", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 86, + "TotalSize": "1.06KB" + }, + "Match": { + "Patterns": 5, + "TotalMatches": 30, + "Matches": [ + { + "Name": "java_user_agent", + "Matches": 9 + }, + { + "Name": "http_method", + "Matches": 10 + }, + { + "Name": "urlencoded_jndi", + "Matches": 1 + }, + { + "Name": "ldap_scheme", + "Matches": 1 + }, + { + "Name": "exploit_class_path", + "Matches": 9 + } + ], + "HashesGenerated": 10, + "AvgSize": "108", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 307, + "TotalSize": "3.17KB" + }, + "Compare": { + "Similarities": 45, + "AvgDistance": "49", + "MinDistance": 39, + "MaxDistance": 88, + "P95Distance": 88 + }, + "Environment": { + "Version": "0.2.1", + "DurationSeconds": "0.00", + "ProcessingRate": "1.06KB/ms", + "SimilarityMode": "fbhash", + "RegexEngine": "pcre2", + "InputMode": "string", + "HashFunction": "48_1", + "DistanceThreshold": 100, + "DiffEnabled": true, + "OnlyOutputSimilar": false, + "LengthEnabled": false, + "InputJSONKey": "", + "SinglePacketInference": true, + "AbstainThreshold": 0.65, + "ProtocolTopK": 3, + "SigmaRulesLoaded": 0 + } +} diff --git a/site/data/loop_step4_codex.stderr b/site/data/loop_step4_codex.stderr new file mode 100644 index 0000000..9851dca --- /dev/null +++ b/site/data/loop_step4_codex.stderr @@ -0,0 +1,211 @@ +{ + "---PRECURSOR_PROTOCOL_HINTS---": "Candidate payload clusters for LLM-guided protocol discovery.", + "SimilarityMode": "fbhash", + "DistanceThreshold": 100, + "Candidates": [ + { + "xxh3_64_sum": "170d6ca84449255c", + "similarity_hash": "fbhash:80:74d9a54cb165463b95315476c44ce79a", + "neighbor_count": 9, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "90519648a3fc0f7", + "similarity_hash": "fbhash:80:b8f9919d9ff2b5b3f2082ef0426dc978", + "neighbor_count": 8, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "5fc49418b456e085", + "similarity_hash": "fbhash:80:a2c00f1789ec419679f3592d6fa0dbc1", + "neighbor_count": 7, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "8e17ec202c88d9a4", + "similarity_hash": "fbhash:80:d576f484ce2c3e7055ef96680c4d5f5d", + "neighbor_count": 6, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "ffd960bd567bf866", + "similarity_hash": "fbhash:80:f82eac81c88020d591f3242c2c5a1e72", + "neighbor_count": 5, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "612a4f3803dc2110", + "similarity_hash": "fbhash:227:294cb799466c30672fbc02a727755001", + "neighbor_count": 4, + "tags": [ + "http_method", + "urlencoded_jndi", + "ldap_scheme", + "jndi_remote_lookup" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "312e3f63a899a72e", + "similarity_hash": "fbhash:80:de765fcd8861dc3c3b17fbe45abb4ae3", + "neighbor_count": 3, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "b4d9e2449d8ee0b6", + "similarity_hash": "fbhash:80:c8f490e698c2ba57c1f88374503b162d", + "neighbor_count": 2, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "45d0ebaa5f04b7a6", + "similarity_hash": "fbhash:80:819a4fd1eaab6918df7e04cf101d4090", + "neighbor_count": 1, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "2f77d6f6ecbfcdf1", + "similarity_hash": "fbhash:80:2d4e564aa0f5e8c207fefe1523d3af85", + "neighbor_count": 0, + "tags": [ + "http_method", + "exploit_class_path", + "java_user_agent" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + } + ] +} +{ + "---PRECURSOR_STATISTICS---": "This JSON is output to STDERR so that you can parse stats separate from the primary output.", + "Input": { + "Count": 10, + "Unique": 10, + "AvgSize": "108", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 86, + "TotalSize": "1.06KB" + }, + "Match": { + "Patterns": 6, + "TotalMatches": 31, + "Matches": [ + { + "Name": "jndi_remote_lookup", + "Matches": 1 + }, + { + "Name": "urlencoded_jndi", + "Matches": 1 + }, + { + "Name": "http_method", + "Matches": 10 + }, + { + "Name": "java_user_agent", + "Matches": 9 + }, + { + "Name": "exploit_class_path", + "Matches": 9 + }, + { + "Name": "ldap_scheme", + "Matches": 1 + } + ], + "HashesGenerated": 10, + "AvgSize": "115", + "MinSize": 86, + "MaxSize": 307, + "P95Size": 307, + "TotalSize": "3.47KB" + }, + "Compare": { + "Similarities": 45, + "AvgDistance": "49", + "MinDistance": 39, + "MaxDistance": 88, + "P95Distance": 88 + }, + "Environment": { + "Version": "0.2.1", + "DurationSeconds": "0.01", + "ProcessingRate": "154B/ms", + "SimilarityMode": "fbhash", + "RegexEngine": "pcre2", + "InputMode": "string", + "HashFunction": "48_1", + "DistanceThreshold": 100, + "DiffEnabled": true, + "OnlyOutputSimilar": false, + "LengthEnabled": false, + "InputJSONKey": "", + "SinglePacketInference": true, + "AbstainThreshold": 0.65, + "ProtocolTopK": 3, + "SigmaRulesLoaded": 0 + } +} diff --git a/site/data/packet_triage.ndjson b/site/data/packet_triage.ndjson new file mode 100644 index 0000000..b74cdec --- /dev/null +++ b/site/data/packet_triage.ndjson @@ -0,0 +1,4 @@ +{"tlsh":"lzjd:8:498bfc24c71b6f6ab1e57dcd1520d55d","similarity_hash":"lzjd:8:498bfc24c71b6f6ab1e57dcd1520d55d","tags":["modbus_mbap"],"protocol_label":"unknown","protocol_abstained":true,"protocol_confidence":0.11090354888959125,"protocol_candidates":[{"protocol":"unknown","score":0.11090354888959125,"evidence":["no protocol heuristics matched","similarity cluster boost from 3 neighbors"]}],"xxh3_64_sum":"e40a7db551b7707f","tlsh_similarities":{"lzjd:17:e4ac19d2d0f0cb47627835204f9bb5e5":100,"lzjd:21:bd02b220d6815f4fa7dc7a9c6f55e468":84,"lzjd:54:eeab9896f636bae081b935f08a3a4981":100}} +{"tlsh":"lzjd:21:bd02b220d6815f4fa7dc7a9c6f55e468","similarity_hash":"lzjd:21:bd02b220d6815f4fa7dc7a9c6f55e468","tags":["tls_handshake_record"],"protocol_label":"tls","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"tls","score":0.99,"evidence":["matched TLS handshake prefix 16 03 xx","tag evidence: tls/ssl","similarity cluster boost from 1 neighbors"]}],"xxh3_64_sum":"e05fe49d391d884a","tlsh_similarities":{"lzjd:54:eeab9896f636bae081b935f08a3a4981":99}} +{"tlsh":"lzjd:17:e4ac19d2d0f0cb47627835204f9bb5e5","similarity_hash":"lzjd:17:e4ac19d2d0f0cb47627835204f9bb5e5","tags":["ssh_banner"],"protocol_label":"ssh","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"ssh","score":0.99,"evidence":["matched SSH identification banner","tag evidence: ssh","similarity cluster boost from 2 neighbors"]},{"protocol":"dns_or_domain_payload","score":0.5278889830934488,"evidence":["domain-like token shape","similarity cluster boost from 2 neighbors"]}],"xxh3_64_sum":"334a496385b190eb","tlsh_similarities":{"lzjd:21:bd02b220d6815f4fa7dc7a9c6f55e468":100,"lzjd:54:eeab9896f636bae081b935f08a3a4981":87}} +{"tlsh":"lzjd:54:eeab9896f636bae081b935f08a3a4981","similarity_hash":"lzjd:54:eeab9896f636bae081b935f08a3a4981","tags":["http_method"],"protocol_label":"http","protocol_abstained":false,"protocol_confidence":0.99,"protocol_candidates":[{"protocol":"http","score":0.99,"evidence":["matched HTTP request/headers","tag evidence: http"]}],"xxh3_64_sum":"53fc4f37dbc42574","tlsh_similarities":{}} diff --git a/site/data/packet_triage.stderr b/site/data/packet_triage.stderr new file mode 100644 index 0000000..145c977 --- /dev/null +++ b/site/data/packet_triage.stderr @@ -0,0 +1,116 @@ +{ + "---PRECURSOR_PROTOCOL_HINTS---": "Candidate payload clusters for LLM-guided protocol discovery.", + "SimilarityMode": "lzjd", + "DistanceThreshold": 100, + "Candidates": [ + { + "xxh3_64_sum": "e40a7db551b7707f", + "similarity_hash": "lzjd:8:498bfc24c71b6f6ab1e57dcd1520d55d", + "neighbor_count": 3, + "tags": [ + "modbus_mbap" + ], + "protocol_label": "unknown", + "protocol_confidence": 0.0, + "protocol_abstained": true + }, + { + "xxh3_64_sum": "334a496385b190eb", + "similarity_hash": "lzjd:17:e4ac19d2d0f0cb47627835204f9bb5e5", + "neighbor_count": 2, + "tags": [ + "ssh_banner" + ], + "protocol_label": "ssh", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "e05fe49d391d884a", + "similarity_hash": "lzjd:21:bd02b220d6815f4fa7dc7a9c6f55e468", + "neighbor_count": 1, + "tags": [ + "tls_handshake_record" + ], + "protocol_label": "tls", + "protocol_confidence": 0.99, + "protocol_abstained": false + }, + { + "xxh3_64_sum": "53fc4f37dbc42574", + "similarity_hash": "lzjd:54:eeab9896f636bae081b935f08a3a4981", + "neighbor_count": 0, + "tags": [ + "http_method" + ], + "protocol_label": "http", + "protocol_confidence": 0.99, + "protocol_abstained": false + } + ] +} +{ + "---PRECURSOR_STATISTICS---": "This JSON is output to STDERR so that you can parse stats separate from the primary output.", + "Input": { + "Count": 5, + "Unique": 5, + "AvgSize": "39", + "MinSize": 12, + "MaxSize": 82, + "P95Size": 50, + "TotalSize": "194B" + }, + "Match": { + "Patterns": 5, + "TotalMatches": 4, + "Matches": [ + { + "Name": "modbus_mbap", + "Matches": 1 + }, + { + "Name": "ssh_banner", + "Matches": 1 + }, + { + "Name": "tls_handshake_record", + "Matches": 1 + }, + { + "Name": "http_method", + "Matches": 1 + } + ], + "HashesGenerated": 4, + "AvgSize": "41", + "MinSize": 12, + "MaxSize": 82, + "P95Size": 50, + "TotalSize": "165B" + }, + "Compare": { + "Similarities": 6, + "AvgDistance": "95", + "MinDistance": 84, + "MaxDistance": 100, + "P95Distance": 100 + }, + "Environment": { + "Version": "0.2.1", + "DurationSeconds": "0.00", + "ProcessingRate": "194B/ms", + "SimilarityMode": "lzjd", + "RegexEngine": "pcre2", + "InputMode": "base64", + "HashFunction": "48_1", + "DistanceThreshold": 100, + "DiffEnabled": true, + "OnlyOutputSimilar": false, + "LengthEnabled": false, + "InputJSONKey": "", + "SinglePacketInference": true, + "AbstainThreshold": 0.65, + "ProtocolTopK": 3, + "SigmaRulesLoaded": 0 + } +} diff --git a/site/data/sigma_triage.ndjson b/site/data/sigma_triage.ndjson new file mode 100644 index 0000000..bf1b217 --- /dev/null +++ b/site/data/sigma_triage.ndjson @@ -0,0 +1,5 @@ +{"tlsh":"lzjd:30:84bdaa48877d88bfe2a48fbe356e7a78","similarity_hash":"lzjd:30:84bdaa48877d88bfe2a48fbe356e7a78","tags":["sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_10"],"sigma_rule_matches":["Suspicious Shell Commands"],"sigma_rule_ids":["d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff"],"xxh3_64_sum":"57101f1f1e986f6e","tlsh_similarities":{"lzjd:27:5d947215c7a88fae1f28e70738ac0b1f":76,"lzjd:37:3aca6111ee8dc822e0e920a6a141ca85":78,"lzjd:35:d83c823cfbf185b907e58cd36375e488":75,"lzjd:47:0fa2ce45986386ef690ca05e11b6f00d":76}} +{"tlsh":"lzjd:27:5d947215c7a88fae1f28e70738ac0b1f","similarity_hash":"lzjd:27:5d947215c7a88fae1f28e70738ac0b1f","tags":["sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_0","sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_6","sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_11"],"sigma_rule_matches":["Suspicious Shell Commands"],"sigma_rule_ids":["d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff"],"xxh3_64_sum":"66ff494dd06cd34","tlsh_similarities":{"lzjd:37:3aca6111ee8dc822e0e920a6a141ca85":77,"lzjd:35:d83c823cfbf185b907e58cd36375e488":81,"lzjd:47:0fa2ce45986386ef690ca05e11b6f00d":79}} +{"tlsh":"lzjd:47:0fa2ce45986386ef690ca05e11b6f00d","similarity_hash":"lzjd:47:0fa2ce45986386ef690ca05e11b6f00d","tags":["sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_3","sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_6"],"sigma_rule_matches":["Suspicious Shell Commands"],"sigma_rule_ids":["d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff"],"xxh3_64_sum":"2300691bae0b63a8","tlsh_similarities":{}} +{"tlsh":"lzjd:37:3aca6111ee8dc822e0e920a6a141ca85","similarity_hash":"lzjd:37:3aca6111ee8dc822e0e920a6a141ca85","tags":["sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_18"],"sigma_rule_matches":["Suspicious Shell Commands"],"sigma_rule_ids":["d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff"],"xxh3_64_sum":"655dee9b52a62a6c","tlsh_similarities":{"lzjd:35:d83c823cfbf185b907e58cd36375e488":78,"lzjd:47:0fa2ce45986386ef690ca05e11b6f00d":78}} +{"tlsh":"lzjd:35:d83c823cfbf185b907e58cd36375e488","similarity_hash":"lzjd:35:d83c823cfbf185b907e58cd36375e488","tags":["sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_4"],"sigma_rule_matches":["Suspicious Shell Commands"],"sigma_rule_ids":["d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff"],"xxh3_64_sum":"be18a7745de37edf","tlsh_similarities":{"lzjd:47:0fa2ce45986386ef690ca05e11b6f00d":78}} diff --git a/site/data/sigma_triage.stderr b/site/data/sigma_triage.stderr new file mode 100644 index 0000000..6e5124e --- /dev/null +++ b/site/data/sigma_triage.stderr @@ -0,0 +1,142 @@ +{ + "---PRECURSOR_PROTOCOL_HINTS---": "Candidate payload clusters for LLM-guided protocol discovery.", + "SimilarityMode": "lzjd", + "DistanceThreshold": 100, + "Candidates": [ + { + "xxh3_64_sum": "57101f1f1e986f6e", + "similarity_hash": "lzjd:30:84bdaa48877d88bfe2a48fbe356e7a78", + "neighbor_count": 4, + "tags": [ + "sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_10" + ], + "protocol_label": null, + "protocol_confidence": null, + "protocol_abstained": null + }, + { + "xxh3_64_sum": "66ff494dd06cd34", + "similarity_hash": "lzjd:27:5d947215c7a88fae1f28e70738ac0b1f", + "neighbor_count": 3, + "tags": [ + "sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_0", + "sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_6", + "sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_11" + ], + "protocol_label": null, + "protocol_confidence": null, + "protocol_abstained": null + }, + { + "xxh3_64_sum": "655dee9b52a62a6c", + "similarity_hash": "lzjd:37:3aca6111ee8dc822e0e920a6a141ca85", + "neighbor_count": 2, + "tags": [ + "sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_18" + ], + "protocol_label": null, + "protocol_confidence": null, + "protocol_abstained": null + }, + { + "xxh3_64_sum": "be18a7745de37edf", + "similarity_hash": "lzjd:35:d83c823cfbf185b907e58cd36375e488", + "neighbor_count": 1, + "tags": [ + "sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_4" + ], + "protocol_label": null, + "protocol_confidence": null, + "protocol_abstained": null + }, + { + "xxh3_64_sum": "2300691bae0b63a8", + "similarity_hash": "lzjd:47:0fa2ce45986386ef690ca05e11b6f00d", + "neighbor_count": 0, + "tags": [ + "sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_3", + "sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_6" + ], + "protocol_label": null, + "protocol_confidence": null, + "protocol_abstained": null + } + ] +} +{ + "---PRECURSOR_STATISTICS---": "This JSON is output to STDERR so that you can parse stats separate from the primary output.", + "Input": { + "Count": 6, + "Unique": 6, + "AvgSize": "46", + "MinSize": 32, + "MaxSize": 73, + "P95Size": 46, + "TotalSize": "277B" + }, + "Match": { + "Patterns": 19, + "TotalMatches": 8, + "Matches": [ + { + "Name": "sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_18", + "Matches": 1 + }, + { + "Name": "sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_4", + "Matches": 1 + }, + { + "Name": "sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_6", + "Matches": 2 + }, + { + "Name": "sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_10", + "Matches": 1 + }, + { + "Name": "sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_3", + "Matches": 1 + }, + { + "Name": "sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_0", + "Matches": 1 + }, + { + "Name": "sigma_d2bd6b47_0fe3_4eec_846d_6d657c8ee6ff_keywords_11", + "Matches": 1 + } + ], + "HashesGenerated": 5, + "AvgSize": "50", + "MinSize": 39, + "MaxSize": 73, + "P95Size": 73, + "TotalSize": "396B" + }, + "Compare": { + "Similarities": 10, + "AvgDistance": "78", + "MinDistance": 75, + "MaxDistance": 81, + "P95Distance": 79 + }, + "Environment": { + "Version": "0.2.1", + "DurationSeconds": "0.00", + "ProcessingRate": "277B/ms", + "SimilarityMode": "lzjd", + "RegexEngine": "pcre2", + "InputMode": "string", + "HashFunction": "48_1", + "DistanceThreshold": 100, + "DiffEnabled": true, + "OnlyOutputSimilar": false, + "LengthEnabled": false, + "InputJSONKey": "", + "SinglePacketInference": false, + "AbstainThreshold": 0.65, + "ProtocolTopK": 3, + "SigmaRulesLoaded": 1 + } +} diff --git a/site/favicon.svg b/site/favicon.svg new file mode 100644 index 0000000..4539bf7 --- /dev/null +++ b/site/favicon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/site/index.html b/site/index.html index c0a8aad..30b4bbe 100644 --- a/site/index.html +++ b/site/index.html @@ -6,196 +6,262 @@ Precursor | Pre-Protocol Similarity Triage + content="Precursor turns raw packets, logs, and binary blobs into labels, similarity clusters, and analyst-ready triage output." /> + -
-
-
-

precursor.hashdb.io

-

Pre-Protocol Payload Triage for Packets, Logs, and Firmware Fragments

-

- Precursor tags payloads with named captures, clusters near-matches with - TLSH/LZJD/FBHash (and optional MRSHv2 adapter mode), and emits JSON designed - for SOC pipelines and LLM-guided protocol discovery loops. -

-
- GitHub - Try Demo Scenarios + +
+ + Precursor logo + + Precursor + pre-protocol payload triage + + + +
+ +
+
+
+

precursor.hashdb.io

+

Find structure in raw payloads before protocol parsers exist.

+

+ Precursor tags, clusters, and prioritizes packet, log, and firmware payloads with one JSON-first CLI. + It is built for early-stage triage where you need signal now, not after parser engineering. +

+ +
-
- -
-
-

Dual Input Shapes

-

Text/base64/hex plus raw-binary blob support via -B.

-
-
-

Similarity Modes

-

TLSH + LZJD + FBHash implemented, MRSHv2 available behind native adapter feature.

-
-
-

Detection Inputs

-

PCRE files, Sigma keyword YAML, or inline patterns feed the same triage pipeline.

-
+
-
-
-

Why Install Precursor

+
+
+

Why Teams Use It

- One command turns opaque payload streams into tags, clusters, and - protocol confidence output you can act on immediately. + Precursor is the bridge between "we have weird bytes" and "we can confidently scope response or build deeper detection."

-
-
-

Input stream

-
- GET /admin HTTP/1.1 - 16 03 03 ... - 00 01 00 00 00 06 11 03 ... -
+
+
+

Security Ops

+

+ Collapse noisy probe traffic into families and rank suspicious payload clusters before writing full parser logic. +

- -
-

Tag + similarity

-
    -
  • tags: ["http_method"]
  • -
  • similarity_hash: "lzjd:..."
  • -
  • tlsh_similarities: {...}
  • -
+
+

DFIR + Firmware

+

+ Tag binary fragments and filesystem magic quickly so analysts can prioritize which artifacts deserve deeper reverse engineering. +

- -
-

Actionable triage

-
-
- http - 0.93 -
-
- tls - 0.90 -
-
- firmware_binary - 0.86 -
-
+
+

Detection Engineering

+

+ Start with Sigma/PCRE labeling, then use similarity + stats to refine into stable, explainable signal. +

-
-
- Sample JSON line -
-
{"protocol_label":"http","protocol_confidence":0.93,"similarity_hash":"lzjd:128:...","tags":["http_method"]}
+
+ +
+
+

Workflow at a Glance

+
    +
  1. + Ingest + String, base64, hex, or raw binary blobs from stdin or folders. +
  2. +
  3. + Label + Named PCRE captures (or Sigma selectors) become portable tags. +
  4. +
  5. + Cluster + TLSH, LZJD, FBHash (optional MRSHv2 adapter) group near-neighbor payloads. +
  6. +
  7. + Refine + `--stats` and protocol hints feed analyst/LLM refinement loops. +
  8. +
-
-
-

Scenario Demos

-

These examples map directly to files committed under samples/scenarios/.

+
+
+

Interactive Scenario Explorer

+

+ Click a scenario to inspect command, real output, and run stats captured from public sample corpora. +

- -
-

-

-
-
- Command - -
-
-
-
-
- Expected signal +
+ +
+

+

+
+ +
+ +
+
+ + +
+
-
-
-
+ + + +
-
-
-

Mini Demo Reels

-

Short terminal playbacks built from real scenario output snapshots.

+
+
+

Analyst Refinement Loop

+

+ This visual shows how adding targeted tags changes measurable signal across iterations, not just intuition. +

+
+
+
+
+

+

+
+
+
+ Step Stats JSON (excerpt) +
+
+
+
- -
-
- - -
-
-
-
-
-

Stats Mode Explained

+
+
+

LLM-Guided Discovery Demo

- Run with --stats to emit a structured run summary on stderr while - keeping payload records on stdout. + Local Codex CLI consumed Precursor stats + sample NDJSON, proposed a new pattern, then we validated the command on the same corpus.

-
-
-

What You Get

-
    -
  • Input: volume and size profile of all processed payloads.
  • -
  • Match: pattern counts, hit totals, and hash generation counts.
  • -
  • Compare: distance summary when enough pairwise comparisons exist.
  • -
  • Environment: execution context, including similarity and regex mode.
  • -
-

- This makes it easy to add basic health checks, throughput baselines, and release regression - guardrails without parsing full result streams. -

+
+
+

Codex Suggestion

+

+
    +
    +
    + Suggested Pattern +
    +
    +
    +
    +
    + Suggested Command +
    +
    +
    -
    -

    Sample Snapshot

    -
    -
    -
    - Sample --stats JSON + +
    +

    Measured Outcome

    +
    +

    Risk:

    +
    +
    + Runtime Note
    -
    +
    -
    -

    High-Impact Use Cases

    -
    +
    +
    +

    How to Read --stats

    +
    +
    -

    Exploit Spray Triage

    -

    Cluster evasive probe families before parser development and identify repeated payload mutations quickly. +

    Input

    +

    + Track payload volume and size distribution so performance regressions or capture shifts are obvious.

    -

    Sigma-to-Stream Validation

    -

    Reuse Sigma keyword intent directly against shell/log streams to validate or tune detections.

    +

    Match

    +

    + See which tags dominate and whether new patterns add precision or just noise. +

    -

    Firmware + DNS Hunt Pivoting

    -

    Tag binary fragments and suspicious DNS query families in one JSON-first workflow.

    +

    Compare + Environment

    +

    + Validate similarity mode, distance behavior, and runtime context when sharing findings or benchmarking. +

    + +
    +
    +

    Install and Run in Under 60 Seconds

    +
    +
    +
    + Quick Start + +
    +
    cargo install precursor
    +cat samples/scenarios/pre-protocol-packet-triage/payloads.b64 \
    +  | precursor -p samples/scenarios/pre-protocol-packet-triage/patterns.pcre \
    +      -m base64 -t -d --similarity-mode lzjd -P --protocol-hints --stats
    +
    + +

    + Background inspiration and historical context: + blog post 1 + and + blog post 2. +

    +
    + + diff --git a/site/precursor-mark.svg b/site/precursor-mark.svg new file mode 100644 index 0000000..2df05ad --- /dev/null +++ b/site/precursor-mark.svg @@ -0,0 +1,30 @@ + + Precursor mark + Rounded square with packet flow lines and similarity nodes. + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/styles.css b/site/styles.css index ed52b58..b0a3d15 100644 --- a/site/styles.css +++ b/site/styles.css @@ -1,539 +1,639 @@ :root { - --paper: #f9f7ef; - --ink: #0f1f2f; - --muted: #4f5f6f; - --accent: #ff5f2e; - --accent-soft: #ffd67c; - --teal: #0f8f8f; - --card: rgba(255, 255, 255, 0.84); - --line: rgba(15, 31, 47, 0.14); + --bg-0: #070b12; + --bg-1: #0d1724; + --bg-2: #16283c; + --surface-0: rgba(13, 23, 36, 0.82); + --surface-1: rgba(18, 31, 48, 0.9); + --surface-2: rgba(25, 42, 64, 0.94); + --line: rgba(159, 195, 232, 0.24); + --line-strong: rgba(159, 195, 232, 0.4); + --text: #f1f6ff; + --muted: #acc0d8; + --accent: #3de0c1; + --accent-2: #ff8b3d; + --danger: #ff6f6f; + --shadow: 0 24px 64px rgba(0, 0, 0, 0.45); } * { box-sizing: border-box; } +html, body { margin: 0; - font-family: "Space Grotesk", sans-serif; - color: var(--ink); - background: var(--paper); + min-height: 100%; } -.bg-layer { +body { + font-family: "Sora", sans-serif; + color: var(--text); + background: radial-gradient(circle at 12% 5%, #173b57 0, transparent 36%), + radial-gradient(circle at 86% 0, #2e223d 0, transparent 42%), + linear-gradient(160deg, var(--bg-0), var(--bg-1) 45%, #0a111d 100%); + line-height: 1.55; +} + +.ambient { + pointer-events: none; position: fixed; inset: 0; z-index: -1; background: - radial-gradient(circle at 12% 12%, rgba(255, 214, 124, 0.45) 0, transparent 42%), - radial-gradient(circle at 86% 0%, rgba(15, 143, 143, 0.24) 0, transparent 38%), - linear-gradient(130deg, #fff9e9 0%, #f1f8f7 52%, #fdf1e6 100%); + radial-gradient(circle at 25% 72%, rgba(61, 224, 193, 0.12) 0, transparent 38%), + radial-gradient(circle at 86% 34%, rgba(255, 139, 61, 0.14) 0, transparent 30%), + radial-gradient(circle at 52% 12%, rgba(80, 156, 255, 0.08) 0, transparent 42%); } -main { - width: min(1080px, 92vw); - margin: 0 auto; - padding: 2.8rem 0 4.2rem; +.topbar { + width: min(1160px, 92vw); + margin: 1.2rem auto 0; + padding: 0.7rem 0.9rem; + border: 1px solid var(--line); + background: rgba(11, 19, 32, 0.75); + backdrop-filter: blur(8px); + border-radius: 16px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.8rem; } -.hero { - display: grid; - gap: 1rem; - padding: 2rem; - border: 1px solid var(--line); - border-radius: 28px; - background: var(--card); - backdrop-filter: blur(5px); - animation: rise-in 0.7s ease-out both; +.brand { + text-decoration: none; + color: var(--text); + display: inline-flex; + align-items: center; + gap: 0.7rem; } -.kicker { - margin: 0; - font-family: "IBM Plex Mono", monospace; - letter-spacing: 0.08em; - text-transform: uppercase; - color: var(--teal); +.brand span { + display: grid; } -h1 { - margin: 0; - font-size: clamp(1.9rem, 4.6vw, 3.15rem); - line-height: 1.07; +.brand strong { + font-size: 0.98rem; + line-height: 1; } -.hero-copy { - margin: 0; - max-width: 72ch; +.brand small { + font-size: 0.7rem; color: var(--muted); - font-size: 1.05rem; + letter-spacing: 0.04em; + text-transform: uppercase; } -.hero-cta { +.topbar nav { display: flex; flex-wrap: wrap; - gap: 0.75rem; + gap: 0.7rem; } -.hero-cta a { +.topbar nav a { + color: var(--muted); text-decoration: none; - color: var(--ink); - border: 1px solid var(--line); + font-size: 0.82rem; + border: 1px solid transparent; + padding: 0.34rem 0.62rem; border-radius: 999px; - padding: 0.54rem 1rem; - background: #fff; - transition: transform 0.2s ease, border-color 0.2s ease; + transition: border-color 0.2s ease, color 0.2s ease, transform 0.2s ease; } -.hero-cta a:hover { +.topbar nav a:hover { + color: var(--text); + border-color: var(--line-strong); transform: translateY(-1px); - border-color: var(--accent); } -.stats { - margin-top: 1.3rem; - display: grid; - gap: 0.9rem; - grid-template-columns: repeat(3, minmax(0, 1fr)); +main { + width: min(1160px, 92vw); + margin: 1.1rem auto 0; + padding-bottom: 3.3rem; } -.stats article { +section { + margin-top: 1.15rem; border: 1px solid var(--line); - border-radius: 20px; - background: rgba(255, 255, 255, 0.72); - padding: 1rem; - animation: rise-in 0.7s ease-out both; + background: var(--surface-0); + border-radius: 22px; + padding: 1.3rem; + box-shadow: var(--shadow); } -.stats h2 { +.section-head h2 { margin: 0; - font-size: 1rem; + font-size: clamp(1.2rem, 3vw, 1.85rem); + letter-spacing: -0.02em; } -.stats p { - margin: 0.4rem 0 0; +.section-head p { + margin: 0.46rem 0 0; color: var(--muted); - font-size: 0.95rem; + max-width: 74ch; } -.teaser { - border: 1px solid var(--line); - border-radius: 24px; - padding: 1rem; - background: rgba(255, 255, 255, 0.8); +.hero { + display: grid; + grid-template-columns: 1.2fr 1fr; + gap: 1rem; + align-items: stretch; } -.teaser-head h2 { +.eyebrow { margin: 0; - font-size: 1.4rem; + text-transform: uppercase; + letter-spacing: 0.08em; + font-size: 0.74rem; + color: var(--accent); } -.teaser-head p { - margin: 0.4rem 0 0; +.hero h1 { + margin: 0.35rem 0 0; + font-size: clamp(1.75rem, 5vw, 3.2rem); + line-height: 1.06; + letter-spacing: -0.04em; +} + +.hero-copy > p { + margin: 0.7rem 0 0; color: var(--muted); + max-width: 64ch; } -.teaser-stage { - margin-top: 0.9rem; - display: grid; - grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 1fr); - gap: 0.6rem; - align-items: stretch; +.hero-actions { + margin-top: 1rem; + display: flex; + flex-wrap: wrap; + gap: 0.62rem; } -.teaser-lane { - border: 1px solid var(--line); - border-radius: 16px; - background: rgba(255, 255, 255, 0.95); - padding: 0.75rem; +.button { + border: 1px solid var(--line-strong); + border-radius: 999px; + padding: 0.56rem 0.96rem; + text-decoration: none; + color: var(--text); + font-size: 0.82rem; + background: rgba(255, 255, 255, 0.04); + transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease; } -.teaser-lane h3 { - margin: 0; - font-size: 0.95rem; +.button.primary { + background: linear-gradient(120deg, rgba(61, 224, 193, 0.25), rgba(255, 139, 61, 0.24)); + border-color: rgba(61, 224, 193, 0.55); } -.teaser-arrow { - align-self: center; - font-family: "IBM Plex Mono", monospace; - color: var(--teal); - font-size: 1.1rem; +.button:hover { + transform: translateY(-1px); + border-color: var(--accent); } -.chip-stack { - margin-top: 0.55rem; +.hero-metrics { + margin-top: 1rem; display: grid; - gap: 0.42rem; + gap: 0.62rem; + grid-template-columns: repeat(3, minmax(0, 1fr)); } -.chip { - display: inline-flex; - width: fit-content; - max-width: 100%; - font-family: "IBM Plex Mono", monospace; - font-size: 0.72rem; - padding: 0.28rem 0.44rem; - border-radius: 9px; - border: 1px solid rgba(15, 31, 47, 0.15); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - animation: chip-drift 2.8s ease-in-out infinite; +.metric { + border: 1px solid var(--line); + border-radius: 14px; + background: var(--surface-1); + padding: 0.7rem; } -.chip-http { - background: rgba(255, 95, 46, 0.14); - animation-delay: 0s; +.metric strong { + display: block; + font-size: 0.9rem; } -.chip-tls { - background: rgba(15, 143, 143, 0.14); - animation-delay: 0.4s; +.metric small { + display: block; + margin-top: 0.22rem; + color: var(--muted); + font-size: 0.72rem; } -.chip-ics { - background: rgba(255, 214, 124, 0.35); - animation-delay: 0.8s; +.hero-terminal, +.terminal { + border: 1px solid var(--line-strong); + border-radius: 14px; + background: #050a12; + overflow: hidden; } -.teaser-list { - margin: 0.58rem 0 0; - padding-left: 1rem; - display: grid; - gap: 0.28rem; +.terminal-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.8rem; + padding: 0.5rem 0.72rem; + border-bottom: 1px solid rgba(173, 202, 233, 0.17); + background: #0b1421; + color: #d6e8ff; + font-family: "JetBrains Mono", monospace; + font-size: 0.72rem; + letter-spacing: 0.06em; + text-transform: uppercase; } -.teaser-list li { - color: var(--muted); - font-size: 0.85rem; +.terminal-head button { + border: 1px solid rgba(173, 202, 233, 0.36); + border-radius: 8px; + background: rgba(255, 255, 255, 0.04); + color: #d6e8ff; + font-family: "JetBrains Mono", monospace; + font-size: 0.69rem; + padding: 0.26rem 0.5rem; + cursor: pointer; } -.teaser-list code { - color: #1f2a35; - font-size: 0.76rem; +pre { + margin: 0; + max-height: 25rem; + overflow: auto; + padding: 0.9rem; } -.cluster-view { - margin-top: 0.58rem; +code { + font-family: "JetBrains Mono", monospace; + font-size: 0.78rem; + line-height: 1.5; + color: #cde7ff; + white-space: pre; +} + +.position-grid { + margin-top: 0.9rem; display: grid; - gap: 0.3rem; + gap: 0.75rem; + grid-template-columns: repeat(3, minmax(0, 1fr)); } -.cluster-row { - display: flex; - justify-content: space-between; - align-items: center; +.position-grid article { border: 1px solid var(--line); - border-radius: 10px; - padding: 0.34rem 0.5rem; - font-family: "IBM Plex Mono", monospace; - font-size: 0.78rem; - background: linear-gradient(90deg, rgba(15, 143, 143, 0.11), rgba(255, 214, 124, 0.14)); + border-radius: 16px; + background: var(--surface-1); + padding: 0.85rem; } -.teaser-terminal { - margin-top: 0.75rem; - border: 1px solid var(--line); - border-radius: 12px; - overflow: hidden; +.position-grid h3 { + margin: 0; + font-size: 1rem; } -.teaser-terminal pre { - background: #131b24; +.position-grid p { + margin: 0.5rem 0 0; + color: var(--muted); + font-size: 0.9rem; } -section { - margin-top: 1.9rem; +.flow-steps { + margin: 0.9rem 0 0; + padding: 0; + list-style: none; + display: grid; + gap: 0.6rem; + grid-template-columns: repeat(4, minmax(0, 1fr)); } -.demo-head h2, -.use-cases h2, -.deploy h2 { - margin: 0; - font-size: 1.4rem; +.flow-steps li { + border: 1px solid var(--line); + border-radius: 14px; + background: linear-gradient(150deg, rgba(61, 224, 193, 0.08), rgba(255, 139, 61, 0.08)); + padding: 0.72rem; } -.demo-head p { - margin: 0.45rem 0 0; +.flow-steps strong { + display: block; + font-size: 0.93rem; +} + +.flow-steps span { + margin-top: 0.3rem; + display: block; color: var(--muted); + font-size: 0.82rem; } -.scenario-tabs { - margin-top: 0.95rem; - display: flex; - flex-wrap: wrap; - gap: 0.65rem; +.scenario-layout { + margin-top: 0.9rem; + display: grid; + grid-template-columns: 320px minmax(0, 1fr); + gap: 0.8rem; } -.scenario-tabs button { +.scenario-list { + display: grid; + gap: 0.55rem; + align-content: start; +} + +.scenario-button { + text-align: left; border: 1px solid var(--line); - border-radius: 999px; - background: #fff; - font-family: "IBM Plex Mono", monospace; - font-size: 0.78rem; - padding: 0.42rem 0.78rem; - color: var(--ink); + background: var(--surface-1); + color: var(--text); + border-radius: 12px; + padding: 0.64rem; cursor: pointer; + transition: border-color 0.18s ease, transform 0.18s ease, background 0.18s ease; } -.scenario-tabs button[aria-selected="true"] { - border-color: var(--accent); - background: var(--accent-soft); +.scenario-button strong { + display: block; + font-size: 0.86rem; } -.scenario-panel { - margin-top: 0.9rem; +.scenario-button small { + display: block; + margin-top: 0.24rem; + color: var(--muted); + font-size: 0.72rem; +} + +.scenario-button[aria-selected="true"] { + border-color: rgba(61, 224, 193, 0.64); + background: linear-gradient(140deg, rgba(61, 224, 193, 0.15), rgba(255, 139, 61, 0.1)); +} + +.scenario-button:hover { + transform: translateY(-1px); + border-color: var(--line-strong); +} + +.scenario-view { border: 1px solid var(--line); - border-radius: 22px; - background: rgba(255, 255, 255, 0.82); - padding: 1rem; + border-radius: 18px; + background: var(--surface-1); + padding: 0.95rem; } -.scenario-panel h3 { +.scenario-view h3 { margin: 0; + font-size: 1.22rem; } -.scenario-panel p { - margin: 0.52rem 0 0.8rem; +.scenario-view p { + margin: 0.42rem 0 0; color: var(--muted); } -.reel-tabs { - margin-top: 0.95rem; +.meta { + margin-top: 0.65rem; display: flex; flex-wrap: wrap; - gap: 0.65rem; + gap: 0.42rem; } -.reel-tabs button { +.chip { border: 1px solid var(--line); border-radius: 999px; - background: #fff; - font-family: "IBM Plex Mono", monospace; - font-size: 0.78rem; - padding: 0.42rem 0.78rem; - color: var(--ink); - cursor: pointer; + padding: 0.22rem 0.52rem; + font-family: "JetBrains Mono", monospace; + font-size: 0.68rem; + color: #cbddf3; + background: rgba(255, 255, 255, 0.03); } -.reel-tabs button[aria-selected="true"] { - border-color: var(--teal); - background: rgba(15, 143, 143, 0.15); +.tabs { + margin-top: 0.72rem; + display: flex; + gap: 0.45rem; + flex-wrap: wrap; } -.reel-player { - margin-top: 0.9rem; +.tab { border: 1px solid var(--line); - border-radius: 22px; - background: rgba(255, 255, 255, 0.82); - padding: 1rem; + border-radius: 999px; + background: rgba(255, 255, 255, 0.03); + color: var(--text); + padding: 0.28rem 0.64rem; + font-family: "JetBrains Mono", monospace; + font-size: 0.72rem; + cursor: pointer; +} + +.tab[aria-selected="true"] { + border-color: rgba(61, 224, 193, 0.62); + background: rgba(61, 224, 193, 0.14); +} + +.data-links { + margin-top: 0.62rem; + font-size: 0.76rem; } -.reel-player pre { - min-height: 14rem; - max-height: 24rem; - background: #131b24; +.data-links a { + color: #7dd3ff; + text-decoration: none; } -.stats-mode-grid { - margin-top: 0.95rem; +.loop-layout { + margin-top: 0.9rem; display: grid; - gap: 0.9rem; - grid-template-columns: 1fr 1.3fr; + grid-template-columns: 320px minmax(0, 1fr); + gap: 0.8rem; } -.stats-legend, -.stats-viz { +.loop-steps { + display: grid; + gap: 0.52rem; +} + +.loop-step { border: 1px solid var(--line); - border-radius: 16px; - background: rgba(255, 255, 255, 0.84); - padding: 0.85rem; + background: var(--surface-1); + border-radius: 12px; + padding: 0.62rem; + cursor: pointer; } -.stats-legend h3, -.stats-viz h3 { - margin: 0; - font-size: 1rem; +.loop-step[aria-selected="true"] { + border-color: rgba(255, 139, 61, 0.7); + background: linear-gradient(140deg, rgba(255, 139, 61, 0.17), rgba(61, 224, 193, 0.08)); } -.stats-legend ul { - margin: 0.6rem 0 0; - padding-left: 1rem; - display: grid; - gap: 0.4rem; +.loop-step strong { + display: block; + font-size: 0.88rem; +} + +.loop-step small { + display: block; + margin-top: 0.24rem; color: var(--muted); + font-family: "JetBrains Mono", monospace; + font-size: 0.7rem; } -.stats-legend li { - font-size: 0.9rem; +.loop-detail { + border: 1px solid var(--line); + border-radius: 18px; + background: var(--surface-1); + padding: 0.95rem; } -.stats-legend code { - color: #1f2a35; - font-size: 0.78rem; +.loop-detail h3 { + margin: 0; } -.stats-note { - margin: 0.75rem 0 0; +.loop-detail p { + margin: 0.5rem 0 0; color: var(--muted); - font-size: 0.9rem; } -.stats-bars { - margin-top: 0.7rem; +.loop-bars { + margin-top: 0.75rem; display: grid; - gap: 0.5rem; + gap: 0.46rem; } -.stats-bar { +.bar-row { display: grid; - gap: 0.28rem; + gap: 0.26rem; } -.stats-bar-head { +.bar-head { display: flex; justify-content: space-between; - font-family: "IBM Plex Mono", monospace; - font-size: 0.74rem; - color: var(--ink); + font-family: "JetBrains Mono", monospace; + font-size: 0.72rem; } -.stats-bar-track { - width: 100%; - height: 8px; - border-radius: 999px; +.bar-track { border: 1px solid var(--line); - background: rgba(15, 31, 47, 0.06); + border-radius: 999px; + height: 8px; overflow: hidden; + background: rgba(255, 255, 255, 0.05); } -.stats-bar-fill { +.bar-fill { height: 100%; - border-radius: 999px; - background: linear-gradient(90deg, var(--teal), var(--accent)); + border-radius: inherit; + background: linear-gradient(90deg, var(--accent), var(--accent-2)); } -.code-wrap { - margin-top: 0.8rem; - border: 1px solid var(--line); - border-radius: 14px; - background: #131b24; - overflow: hidden; +.terminal.compact pre { + max-height: 17rem; } -.code-head { - display: flex; - align-items: center; - justify-content: space-between; - padding: 0.5rem 0.7rem; - background: #1f2a35; - color: #d9e5f2; - font-family: "IBM Plex Mono", monospace; - font-size: 0.72rem; - letter-spacing: 0.08em; - text-transform: uppercase; +.llm-grid { + margin-top: 0.9rem; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.8rem; } -.code-head button { - border: 1px solid #395167; - border-radius: 8px; - background: #111821; - color: #d9e5f2; - font-family: "IBM Plex Mono", monospace; - font-size: 0.72rem; - padding: 0.3rem 0.52rem; - cursor: pointer; +.llm-card { + border: 1px solid var(--line); + border-radius: 16px; + background: var(--surface-1); + padding: 0.85rem; } -pre { +.llm-card h3 { margin: 0; - padding: 0.88rem 0.95rem; - overflow: auto; } -code { - font-family: "IBM Plex Mono", monospace; - color: #d9e5f2; - font-size: 0.84rem; - line-height: 1.45; +.llm-card p, +.llm-card li { + color: var(--muted); + font-size: 0.88rem; +} + +.llm-card ul { + margin: 0.55rem 0 0; + padding-left: 1rem; } -.cards { - margin-top: 0.95rem; +.delta-grid { + margin-top: 0.52rem; display: grid; - gap: 0.9rem; - grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.5rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); } -.cards article { +.delta { border: 1px solid var(--line); - border-radius: 16px; - background: rgba(255, 255, 255, 0.84); - padding: 0.85rem; + border-radius: 12px; + background: rgba(255, 255, 255, 0.03); + padding: 0.55rem; } -.cards h3 { - margin: 0; - font-size: 1rem; +.delta strong { + display: block; + font-size: 0.84rem; } -.cards p { - margin: 0.4rem 0 0; +.delta span { color: var(--muted); - font-size: 0.95rem; + font-family: "JetBrains Mono", monospace; + font-size: 0.72rem; } -.deploy ol { - margin: 0.85rem 0 0; - padding-left: 1.2rem; +.risk { + margin-top: 0.7rem; +} + +.install .terminal { + margin-top: 0.8rem; +} + +.sources { + margin-top: 0.8rem; + font-size: 0.84rem; color: var(--muted); - display: grid; - gap: 0.35rem; } -.deploy code { - color: #f5bd7d; +.sources a { + color: #7dd3ff; + text-decoration: none; } -@keyframes rise-in { - from { - transform: translateY(10px); - opacity: 0; - } - to { - transform: translateY(0); - opacity: 1; - } +footer { + width: min(1160px, 92vw); + margin: 0 auto 1.7rem; + color: var(--muted); + font-size: 0.78rem; + text-align: center; } -@keyframes chip-drift { - 0% { - transform: translateX(0); - } - 50% { - transform: translateX(5px); +@media (max-width: 1050px) { + .hero, + .scenario-layout, + .loop-layout, + .llm-grid { + grid-template-columns: 1fr; } - 100% { - transform: translateX(0); + + .scenario-list, + .loop-steps { + grid-template-columns: repeat(2, minmax(0, 1fr)); } } -@media (max-width: 900px) { - .stats, - .cards, - .stats-mode-grid { - grid-template-columns: 1fr; +@media (max-width: 760px) { + .topbar { + flex-direction: column; + align-items: flex-start; } - .teaser-stage { + .hero-metrics, + .position-grid, + .flow-steps, + .delta-grid, + .scenario-list, + .loop-steps { grid-template-columns: 1fr; } - .teaser-arrow { - transform: rotate(90deg); - justify-self: center; + section { + padding: 1rem; } - main { - padding-top: 1.3rem; + .topbar nav { + width: 100%; } }