From 0a2ee71cccbf98ef9bcc1f1cd093c08357ebcd89 Mon Sep 17 00:00:00 2001 From: JiahuiGu Date: Mon, 25 May 2026 16:51:21 +0800 Subject: [PATCH] fix(skill): guard pickle.loads in darwinian-evolver show_snapshot with explicit flag (#29276) show_snapshot.py unpickled a user-supplied path unconditionally. pickle.loads is equivalent to arbitrary code execution, so a snapshot from an untrusted source = RCE. Require an explicit --i-trust-this-file acknowledgement before calling pickle.loads, and emit a stderr warning when proceeding. Co-authored-by: Jiahui-Gu --- .../scripts/show_snapshot.py | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/optional-skills/research/darwinian-evolver/scripts/show_snapshot.py b/optional-skills/research/darwinian-evolver/scripts/show_snapshot.py index 5dd559570dd..bae4bfae69a 100644 --- a/optional-skills/research/darwinian-evolver/scripts/show_snapshot.py +++ b/optional-skills/research/darwinian-evolver/scripts/show_snapshot.py @@ -25,18 +25,41 @@ def main() -> int: help="Organism attribute to display. Defaults to the first str field found.", ) ap.add_argument("--top", type=int, default=None, help="Show only top N by score.") + ap.add_argument( + "--i-trust-this-file", + action="store_true", + help=( + "Required acknowledgement that the snapshot is from a trusted source. " + "pickle.loads executes arbitrary code embedded in the file (RCE) and " + "must NEVER be run on snapshots received from untrusted parties." + ), + ) args = ap.parse_args() if not args.snapshot.exists(): sys.exit(f"snapshot not found: {args.snapshot}") + if not args.i_trust_this_file: + sys.exit( + "refusing to unpickle: pickle.loads is equivalent to executing arbitrary " + "code from the snapshot file. Only proceed if you created/control this " + "file, then re-run with --i-trust-this-file.\n" + f" file: {args.snapshot}" + ) + + print( + f"WARNING: unpickling {args.snapshot} — this executes code embedded in the " + "file. Only safe for snapshots you produced yourself.", + file=sys.stderr, + ) + # The outer pickle wraps a dict; the inner pickle contains the actual organism # objects, which must be importable under their original dotted path. If you # ran a custom driver, make sure its module is on sys.path before calling this. - outer = pickle.loads(args.snapshot.read_bytes()) + outer = pickle.loads(args.snapshot.read_bytes()) # noqa: S301 — gated by --i-trust-this-file if not isinstance(outer, dict) or "population_snapshot" not in outer: sys.exit("not a darwinian-evolver snapshot (no population_snapshot key)") - inner = pickle.loads(outer["population_snapshot"]) + inner = pickle.loads(outer["population_snapshot"]) # noqa: S301 — gated by --i-trust-this-file pairs = inner["organisms"] # list of (Organism, EvaluationResult) print(f"# organisms: {len(pairs)}\n")