#356 reported that skills can ship clean decoy .py sources plus malicious __pycache__/*.pyc (PEP 552 UNCHECKED_HASH) and still score SAFE, because discovery skips __pycache__ and analysis never inspects bytecode.
#357 closed that by adding SC8 (Shipped Python Bytecode): detect __pycache__ / .pyc / .pyo presence and floor the risk score to block install.
That is a useful mitigation for one known hiding place. It is not a fix for the root cause.
The root cause is an incomplete denylist of paths/artifacts that the scanner refuses to analyze, while the agent runtime can still load or execute content from those locations (or from similarly skipped locations).
CWE mapping:
- CWE-184: Incomplete List of Disallowed Items - the scanner maintains an explicit skip/denylist (
_SKIP_DIRS, binary extensions, SC8’s own walk skips, etc.). SC8 only adds one more special case (__pycache__ / .pyc) to that list’s alarm path. Other denylisted items remain silently unscanned and unflagged.
- CWE-693: Protection Mechanism Failure - SkillSpector’s multi-layer analysis (static + optional LLM) is the protection mechanism. Anything discovery excludes never reaches those layers. Presence-alerting for one excluded class does not restore the protection for the rest.
SC8’s own walker still skips .git, node_modules, .venv, venv, .tox, .pytest_cache - so even the “fail closed on bytecode” path can miss bytecode nested under those trees. The same architectural pattern that made __pycache__ invisible still applies to other skipped names.
Why SC8 is incomplete
Before #357:
discover → skip __pycache__ → analyze decoy .py → SAFE
runtime → load malicious .pyc → payload executes
After #357:
discover → still skip __pycache__ (contents never analyzed)
SC8 → see __pycache__ / .pyc exist → score floor → DO_NOT_INSTALL
What changed: an alarm on a specific denylist entry.
What did not change:
- Bytecode contents are still not analyzed (no disassembly / no
.py↔.pyc integrity check).
- Other
_SKIP_DIRS entries remain silent skips with no equivalent floor.
- Hidden files / other out-of-scope artifacts remain a parallel evasion surface (same “not analyzed ⇒ not scored” property).
- The fix is name-specific, not property-based (“runtime-reachable + unanalyzed ⇒ block”).
The next bypass does not need __pycache__. It only needs any other skipped or unscanned location the runtime (or the skill’s own loader) can still reach - the same class of failure as before SC8, with a different label on the folder.
Incomplete denylist (non-exhaustive)
These are illustrative of the same class, not a claim that each is an identical .pyc import smuggle today:
| Skipped / unscanned surface |
Why it matters |
__pycache__ / .pyc |
Addressed by SC8 presence only - contents still blind |
.git |
Still in skip lists; can be a file or tree; never sent to analyzers/LLM |
node_modules |
Still skipped by discovery and by SC8’s own walk |
.venv / venv |
Same |
.tox / .pytest_cache |
Same |
| Dotfiles / other OUT_OF_SCOPE paths |
Never enter components; no SC8-style floor |
| Other binary / non-text artifacts the agent may execute or load |
Hashed or skipped; not semantically analyzed |
Hard-coding SC9 for node_modules, SC10 for .git, … repeats the denylist mistake. Attackers only need the next name that is skipped but still reachable.
Expected Behavior
The protection mechanism should be based on a durable invariant, for example:
- Do not silently exclude runtime-reachable artifacts from analysis - or
- Fail closed on any unanalyzed, potentially executable/loadable artifact (agnostic to
__pycache__ vs node_modules vs .git), or
- For Python specifically: analyze bytecode (disassemble / verify against companion
.py), not merely detect that bytecode files exist.
Presence detection for one known cache directory is a stopgap, not that invariant.
Actual Behavior
Suggested direction
Prefer property-based handling over growing SC* special cases:
IF artifact is excluded from content analysis
AND artifact is under the skill install/load path
AND artifact type is executable / importable / loader-reachable
THEN emit a blocking finding (or analyze it)
Alternatively, remove security-sensitive paths from silent skip lists and treat them as first-class scan inputs (with size/ledger guards as needed).
SC8 can remain as a Python-specific signal, but it should not be treated as closing the incomplete-denylist issue.
References
Environment
Checklist
#356 reported that skills can ship clean decoy
.pysources plus malicious__pycache__/*.pyc(PEP 552UNCHECKED_HASH) and still score SAFE, because discovery skips__pycache__and analysis never inspects bytecode.#357 closed that by adding SC8 (Shipped Python Bytecode): detect
__pycache__/.pyc/.pyopresence and floor the risk score to block install.That is a useful mitigation for one known hiding place. It is not a fix for the root cause.
The root cause is an incomplete denylist of paths/artifacts that the scanner refuses to analyze, while the agent runtime can still load or execute content from those locations (or from similarly skipped locations).
CWE mapping:
_SKIP_DIRS, binary extensions, SC8’s own walk skips, etc.). SC8 only adds one more special case (__pycache__/.pyc) to that list’s alarm path. Other denylisted items remain silently unscanned and unflagged.SC8’s own walker still skips
.git,node_modules,.venv,venv,.tox,.pytest_cache- so even the “fail closed on bytecode” path can miss bytecode nested under those trees. The same architectural pattern that made__pycache__invisible still applies to other skipped names.Why SC8 is incomplete
Before #357:
After #357:
What changed: an alarm on a specific denylist entry.
What did not change:
.py↔.pycintegrity check)._SKIP_DIRSentries remain silent skips with no equivalent floor.The next bypass does not need
__pycache__. It only needs any other skipped or unscanned location the runtime (or the skill’s own loader) can still reach - the same class of failure as before SC8, with a different label on the folder.Incomplete denylist (non-exhaustive)
These are illustrative of the same class, not a claim that each is an identical
.pycimport smuggle today:__pycache__/.pyc.gitnode_modules.venv/venv.tox/.pytest_cacheHard-coding SC9 for
node_modules, SC10 for.git, … repeats the denylist mistake. Attackers only need the next name that is skipped but still reachable.Expected Behavior
The protection mechanism should be based on a durable invariant, for example:
__pycache__vsnode_modulesvs.git), or.py), not merely detect that bytecode files exist.Presence detection for one known cache directory is a stopgap, not that invariant.
Actual Behavior
__pycache__/.pyc→ SC8 HIGH + score floor → install blocked (good for this PoC only).Suggested direction
Prefer property-based handling over growing SC* special cases:
Alternatively, remove security-sensitive paths from silent skip lists and treat them as first-class scan inputs (with size/ledger guards as needed).
SC8 can remain as a Python-specific signal, but it should not be treated as closing the incomplete-denylist issue.
References
Environment
Checklist
__pycache__PoC alone