# Project Manifest: CorbelPurge (v0.3.0) ## Strict Rust Secure Document Sanitizer & Threat Neutralizer **Author:** Jeremy Anderson — https://git.dcos.net/dcosnet/corbel **License:** GPL-3.0-or-later **Status:** v0.2.0 MVP shipped. v0.3.0 planning: study mode, payload carving v2, scanner extensibility, GUI navigation improvements. --- ## 1. Executive Summary CorbelPurge is a high-assurance document sanitizer written in strict Rust (`#![forbid(unsafe_code)]`) for **PDF, EPUB, Markdown, and DOCX** formats. It parses documents into a unified intermediate representation, runs a layered contextual scanner that distinguishes educational security literature from active malicious injections, then produces cleansed derivatives with all executable content stripped. Malicious payloads are carved into quarantine tarballs with full forensic reports. **Threat model:** CorbelPurge is a researcher's tool for studying old exploits, not a target itself. The scanner safely ingests adversarial samples (zip bombs, malicious docs from malware corpora) without choking, so the researcher can study them. The tool itself is assumed to not be the target. --- ## 2. Core Specifications & Non-Goals ### Core Features - **Multi-Format Support:** Native parsing pipelines for PDF (`lopdf`), EPUB (`zip`), Markdown (`pulldown-cmark`), DOCX (`zip` + XML regex). - **Unified Intermediate Representation:** All parsers emit the same `Document` struct (`src/core/types.rs`). Downstream modules never touch format-specific structures. - **Automated Threat Scanning:** Two-pass engine — executable vectors go through `heuristics::classify_vector()`, text nodes go through `context_filter::evaluate()`, then findings are annotated by `cve_tags::match_cve()`. - **Context-Aware Heuristics:** Differentiation between static text content (including code blocks discussing vulnerabilities) and executable/malicious injection vectors. Academic markers (CVE IDs, "remediation", code-block context) whitelist educational content. - **Weaponization Detection:** Long hex-encoded runs (16+ `\xNN`), 64+ char base64 blobs, NOP sleds, Metasploit stager prologues are flagged as malicious regardless of context. - **CVE Tagging:** 7 known exploits (CVE-2010-0188, CVE-2018-4990, CVE-2017-11882, CVE-2018-0802, CVE-2017-8570, CVE-2017-0199, EPUB-SCRIPT-INJECTION) with byte-signature and vector-type matching. - **Forensic Extraction & Reporting:** JSON + Markdown reports with SHA-256, vector location, classification, CVE tag, context notes. Payload carving into standalone `.bin` files inside quarantine tarballs. - **Secure Quarantine:** Compressed `quarantine__.tar.gz` containing original file, report, and extracted payloads. - **Document Cleansing (two modes):** - *Markdown mode* (default): produces a safe Markdown derivative via `cleanse::sanitizer::sanitize()`. A Markdown file cannot carry executable content by definition. - *PreserveFormat mode* (`--preserve-format`): repackages the document in its original format (PDF/EPUB/DOCX) with malicious entries stripped via `cleanse::repackage::repackage()`. - **Zip-Bomb Defense:** `util::read_with_cap()` counts actual decompressed bytes rather than trusting ZIP central directory size headers. Configurable via `Config::epub_entry_scan_cap` (default 8 MiB). - **Optional GUI:** iced 0.13 dashboard (`src/bin/gui.rs`) with dark theme, collapsible panels, console log, sidebar stats, file picker (`rfd::AsyncFileDialog`). - **No-arg-parsing-crates:** The CLI (`src/main.rs`) parses its own arguments to keep the dependency tree minimal — a defensive measure for security-critical software. ### Explicit Non-Goals - No document editing, annotation writing, form-filling, or signature generation. - No execution of embedded active scripting (PDF JS, EPUB scripts, VBA macros). - No sandbox hardening or fuzzing of the tool itself (it is not the target). - No bundled historical exploit corpus (too risky to ship). - No magic-byte sniffing for format detection (extension-only is the safe failure mode — a mismatched extension causes a parse error). --- ## 3. System Architecture & Tech Stack CorbelPurge uses safe Rust primitives, relying on memory-safe parsers to avoid buffer overflow vulnerabilities common in legacy C/C++ document viewers. The `#![forbid(unsafe_code)]` crate attribute is enforced at compile time. ### Dependency Table | Purpose | Crate | Version | |---------|-------|---------| | PDF structure inspection | `lopdf` | 0.34 | | EPUB / DOCX containers | `zip` | 2 | | Markdown parsing | `pulldown-cmark` | 0.12 | | Quarantine packaging | `tar` + `flate2` | 0.4 / 1 | | Serialization | `serde` + `serde_json` | 1 | | Hashing | `sha2` + `hex` | 0.10 / 0.4 | | Datetime | `chrono` | 0.4 | | Context filter | `regex` | 1 | | Error handling | `thiserror` | 2 | | GUI (optional) | `iced` | 0.13 | | File picker (optional) | `rfd` | 0.15 | | Async runtime (optional) | `tokio` | 1 | | Test utilities | `tempfile` + `pretty_assertions` | 3 / 1 | ### Feature Flags - `default` — headless CLI + library only. No GUI dependencies. - `gui` — adds the iced GUI binary (`corbel-purge-gui`) and depends on `iced`, `rfd`, `tokio`. --- ## 4. Unified Intermediate Representation Every parser emits the same [`Document`](src/core/types.rs) struct. The scanner, quarantine, and cleanse modules consume only this UIR — they never touch format-specific structures. Adding a new format requires writing exactly one new `DocumentParser` impl and adding a match arm in `Dispatcher::parse()` (`src/parsers/mod.rs`). Key types defined in `src/core/types.rs`: - **`DocumentFormat`** — `Pdf`, `Epub`, `Markdown`, `Docx`. Detected by file extension via `DocumentFormat::from_path()` in `pipeline.rs`. - **`Location`** — per-format source location (PDF object/stream IDs, EPUB ZIP entry paths, Markdown line:col). Used in findings to report exactly where a threat was found. - **`TextContext`** — semantic context of extracted text (`Paragraph`, `Heading`, `CodeBlock`, `CodeSpan`, `Hyperlink`, `BlockQuote`, `Metadata`, `ExecutableHook`). The heart of the context-aware scanner. - **`TextNode`** — extracted text with location + context. Emitted by parsers for all rendered/static content. - **`ExecutableVector`** — a potentially dangerous element (JS action, embedded file, script tag, OLE object, external link) with raw bytes and optional decoded preview. - **`VectorType`** — 15 variants covering PDF (JS, Launch, URI, GoToR, EmbeddedFile, WidgetAction, AcroForm), EPUB (Script, ExternalResource, Object), Markdown (Hyperlink), DOCX (Macro, ExternalLink, EmbeddedObject, ActiveX), plus UnknownPayload. - **`Document`** — the top-level IR: format, raw bytes, SHA-256, metadata, text nodes, executable vectors. - **`ThreatClassification`** — `Benign`, `Suspicious`, `EducationalContent`, `Malicious(MaliciousType)`. - **`MaliciousType`** — 8 specific categories (ActiveJavaScriptInjection, LaunchAction, MaliciousEmbeddedFile, ObfuscatedShellcode, SuspiciousUri, EpubActiveScript, DocxActiveContent, Other). - **`Finding`** — classification, location, vector type, payload preview, context notes, recommendation. - **`Recommendation`** — `Allow`, `WhitelistAsEducational`, `Quarantine`, `QuarantineAndCleanse`. - **`ScanReport`** — aggregate of all findings with counts and metadata. - **`CorbelError`** — exhaustive error enum (Io, Serde, PdfParse, EpubParse, MarkdownParse, UnknownFormat, ThreatDetected, Quarantine, Cleanse, Internal). --- ## 5. Security Scanning Pipeline The pipeline is orchestrated by `Pipeline::run()` in `src/core/pipeline.rs`. Given an input path, it: reads bytes, detects format, dispatches to the correct parser, runs the scanner, then conditionally quarantines and cleanses. ### Step 0: Weaponization Check `context_filter::has_weaponization_indicators()` runs first on every text node. Pure obfuscation indicators (16+ `\xNN` runs, 64+ char base64 blobs, 2+ shell commands in non-code context) are flagged as `ObfuscatedShellcode` regardless of surrounding context. Code blocks and block quotes get an exception — weaponized-looking content there is almost certainly a research writeup. ### Step 1: Structural vs. Textual Isolation The scanner in `scanner::scan()` walks two separate lists: - **Executable Vectors** (untrusted by default) — dispatched to `heuristics::inspect_vector()` which calls `classify_vector()`. This function matches against `signatures.rs` tables (file signatures, shellcode patterns, phishing TLDs, brand homographs, URL shorteners, suspicious keywords) and the `Config::allowed_uri_schemes` whitelist. - **Static Text Nodes** (context-dependent) — dispatched to `context_filter::evaluate()` which looks for suspicious signatures (`/JavaScript`, `eval(`, `shellcode`, `exploit`, etc.) and then checks whether the surrounding context is educational. ### Step 2: Context-Aware Filtering `context_filter::looks_educational()` checks for: - **Structural signals:** code blocks, code spans, block quotes — always educational. - **Lexical signals:** presence of academic markers ("CVE-", "vulnerability", "remediation", "patch", "mitigation", "advisory", "for example", "proof of concept", etc.) **Decision:** A signature in an `ExecutableHook` context is flagged as malicious immediately. A signature in a code block describing a CVE with remediation steps is whitelisted as `EducationalContent`. ### Step 3: CVE Tagging After classification, `scanner::scan()` calls `cve_tags::match_cve()` for each finding. If a CVE matches, the ID and name are appended to the finding's `context_notes` field. The forensic reporter (`reporter.rs`) includes the full CVE description. | CVE ID | Name | Detection Signal | |--------|------|------------------| | CVE-2017-11882 | Equation Editor RCE | DOCX OLE + "Equation" in payload | | CVE-2018-0802 | Equation Editor RCE (variant) | DOCX OLE + PE signature, no "Equation" | | CVE-2017-8570 | Office RTF OLE Object RCE | DOCX OLE + RTF magic bytes | | CVE-2017-0199 | Office OLE2Link RCE | DOCX external link + `.hta` target | | CVE-2018-4990 | Adobe Reader JS RCE | PDF JS action >500 bytes | | CVE-2010-0188 | PDF LibTiff Buffer Overflow | PDF embedded file + TIFF magic | | EPUB-SCRIPT-INJECTION | EPUB Active Script Injection | EPUB `