any2md 1.0.6__tar.gz → 1.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {any2md-1.0.6 → any2md-1.1.1}/.gitignore +0 -1
- {any2md-1.0.6 → any2md-1.1.1}/CHANGELOG.md +172 -6
- {any2md-1.0.6 → any2md-1.1.1}/CONTRIBUTING.md +18 -0
- {any2md-1.0.6 → any2md-1.1.1}/PKG-INFO +19 -8
- {any2md-1.0.6 → any2md-1.1.1}/README.md +16 -7
- {any2md-1.0.6 → any2md-1.1.1}/SECURITY.md +22 -0
- any2md-1.1.1/any2md/__init__.py +11 -0
- {any2md-1.0.6 → any2md-1.1.1}/any2md/_docling.py +1 -1
- any2md-1.1.1/any2md/_docling_cache.py +436 -0
- any2md-1.1.1/any2md/_http.py +199 -0
- any2md-1.1.1/any2md/_logging.py +61 -0
- {any2md-1.0.6 → any2md-1.1.1}/any2md/cli.py +12 -7
- {any2md-1.0.6 → any2md-1.1.1}/any2md/config.py +6 -1
- {any2md-1.0.6 → any2md-1.1.1}/any2md/converters/__init__.py +2 -2
- {any2md-1.0.6 → any2md-1.1.1}/any2md/converters/docx.py +18 -19
- {any2md-1.0.6 → any2md-1.1.1}/any2md/converters/html.py +17 -5
- {any2md-1.0.6 → any2md-1.1.1}/any2md/converters/pdf.py +43 -21
- {any2md-1.0.6 → any2md-1.1.1}/any2md/converters/txt.py +4 -5
- {any2md-1.0.6 → any2md-1.1.1}/any2md/frontmatter.py +24 -6
- {any2md-1.0.6 → any2md-1.1.1}/any2md/heuristics.py +9 -6
- {any2md-1.0.6 → any2md-1.1.1}/any2md/pipeline/cleanup.py +1 -1
- {any2md-1.0.6 → any2md-1.1.1}/any2md/utils.py +57 -10
- {any2md-1.0.6 → any2md-1.1.1}/docs/architecture.md +13 -10
- {any2md-1.0.6 → any2md-1.1.1}/docs/cli-reference.md +1 -1
- {any2md-1.0.6 → any2md-1.1.1}/docs/output-format.md +19 -19
- {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/plans/2026-04-26-any2md-v1-phase1-foundation.md +19 -19
- {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/plans/2026-04-26-any2md-v1-phase4-polish-config-docs.md +11 -11
- any2md-1.1.1/docs/superpowers/plans/2026-05-04-v1.1.0-persistent-converter.md +2243 -0
- any2md-1.1.1/docs/superpowers/plans/2026-05-06-v1.1.1-security-hardening.md +2543 -0
- {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/specs/2026-04-26-any2md-v1-design.md +25 -25
- any2md-1.1.1/docs/superpowers/specs/2026-05-04-v1.1.0-persistent-converter-design.md +951 -0
- any2md-1.1.1/docs/superpowers/specs/2026-05-06-v1.1.1-security-design.md +553 -0
- {any2md-1.0.6 → any2md-1.1.1}/docs/troubleshooting.md +76 -0
- {any2md-1.0.6 → any2md-1.1.1}/docs/upgrading-from-0.7.md +9 -9
- {any2md-1.0.6 → any2md-1.1.1}/pyproject.toml +2 -0
- any2md-1.1.1/requirements.txt +81 -0
- any2md-1.1.1/scripts/bench_docling_cache.py +87 -0
- {any2md-1.0.6 → any2md-1.1.1}/scripts/validate-phase1.sh +1 -1
- any2md-1.1.1/tests/conftest.py +43 -0
- any2md-1.1.1/tests/integration/test_docling_persistence.py +105 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_txt.py +1 -1
- any2md-1.1.1/tests/unit/test_atomic_write_symlink_race.py +39 -0
- any2md-1.1.1/tests/unit/test_config_toml_warn.py +36 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_content_hash.py +1 -1
- any2md-1.1.1/tests/unit/test_docling_cache.py +703 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_frontmatter_compose.py +2 -2
- any2md-1.1.1/tests/unit/test_frontmatter_key_injection.py +59 -0
- any2md-1.1.1/tests/unit/test_heuristics_wikipedia_host.py +22 -0
- any2md-1.1.1/tests/unit/test_http_ip_allowlist.py +68 -0
- any2md-1.1.1/tests/unit/test_http_ssrf_pin.py +63 -0
- any2md-1.1.1/tests/unit/test_http_user_agent.py +10 -0
- any2md-1.1.1/tests/unit/test_lockfile_freshness.py +41 -0
- any2md-1.1.1/tests/unit/test_logging_module.py +85 -0
- any2md-1.1.1/tests/unit/test_pdf_image_symlink.py +54 -0
- any2md-1.1.1/tests/unit/test_security_error_strings.py +86 -0
- any2md-1.1.1/tests/unit/test_url_creds_scrubbed.py +76 -0
- any2md-1.1.1/tests/unit/test_workflow_permissions.py +44 -0
- any2md-1.0.6/any2md/__init__.py +0 -3
- any2md-1.0.6/any2md/_http.py +0 -106
- any2md-1.0.6/requirements.txt +0 -10
- any2md-1.0.6/tests/conftest.py +0 -25
- {any2md-1.0.6 → any2md-1.1.1}/CODE_OF_CONDUCT.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/LICENSE +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/any2md/__main__.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/any2md/pipeline/__init__.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/any2md/pipeline/structured.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/any2md/pipeline/text.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/any2md/validators.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/assets/logo-original.jpeg +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/assets/logo.png +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/assets/social-preview.jpg +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/plans/2026-04-26-any2md-v1-phase2-docling-backend.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/plans/2026-04-26-any2md-v1-phase3-figures-ocr-text-lane.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/plans/2026-04-26-any2md-v1.0.2-implementation.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/plans/2026-04-26-issue-17-toc-followups.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/plans/2026-04-27-v1.0.6-security-hardening.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/specs/2026-04-26-any2md-v1.0.2-design.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/specs/2026-04-26-issue-17-toc-followups-design.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/specs/2026-04-26-v1.0.3-empty-title-orphan-punct-design.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/docs/superpowers/specs/2026-04-27-v1.0.6-security-hardening-design.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/scripts/audit-outputs.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/__init__.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/cli/__init__.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_backend.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_existing_flags.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_exit_codes.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_high_fidelity.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_meta.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_no_arxiv_lookup.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_ocr_save_images.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_quiet_verbose.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/cli/test_cli_strict.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/__init__.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/docs/ligatures_and_softhyphens.txt +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/docs/multi_column.pdf +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/docs/table_heavy.docx +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/docs/web_page.html +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/make_fixtures.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/snapshots/ligatures_and_softhyphens.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/snapshots/multi_column.docling.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/snapshots/multi_column.fallback.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/snapshots/table_heavy.docling.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/snapshots/table_heavy.fallback.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/fixtures/snapshots/web_page.md +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/integration/__init__.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_docx_backend_override.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_docx_docling.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_docx_fallback_on_warn.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_docx_mammoth_fallback.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_html_trafilatura.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_pdf_backend_override.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_pdf_docling.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_pdf_pymupdf_fallback.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_save_images.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/integration/test_snapshots.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/__init__.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/__init__.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_footnote_markers.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_html_entities.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_html_entities_double_encoded.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_ligatures.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_nfc.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_quotes_dashes.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_soft_hyphens.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_validate.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_cleanup_whitespace.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_runner.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_strip_orphan_punctuation.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_structured_body_cleanup_stages.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_structured_compact_tables.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_structured_heading_hierarchy.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_structured_lift_figures.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_structured_normalize_cites.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_dedupe_paragraphs.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_dedupe_toc.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_dedupe_toc_table.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_dehyphenate.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_repair_line_wraps.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_restore_lists_code.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_strip_cover_artifacts.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_strip_headers_footers.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_strip_repeated_byline.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/pipeline/test_text_strip_web_fragments.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/scripts/__init__.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/scripts/test_audit_outputs.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_atomic_write.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_auto_id.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_config.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_config_walkup.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_docling_helper.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_frontmatter_helpers.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_heuristics.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_http_safe.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_log_sanitizer.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_meta_overrides.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_meta_reserved_keys.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_pdf_complexity.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_safe_dir_name.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_validators.py +0 -0
- {any2md-1.0.6 → any2md-1.1.1}/tests/unit/test_zipbomb_guard.py +0 -0
|
@@ -4,6 +4,172 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [1.1.1] — 2026-05-DD
|
|
8
|
+
|
|
9
|
+
Security patch release. Closes 10 findings from a comprehensive scan
|
|
10
|
+
(bandit, snyk-code, gitleaks, pip-audit, trivy, manual review) plus
|
|
11
|
+
a 5-round adversarial review of the implementation plan.
|
|
12
|
+
|
|
13
|
+
### Security
|
|
14
|
+
|
|
15
|
+
- **SSRF intra-hop DNS-rebind**: `safe_fetch` now resolves the
|
|
16
|
+
hostname once, validates the IP against an allowlist, and passes
|
|
17
|
+
the validated IP through to `socket.create_connection` via a
|
|
18
|
+
custom `HTTPSConnection`. Defeats attacks that return a public IP
|
|
19
|
+
to a validator and a private IP (e.g., `169.254.169.254`) to the
|
|
20
|
+
connector. `ProxyHandler({})` explicitly suppresses
|
|
21
|
+
`HTTP_PROXY`/`HTTPS_PROXY` env vars that would otherwise route
|
|
22
|
+
traffic past the validator.
|
|
23
|
+
- **IP allowlist**: switched from a four-flag denylist
|
|
24
|
+
(`is_private | is_reserved | is_loopback | is_link_local`) to an
|
|
25
|
+
allowlist (`is_global and not is_multicast and not is_unspecified`).
|
|
26
|
+
**Breaking** for users on multicast / CGNAT / IETF-reserved
|
|
27
|
+
networks fetching same-network resources; pre-fetch with `curl`
|
|
28
|
+
and pass `--from-file` as a workaround.
|
|
29
|
+
- **Frontmatter override-key validation**: keys must now match
|
|
30
|
+
`^[A-Za-z_][A-Za-z0-9_-]*$`. Defends against newline-injection in
|
|
31
|
+
TOML basic-string keys that produced split YAML lines.
|
|
32
|
+
**Breaking** for TOML configs with quoted keys containing
|
|
33
|
+
whitespace or punctuation.
|
|
34
|
+
- **URL credentials and sensitive query parameters** in `userinfo`
|
|
35
|
+
and matching `(?i)(token|api[_-]?key|access[_-]?token|bearer|signature|sig|password|secret|aws[_-]?session[_-]?token)$`
|
|
36
|
+
are scrubbed before any logging, persistence, or filename
|
|
37
|
+
derivation. IPv6 host brackets preserved across the scrub.
|
|
38
|
+
- **Docling image saves** use `O_EXCL|O_NOFOLLOW` to refuse symlink
|
|
39
|
+
TOCTOU.
|
|
40
|
+
- **`atomic_write_text`** tightens the symlink-check window via
|
|
41
|
+
`os.lstat` immediately before `os.replace`. Parent-dir hardening
|
|
42
|
+
deferred to v1.2 per the operator-trusted threat model.
|
|
43
|
+
- **Stderr/stdout sanitizer** extended with Unicode bidi
|
|
44
|
+
(U+202A–E, U+2066–9), zero-width chars (U+200B–F, U+FEFF), and
|
|
45
|
+
the BOM. New `any2md._logging` module; `_sanitize_log_text`
|
|
46
|
+
re-exported from `any2md.converters.docx` for one release.
|
|
47
|
+
- **`publish.yml` workflow**: top-level `permissions: contents: read`;
|
|
48
|
+
both publish jobs flip to `attestations: true` for PEP 740 sigstore
|
|
49
|
+
provenance.
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- `wikipedia.org` heuristic host check no longer matches
|
|
54
|
+
`evilwikipedia.org`. Pure correctness fix; impact was incorrect
|
|
55
|
+
text formatting only, not a security boundary.
|
|
56
|
+
- `_USER_AGENT` no longer hardcoded to `any2md/1.0.6`; derives from
|
|
57
|
+
`__version__`.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- `requirements.txt` is now a generated artifact; canonical source
|
|
62
|
+
is `.devcontainer/requirements.lock` with hashes
|
|
63
|
+
(`--require-hashes` enforced in `.devcontainer/devcontainer.json`).
|
|
64
|
+
See `CONTRIBUTING.md` for the dep-update workflow.
|
|
65
|
+
- `load_toml()` now warns to stderr on parse error rather than
|
|
66
|
+
silently returning `{}`.
|
|
67
|
+
- `.devcontainer/` is now tracked in git (was previously ignored)
|
|
68
|
+
so the security-critical Codespace bootstrap config is
|
|
69
|
+
version-controlled.
|
|
70
|
+
|
|
71
|
+
## [1.1.0] — 2026-05-04
|
|
72
|
+
|
|
73
|
+
Performance and library-mode hygiene release.
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
|
|
77
|
+
- `any2md.release_models()` — imperative escape hatch to free
|
|
78
|
+
cached Docling `DocumentConverter` instances. Stability:
|
|
79
|
+
experimental for v1.1.0.
|
|
80
|
+
- `any2md.docling_session()` — contextmanager (preferred shape
|
|
81
|
+
for library use). Stability: experimental for v1.1.0.
|
|
82
|
+
- `ANY2MD_DOCLING_CACHE=0` env-var to disable the persistent
|
|
83
|
+
converter cache (per-call construction, equivalent to v1.0.x
|
|
84
|
+
behavior).
|
|
85
|
+
- `__all__` declared on `any2md/__init__.py` (formalizes the
|
|
86
|
+
public surface).
|
|
87
|
+
- New benchmark `scripts/bench_docling_cache.py` for maintainer
|
|
88
|
+
pre-release verification.
|
|
89
|
+
|
|
90
|
+
### Changed
|
|
91
|
+
|
|
92
|
+
- `DocumentConverter` is now constructed once per process and
|
|
93
|
+
reused across all files in a batch (was: once per file). New
|
|
94
|
+
one-time stderr line `Loading Docling models (one-time)...`
|
|
95
|
+
printed on first PDF/DOCX in non-quiet TTY runs.
|
|
96
|
+
- `.github/workflows/ci.yml` now runs `pytest tests/unit` on each
|
|
97
|
+
push and PR (was: lint + import smoke + pip-audit only).
|
|
98
|
+
|
|
99
|
+
### Performance
|
|
100
|
+
|
|
101
|
+
- Measured: CPU first-call ~2.47s (model load), warm calls ~0.87s
|
|
102
|
+
(vs prior ~2.47s per file in v1.0.x). Mac MPS first-call ~2.77s,
|
|
103
|
+
warm ~0.14s. RSS floor +535 MB sustained for the process lifetime
|
|
104
|
+
(see `docs/troubleshooting.md` for `release_models()` mitigation).
|
|
105
|
+
|
|
106
|
+
### Migration
|
|
107
|
+
|
|
108
|
+
- None required for CLI users. The `Loading Docling models...`
|
|
109
|
+
stderr line is the only user-visible default-output change.
|
|
110
|
+
- Library/embedder users holding any2md across long-running
|
|
111
|
+
processes should call `any2md.release_models()` or use
|
|
112
|
+
`with any2md.docling_session():` to free model state between
|
|
113
|
+
workloads.
|
|
114
|
+
- Test/instrumentation code that monkey-patches
|
|
115
|
+
`docling.document_converter.DocumentConverter` AFTER any2md's
|
|
116
|
+
first call will see no effect on subsequent calls (cache returns
|
|
117
|
+
the pre-patch instance). Workaround: call `release_models()`
|
|
118
|
+
after applying the patch, or set `ANY2MD_DOCLING_CACHE=0` in the
|
|
119
|
+
test environment.
|
|
120
|
+
|
|
121
|
+
### Stability notice
|
|
122
|
+
|
|
123
|
+
The new public APIs (`release_models`, `docling_session`) are
|
|
124
|
+
marked experimental for v1.1.0; their shape may change in
|
|
125
|
+
subsequent v1.x releases without a major bump until promoted to
|
|
126
|
+
stable in a later release.
|
|
127
|
+
|
|
128
|
+
### Known limitations
|
|
129
|
+
|
|
130
|
+
- HF Hub outages during cold-start now cause N retries instead of
|
|
131
|
+
one silent fallback (the v1.0.x failure-cache behavior was
|
|
132
|
+
rejected on quality grounds). Bounded retry-with-backoff
|
|
133
|
+
deferred to a future hardening PR.
|
|
134
|
+
- Forking a process (`multiprocessing` with `fork` start method)
|
|
135
|
+
after the first `convert()` call inherits torch/CUDA state
|
|
136
|
+
unsafely regardless of cache. Use
|
|
137
|
+
`multiprocessing.set_start_method("spawn")` or call
|
|
138
|
+
`release_models()` before fork.
|
|
139
|
+
- `os.register_at_fork` is POSIX-only; on Windows the fork-reset
|
|
140
|
+
path is dead code (no fork on Windows). Cache works normally
|
|
141
|
+
on Windows.
|
|
142
|
+
|
|
143
|
+
## [1.0.7] — 2026-04-29
|
|
144
|
+
|
|
145
|
+
Branding/rebrand release. The frontmatter contract that any2md emits is
|
|
146
|
+
now described as "SAGE-compatible" (Security Analysis and Guidance
|
|
147
|
+
Exchange) — the upstream specification was renamed from SSRM (Structured
|
|
148
|
+
Security Reasoning Markdown) to SAGE. No functional or schema changes:
|
|
149
|
+
field names, ordering, controlled vocabularies, and `content_hash`
|
|
150
|
+
derivation are byte-identical to v1.0.6. Existing v1.0.6 outputs remain
|
|
151
|
+
valid SAGE-compatible documents without modification.
|
|
152
|
+
|
|
153
|
+
### Changed
|
|
154
|
+
|
|
155
|
+
- README, CHANGELOG, and all of `docs/` updated to reference SAGE /
|
|
156
|
+
Security Analysis and Guidance Exchange.
|
|
157
|
+
- `--auto-id` CLI help text now reads "SAGE-conformant document_id"
|
|
158
|
+
(previously "SSRM-conformant"). Behavior unchanged.
|
|
159
|
+
- Module/function docstrings in `any2md/frontmatter.py`,
|
|
160
|
+
`any2md/converters/txt.py`, and `any2md/pipeline/cleanup.py` updated
|
|
161
|
+
to reference SAGE §3 / §5.1 (same section numbers as the upstream
|
|
162
|
+
SSRM v1.0-RC1 spec).
|
|
163
|
+
- Two test functions renamed for naming consistency
|
|
164
|
+
(`test_txt_end_to_end_writes_sage_compat_output`,
|
|
165
|
+
`test_compose_emits_required_sage_fields`). Pytest auto-discovery is
|
|
166
|
+
unaffected; assertions unchanged.
|
|
167
|
+
|
|
168
|
+
### Migration
|
|
169
|
+
|
|
170
|
+
None required. Public API, CLI flags, frontmatter shape, and
|
|
171
|
+
`content_hash` outputs are all preserved bit-for-bit.
|
|
172
|
+
|
|
7
173
|
## [1.0.6] — 2026-04-27
|
|
8
174
|
|
|
9
175
|
Security hardening release. Closes 8 actionable findings from the
|
|
@@ -318,7 +484,7 @@ Patch release. Adds an explicit backend-selection CLI flag.
|
|
|
318
484
|
First stable release of any2md v1.0. Validated end-to-end against
|
|
319
485
|
real-world documents (a 164-page PDF technical standard via Docling,
|
|
320
486
|
a multi-page academic DOCX, and a Wikipedia article via trafilatura).
|
|
321
|
-
The output contract —
|
|
487
|
+
The output contract — SAGE-compatible frontmatter, deterministic
|
|
322
488
|
`content_hash`, NFC + LF body normalization — is now stable. Downstream
|
|
323
489
|
consumers can rely on this shape.
|
|
324
490
|
|
|
@@ -340,7 +506,7 @@ Phase 4: configuration, polish, and the v1.0 documentation set.
|
|
|
340
506
|
- `--meta-file PATH` flag plus auto-discovery of `.any2md.toml` (walks up
|
|
341
507
|
from cwd). The `[meta]` table supplies frontmatter overrides; the
|
|
342
508
|
`[document_id]` table supplies `--auto-id` prefix and type code.
|
|
343
|
-
- `--auto-id` flag — generates an
|
|
509
|
+
- `--auto-id` flag — generates an SAGE-conformant `document_id` as
|
|
344
510
|
`{PREFIX}-{YYYY}-{TYPE}-{SHA8}`. Defaults to `LOCAL`/`DOC`; override via
|
|
345
511
|
the `[document_id]` table in `.any2md.toml`.
|
|
346
512
|
- `--strict` flag — promotes pipeline validation warnings (heading
|
|
@@ -359,7 +525,7 @@ Phase 4: configuration, polish, and the v1.0 documentation set.
|
|
|
359
525
|
- Comprehensive documentation set:
|
|
360
526
|
- `README.md` rewritten with educational tone — every command preceded
|
|
361
527
|
by why you'd run it and followed by what you'll see.
|
|
362
|
-
- `docs/output-format.md` —
|
|
528
|
+
- `docs/output-format.md` — SAGE-compatible field reference and the
|
|
363
529
|
`content_hash` recomputation recipe.
|
|
364
530
|
- `docs/cli-reference.md` — flag-by-flag reference with use cases and a
|
|
365
531
|
worked-example matrix.
|
|
@@ -462,14 +628,14 @@ any2md transparently falls back to pymupdf4llm (PDF) and mammoth (DOCX).
|
|
|
462
628
|
|
|
463
629
|
First prerelease of any2md v1.0. Phase 1 of 5: foundation only — no Docling
|
|
464
630
|
backend yet, no new CLI flags. Output frontmatter has been rewritten to be
|
|
465
|
-
|
|
631
|
+
SAGE-compatible (Security Analysis and Guidance Exchange) — this is a
|
|
466
632
|
breaking change for downstream consumers parsing v0.7 output. See
|
|
467
633
|
`docs/superpowers/specs/2026-04-26-any2md-v1-design.md` for the full
|
|
468
634
|
v1.0 design and `docs/superpowers/plans/2026-04-26-any2md-v1-phase1-foundation.md`
|
|
469
635
|
for this phase's task plan.
|
|
470
636
|
|
|
471
637
|
### Added
|
|
472
|
-
- New `any2md/frontmatter.py` module —
|
|
638
|
+
- New `any2md/frontmatter.py` module — SAGE-compatible YAML emitter with the
|
|
473
639
|
`SourceMeta` dataclass, deterministic `compute_content_hash`,
|
|
474
640
|
`estimate_tokens`, `recommend_chunk_level`, `extract_abstract`, and
|
|
475
641
|
`derive_title` helpers, plus the central `compose()` function (the only
|
|
@@ -495,7 +661,7 @@ for this phase's task plan.
|
|
|
495
661
|
`reportlab`, `ruff`.
|
|
496
662
|
|
|
497
663
|
### Changed
|
|
498
|
-
- **BREAKING:** Output frontmatter shape is now
|
|
664
|
+
- **BREAKING:** Output frontmatter shape is now SAGE-compatible. New required
|
|
499
665
|
fields: `document_id` (empty string by default — opt-in via future
|
|
500
666
|
`--auto-id`), `version`, `date`, `status: "draft"`, `document_type` (empty
|
|
501
667
|
for non-security documents), `content_domain` (empty array),
|
|
@@ -131,6 +131,24 @@ The flow:
|
|
|
131
131
|
Don't tag and release directly without the PR — CI gates exist for a
|
|
132
132
|
reason.
|
|
133
133
|
|
|
134
|
+
## Updating dependencies
|
|
135
|
+
|
|
136
|
+
Dependencies are declared in `pyproject.toml` and locked into both:
|
|
137
|
+
|
|
138
|
+
- `.devcontainer/requirements.lock` — hash-locked, used by Codespace `postCreateCommand` (`pip install --require-hashes`).
|
|
139
|
+
- `requirements.txt` — non-hash form, kept for backwards compatibility with `pip install -r requirements.txt` consumers.
|
|
140
|
+
|
|
141
|
+
To add or update a dep:
|
|
142
|
+
|
|
143
|
+
1. Edit `pyproject.toml` `[project] dependencies` (or the relevant `[project.optional-dependencies]` group).
|
|
144
|
+
2. Regenerate both lockfiles in a venv with `pip-tools` installed:
|
|
145
|
+
|
|
146
|
+
pip-compile pyproject.toml --generate-hashes --resolver=backtracking \
|
|
147
|
+
-o .devcontainer/requirements.lock
|
|
148
|
+
pip-compile pyproject.toml --resolver=backtracking -o requirements.txt
|
|
149
|
+
|
|
150
|
+
3. Commit all three files together.
|
|
151
|
+
|
|
134
152
|
## Pull request process
|
|
135
153
|
|
|
136
154
|
- Branch off `main` (or off a phase branch like `v1.0` if one is open for
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: any2md
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: Convert PDF, DOCX, HTML, and TXT files — or web pages by URL — to clean, LLM-optimized Markdown with YAML frontmatter.
|
|
5
5
|
Project-URL: Homepage, https://github.com/rocklambros/any2md
|
|
6
6
|
Project-URL: Issues, https://github.com/rocklambros/any2md/issues
|
|
@@ -20,11 +20,13 @@ Requires-Dist: markdownify<2,>=0.13.0
|
|
|
20
20
|
Requires-Dist: pillow<13,>=12.2.0
|
|
21
21
|
Requires-Dist: pymupdf4llm<1,>=0.0.17
|
|
22
22
|
Requires-Dist: pymupdf<2,>=1.24.0
|
|
23
|
+
Requires-Dist: tomli>=2.0.0; python_version < '3.11'
|
|
23
24
|
Requires-Dist: trafilatura<3,>=1.12.0
|
|
24
25
|
Requires-Dist: urllib3<3,>=2.6.3
|
|
25
26
|
Provides-Extra: dev
|
|
26
27
|
Requires-Dist: pytest-snapshot>=0.9.0; extra == 'dev'
|
|
27
28
|
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pyyaml>=6.0; extra == 'dev'
|
|
28
30
|
Requires-Dist: reportlab>=4.0.0; extra == 'dev'
|
|
29
31
|
Requires-Dist: ruff>=0.6.0; extra == 'dev'
|
|
30
32
|
Provides-Extra: high-fidelity
|
|
@@ -52,7 +54,7 @@ The full design rationale lives in [docs/superpowers/specs/2026-04-26-v1.0.3-emp
|
|
|
52
54
|
|
|
53
55
|
## What this is
|
|
54
56
|
|
|
55
|
-
any2md ingests heterogeneous source documents and emits Markdown with a fixed YAML frontmatter contract. The frontmatter shape is
|
|
57
|
+
any2md ingests heterogeneous source documents and emits Markdown with a fixed YAML frontmatter contract. The frontmatter shape is SAGE-compatible (Security Analysis and Guidance Exchange — a documented schema for LLM-consumable documents), so retrieval pipelines, embeddings jobs, and chunking utilities downstream can rely on field names, types, and a deterministic `content_hash` for cache invalidation. The body is NFC-normalized with LF line endings, and the heading hierarchy is guaranteed to start at H1 and not skip levels. The goal is one stable shape, regardless of whether a document started life as a scanned PDF, a Word doc, a Wikipedia page, or a plain text dump.
|
|
56
58
|
|
|
57
59
|
## Quick start
|
|
58
60
|
|
|
@@ -144,8 +146,8 @@ document_id: "" # empty unless you pass --aut
|
|
|
144
146
|
version: "1" # "1" for source files without an embedded version
|
|
145
147
|
date: "2026-03-15" # docx core props "modified" → ISO-8601
|
|
146
148
|
status: "draft" # always "draft" for converted documents
|
|
147
|
-
document_type: "" #
|
|
148
|
-
content_domain: [] #
|
|
149
|
+
document_type: "" # SAGE controlled vocab — empty for non-security docs
|
|
150
|
+
content_domain: [] # SAGE controlled vocab — empty array when unknown
|
|
149
151
|
authors: # extracted from docProps/core.xml dc:creator
|
|
150
152
|
- "Rock Lambros"
|
|
151
153
|
organization: "" # docProps/app.xml Company; empty when absent
|
|
@@ -166,7 +168,7 @@ produced_by: "Microsoft® Word for Microsoft 365" # software that produced the
|
|
|
166
168
|
---
|
|
167
169
|
```
|
|
168
170
|
|
|
169
|
-
Fields under `# any2md extension field` are retained from v0.7 for traceability and observability — they're not part of the
|
|
171
|
+
Fields under `# any2md extension field` are retained from v0.7 for traceability and observability — they're not part of the SAGE contract proper, but they live in the same frontmatter block. See [docs/output-format.md](docs/output-format.md) for the full field reference.
|
|
170
172
|
|
|
171
173
|
### Before / after
|
|
172
174
|
|
|
@@ -360,9 +362,18 @@ any2md -f -r ./corpus/
|
|
|
360
362
|
|
|
361
363
|
You'll see the OK lines as if the conversion were fresh; previously-written outputs are overwritten.
|
|
362
364
|
|
|
365
|
+
> **v1.1.0+:** when processing PDFs or DOCX files via the Docling
|
|
366
|
+
> backend, the converter (and its loaded model weights) is constructed
|
|
367
|
+
> once on the first PDF/DOCX in an invocation and reused for all
|
|
368
|
+
> subsequent files; subsequent files extract in ~0.5–1s rather than
|
|
369
|
+
> re-paying the ~2.5s cold-load tax. Library users embedding any2md
|
|
370
|
+
> in long-running processes should call `any2md.release_models()` or
|
|
371
|
+
> use `with any2md.docling_session():` — see
|
|
372
|
+
> `docs/troubleshooting.md`.
|
|
373
|
+
|
|
363
374
|
## The output format
|
|
364
375
|
|
|
365
|
-
Every file converted by any2md has the same frontmatter shape, regardless of source format. The shape is **
|
|
376
|
+
Every file converted by any2md has the same frontmatter shape, regardless of source format. The shape is **SAGE-compatible** — it matches the field names, types, and ordering of the Security Analysis and Guidance Exchange specification, but values aren't required to match SAGE's controlled vocabularies (most converted documents aren't security research). Fields that require a controlled vocabulary (`document_type`, `content_domain`, `tlp`) are emitted empty unless you supply them via `--meta` or `.any2md.toml`.
|
|
366
377
|
|
|
367
378
|
### Field auto-fill summary
|
|
368
379
|
|
|
@@ -407,7 +418,7 @@ You'll see the OK line as usual. The frontmatter of `Text/paper.md` will have `o
|
|
|
407
418
|
|
|
408
419
|
### `.any2md.toml`
|
|
409
420
|
|
|
410
|
-
You'd use this when you have a stable set of frontmatter defaults that should apply to every conversion in a project. The file is auto-discovered by walking up from the current working directory, so you can drop one at the root of your corpus repo and forget about it. Worked example for a security-research org producing
|
|
421
|
+
You'd use this when you have a stable set of frontmatter defaults that should apply to every conversion in a project. The file is auto-discovered by walking up from the current working directory, so you can drop one at the root of your corpus repo and forget about it. Worked example for a security-research org producing SAGE-conforming outputs:
|
|
411
422
|
|
|
412
423
|
```toml
|
|
413
424
|
[meta]
|
|
@@ -561,7 +572,7 @@ The two-lane design exists because Docling output is layout-correct (running agg
|
|
|
561
572
|
|
|
562
573
|
## Migrating from v0.7
|
|
563
574
|
|
|
564
|
-
v1.0 emits
|
|
575
|
+
v1.0 emits SAGE-compatible frontmatter — that's the one intended breaking change. v0.7's small frontmatter (`title`, `source_file`, `pages`, `type`, `word_count`) becomes a fuller block with `document_id`, `version`, `date`, `status`, `content_hash`, `token_estimate`, `authors`, and several other fields. The v0.7 fields are retained as any2md extension fields in the same block, so traceability is preserved, but downstream parsers that expected the old fixed shape will need to update. The body is now NFC-normalized with LF line endings, which means `content_hash` is reproducible. If you need bit-for-bit v0.7 output, pin `any2md==0.7.0`. The full field-by-field migration guide and behavior-change list lives in [docs/upgrading-from-0.7.md](docs/upgrading-from-0.7.md).
|
|
565
576
|
|
|
566
577
|
## Security
|
|
567
578
|
|
|
@@ -19,7 +19,7 @@ The full design rationale lives in [docs/superpowers/specs/2026-04-26-v1.0.3-emp
|
|
|
19
19
|
|
|
20
20
|
## What this is
|
|
21
21
|
|
|
22
|
-
any2md ingests heterogeneous source documents and emits Markdown with a fixed YAML frontmatter contract. The frontmatter shape is
|
|
22
|
+
any2md ingests heterogeneous source documents and emits Markdown with a fixed YAML frontmatter contract. The frontmatter shape is SAGE-compatible (Security Analysis and Guidance Exchange — a documented schema for LLM-consumable documents), so retrieval pipelines, embeddings jobs, and chunking utilities downstream can rely on field names, types, and a deterministic `content_hash` for cache invalidation. The body is NFC-normalized with LF line endings, and the heading hierarchy is guaranteed to start at H1 and not skip levels. The goal is one stable shape, regardless of whether a document started life as a scanned PDF, a Word doc, a Wikipedia page, or a plain text dump.
|
|
23
23
|
|
|
24
24
|
## Quick start
|
|
25
25
|
|
|
@@ -111,8 +111,8 @@ document_id: "" # empty unless you pass --aut
|
|
|
111
111
|
version: "1" # "1" for source files without an embedded version
|
|
112
112
|
date: "2026-03-15" # docx core props "modified" → ISO-8601
|
|
113
113
|
status: "draft" # always "draft" for converted documents
|
|
114
|
-
document_type: "" #
|
|
115
|
-
content_domain: [] #
|
|
114
|
+
document_type: "" # SAGE controlled vocab — empty for non-security docs
|
|
115
|
+
content_domain: [] # SAGE controlled vocab — empty array when unknown
|
|
116
116
|
authors: # extracted from docProps/core.xml dc:creator
|
|
117
117
|
- "Rock Lambros"
|
|
118
118
|
organization: "" # docProps/app.xml Company; empty when absent
|
|
@@ -133,7 +133,7 @@ produced_by: "Microsoft® Word for Microsoft 365" # software that produced the
|
|
|
133
133
|
---
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
Fields under `# any2md extension field` are retained from v0.7 for traceability and observability — they're not part of the
|
|
136
|
+
Fields under `# any2md extension field` are retained from v0.7 for traceability and observability — they're not part of the SAGE contract proper, but they live in the same frontmatter block. See [docs/output-format.md](docs/output-format.md) for the full field reference.
|
|
137
137
|
|
|
138
138
|
### Before / after
|
|
139
139
|
|
|
@@ -327,9 +327,18 @@ any2md -f -r ./corpus/
|
|
|
327
327
|
|
|
328
328
|
You'll see the OK lines as if the conversion were fresh; previously-written outputs are overwritten.
|
|
329
329
|
|
|
330
|
+
> **v1.1.0+:** when processing PDFs or DOCX files via the Docling
|
|
331
|
+
> backend, the converter (and its loaded model weights) is constructed
|
|
332
|
+
> once on the first PDF/DOCX in an invocation and reused for all
|
|
333
|
+
> subsequent files; subsequent files extract in ~0.5–1s rather than
|
|
334
|
+
> re-paying the ~2.5s cold-load tax. Library users embedding any2md
|
|
335
|
+
> in long-running processes should call `any2md.release_models()` or
|
|
336
|
+
> use `with any2md.docling_session():` — see
|
|
337
|
+
> `docs/troubleshooting.md`.
|
|
338
|
+
|
|
330
339
|
## The output format
|
|
331
340
|
|
|
332
|
-
Every file converted by any2md has the same frontmatter shape, regardless of source format. The shape is **
|
|
341
|
+
Every file converted by any2md has the same frontmatter shape, regardless of source format. The shape is **SAGE-compatible** — it matches the field names, types, and ordering of the Security Analysis and Guidance Exchange specification, but values aren't required to match SAGE's controlled vocabularies (most converted documents aren't security research). Fields that require a controlled vocabulary (`document_type`, `content_domain`, `tlp`) are emitted empty unless you supply them via `--meta` or `.any2md.toml`.
|
|
333
342
|
|
|
334
343
|
### Field auto-fill summary
|
|
335
344
|
|
|
@@ -374,7 +383,7 @@ You'll see the OK line as usual. The frontmatter of `Text/paper.md` will have `o
|
|
|
374
383
|
|
|
375
384
|
### `.any2md.toml`
|
|
376
385
|
|
|
377
|
-
You'd use this when you have a stable set of frontmatter defaults that should apply to every conversion in a project. The file is auto-discovered by walking up from the current working directory, so you can drop one at the root of your corpus repo and forget about it. Worked example for a security-research org producing
|
|
386
|
+
You'd use this when you have a stable set of frontmatter defaults that should apply to every conversion in a project. The file is auto-discovered by walking up from the current working directory, so you can drop one at the root of your corpus repo and forget about it. Worked example for a security-research org producing SAGE-conforming outputs:
|
|
378
387
|
|
|
379
388
|
```toml
|
|
380
389
|
[meta]
|
|
@@ -528,7 +537,7 @@ The two-lane design exists because Docling output is layout-correct (running agg
|
|
|
528
537
|
|
|
529
538
|
## Migrating from v0.7
|
|
530
539
|
|
|
531
|
-
v1.0 emits
|
|
540
|
+
v1.0 emits SAGE-compatible frontmatter — that's the one intended breaking change. v0.7's small frontmatter (`title`, `source_file`, `pages`, `type`, `word_count`) becomes a fuller block with `document_id`, `version`, `date`, `status`, `content_hash`, `token_estimate`, `authors`, and several other fields. The v0.7 fields are retained as any2md extension fields in the same block, so traceability is preserved, but downstream parsers that expected the old fixed shape will need to update. The body is now NFC-normalized with LF line endings, which means `content_hash` is reproducible. If you need bit-for-bit v0.7 output, pin `any2md==0.7.0`. The full field-by-field migration guide and behavior-change list lives in [docs/upgrading-from-0.7.md](docs/upgrading-from-0.7.md).
|
|
532
541
|
|
|
533
542
|
## Security
|
|
534
543
|
|
|
@@ -81,3 +81,25 @@ If you operate any2md in a hardened environment:
|
|
|
81
81
|
|
|
82
82
|
No publicly disclosed vulnerabilities to date. Future advisories will
|
|
83
83
|
appear at https://github.com/rocklambros/any2md/security/advisories.
|
|
84
|
+
|
|
85
|
+
## Trust boundaries
|
|
86
|
+
|
|
87
|
+
`any2md` is an operator-trusted CLI distributed via PyPI for desktop and CI use.
|
|
88
|
+
|
|
89
|
+
**Trusted:** the operator, their filesystem, their `--output-dir`, and the Python interpreter they invoke. CLI flags and `--meta key=val` arguments are operator-controlled.
|
|
90
|
+
|
|
91
|
+
**Untrusted:**
|
|
92
|
+
- URLs passed to `any2md` (may resolve to internal IPs, redirect, or carry credentials in userinfo/query).
|
|
93
|
+
- Documents (PDF/DOCX/HTML) passed for conversion (may embed crafted images, control characters, malicious metadata).
|
|
94
|
+
- `.any2md.toml` config files discovered via cwd-walking (an attacker who can drop a file in any ancestor directory can inject frontmatter overrides).
|
|
95
|
+
- HTTP responses from URLs (may carry hostile redirects, oversized bodies, control characters in headers).
|
|
96
|
+
|
|
97
|
+
**Out of scope:** multi-tenant service hosting; running `any2md` against attacker-supplied output directories; supply-chain compromise of pinned dependencies; protection against an operator who has chosen `--output-dir` pointing at a shared/symlinked directory.
|
|
98
|
+
|
|
99
|
+
## Dependency-drift policy
|
|
100
|
+
|
|
101
|
+
If `pip-audit` reports a NEW HIGH/CRITICAL vulnerability between baseline and release-cut, the policy is:
|
|
102
|
+
|
|
103
|
+
1. Bump the dep within its compatible range (regenerate `.devcontainer/requirements.lock` and `requirements.txt`) and re-cut the rc.
|
|
104
|
+
2. If no compatible fix exists, document in CHANGELOG `## Known issues` and ship only if the codepath is mitigated by other code (e.g., XXE blocked by `defusedxml`).
|
|
105
|
+
3. If neither is possible, hold the release until upstream lands a fix.
|