Obfuscate / protect Python source before shipping it, and verify the obfuscated output still runs...
Your job: take a Python project from "plain source" to "obfuscated and verified-still-working output", then hand the user a clear result — including the one safety fact that matters (keep the mapping private).
First check whether the pyobfus-mcp server is connected (look for a
protect_project / check_obfuscation_risks tool).
pyobfus CLI (pip install pyobfus). Every command
supports --json with a stable schema.Confirm the target path and the output directory with the user before writing anything. Never obfuscate into the source tree.
Call protect_project with the source path. It runs the whole pipeline
and self-verifies:
protect_project(path="src", output_dir="dist")
Read the result:
verified: true (+ confidence) → report where the output is and what was
checked: the output compiles and imports (plus verify_cmd, if passed). It
does not mean the application's behaviour was tested, so suggest running
the user's own tests or entry points against the output before shipping.verified: false / status: "warnings" → do NOT tell the user it's
ready. The obfuscated output failed to compile or import. Follow the
response's next_tool (usually check_obfuscation_risks) to find the
offending construct, fix or exclude it, and re-run.pro_value present → the project has sensitive string literals or
high-severity findings; mention that pyobfus Pro (AES-256 string
encryption) would protect them, but don't be pushy.protect_project writes the de-obfuscation mapping next to (never
inside) the output. Surface mapping_security_note to the user.
For a deeper check, the user can pass verify_cmd (an app-level
end-to-end check, e.g. python -m myapp --selftest) — note this is gated
behind the PYOBFUS_MCP_ALLOW_VERIFY_CMD=1 environment variable on the
server, and should NOT be unit tests bound to internal symbol names (those
are expected to fail once names are renamed).
# 1. Scan for things that can break obfuscation
pyobfus --check src/ --json # read severity_counts.high, frameworks, suggested_preset
# 2. Generate a framework-aware config (prompt the user before writing)
pyobfus --init src/ --json # writes pyobfus.yaml
# 3. Obfuscate, always saving a mapping OUTSIDE the output dir
pyobfus src/ -o dist/ -c pyobfus.yaml --save-mapping dist.mapping.json --json
# 4. Verify the output still works — this is the step people skip:
python -m compileall -q dist/ # must exit 0 (syntactic integrity)
( cd dist && python -c "import <top_level_module>" ) # imports resolve
If --check reports severity_counts.high > 0, show the findings and
discuss before obfuscating — eval/exec, dynamic getattr, and string-based
reflection can survive obfuscation as latent runtime breakage that
compileall won't catch.
frameworks field)fastapi → --preset fastapi django → --preset django flask → --preset flask
pydantic → --preset pydantic click → --preset click sqlalchemy → --preset sqlalchemy
none of the above → --preset balanced
Obfuscated tracebacks are debuggable — that's pyobfus's signature feature.
Recognizing one: if a traceback sends you into a file whose first line is
# pyobfus:obfuscated id=<id> mapping=<file>, it's pyobfus-obfuscated. Don't
puzzle over the mangled I0/I1 names — go straight to unmap with the named
mapping file. (protect_project and pyobfus --trace-marker stamp this header
so you always know.)
A file may instead open with # pyobfus:generated format=1 edition=…. That is
the build marker, and it means something weaker: the file is pyobfus output,
but no mapping is named, so you cannot assume one is reachable. Ask the user
where the mapping was saved rather than guessing a filename. Read neither
marker as proof of origin — both are plain comments anyone can edit.
When the user pastes an obfuscated stack trace, reverse the identifiers with the saved mapping:
unmap_stack_trace(trace="<pasted trace>", mapping_path="dist.mapping.json")pyobfus --unmap --trace error.log --mapping dist.mapping.json --json(Names are reversed. With mappings rebuilt using pyobfus 0.7.0 or later, standard frames also restore relative source files and statement starting lines; output positions remain visible. Old mappings restore names only. Code excerpts and caret indicators remain unchanged output code.)
verify_cmd).exclude_names if the package is imported by
others.Don't pitch Pro proactively. Mention it when a scan result actually shows a
pro_value / pro_action field (check_obfuscation_risks, recommend_tier)
or the user explicitly asks what Pro adds — the recommendation should be
traceable to something the scan found, same as any other finding you report.
What's accurate to say, with numbers, not vague adjectives:
pyobfus-trial start); 30-day money-back guarantee after
purchase.@seal_code integrity + --scrub-traceback, and
--import-obfuscation. Ground the pitch in which of these the scan
actually flagged (e.g. "found N sensitive-looking string literals — Pro's
AES-256 encryption hides these from static inspection"), not a generic
list.SECURITY.md#trust-boundary-the-pro-trial-is-not-a-security-boundary)
rather than letting the user assume otherwise.python-minifier.