# Risk map — microsoft/markitdown

> Canonical page: https://codevetter.com/unpack/markitdown/risk-map

# Risk map — microsoft/markitdown

Scanned commit b8f79c57ebc0044be41323d89b2a45d3fda8460e (2026-09-21). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

The dominant risk theme is I/O breadth: the library deliberately performs filesystem, network, and subprocess I/O with the caller's privileges, and the README explicitly warns callers to sanitize inputs. Specific hotspots: unrestricted HTTP fetching (SSRF) via convert_uri, file:// URI local file reads, unbounded in-memory buffering of remote/stream content, subprocess invocation of exiftool, plugin code execution via entry points, and an unauthenticated MCP HTTP mode. Defusedxml and an exiftool version check show some hardening is present.
- convert_uri fetches arbitrary http(s) URLs with the process's requests session and no allowlist — the README's Security Considerations section tells integrators to block private/loopback/link-local/metadata-service addresses themselves; nothing in code enforces this. — [packages/markitdown/src/markitdown/_markitdown.py#L517-527](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown/src/markitdown/_markitdown.py#L517-L527) [README.md#L394-400](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/README.md#L394-L400)
- file:// URIs resolve to local paths and convert_local opens them; file_uri_to_path rejects non-localhost netlocs and UNC/device paths but permits any absolute local path — the MCP tool exposes this to remote clients in HTTP mode. — [packages/markitdown/src/markitdown/_uri_utils.py#L15-39](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown/src/markitdown/_uri_utils.py#L15-L39) [packages/markitdown-mcp/src/markitdown_mcp/__main__.py#L19-24](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown-mcp/src/markitdown_mcp/__main__.py#L19-L24)
- The MCP server's --http mode has NO authentication; the code itself prints a warning when --host is non-loopback stating any reachable user 'can read files and fetch network resources accessible to this user'. — [packages/markitdown-mcp/src/markitdown_mcp/__main__.py#L114-127](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown-mcp/src/markitdown_mcp/__main__.py#L114-L127)
- Remote and stream inputs are buffered fully into memory: convert_response iter_content into BytesIO and convert_stream buffers non-seekable streams — a hostile or huge response/stream can exhaust memory. — [packages/markitdown/src/markitdown/_markitdown.py#L594-598](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown/src/markitdown/_markitdown.py#L594-L598) [packages/markitdown/src/markitdown/_markitdown.py#L435-444](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown/src/markitdown/_markitdown.py#L435-L444)
- exiftool_metadata shells out to subprocess.run([exiftool_path, ...]); the exiftool path is resolved from the EXIFTOOL_PATH env var, kwargs, or a PATH search restricted to well-known directories, and a version gate rejects exiftool <12.24 citing CVE-2021-22204. — [packages/markitdown/src/markitdown/converters/_exiftool.py#L20-47](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown/src/markitdown/converters/_exiftool.py#L20-L47) [packages/markitdown/src/markitdown/_markitdown.py#L202-224](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown/src/markitdown/_markitdown.py#L202-L224)
- Plugin loading executes arbitrary installed Python code: entry_points(group='markitdown.plugin').load() then register_converters(); failures are only warned about. Plugins are off by default but -p/--use-plugins or enable_plugins=True activates all of them. — [packages/markitdown/src/markitdown/_markitdown.py#L113-130](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown/src/markitdown/_markitdown.py#L113-L130) [packages/markitdown/src/markitdown/_markitdown.py#L302-320](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown/src/markitdown/_markitdown.py#L302-L320)
- The _convert loop asserts (not raises) on stream-position invariants ('assert cur_pos == file_stream.tell()') — running under python -O disables these checks, silently removing the safety net that protects converters from a misbehaving accepts(). — [packages/markitdown/src/markitdown/_markitdown.py#L625-669](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown/src/markitdown/_markitdown.py#L625-L669)
- Each MCP convert_to_markdown call constructs a brand-new MarkItDown() — re-registering ~17 converters and re-instantiating magika.Magika() (a model load) per request, which is a latency/CPU cost on every tool call. (inferred) — [packages/markitdown-mcp/src/markitdown_mcp/__main__.py#L22](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown-mcp/src/markitdown_mcp/__main__.py#L22) [packages/markitdown/src/markitdown/_markitdown.py#L168](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown/src/markitdown/_markitdown.py#L168)
- BeautifulSoup 'html.parser' and defusedxml are used for untrusted markup/XML (EPUB container/OPF via defusedxml.minidom), but docx pre-processing parses document.xml with BeautifulSoup and xml.etree.ElementTree — XML bomb surface depends on those parsers' defaults rather than defusedxml. (inferred) — [packages/markitdown/src/markitdown/converters/_epub_converter.py#L5-71](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown/src/markitdown/converters/_epub_converter.py#L5-L71) [packages/markitdown/src/markitdown/converter_utils/docx/pre_process.py#L1-10](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown/src/markitdown/converter_utils/docx/pre_process.py#L1-L10)
- HtmlConverter's markdownify path can hit RecursionError on deeply nested HTML; it falls back to get_text() unless a 'strict' kwarg is set — acceptable degradation but callers get silently different output shape. — [packages/markitdown/src/markitdown/converters/_html_converter.py#L68-81](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown/src/markitdown/converters/_html_converter.py#L68-L81)
- ZipConverter swallows FileConversionException/UnsupportedFormatException per member and the archive is read via zipObj.read(entry) fully into memory per entry; nested zips recurse through the same converter stack without a depth limit visible in this file. — [packages/markitdown/src/markitdown/converters/_zip_converter.py#L108-128](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown/src/markitdown/converters/_zip_converter.py#L108-L128)
- Error detail asymmetry: FileConversionException aggregates per-converter exception messages (which can include SDK/internal detail) while the MCP layer deliberately sanitizes to generic ToolError strings — good for the MCP boundary, but library callers receive verbose internals. — [packages/markitdown/src/markitdown/_exceptions.py#L52-82](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown/src/markitdown/_exceptions.py#L52-L82) [packages/markitdown-mcp/src/markitdown_mcp/__main__.py#L27-47](https://github.com/microsoft/markitdown/blob/b8f79c57ebc0044be41323d89b2a45d3fda8460e/packages/markitdown-mcp/src/markitdown_mcp/__main__.py#L27-L47)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
