Skip to content

feat(cli): trust gating with --untrusted / --trust-level#26

Merged
frederikbeimgraben merged 1 commit into
mainfrom
feat/cli-trust
Jun 4, 2026
Merged

feat(cli): trust gating with --untrusted / --trust-level#26
frederikbeimgraben merged 1 commit into
mainfrom
feat/cli-trust

Conversation

@frederikbeimgraben
Copy link
Copy Markdown
Owner

Why

Red-Team finding #9: the pytex CLI has no trust gating. By default it executes .py inputs, evaluates .tex pytex(...) replacements and Markdown eval comments, and runs tectonic with shell-escape on. That is RCE-by-design — fine for your own documents, dangerous on foreign/untrusted source. The trust model previously lived only in the pytex_api layer.

What

  • CLI = TRUSTED context, stated explicitly in the module docstring, the argparse description/help, the README, and the wiki.
  • --untrusted (shorthand for --trust-level untrusted) and --trust-level {trusted,sandboxed,untrusted} (mutually exclusive group). Default stays trusted → fully backwards-compatible.
  • Non-trusted levels route the build through the existing pytex_api trust policy via render_blob, not duplicated logic:
    • .py / .tex.py refused (no exec),
    • .tex pytex(...) markers + Markdown eval comments stay inert,
    • shell-escape forced off, package allowlist enforced (dangerous packages like minted rejected),
    • resource limits; sandboxed additionally requires the Podman sandbox for PDFs.

How it docks onto pytex_api

_run dispatches on cfg.trust: TRUSTED keeps the in-process pipeline unchanged; any other level calls _run_untrusted, which reads the source bytes, maps the suffix to an InputKind (.pyTEX_PY, .texTEX, .mdMARKDOWN), builds a BuildRequest with the chosen TrustLevel, and calls render_blob. ApiError is mapped to BuildError so main reports it like any other failure. TrustLevel is imported from pytex_api._models (not the package root) to avoid a circular import — pytex_api/__init__ imports pytex_builder.console.

Tested vectors

  • --untrusted on a .py that writes a file on import → blocked at the trust gate (exit 1, output absent, side effect never runs, error names TRUSTED).
  • --untrusted on .tex requesting minted (the shell-escape package) → rejected.
  • --untrusted on benign .tex → renders, but the pytex(...) marker survives verbatim (inert).
  • Trusted default still executes .py (regression guard).
  • Flag parsing: default trusted, --untrusted, --trust-level sandboxed, and mutual exclusion.

Status

  • Full suite: 903 passed, 3 skipped.
  • basedpyright src (configured root): 0 errors, 0 warnings.
  • ruff format --check + ruff check: green.
  • Wiki "CLI and Input Modes" page updated with a trust section (pushed).
  • CHANGELOG entry added.

🤖 Generated with Claude Code

The pytex CLI executes .py inputs, evaluates .tex pytex(...) replacements
and Markdown eval comments, and enables shell-escape by default. That is
remote-code-execution by design and is safe only for first-party documents
(Red-Team finding #9). The trust model previously existed only in the
pytex_api layer.

Make the CLI a TRUSTED context explicitly, and add a --untrusted flag
(shorthand for --trust-level untrusted) plus --trust-level {trusted,
sandboxed,untrusted}. Non-trusted levels route the build through the
existing pytex_api trust policy via render_blob rather than duplicating
the gating: no Python exec, inert .tex/Markdown code surfaces, shell-escape
forced off, package allowlist, resource limits, and (sandboxed) the Podman
sandbox. The default stays trusted, so existing invocations are unchanged.

TrustLevel is imported from pytex_api._models (not the package root) to
avoid a circular import: pytex_api/__init__ imports pytex_builder.console.

Document the trust model in the CLI help text, README, and wiki, and add
tests covering the blocked .py-exec and shell-escape-package vectors, the
inert .tex path, the unchanged trusted default, and the new flags.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@frederikbeimgraben frederikbeimgraben merged commit 930b955 into main Jun 4, 2026
1 check passed
@frederikbeimgraben frederikbeimgraben deleted the feat/cli-trust branch June 4, 2026 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant