SkillSpector inventories hidden regular files and inspects ZIP-compatible content locally. The container is recognized from its bytes and internal structure rather than its filename extension. Supported document containers are DOCX, XLSX, and PPTX; generic ZIP and nested ZIP-compatible members use the same traversal policy.
Nested members use a stable virtual path that retains their full provenance:
outer-file!/nested.zip!/scripts/setup.sh
JSON component rows preserve this virtual path in path, matching finding locations and
inspection-ledger entries. Archive members also expose their existing inventory provenance:
{
"path": "bundle.whl!/package/module.py",
"type": "python",
"executable": true,
"outer_path": "bundle.whl",
"nested_path": "package/module.py",
"container_type": "zip",
"container_ancestry": ["zip"],
"container_depth": 1
}outer_path names the physical container relative to that component's source root. nested_path
retains the full member chain inside it, including any nested !/ boundaries. Each member keeps
its own component row, executable flag, type, line count, and byte size. Ordinary files do not gain
archive fields merely because their names contain !/.
Consumers must distinguish a virtual member from an on-disk file: executable: true describes
the member's content, not the existence of its virtual path on disk. Use the source identity and
full path to join components with findings; do not collapse members by outer_path. When
validating a report, check archive provenance and containment within the corresponding source
root, and retain incomplete-inspection failures. These additive fields do not change existing
consumer validators automatically, prove that every member was inspected, or justify extracting
or executing archive content.
- Members are read in memory. SkillSpector never extracts, renders, imports, installs, or executes archive content.
- Absolute paths, parent traversal, drive-qualified paths, and link members are not followed.
- Hidden files, recognized containers, and all nested content are local-only and are never included in an external LLM request.
- Deterministic HIGH findings survive optional LLM meta-analysis.
- A zero-finding result does not make opaque or uninspected content complete.
Archive inspection uses one shared budget for the whole skill bundle. Opening another outer container does not reset the member, expanded-byte, or time budget. The bundle scanner may pass a smaller remaining artifact or byte allowance, and its deadline always takes precedence.
| Bound | Limit | Scope |
|---|---|---|
| Container depth | 3 | One outer-to-inner provenance chain |
| Members | 1,000 | All outer and recursively nested containers combined |
| Expanded member bytes | 25 MiB | All outer and recursively nested containers combined |
| Central directory | 4 MiB | Each container, checked before creating ZIP metadata objects |
| Materialized member | 1,000,000 bytes | Each member |
| Compression ratio | 100:1 | Each member |
| Inspection wall time | 5 seconds | All outer and recursively nested containers combined |
The 1,000-member and 25 MiB ceilings are also reduced to the bundle scanner's remaining 10,000-artifact and 64 MiB canonical-byte budgets. Nested members therefore cannot obtain a fresh allowance after ordinary files have consumed part of the bundle budget.
Before Python's ZIP reader is invoked, SkillSpector validates the terminal EOCD or ZIP64 records, the declared central-directory count and byte size, and the actual sequence of central-directory headers. This preflight prevents a forged entry count from causing an unbounded metadata list. Already-bounded outer bytes are reused from the bundle cache instead of being read a second time.
These are resource-safety limits, not trust configuration. They are intentionally not user-managed allowlists. See Analysis Resource Bounds for the enclosing bundle, parser, ledger, and finding ceilings.
Malformed, encrypted, truncated, unreadable, unsafe-path, link, unsupported, and over-budget
members are recorded as inspection-ledger exceptions. Readable members retain their exact raw
bytes and a local-only decoded view. Unreadable members retain an opaque inventory record with a
partial or failed disposition; they are never represented as successfully analyzed.
The scan continues safely when possible, but any relevant omitted or partially inspected content
makes the analysis incomplete. A result that would otherwise be SAFE is reported as at least
CAUTION; the MCP verdict sets safe_to_install to false. CLI users can make incomplete analysis
a failing gate with --fail-on-incomplete. Inspection exceptions and their affected outer or nested
paths are surfaced in terminal, JSON, Markdown, and SARIF output.
If a real disk path containing !/ has the same identity as an archive member, the scan
records artifact_path_collision, retains the disk bytes, and withholds the archive member.
This fails the scan (CLI exit 2 and MCP safe_to_install=false). Rename the disk directory
ending in ! to remove the ambiguity. Non-colliding directory names ending in ! remain valid.
The colliding member file is withheld; distinct descendant members still receive analysis.
Exceptions for that withheld file or a skipped duplicate descendant are reported against its
archive container. Disk format and malformed-container errors stay on the disk file, and an
analyzed disk descendant is not marked partial merely because the archive copy was skipped.
SC9 is a deterministic HIGH finding when executable content is concealed inside an Office document container or a hidden/disguised artifact. Executability is established from an executable suffix, a shebang, or archive mode bits. A benign document without executable members does not produce SC9.
SC9 reports evidence and risk; it does not execute the member or prescribe an installation decision.
At the scan root, visible source scripts beneath .agents/skills/<name>/ and
.claude/skills/<name>/ are not treated as concealed when that skill directory
contains a regular SKILL.md or skill.md manifest. These paths remain hidden
and local-only: their contents are not sent to an external LLM. When LLM
analysis is enabled, the inspection ledger records that LLM review was
withheld, so the report does not imply that those scripts received semantic
review. This narrow exception does not cover
hidden skill names, hidden descendants, mismatched shebangs, unsupported script
types, or nested client directories.