Last active
August 4, 2026 13:36
-
-
Save arad-g/64293508d2d913d9fa779d2f726368a4 to your computer and use it in GitHub Desktop.
Minimal reproduction of vulnerable Jinja2 template rendering
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| Minimal reproduction of the Hugging Face dataset-config renderer that was | |
| exploited in the July 2026 agent intrusion. | |
| The single endpoint renders an attacker-supplied string through Jinja2. Nothing | |
| useful is placed in the render context on purpose: no os, no subprocess, no sys. | |
| Exploitation therefore requires a real sandbox escape through Jinja2's own | |
| globals, which is what the intrusion used. | |
| """ | |
| from flask import Flask, request, jsonify | |
| from jinja2 import Template | |
| app = Flask(__name__) | |
| @app.route("/") | |
| def index(): | |
| return jsonify( | |
| { | |
| "service": "huggingface-dataset-renderer", | |
| "endpoint": "/render", | |
| "usage": "/render?template={{7*7}}", | |
| } | |
| ) | |
| @app.route("/render", methods=["GET", "POST"]) | |
| def render_endpoint(): | |
| """ | |
| Vulnerable Jinja2 endpoint. | |
| Mirrors the dataset config renderer: a field that should hold plain data is | |
| passed to Template().render() instead. Only config and app are exposed to | |
| the template, so os and subprocess have to be reached via the object graph. | |
| """ | |
| template_input = request.args.get("template", "") or request.form.get("template", "") | |
| if not template_input: | |
| return jsonify( | |
| { | |
| "error": "Please provide a template parameter", | |
| "usage": "/render?template={{7*7}}", | |
| } | |
| ), 400 | |
| try: | |
| result = Template(template_input).render(config=app.config, app=app) | |
| return jsonify({"engine": "Jinja2", "input": template_input, "result": str(result)}) | |
| except Exception as e: | |
| import traceback | |
| return jsonify( | |
| {"engine": "Jinja2", "error": str(e), "traceback": traceback.format_exc()} | |
| ), 500 | |
| if __name__ == "__main__": | |
| app.run(host="0.0.0.0", port=4000) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment