8.3 KiB
Executable File
CorbelPurge
Strict Rust document sanitizer & threat neutralizer for PDF, EPUB, Markdown, and DOCX.
Author: Jeremy Anderson — https://git.dcos.net/dcosnet/corbel License: GPL-3.0-or-later
CorbelPurge parses documents into a unified intermediate representation (Document struct), runs a layered contextual scanner that distinguishes educational security literature from active malicious injections, and produces cleansed derivatives with all executable content stripped. Malicious payloads are carved into quarantine tarballs with full forensic reports.
What it does
Given a file path, Pipeline::run() in src/core/pipeline.rs:
- Detects format via
DocumentFormat::from_path()(extension-based:.pdf,.epub,.md/.markdown,.docx). - Parses via
parsers::Dispatcherinto aDocumentcontainingTextNode(static text with semantic context) andExecutableVector(active content like JS streams, embedded files, script tags, VBA macros) items. - Scans with a two-pass engine:
heuristics::inspect_vector()classifies every executable vector against file-signature tables, shellcode patterns, phishing heuristics, and URI allowlists.context_filter::evaluate()checks text nodes for suspicious signatures and determines whether the surrounding context is educational (code blocks, CVE writeups, academic language) or weaponized.cve_tags::match_cve()annotates findings with known exploit IDs.
- Quarantines (when malicious findings exist) —
quarantine::handle()carves payloads intoquarantine_<ts>_<sha>.tar.gzwithoriginal.<ext>,report.json,report.md, and one.binper payload. - Cleanses (when recommended) — produces a sanitized derivative:
- Markdown mode (default):
cleanse::sanitizer::sanitize()emits a safe Markdown file. Text nodes at malicious locations are stripped; hyperlinks lose their destinations. - PreserveFormat mode (
--preserve-format):cleanse::repackage::repackage()rebuilds the original format with malicious entries removed. EPUB entries are stripped from the ZIP, DOCX macros/embeddings/external-links are removed, PDF objects are deleted via lopdf.
- Markdown mode (default):
Supported formats: PDF, EPUB, Markdown, DOCX.
Build
# Headless CLI (default — no GUI deps)
cargo build --release
# With iced GUI
cargo build --release --features gui --bin corbel-purge-gui
Requires Rust 1.70+ (stable).
Binaries:
target/release/corbel-purge— headless CLItarget/release/corbel-purge-gui— iced dashboard GUI (guifeature)
CLI usage
The CLI is in src/main.rs and parses its own arguments (no clap dependency).
# Scan a single file
corbel-purge scan path/to/suspicious.pdf
# Scan with format-preserving output (keeps original format)
corbel-purge scan path/to/book.epub --preserve-format
# Scan with a custom workspace
corbel-purge scan path/to/file.pdf --workspace /tmp/corbel
# Scan a directory recursively
corbel-purge scan-dir path/to/inbox --recursive --workspace /tmp/corbel
# CI gate: exit non-zero on threat
corbel-purge scan path/to/file.pdf --abort-on-threat
# Quiet mode: print only the JSON report path
corbel-purge scan path/to/file.pdf --quiet
# Version
corbel-purge --version
Exit codes
| Code | Meaning |
|---|---|
| 0 | No threats found |
| 1 | Hard error (parse failure, IO, etc.) |
| 2 | One or more malicious findings |
Environment variables
| Variable | Default | Purpose |
|---|---|---|
CORBEL_QUARANTINE_DIR |
./corbel_quarantine |
Quarantine tarball output dir |
CORBEL_CLEANSE_DIR |
./corbel_clean |
Cleansed document output dir |
CORBEL_ABORT_ON_THREAT |
false |
Abort on first malicious finding |
CORBEL_EMIT_SUSPICIOUS |
true |
Include Suspicious findings in report |
CORBEL_EMIT_MARKDOWN_REPORT |
true |
Generate Markdown report alongside JSON |
Library usage
use corbel_purge::{Pipeline, Config, CleanseMode};
// Scan a file on disk
let pipeline = Pipeline::with_config(Config::with_workspace("/tmp/output"));
let result = pipeline.run("suspicious.pdf")?;
println!("malicious: {}", result.scan_report.malicious_count());
// Scan in-memory bytes (e.g. from an email gateway)
let bytes = std::fs::read("upload.docx")?;
let result = pipeline.run_on_bytes(
bytes,
corbel_purge::DocumentFormat::Docx,
Some("upload.docx".into()),
)?;
// PreserveFormat mode
let mut config = Config::default();
config.cleanse_mode = CleanseMode::PreserveFormat;
let pipeline = Pipeline::with_config(config);
let result = pipeline.run("book.epub")?;
Source layout
src/
├── main.rs # CLI: scan, scan-dir, --version, arg parsing
├── lib.rs # Crate root, CorbelError enum, public re-exports
├── util.rs # sha256_hex(), truncate_with_ellipsis(), read_with_cap()
├── bin/
│ └── gui.rs # iced 0.13 dashboard GUI (gui feature)
├── core/
│ ├── types.rs # Document, TextNode, ExecutableVector, Finding, ScanReport,
│ │ # Location, TextContext, VectorType, ThreatClassification,
│ │ # MaliciousType, Recommendation, DocumentMetadata
│ ├── config.rs # Config, CleanseMode, env overrides, workspace dirs
│ └── pipeline.rs # Pipeline, PipelineResult, DocumentFormat::from_path()
├── parsers/
│ ├── mod.rs # DocumentParser trait, Dispatcher
│ ├── pdf_parser.rs # lopdf-based PDF structure walker
│ ├── epub_parser.rs # ZIP-based EPUB container inspector
│ ├── md_parser.rs # pulldown-cmark text extractor
│ └── docx_parser.rs # OOXML ZIP inspector (regex XML extraction)
├── scanner/
│ ├── mod.rs # scan() entrypoint, CVE tag injection
│ ├── heuristics.rs # classify_vector() for executable vectors
│ ├── context_filter.rs # evaluate() for text nodes, educational vs weaponized
│ ├── signatures.rs # PHISHING_TLDS, KNOWN_FILE_SIGNATURES,
│ │ # SHELLCODE_PATTERNS, COMMON_PHISHING_BRANDS,
│ │ # SUSPICIOUS_URL_KEYWORDS, URL_SHORTENER_DOMAINS
│ └── cve_tags.rs # CVE_TABLE (7 entries), match_cve(), cve_tag()
├── quarantine/
│ ├── mod.rs # handle() — tarball writer, QuarantineOutcome
│ ├── extractor.rs # extract_payloads(), ExtractedPayload, filename sanitization
│ └── reporter.rs # build_json_report(), build_markdown_report()
├── cleanse/
│ ├── mod.rs # cleanse() dispatcher (Markdown vs PreserveFormat)
│ ├── sanitizer.rs # sanitize() — Markdown re-serializer, build_clean_markdown()
│ └── repackage.rs # repackage() — format-preserving (EPUB/DOCX/PDF)
└── ui/
├── mod.rs # GUI module gate (behind `gui` feature)
├── viewer.rs # Stub
└── alert_modal.rs # Stub
Testing
# Regenerate test fixtures (requires pypdf, reportlab, python-docx)
pip install pypdf reportlab python-docx
python3 scripts/gen_fixtures.py
python3 scripts/gen_md_epub_fixtures.py
python3 scripts/gen_docx_fixtures.py
python3 scripts/gen_zip_bomb_fixtures.py
# Run all tests
cargo test
Adding a new format
- Create
src/parsers/<format>_parser.rsimplementingDocumentParser. - Add a variant to
DocumentFormatinsrc/core/types.rs(and itsDisplayimpl). - Add a match arm in
Dispatcher::parse()insrc/parsers/mod.rs. - Add an extension check in
DocumentFormat::from_path()insrc/core/pipeline.rs. - Add repackage support in
src/cleanse/repackage.rs(optional).
The scanner, quarantine, and cleanse modules consume only the Document UIR and require no changes for basic format support.
Non-goals
- No document editing or annotation.
- No execution of embedded scripting (PDF JS, EPUB scripts, VBA macros).
- No network access during scanning — all analysis is local.
- No sandbox hardening of the tool itself.
License
GPL-3.0-or-later. Copyright (c) 2026 Jeremy Anderson. https://git.dcos.net/dcosnet/corbel
