claude-skills/scripts/smoke_exceptions.txt
Claude 9ed709aef3
fix(book-to-skill): guard EPUB XML, cap zip expansion, refuse symlinked trees
Addresses the second automated review on PR #941. All three code findings
verified against the actual behaviour, not just patched.

[Medium] shutil.copytree dereferenced symlinks the validator never saw. The
validator checks SKILL.md, the three supporting files and chapters/*.md; the
copy then followed a link anywhere else in the tree (assets/, any subdirectory)
and baked the target's real content into a package that can go out as
--distribution shareable. _assert_no_symlinks() now walks the whole tree and
refuses, and runs BEFORE the validation branch so --skip-validation cannot
bypass it. copytree also passes symlinks=True so loosening that check later
cannot silently reintroduce dereferencing.
Verified: a symlink in assets/ pointing at a secret file is refused both with
and without --skip-validation, the secret never lands in a package, and a clean
tree still emits.

[Medium] The DOCX XXE/entity guard did not extend to EPUB's ebooklib path.
Upstream hardened DOCX only. EPUB is the same zip-of-XML shape and ebooklib —
one of the packages this skill recommends installing — parsed container.xml,
the OPF and content docs with no equivalent pre-check. The guard moved to a new
book_to_skill/zip_safety.py and now runs for both formats.
Verified: an EPUB whose OPF declares an entity is refused; a clean EPUB still
extracts and detects its chapter.

[Low] No size cap before decompressing zip members. Every archive read now goes
through safe_read(), which checks the declared uncompressed size and the
compression ratio against the central directory before decompressing, and
charges actual bytes against a per-archive budget so a lying directory cannot
get past it either.
Verified: a 200 MB / 1029x bomb is refused at ~14 MB peak RSS instead of being
materialized.

[Low] The PR body's "12 numbered items" was stale against README's list. Fixed
in the PR description; the in-repo count is synced to 18 across plugin.json,
CLAUDE.md and CHANGELOG.

Recorded as deviations 17 and 18. Counters: tools 662 -> 663 (zip_safety.py);
that module is allowlisted in smoke_exceptions.txt like its siblings.

All gates green: compileall, check_paths --all, check_dual_publish,
smoke_scripts (0 failed), derive_counters --check, check_plugin_json --all
(0 FAIL). Security auditor unchanged at 0 critical / 4 high.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zu9Gmm9S78c2t3kDLnpPX
2026-08-05 07:31:04 +00:00

91 lines
7 KiB
Text

# By-design exceptions for scripts/smoke_scripts.py (gate G8).
# Format: <path relative to repo root> # reason
# These scripts intentionally do not implement a CLI `--help` contract.
# Fixed-contract evaluators: argv contract is fixed by engineering/autoresearch-agent
# (called as `evaluator.py <target-file>`; exit code IS the metric channel).
engineering/autoresearch-agent/evaluators/benchmark_size.py # fixed-contract autoresearch evaluator
engineering/autoresearch-agent/evaluators/benchmark_speed.py # fixed-contract autoresearch evaluator
engineering/autoresearch-agent/evaluators/build_speed.py # fixed-contract autoresearch evaluator
engineering/autoresearch-agent/evaluators/llm_judge_content.py # fixed-contract autoresearch evaluator
engineering/autoresearch-agent/evaluators/llm_judge_copy.py # fixed-contract autoresearch evaluator
engineering/autoresearch-agent/evaluators/llm_judge_prompt.py # fixed-contract autoresearch evaluator
engineering/autoresearch-agent/evaluators/memory_usage.py # fixed-contract autoresearch evaluator
engineering/autoresearch-agent/evaluators/test_pass_rate.py # fixed-contract autoresearch evaluator
# Claude Code hooks: invoked by the hook runner with a JSON payload on stdin,
# not as CLI tools (exit 2 is a signaling channel for PreToolUse).
engineering/security-guidance/hooks/security_reminder_hook.py # PreToolUse stdin hook
productivity/handoff/hooks/session_start.py # SessionStart stdin hook
productivity/handoff/hooks/session_end.py # SessionEnd stdin hook
# skillopt_sleep engine (engineering/skillopt-sleep, vendored from
# microsoft/SkillOpt): internal package modules, not standalone CLI scripts.
# The real entry point is `python -m skillopt_sleep` (verified: exits 0 on
# --help). Running any single file directly breaks two ways: (1)
# skillopt_sleep/types.py shadows the stdlib `types` module once the file's
# own directory is prepended to sys.path, which cascades into
# "cannot import name 'GenericAlias'/'asdict' from partially initialized
# module" for every sibling that (transitively) imports dataclasses/typing;
# (2) experiments/*.py use absolute `from skillopt_sleep.x import y` imports
# that require the package's parent dir on sys.path, which standalone
# execution doesn't provide.
engineering/skillopt-sleep/skillopt_sleep/__main__.py # python -m skillopt_sleep entry point only
engineering/skillopt-sleep/skillopt_sleep/backend.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/budget.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/config.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/consolidate.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/cycle.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/dream.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/experiments/gbrain_bench.py # requires skillopt_sleep on sys.path
engineering/skillopt-sleep/skillopt_sleep/experiments/personas.py # requires skillopt_sleep on sys.path
engineering/skillopt-sleep/skillopt_sleep/experiments/run_experiment.py # requires skillopt_sleep on sys.path
engineering/skillopt-sleep/skillopt_sleep/experiments/run_gbrain.py # requires skillopt_sleep on sys.path
engineering/skillopt-sleep/skillopt_sleep/experiments/run_transfer.py # requires skillopt_sleep on sys.path
engineering/skillopt-sleep/skillopt_sleep/experiments/sweep.py # requires skillopt_sleep on sys.path
engineering/skillopt-sleep/skillopt_sleep/gate.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/harvest.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/harvest_codex.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/harvest_sources.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/judges.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/llm_miner.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/memory.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/mine.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/replay.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/rollout.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/scheduler.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/slow_update.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/staging.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/state.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/tasks_file.py # internal module, types.py shadow
engineering/skillopt-sleep/skillopt_sleep/types.py # internal module; module itself IS the stdlib-name collision
# book_to_skill extraction library (engineering/book-to-skill, vendored from
# virgiliojr94/book-to-skill): internal package modules imported as
# `book_to_skill.*`, not standalone CLI scripts.
#
# Listed here are the modules that FAIL the G8 probe — those with absolute
# `book_to_skill.*` imports, which raise `No module named 'book_to_skill'` before
# they could parse a flag. The package's remaining modules (config.py,
# exceptions.py, sanitize.py, parsers/__init__.py, parsers/pdf.py,
# parsers/text.py) are equally not-CLIs; they pass G8 only because they have no
# argv handling at all, so `--help` no-ops and exits 0. This list is therefore
# "modules the probe trips on", not "modules that aren't CLIs" — the latter is
# the whole package.
#
# The four real entry points DO implement the CLI contract and are smoke-tested
# normally:
# scripts/extract_document.py, book_skill_validator.py,
# token_budget_estimator.py, skill_plugin_emitter.py (--help + --sample, exit 0)
# Unlike the skillopt_sleep case below, no module here shadows a stdlib name:
# upstream's parsers/html.py was renamed to parsers/html_text.py precisely so
# `import html.parser` cannot resolve to a sibling.
engineering/book-to-skill/skills/book-to-skill/scripts/book_to_skill/__init__.py # package init, not a CLI
engineering/book-to-skill/skills/book-to-skill/scripts/book_to_skill/dependencies.py # internal module
engineering/book-to-skill/skills/book-to-skill/scripts/book_to_skill/utils.py # internal module
engineering/book-to-skill/skills/book-to-skill/scripts/book_to_skill/zip_safety.py # internal module
engineering/book-to-skill/skills/book-to-skill/scripts/book_to_skill/parsers/calibre.py # internal module
engineering/book-to-skill/skills/book-to-skill/scripts/book_to_skill/parsers/docx.py # internal module
engineering/book-to-skill/skills/book-to-skill/scripts/book_to_skill/parsers/epub.py # internal module
engineering/book-to-skill/skills/book-to-skill/scripts/book_to_skill/parsers/html_text.py # internal module
engineering/book-to-skill/skills/book-to-skill/scripts/book_to_skill/parsers/rtf.py # internal module