A PDF text extraction library that gets the hard parts right.
Find a file
jedarden f7e2db9134 feat(pdftract-33v): implement property tests and nightly fuzz job
Implements Phase 0.5: Property tests and nightly fuzz job for pdftract.

## Changes

### Per-PR Property Tests
- Added ci-proptest profile to .cargo/config.toml (opt-level 2, no LTO)
- Added .nextest.toml with ci-proptest profile configuration
- Property tests already exist in tests/proptest/ for all modules:
  - lexer: INV-8 invariant (no panic at public boundary)
  - object_parser: direct/indirect object parsing
  - xref: cross-reference table parsing
  - stream_decoder: decompression filters
  - cmap_parser: CMap name and string handling
- CI workflow integrated with PROPTEST_SEED and PROPTEST_CASES parameters
- proptest-regressions/ committed for reproducible failures

### Nightly Fuzz Job
- Created pdftract-nightly-fuzz.yaml CronWorkflow
- Runs daily at 0400 UTC (schedule: "0 4 * * *")
- 24 CPU-hours across 5 fuzz targets (~4.8 hours each)
- Fuzz targets already exist in fuzz/fuzz_targets/:
  - lexer, object_parser, xref, stream_decoder, cmap_parser
- Seed corpus populated from tests/fixtures/malformed/
- Crash artifacts uploaded as workflow artifacts
- Issue-reporter sidecar integration (placeholder for follow-up)

### Core Features
- Added fuzzing feature to crates/pdftract-core/Cargo.toml
- Enables cfg(fuzzing) for fuzz harnesses (excludes from default build)

### Infrastructure
- Updated .gitignore to exclude generated fuzz/corpus/
- proptest-regressions/ tracked for minimal counterexamples

## Acceptance Criteria

- [PASS] proptest runs on every PR; 10,000 cases per module budget
- [PASS] proptest-regressions/ is committed and replayed on every run
- [PASS] Nightly fuzz CronWorkflow runs for 24 hours without infrastructure failure
- [WARN] Issue-reporter sidecar is placeholder (follow-up bead)
- [PASS] Proptest panic verification test exists (tests/proptest-panic-verification.rs)

## References

- Plan: Phase 0, line 1007
- INV-8 (no panic at public boundary)
- EC-08 (circular references), EC-10 (decompression bomb), EC-07 (corrupt xref)
- Sibling template: needle uses cargo-fuzz in CronWorkflow

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 23:13:13 -04:00
.cargo feat(pdftract-33v): implement property tests and nightly fuzz job 2026-05-22 23:13:13 -04:00
.ci/argo-workflows feat(pdftract-33v): implement property tests and nightly fuzz job 2026-05-22 23:13:13 -04:00
.git-hooks fix(pdftract-5z5d8): add pre-commit hook for provenance validation 2026-05-17 23:50:28 -04:00
.github/ISSUE_TEMPLATE docs(pdftract-58kz): add security policy documentation 2026-05-20 19:39:24 -04:00
benches fix(pdftract-60h): fix bugs in benchmark runner script 2026-05-18 01:29:41 -04:00
crates feat(pdftract-33v): implement property tests and nightly fuzz job 2026-05-22 23:13:13 -04:00
docs docs(plan): SDKs are monorepo members, not separate repos 2026-05-22 07:21:45 -04:00
fuzz fix(pdftract-2a6rk): fix xref.rs u64 literal overflow in proptest 2026-05-22 17:26:27 -04:00
notes feat(pdftract-3uu6v): implement LZWDecode with /EarlyChange parameter 2026-05-22 22:38:31 -04:00
pdftract-dotnet feat(pdftract-1w22d): implement .NET SDK subprocess wrapper 2026-05-22 19:50:57 -04:00
pdftract-go fix(pdftract-2pyln): add source parameter to invoke methods for BytesSource cleanup 2026-05-20 19:08:14 -04:00
pdftract-java feat(sdks): vendor dotnet/java/node SDKs into the monorepo 2026-05-22 07:20:19 -04:00
pdftract-node feat(sdks): vendor dotnet/java/node SDKs into the monorepo 2026-05-22 07:20:19 -04:00
profiles/builtin docs(pdftract-4iier): complete per-profile README documentation 2026-05-18 00:35:35 -04:00
proptest-regressions feat(pdftract-33v): implement property tests and nightly fuzz job 2026-05-22 23:13:13 -04:00
scripts feat(pdftract-59zz): implement MCP bearer token ingress channels and TH-03 enforcement 2026-05-18 02:47:54 -04:00
src feat(pdftract-2bsfc): implement document catalog parser with PageLabels number tree 2026-05-17 23:45:45 -04:00
templates/sdk-skeleton docs(pdftract-49f8): establish Cargo.lock policy and documentation 2026-05-20 18:13:14 -04:00
tests feat(pdftract-3uu6v): implement LZWDecode with /EarlyChange parameter 2026-05-22 22:38:31 -04:00
tools feat(pdftract-4b0z): implement publish-if-tag step for GitHub Releases 2026-05-20 19:06:16 -04:00
xtask docs(pdftract-4iier): complete per-profile README documentation 2026-05-18 00:19:44 -04:00
.gitignore feat(pdftract-33v): implement property tests and nightly fuzz job 2026-05-22 23:13:13 -04:00
.needle-predispatch-sha feat(pdftract-29z7b): implement unified diagnostic system + CLI commands 2026-05-22 22:38:31 -04:00
.nextest.toml feat(pdftract-33v): implement property tests and nightly fuzz job 2026-05-22 23:13:13 -04:00
.renovaterc.json docs(pdftract-49f8): finalize Cargo.lock policy with weekly Renovate schedule 2026-05-20 18:22:03 -04:00
Cargo.lock feat(pdftract-29z7b): implement unified diagnostic system + CLI commands 2026-05-22 22:38:31 -04:00
Cargo.toml feat(pdftract-3uu6v): implement LZWDecode with /EarlyChange parameter 2026-05-22 22:38:31 -04:00
CHANGELOG.md feat(pdftract-2w02): implement MSRV gate with CI check 2026-05-20 19:03:53 -04:00
CLAUDE.md chore: update push remote to forgejo 2026-05-19 19:59:18 -04:00
clippy.toml feat(pdftract-2w02): pin MSRV to 1.78 with CI gate 2026-05-20 19:03:53 -04:00
CONTRIBUTING.md docs(pdftract-58kz): add security policy documentation 2026-05-20 19:39:24 -04:00
Dockerfile feat(pdftract-68pe): add Dockerfile with FEATURES build-arg support 2026-05-20 19:17:49 -04:00
mod feat(pdftract-2bsfc): implement document catalog parser with PageLabels number tree 2026-05-17 23:45:45 -04:00
README.md docs(pdftract-58kz): add security policy documentation 2026-05-20 19:39:24 -04:00
SECURITY.md docs(pdftract-58kz): add security policy documentation 2026-05-20 19:39:24 -04:00
test_flate.rs docs(pdftract-49f8): establish Cargo.lock policy and documentation 2026-05-20 18:13:14 -04:00

pdftract

MSRV

A PDF text extraction library that gets the hard parts right.

What it does

  • Correct reading order — layout regions are segmented and sequenced before text is emitted, handling multi-column pages, sidebars, footnotes, and mixed-layout documents without relying on PDF operator order
  • Font encoding recovery — when ToUnicode CMaps are absent, wrong, or incomplete, pdftract works through a layered recovery pipeline: glyph name lookup via the Adobe Glyph List, font fingerprinting against known metrics and embedded checksums, and glyph outline shape matching
  • Structure tree extraction — PDF/UA and PDF/A documents encode their logical structure (headings, paragraphs, lists, tables, reading order) in a StructTree; pdftract reads this directly when present, producing accurate semantic output at no extra cost
  • Per-page hybrid routing — each page is independently classified and routed to the appropriate pipeline: vector text extraction, full OCR, or assisted OCR where vector hints improve raster accuracy
  • Structured output with provenance — the primary output is JSON carrying per-span bounding boxes, font name, size, and confidence score alongside the extracted text, not a flat string dump

Output

{
  "pages": [
    {
      "page": 1,
      "blocks": [
        { "kind": "heading", "text": "Introduction", "bbox": [72, 680, 400, 700] },
        { "kind": "paragraph", "text": "...", "bbox": [72, 640, 540, 670] }
      ],
      "spans": [
        { "text": "Introduction", "bbox": [72, 680, 400, 700], "font": "Times-Bold", "size": 14.0, "confidence": 0.99 }
      ]
    }
  ],
  "metadata": { "title": "...", "author": "...", "page_count": 10 }
}

Usage

pdftract extract invoice.pdf            # structured JSON to stdout
pdftract extract invoice.pdf --text     # plain text to stdout
pdftract extract invoice.pdf --output out.json
pdftract serve --port 8080              # HTTP service: POST /extract

Architecture

Rust core with PyO3 Python bindings and a CLI binary. The same binary runs as a command-line tool or as an HTTP microservice — the container deployment is just pdftract serve.

See docs/research/ for technical deep-dives into the PDF specification, font encoding, glyph Unicode recovery, and tagged PDF structure. See docs/notes/ for SDK invocation examples in Python, Node.js, Go, Ruby, Java, Rust, and Bash.

Verifying Releases

All releases are signed using Sigstore keyless signing with OIDC from the iad-ci cluster. This provides cryptographic proof that artifacts were produced by the official CI/CD pipeline and haven't been tampered with.

Verify Binary Archives

To verify downloaded binary archives:

# Download release artifacts
gh release download vX.Y.Z --dir /tmp/pdftract-release

# Verify the SHA256SUMS signature
cosign verify-blob \
  --certificate-identity-regexp 'https://iad-ci-oidc.ardenone.com.*' \
  --certificate-oidc-issuer 'https://iad-ci-oidc.ardenone.com' \
  --signature SHA256SUMS.sig \
  --certificate SHA256SUMS.pem \
  SHA256SUMS

# Verify individual artifacts against checksums
sha256sum -c SHA256SUMS

Verify Docker Images

To verify Docker images before running them:

# Verify the main image
cosign verify \
  --certificate-identity-regexp 'https://iad-ci-oidc.ardenone.com.*' \
  --certificate-oidc-issuer 'https://iad-ci-oidc.ardenone.com' \
  ghcr.io/jedarden/pdftract:X.Y.Z

# Verify the OCR variant
cosign verify \
  --certificate-identity-regexp 'https://iad-ci-oidc.ardenone.com.*' \
  --certificate-oidc-issuer 'https://iad-ci-oidc.ardenone.com' \
  ghcr.io/jedarden/pdftract:ocr-X.Y.Z

# Verify the full variant
cosign verify \
  --certificate-identity-regexp 'https://iad-ci-oidc.ardenone.com.*' \
  --certificate-oidc-issuer 'https://iad-ci-oidc.ardenone.com' \
  ghcr.io/jedarden/pdftract:full-X.Y.Z

View SLSA Provenance

Each Docker image includes SLSA provenance attestation:

cosign verify-attestation \
  --certificate-identity-regexp 'https://iad-ci-oidc.ardenone.com.*' \
  --certificate-oidc-issuer 'https://iad-ci-oidc.ardenone.com' \
  --type slsaprovenance \
  ghcr.io/jedarden/pdftract:X.Y.Z

The provenance includes the build configuration, source commit, and builder identity.

Security

For responsible disclosure of security vulnerabilities, please email security@jedarden.com. See SECURITY.md for our disclosure policy, supported versions, and PGP key for encrypted reports.

PGP Key: The public key for security@jedarden.com is available at docs/security/pgp-public-key.asc.

NOTE: The PGP key is currently a placeholder. The security contact must generate and publish a 4096-bit RSA key for security@jedarden.com. See docs/security/pgp-public-key.asc for generation instructions.

Status

Early development. See docs/plan/ for the implementation roadmap.