Start With the Bytes: Building a Bounded Inspection Surface

SkillSpector’s first security decision happens before it searches for a vulnerability. It must decide how to receive an untrusted skill, how much material to admit, and which bytes can become evidence. This chapter follows that boundary from a Git repository, URL, archive, file, or directory to a typed and provenance-aware inspection surface, so readers can distinguish safe retrieval from trusted content and a clean result from complete coverage.

That order is easy to overlook. A scanner that handles input casually can be attacked through the act of scanning: a URL can point toward an internal service, a ZIP can expand far beyond its apparent size, a path can cross a symlink, or a directory can contain more entries than the analysis was designed to process. SkillSpector treats these possibilities as part of the security problem rather than as ordinary setup.

Retrieval Is a Security Boundary

SkillSpector accepts several forms of source because skills do not arrive in one standard package. The input handler can resolve a Git repository, a direct file URL, a local ZIP archive, a single Markdown file, or a local directory. The analyzers ultimately work over a local directory, but that normalization does not make the source trusted. It only gives the rest of the workflow a consistent place to begin.

The first protection is control over remote destinations. Git and file downloads are checked against permitted host families, and the handler rejects hosts that resolve to private, loopback, link-local, or reserved addresses. This matters because a scanner that follows an attacker-controlled URL could otherwise become a network probe. A public-looking hostname can resolve to an internal address, and a redirect can change the destination after the initial decision. Host validation and explicit redirect handling reduce that risk before content is downloaded.

The same principle applies to local paths. SkillSpector rejects symlinked or junctioned inputs and checks parent components rather than trusting only the final filename. When the platform supports secure no-follow operations, files are opened relative to validated directories without following a replacement link at open time. The goal is not to prove that a local directory is benign. The goal is to ensure that the bytes read belong to the intended inspection scope instead of a path that changed underneath the scanner.

Archives create a different failure mode. The filename skill.zip says very little about what its central directory declares or what extraction would do. SkillSpector rejects absolute and parent-traversal paths, ambiguous separators, links, encrypted entries, special-file entries, duplicate extraction paths, and inconsistent archive metadata. It checks the archive structure before allowing the ZIP reader to materialize an attacker-controlled directory listing, then extracts members with streaming checks rather than assuming the declared total is safe.

There are two important size boundaries for remote and archive inputs. The ingest path caps the material that can land on disk at 100 MiB and limits a ZIP to 10,000 members. These limits contain the acquisition operation itself. They are separate from the downstream per-file analysis cap: a multi-file skill may fit within the ingest limit while one large file still receives only bounded analysis. Keeping those limits separate prevents a reader from mistaking “the download was admitted” for “every admitted byte was analyzed in full.”

A limit is not an accusation. A large repository may be legitimate, and a ZIP may contain many harmless assets. The security meaning is that the scanner will not silently process an arbitrary prefix and call the result complete. If a bound is reached, the source or affected work must remain visible as partial, rejected, or otherwise incomplete.

The resulting mental model is a sequence of guarded transitions:

sequenceDiagram participant C as Caller participant I as Input handler participant B as Build context participant A as Artifact inventory participant L as Inspection ledger C->>I: Submit source I->>I: Validate host, path, archive, and limits alt Safe source within bounds I->>B: Materialize local inspection directory B->>A: Read bounded bytes and classify artifacts A->>L: Record scope and disposition else Rejected or over budget I->>L: Record bounded failure or partial scope end B-->>C: Evidence surface with coverage status

The diagram’s key distinction is between the input handler and the build context. The input handler owns whether the source can be materialized safely. The build context owns what can be read from that materialized source. A successful transition between them is necessary for analysis, but it does not establish the author’s identity, the source’s intent, or the safety of installing it.

From a Directory to Canonical Evidence

Once a source is resolved, SkillSpector builds a bounded snapshot rather than passing an unexamined tree directly to every analyzer. The raw bytes are the source of truth. Text used by analyzers is a derived view, which means decoding, normalization, and concealment detection do not have to replace the original evidence.

That choice solves a practical problem. Filename extensions are useful hints, but they are attacker-controlled labels. A file named notes.txt may contain binary data; a file with an image-like suffix may contain readable text; a malformed or partially decoded file may not support the same analysis as an ordinary Markdown document. SkillSpector classifies artifacts as text, binary, or opaque from their bytes and decodability. It also records whether an extension is misleading. A binary or opaque artifact is not treated as an empty text file simply because no text pattern matched it.

The build context keeps several related views. A raw cache preserves the bounded bytes used as the canonical snapshot. A decoded local cache gives static analyzers readable text, including a loss-tolerant projection when necessary. A provider-safe cache contains only content eligible for external LLM analysis. The separation is important because the scanner has two different obligations: inspect as much as possible locally, and avoid sending content that the provider should not receive.

Security text views handle a further difficulty: some attacks depend on characters humans do not see or do not interpret as part of a word. SkillSpector can derive normalized and compact views using Unicode normalization and confusable mappings. It can also reconstruct narrowly defined concealed instruction forms. These views retain source-offset information, so a match found after normalization can be mapped back to a location in the supplied text. The scanner is not merely asking whether a transformed string contains a keyword; it is preserving a path from the transformed signal to the bytes a reviewer must inspect.

That mapping also limits what normalization is allowed to mean. A general-purpose “remove all punctuation and spaces” transformation could create false matches by joining unrelated words or identifiers. SkillSpector’s security views are more selective: compacting and obfuscation handling are bounded and contextual, and the static runner restores source lines after scanning derived windows. The purpose is to expose a concealment technique without turning every unusual Unicode sequence into a vulnerability.

The same evidence discipline governs hidden and nested artifacts. SkillSpector inventories hidden regular files and inspects ZIP-compatible content inside supported document containers and nested archives. Nested members receive stable virtual paths that preserve their outer-to-inner relationship. Members are read in memory; the scanner does not render, import, install, or execute them. Paths, links, recursion depth, member count, expanded bytes, compression ratio, and inspection time share cumulative budgets across the bundle. Opening another container does not reset the allowance.

This produces two kinds of output at once. One is the finding stream: signals such as a concealed executable, a misleading extension, embedded NUL bytes, or instruction text using separators to evade pattern matching. The other is the artifact inventory and its dispositions. A file may be analyzed, partial, failed, opaque, or out of scope. A nested binary member can be retained as local evidence but excluded from an external model request. A recognized OpenSSF Model Signing bundle can remain in the inventory while being excluded from content analysis; the repository says that its structural recognizer does not verify the signature, certificate chain, transparency-log entry, or signer identity.

The snapshot also gives later stages a stable ownership rule. The raw cache selected during context construction is the evidence that static analyzers and report fields are meant to describe; they do not need to reread a mutable directory and silently obtain a different answer. The provider-safe cache is a different projection of that same bounded work, not a second source of truth. This matters when a file disappears, changes, or turns out to be unreadable between discovery and analysis. The ledger can preserve that failure instead of allowing a later empty cache lookup to look like an empty file.

Manifest parsing follows the same pattern. SkillSpector reads only a bounded frontmatter prefix and projects the fields it understands, such as name, description, triggers, permissions, allowed tools, and parameter metadata. Limits on YAML nodes, nesting, projected records, characters, and parsing time prevent a compact but adversarial manifest from expanding without control. If the claimed frontmatter is malformed or incomplete within those limits, the manifest can remain empty while the artifact is marked partial and the parse limitation is recorded. An empty manifest in that situation means “the declaration was not safely understood,” not “the skill declared no permissions.”

References in the primary instructions are another bridge between what a skill says and what it contains. A Markdown instruction can point to a local script, document, or configuration file that would not be obvious from the root manifest. SkillSpector extracts path-like references under separate bounds and records whether each target resolves, is missing, or is ambiguous. A referenced binary or opaque artifact is not promoted to successfully analyzed content merely because its path was found. This prevents a common concealment strategy: place the sensitive behavior in a secondary file and rely on a superficial scan of the primary instructions.

These distinctions prevent a common logical error. Suppose a skill contains a hidden archive member that exceeds the cumulative inspection budget. The absence of a finding inside that member does not support the statement that the member is clean. The strongest supported statement is that the member was not fully inspected and that the result is incomplete. Conversely, a benign document without executable members does not become suspicious merely because the scanner looked inside it. Inventory provides the context needed to interpret both positive and negative results.

The inspection ledger makes those facts durable. It records bounded reasons for work that was completed, skipped, failed, partially completed, or outside scope. The reasons are deliberately sanitized and machine-readable rather than a dump of attacker-controlled error text. Later stages can use the ledger to calculate coverage and to keep a partial scan from being promoted to a complete one.

The practical consequence is that SkillSpector’s first deliverable is not a score. It is a controlled evidence surface with answers to four preliminary questions:

  1. Which source was admitted, and under what acquisition rules?
  2. Which artifacts were discovered and retained?
  3. Which bytes became local, static, or provider-safe content?
  4. Which work was complete, partial, failed, opaque, or out of scope?

Only after those questions have answers does a finding acquire a reliable context. The score and recommendation come later because they depend on what the analyzers were actually allowed to see.

Why “Complete” Is a Security Property

Completeness is not the same as breadth. SkillSpector may inspect many files and still have an important gap if a relevant file is opaque, a manifest could not be parsed within its limits, a nested member was omitted, or a semantic batch failed. The resource limits are safety boundaries for the inspection process, not allowlists that make the admitted material trustworthy.

This is also why a local-only decision can be rational without being universally superior. Static analysis keeps skill content local, which may be essential for confidential code or an air-gapped environment. LLM analysis can add useful context for intent and description-behavior mismatch, but it changes the egress boundary. An operator must choose based on the sensitivity of the bundle and the value of semantic coverage. Either choice still requires attention to the completeness status.

The most defensible reading of a clean-looking result is therefore conditional: no active finding was retained in the evidence that completed the applicable checks. That is a meaningful result, but it is narrower than “the skill is safe.” SkillSpector’s acquisition and evidence stages make that narrowness visible before the later analyzers ask what the content can do.

References