Thanks for your interest in improving this project. This document describes how to report issues, propose changes, and add new test cases.
- File non-security issues at https://github.com/jonaslejon/malicious-pdf/issues.
- Search existing issues first to avoid duplicates.
- Include: command run, expected vs. actual output, Python version, OS, and the PDF viewer / processor under test if relevant.
- Bug reports and enhancement requests are accepted in English.
- Maintainers aim to acknowledge new reports within 14 days.
Do not open a public issue for security vulnerabilities. Follow the private
disclosure process documented in SECURITY.md. The maintainer
aims for an initial response within 14 days.
- Fork the repo and branch from
main. - Keep changes focused — one technique or fix per PR.
- Follow the project conventions described under "Project conventions" below.
- Update
README.md(test matrix and file count) andCHANGELOG.mdwhen user-visible behavior changes. - Confirm the smoke test passes locally:
python3 malicious-pdf.py https://example.com ls output/ | wc -l # should match the documented test count
- Open a PR describing what changed and why, and link any relevant CVE, advisory, or research blog post.
- Pure Python 3, single file (
malicious-pdf.py). PDFs are constructed as raw strings/bytes — do not introduce external PDF libraries. - Each test case is a
create_malpdfN(filename, host)function registered in thepdf_generatorsdict inmain(). - Test numbering has a gap at
27(removed as a duplicate). Do not renumber existing tests. - Sub-tests use string keys in
pdf_generators('33_1','34_2', …) — not floats, to avoid precision issues like33.10 == 33.1. - Special cases:
test11(EICAR) andtest14(.svg) take no host / produce a non-PDF extension;test29writes in binary mode due to embedded font data. - Credit injection and obfuscation are post-processing steps applied after generation — do not call them from inside generators.
Each PDF must exercise exactly one unique technique. Before writing code, verify the technique is not already covered:
- Is the action type already covered? (e.g.,
/GoToRistest7— don't add another) - Is the JS API already covered? (e.g.,
this.submitForm()istest33_1) - UNC variants count as separate from HTTPS variants
(e.g.,
test5=/URIwith HTTPS,test34_4=/URIwith UNC) - Different trigger mechanisms (
OpenActionvs/AA/PVvs/Names) count as different tests test27was removed because it combinedtest3+test23— do not create multi-technique PDFs
Then:
- Add
def create_malpdfN(filename, host)beforemain(). - Register it in the
pdf_generatorsdict inmain(). - Update the
README.mdtest matrix and the documented file count. - Bump
EXPECTED_PDF(orEXPECTED_SVG) in.github/workflows/smoke-test.ymlso the CI count assertion stays accurate. - Add a short entry under
## [Unreleased]inCHANGELOG.md.
- Every PR that adds or changes a generator must keep the smoke test green
(CI runs
python3 malicious-pdf.py https://example.comon every push and asserts the expected number of.pdffiles is produced). - New generators should be exercised at all supported obfuscation levels
(
--obfuscate 0through--obfuscate 4) before merging when feasible. - Manual viewer testing (Acrobat / PDF.js / Chrome PDFium) is encouraged for any change that affects an existing validated payload — note results in the PR description.
- Pure Python 3, no external PDF libraries — PDFs are constructed as raw
strings/bytes (see
CLAUDE.md). - Match the existing style of nearby code; no large reformatting in functional PRs.
- Keep generator functions self-contained; do not apply credit or obfuscation inside generators (those are post-processing steps).
By submitting a contribution you agree that it will be released under the project's BSD 2-Clause License.