Skip to content
 
 

Repository files navigation

DeckProbe

ffprobe for PDF, Microsoft Office, and Apple iWork documents.

Ask for the facts you need. Get structured JSON with confidence, evidence, and measured I/O cost.

CI License: MIT Rust 1.88+

Install · Quickstart · npm package · Execution modes · Examples · Formats · CLI reference

DeckProbe is a target-driven Rust engine, native CLI, and browser SDK for inspecting untrusted PDF, Microsoft Office, and modern Apple iWork documents without rendering them or starting a desktop office suite. Instead of eagerly unpacking everything, it chooses the cheapest probe path that can satisfy the targets and confidence you requested.

$ deckprobe --pretty -t slide_count deck.pptx
{
  "driver": { "id": "powerpoint", "profile": "pptx" },
  "results": {
    "<target>": {
      "status": "resolved",
      "value": 31,
      "confidence": "high",
      "path": "<selected-path>"
    }
  },
  ...
}

User-facing examples use short target names; reports retain stable canonical keys for machine consumers.

Why DeckProbe

Need What DeckProbe does
Fast, focused inspection Runs only the paths needed for targets such as page count or slide count.
Agent-friendly automation Emits deterministic schema-v2 JSON for success, partial results, and errors, with stable codes and target-level evidence.
Predictable work Enforces physical-read, decompression, archive-entry, and wall-clock budgets.
Broad PDF compatibility Uses normal parsing first, then bounded safe xref normalization/reconstruction for common damaged-but-readable PDFs.
Modern and Legacy Office Reads OOXML plus .doc, .xls, and .ppt metadata and core statistics through the same target vocabulary.
Modern Apple iWork Validates and inspects .key, .numbers, and .pages ZIP/IWA packages through bounded Snappy and Protobuf paths.
Format safety Routes by filename extension, then verifies the container and required internal main part before reporting values.

The native CLI needs no Python, JVM, Microsoft Office installation, or external PDF library at runtime. The browser SDK runs the same engine locally through WebAssembly and does not upload document bytes.

Quickstart

Install from source

Requires Rust 1.88 or newer and Git. This builds the CLI on the user's machine and installs it in the Cargo user directory, so it normally does not require administrator privileges:

cargo install --git https://github.com/deckflow/deckprobe --locked deckprobe

Install a prebuilt release

The installers choose the matching CPU and platform archive and install into the Cargo user directory. Installing there does not require administrator privileges. Use an elevated shell only when deliberately installing into a system directory.

macOS (Apple Silicon or Intel)

curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/deckflow/deckprobe/releases/latest/download/deckprobe-installer.sh | sh

Linux (x86-64 or ARM64; GNU or musl)

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/deckflow/deckprobe/releases/latest/download/deckprobe-installer.sh | sh

Windows (x86-64, MSVC)

powershell -ExecutionPolicy ByPass -c "irm https://github.com/deckflow/deckprobe/releases/latest/download/deckprobe-installer.ps1 | iex"

If the PowerShell installer updates the user Path, restart the shell before running deckprobe.

Every release contains platform archives, per-archive SHA-256 files, a consolidated checksum file, and GitHub build-provenance attestations. The shell installer verifies its embedded checksum when sha256sum is available. For a manual verification, download the matching archive and checksum from the same GitHub Release and use shasum -a 256 on macOS, sha256sum on Linux, or Get-FileHash -Algorithm SHA256 on Windows.

The checksum protects against accidental corruption and the provenance attestation records how the artifact was built. They are not the same as an OS code signature: this project does not currently publish macOS Developer ID notarization or Windows Authenticode signatures. macOS Gatekeeper or Windows SmartScreen may therefore ask for an explicit confirmation for a downloaded binary. A source-built binary is also not project-signed, but a locally built executable normally does not carry the quarantine marker attached to browser downloads.

The one-line installer commands are a convenience for trusted environments. For a controlled or offline installation, download the installer and archive from the GitHub Release, inspect them locally, verify the checksum and (where required) the build-provenance attestation, then run the installer.

At runtime DeckProbe does not need root or administrator access. It needs execute permission on the binary and read permission on the document being inspected; output is written to standard output. Hardened environments can still impose additional noexec, SELinux/AppArmor, or enterprise execution policies.

See the installation guide for custom install locations, upgrades, and uninstallation.

Verify the installation and probe a file:

deckprobe --version
deckprobe --pretty report.pdf

The default metadata probe returns common metadata plus useful format-specific facts. For a focused query, name one or more targets:

deckprobe --pretty \
  -t format,slide_count \
  deck.pptx

Target options are repeatable and unambiguous short names are resolved after format detection:

deckprobe -l m -t title -t slide_count -p deck.pptx
deckprobe -t @summary,@security --view values deck.pptx

Optional zero-additional-path values and per-target confidence are explicit:

deckprobe -t slide_count -o orientation,aspect_ratio \
  -C slide_count=x deck.pptx

Probe one raw document from stdin by supplying the logical filename used for format routing:

cat report.pdf | deckprobe -n report.pdf -

Process multiple paths or named base64 payloads as JSONL. DeckProbe writes one compact schema-v2 result for each non-empty input line:

printf '%s\n' \
  '{"path":"report.pdf"}' \
  '{"path":"deck.pptx"}' | deckprobe --jsonl -t @summary

JavaScript package

The independently published @deckflow/deckprobe package ships the deckprobe command for Node and runs the same target-driven Rust engine in WebAssembly for browsers and Node APIs.

Install from npm

npm install @deckflow/deckprobe

That installs the deckprobe command as well. It is the same native binary the standalone installers ship, delivered through a per-platform optional dependency, so every flag, help page, report, and exit code is identical:

npx @deckflow/deckprobe --help
npx @deckflow/deckprobe -t slide_count deck.pptx

Under Node the package also exposes probeFile(), which reads a file and returns the same report the CLI writes for it. Note that it holds the whole file in memory, while the CLI reads only the paths a probe needs — prefer the command, or --jsonl for batches, on large inputs.

import { probeFile } from "@deckflow/deckprobe";

const report = await probeFile("deck.pptx", { targets: ["@summary"] });

Browser SDK

In the browser it accepts File, Blob, ArrayBuffer, and Uint8Array inputs; document bytes do not leave the browser.

Need Import Use it when
Main-thread probe and discovery @deckflow/deckprobe A small, interaction-adjacent probe can use the UI thread.
Off-main-thread probe @deckflow/deckprobe/worker A user-provided or deep probe must not block rendering.
TypeScript types @deckflow/deckprobe Type ProbeResult, ProbeCallOptions, or the discovery responses.

probe() initializes WASM lazily. Call initDeckProbe() during application startup or idle time when the first user interaction should use the warm path.

import { initDeckProbe, probe } from "@deckflow/deckprobe";

await initDeckProbe();

const report = await probe(file, {
  targets: ["@summary", "@security"],
  level: "metadata",
});
Input Filename handling
File Uses File.name automatically.
Blob, ArrayBuffer, Uint8Array Pass name with a filename extension so DeckProbe can route the format.

For example, probe fetched bytes with an explicit logical filename:

const bytes = await fetch("/documents/quarterly-deck").then((response) =>
  response.arrayBuffer(),
);
const report = await probe(bytes, {
  name: "quarterly-deck.pptx",
  targets: ["powerpoint.slide_count"],
});

Use the Worker entry point for user-provided files, deep probes, or any flow where parsing must not block the UI thread. Reuse one worker for a batch and terminate it when the owning screen or job ends:

import { createDeckProbeWorker } from "@deckflow/deckprobe/worker";

const worker = createDeckProbeWorker();
try {
  const report = await worker.probe(file, {
    targets: ["@summary"],
    level: "deep",
  });
} finally {
  worker.terminate();
}

The Worker entry is a module worker resolved relative to the installed package; verify that the application's bundler preserves module-worker URLs. Calling terminate() cancels pending probes, so create a new worker for a later batch. formats(), targets(format), schema(), and version() are available from the main entry point for discovery and integration tooling. See the package guide for the complete API.

Bundler setup

The package resolves its WebAssembly binary relative to its own JavaScript wrapper, so a bundler that relocates the wrapper without the binary breaks initialization. Vite's dev server does this in every version and reports either HTTP status code is not ok or expected magic word 00 61 73 6d, while vite build works — exclude the package from dependency pre-bundling:

// vite.config.ts
export default defineConfig({
  optimizeDeps: { exclude: ["@deckflow/deckprobe"] },
});

Main-thread-only applications can instead pass the binary URL to initDeckProbe() via the @deckflow/deckprobe/wasm export. See the package guide's bundler notes for both fixes, the per-version error messages, and the CDN, sub-path, and CSP cases.

Execution modes

DeckProbe deliberately exposes different execution modes instead of treating every workload as a new process. Choose the mode that matches the lifetime of your application:

Mode Best for Lifecycle and trade-off
Native CLI, single-shot Shell commands, CI steps, one-off automation Starts a new process per input; simplest invocation and full end-to-end CLI cost.
Native CLI, persistent JSONL Servers, queues, and high-volume local batches One deckprobe --jsonl process stays alive and processes one JSON record per line; avoids startup cost while keeping every document probe independent.
Browser SDK, main thread Short, interaction-adjacent browser checks Lowest browser transport overhead, but a deep probe can occupy the UI thread.
Browser SDK, module Worker Upload screens, large files, and deep browser inspection Keeps the UI responsive; includes byte-copy, message, and response-transfer cost.

The browser paths run the same planner and bounded engine work as native execution. In practical terms: use JSONL when a native service processes a queue; use the main-thread SDK only when the expected probe is small enough to fit the UI budget; and use the Worker SDK as the default for user files and deep analysis.

Common recipes

Probe only low-cost identity targets:

deckprobe -l h -t @header suspicious.docx

Require an exact slide count. The planner selects presentation.xml instead of the cheaper saved statistic:

deckprobe -c x \
  -t slide_count \
  deck.pptx

Preview the selected paths without executing non-header probes:

deckprobe -P -t @default deck.pptx

Bound work on an untrusted archive and fail if a requested target cannot be resolved:

deckprobe -s \
  -b 8388608 \
  -x 16777216 \
  -e 5000 \
  -T 1000 \
  -t format,has_macros \
  upload.docm

Discover capabilities from the CLI itself:

deckprobe --pretty formats
deckprobe --pretty targets --format pdf
deckprobe --pretty targets --format docx
deckprobe --pretty targets --format xlsx
deckprobe --pretty targets --format pptx
deckprobe --pretty targets --format key
deckprobe --pretty targets --format numbers
deckprobe --pretty targets --format pages

Target presets are composable:

Preset Meaning
@header Low-cost identity targets.
@default Driver defaults for the selected probe level.
@summary Identity, common metadata, and primary structure.
@security Encryption, macros, signatures, external relationships, and active content.
@structure Format-owned counts, names, dimensions, and structure.
@assets Images, media, previews, fonts, and embedded-object summaries.
@quality Integrity, repair, extension, and conformance signals.
@format Format-owned targets available at the selected level.
@all Every target available at the selected level.

The summary preset excludes statistics that currently require a full-file path; request those explicitly or through @structure.

Supported formats

Driver Profiles Current inspection depth
PDF .pdf Header and Info metadata, page/object counts, xref type, signatures, links, attachments, JavaScript, forms, annotations, and XMP presence.
Word .docx, .docm, .dotx, .dotm OPC metadata, saved statistics, exact paragraph/table structure, security signals, comments, and image assets.
Excel .xlsx, .xlsm, .xltx, .xltm, .xlsb OPC metadata, worksheets/names/visibility, shared strings, tables, charts, pivots, security signals, and image assets; XLSB identity only.
PowerPoint .pptx, .pptm, .ppsx, .ppsm, .potx, .potm OPC metadata, slides/hidden slides, masters/layouts/notes, slide size, security signals, charts, comments, images, and media.
Legacy Office .doc, .dot, .xls, .xlt, .ppt, .pps, .pot Validated CFB main streams, SummaryInformation metadata, macros, embedded-object signals, and core Word/Excel/PowerPoint statistics.
Keynote .key Modern IWA identity/integrity plus slide canvas, orientation, hidden/notes/build/transition slide counts, and referenced table models.
Numbers .numbers Modern IWA identity/integrity, exact ordered sheets, referenced table dimensions, hidden/filtered dimensions, and persisted formula definitions.
Pages .pages Modern IWA identity/integrity, sections, page geometry, change tracking, body-text structural counts, cached pagination, and referenced table models.

The shared deep IWA path also exposes total archive-object counts, raw numeric message-type counts, and stable semantic object-class counts. The filename extension selects the driver path, after which DeckProbe verifies the container signature and format-specific root objects. Apple iWork support is intentionally limited to the modern ZIP/IWA generation; legacy XML packages containing index.apxl or index.xml return structured UNSUPPORTED_FORMAT JSON. A suffix/content mismatch returns MALFORMED_INPUT. Target discovery includes applicable and supported_levels, and scenario selectors only include targets backed by the selected driver's executable paths.

How probing works

requested targets + confidence + budget
                  │
          extension dispatcher
                  │
       container + type validation
                  │
       lowest-cost valid probe plan
          ┌───────┼────────┬───────────┬────────────┐
         PDF     Word     Excel    PowerPoint    Apple iWork
                   \        |        /             │
                    shared OOXML paths       ZIP/IWA/Snappy/Proto
                  │
       values + evidence + actual cost

Each driver owns its targets, format options, candidate paths, and parsing logic. Word, Excel, and PowerPoint share bounded ZIP/OPC/XML paths. Keynote, Numbers, and Pages share a bounded ZIP/plist/IWA/Snappy/Protobuf layer while retaining separate profile validation and targets.

JSON contract

Schema version 2 uses one JSON envelope on standard output. Top-level status is ok, partial, or error; errors include a stable code, message, and exit code. The tracked JSON Schema is suitable for generated clients and Agent tool contracts. Every requested target has an explicit status such as resolved, estimated, planned, unknown, unsupported, budget_exceeded, or failed. Resolved evidence includes:

  • value and its target-defined type;
  • confidence and numeric confidence_score;
  • the executed path and evidence source;
  • deterministic physical-byte, expanded-byte, and random-read counters for the whole probe.

Wall-clock elapsed_ms is omitted by default so identical inputs and options produce byte-identical JSON. Add --telemetry when timing is needed.

Use --strict when unresolved targets should make the command exit non-zero. Use --view values when a compact target-to-value map is preferable to the complete evidence envelope. deckprobe schema prints the exact bundled contract, and deckprobe completion SHELL generates completions from the live command model.

CLI reference

Start with the built-in help or the complete CLI reference:

deckprobe --help
deckprobe targets --help

To generate the man page from the same CLI definition:

deckprobe generate man > deckprobe.1
man ./deckprobe.1

Current limitations

DeckProbe 2.2 intentionally does not render files, run OCR, execute macros, follow external links, or send documents to a remote service.

  • PDF metadata currently reads the Info dictionary; XMP merging is not implemented.
  • PDF metadata/deep paths use a bounded full in-memory parser after the header path; safe xref recovery does not attempt damaged encrypted PDFs.
  • Legacy Office focuses on metadata and core counts rather than complete fidelity for every historical binary record variant.
  • .xlsb deep workbook parsing is not implemented.
  • Legacy XML iWork documents are recognized and rejected; only modern IWA packages are supported.
  • Pages exposes persisted page geometry, body-text structure, and cached pagination; rendered text-layout reconstruction is not implemented.
  • Network-backed range sources, persistent cache, and plugin loading are not implemented.

Development

cargo build -p deckprobe
cargo test --workspace
cargo check -p deckprobe-wasm --target wasm32-unknown-unknown
(cd packages/deckprobe-js && npm ci && npm run build && npx playwright install chromium && npm test)

Every release is validated by the maintainers' quality gate—format and lint checks, workspace tests, the Browser SDK contract tests, and a correctness-and-performance comparison against the previous release—before it is published to this repository.

Contributions are welcome—read CONTRIBUTING.md. Treat every input document as untrusted and report vulnerabilities privately as described in SECURITY.md.

License

DeckProbe is available under the MIT License.

About

ffprobe for documents — probe PDF, Microsoft Office, and Apple iWork files without opening them. Rust CLI + browser WASM SDK returning structured JSON with confidence, evidence, and measured I/O cost.

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages