From dc014c537702089be3aadcf9dca4c12dd9241395 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Mon, 24 Aug 2026 11:37:32 -0700 Subject: [PATCH] [eric] skills: an export never ships a venv, and a licence id is not an API key (ENG-401) Both halves diagnosed by Haik Decie. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_018foyDoK19jjbYdudfzQVkZ --- backend/apps/swarm/entities/skills.py | 13 ++++++++- backend/common/secret_scan.py | 6 +++- backend/tests/test_secret_scan_shapes.py | 34 ++++++++++++++++++++++ backend/tests/test_skill_export_prunes.py | 35 +++++++++++++++++++++++ 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 backend/tests/test_secret_scan_shapes.py create mode 100644 backend/tests/test_skill_export_prunes.py diff --git a/backend/apps/swarm/entities/skills.py b/backend/apps/swarm/entities/skills.py index f8134a3c..013bb9ae 100644 --- a/backend/apps/swarm/entities/skills.py +++ b/backend/apps/swarm/entities/skills.py @@ -7,6 +7,8 @@ but the body still rides the central scrub in case someone pasted a token in.""" from __future__ import annotations import os + +from backend.apps.outputs.workspace_io import WALK_SKIP_DIRS import shutil from backend.apps.skills import skills as store @@ -95,9 +97,18 @@ class SkillExportable: def p_read_supporting_files(skill_dir: str) -> dict[str, bytes]: - """Every file in a skill folder except SKILL.md, as {relpath: bytes}.""" + """Every file in a skill folder except SKILL.md, as {relpath: bytes}. + + Prunes the same build/venv dirs the app exporter has always pruned. This walker bound `dirs` + and never used it, so exporting a skill with a Python venv swept every file under `.venv/` into + the bundle: a python3.13 tree with absolute paths baked in, dead on any other machine, and big + enough that one vendored SPDX licence list tripped the secret scanner and hard-blocked the + export with advice ("remove the secret") that no user could follow. Credit: Haik Decie, who + diagnosed both halves. + """ out: dict[str, bytes] = {} for root, p_dirs, names in os.walk(skill_dir): + p_dirs[:] = [d for d in p_dirs if d not in WALK_SKIP_DIRS] for n in names: full = os.path.join(root, n) rel = os.path.relpath(full, skill_dir) diff --git a/backend/common/secret_scan.py b/backend/common/secret_scan.py index 1bcd0188..dc506ba7 100644 --- a/backend/common/secret_scan.py +++ b/backend/common/secret_scan.py @@ -15,7 +15,11 @@ REDACTED = "[redacted]" # Literal-secret shapes someone might paste into a file, skill body, or setting. SECRET_SHAPE_PATTERNS = ( re.compile(r"sk-ant-[A-Za-z0-9_\-]{16,}"), - re.compile(r"sk-[A-Za-z0-9_\-]{16,}"), + # A real sk- key carries a long DASHLESS run; dashes were counted as key material, so the SPDX + # licence id "Asterisk-linking-protocols-exception" read as an OpenAI key and hard-blocked a + # skill export with advice ("remove the secret") that no user could act on. Requiring the run + # keeps every real shape (sk-proj-<48>, sk-ant-api03-<95>) and drops dictionary-words-with-dashes. + re.compile(r"sk-[A-Za-z0-9_\-]*[A-Za-z0-9_]{20,}"), re.compile(r"AIza[A-Za-z0-9_\-]{20,}"), # Google API key shape re.compile(r"gh[pousr]_[A-Za-z0-9]{20,}"), # GitHub tokens re.compile(r"Bearer\s+[A-Za-z0-9._\-]{16,}"), diff --git a/backend/tests/test_secret_scan_shapes.py b/backend/tests/test_secret_scan_shapes.py new file mode 100644 index 00000000..579c529e --- /dev/null +++ b/backend/tests/test_secret_scan_shapes.py @@ -0,0 +1,34 @@ +"""The scanner blocks an export, so a false positive costs the user something they cannot fix. + +Reported by Haik Decie: exporting a skill failed with "a secret-shaped value is in +.../packaging/licenses/_spdx.py; remove it" and there was no secret to remove. The SPDX licence id +`Asterisk-linking-protocols-exception` contains `sk-linking-protocols-exception`, and the pattern +counted dashes as key material. +""" + +from backend.common.secret_scan import looks_secret, redact_secret_shapes + + +def test_a_licence_identifier_is_not_a_key(): + assert looks_secret("Asterisk-linking-protocols-exception") is False + assert looks_secret("sk-linking-protocols-exception") is False + + +def test_real_key_shapes_are_still_caught(): + # The control that keeps this from being a blanket weakening. + assert looks_secret("sk-proj-" + "a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6q7R8s9T0") is True + assert looks_secret("sk-ant-api03-" + "x" * 95) is True + assert looks_secret("sk-" + "A1b2C3d4E5f6G7h8I9j0K1l2") is True + assert looks_secret("AIza" + "B" * 30) is True + assert looks_secret("ghp_" + "c" * 30) is True + + +def test_other_dictionary_dashed_words_stay_clear(): + for benign in ("task-list-runner-exception", "disk-usage-report-helper", + "risk-scoring-model-weights", "sk-a-b-c-d-e-f"): + assert looks_secret(benign) is False, benign + + +def test_redaction_still_removes_a_real_key(): + out = redact_secret_shapes("key=sk-proj-" + "z" * 40) + assert "sk-proj-" not in out and "[redacted]" in out diff --git a/backend/tests/test_skill_export_prunes.py b/backend/tests/test_skill_export_prunes.py new file mode 100644 index 00000000..e6499a16 --- /dev/null +++ b/backend/tests/test_skill_export_prunes.py @@ -0,0 +1,35 @@ +"""A skill export must not ship a venv. + +Reported by Haik Decie: exporting a skill with a Python venv failed on a secret-shaped value inside +`.venv/.../packaging/licenses/_spdx.py`. Two stacked bugs; this pins the root cause. The walker +bound `dirs` and never pruned it, so it swept a python3.13 tree with absolute paths baked in, dead +on any other machine, into the bundle. The app exporter has always pruned the same set. +""" + +import os + +from backend.apps.swarm.entities.skills import p_read_supporting_files +from backend.apps.outputs.workspace_io import WALK_SKIP_DIRS + + +def test_a_venv_never_reaches_the_bundle(tmp_path): + skill = tmp_path / "s" + (skill / ".venv" / "lib" / "python3.13" / "site-packages").mkdir(parents=True) + (skill / ".venv" / "lib" / "python3.13" / "site-packages" / "_spdx.py").write_text("x") + (skill / "node_modules").mkdir() + (skill / "node_modules" / "big.js").write_text("x") + (skill / "helper.py").write_text("real content") + (skill / "SKILL.md").write_text("# skill") + + out = p_read_supporting_files(str(skill)) + assert "helper.py" in out, "real supporting files must still ship" + assert not any(".venv" in k for k in out), f"venv leaked: {list(out)}" + assert not any("node_modules" in k for k in out) + assert "SKILL.md" not in out + + +def test_it_prunes_the_same_set_the_app_exporter_does(): + # One definition of "do not ship this", not two that can drift. + src = open("backend/apps/swarm/entities/skills.py").read() + assert "WALK_SKIP_DIRS" in src + assert ".venv" in WALK_SKIP_DIRS and "node_modules" in WALK_SKIP_DIRS