Skip to content
47 changes: 47 additions & 0 deletions docs/passive-documentation-images.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Passive documentation images

An unsupported image format does not by itself establish analysis evasion.
SkillSpector keeps image coverage limitations visible while omitting format-only
AE1 for a positively identified documentation image with corroborated bytes.
Independent findings and genuine analysis failures remain blocking.

Two reference forms are supported:

- A rendered Markdown image, such as `![Overview](assets/overview.png)`.
- An unindented standalone paragraph such as ``Asset: `assets/overview.png`.``
The supported labels are Asset, Image, Illustration, Diagram, Figure,
Screenshot, Photo and Picture, case-insensitively. The label must be followed
by a colon and one single-backtick literal `.png`, `.jpg` or `.jpeg` path.
A final period is optional. Blank paragraph boundaries are required.

The declaration form has a distinct `documentation_image` reference kind.
Declarations and newly supported rendered JPEG references carry a `source_sha256`
binding. Finalization verifies their exact source bytes, position, role and target
again, with complete bounded reference extraction. The existing rendered-PNG
contract remains unchanged. Frontmatter, list or blockquote continuations, fenced or
indented code, HTML, open code spans, command paragraphs, multiple paths and
mismatched delimiters do not qualify. An independent executable or ambiguous use
of the same target keeps AE1, including when the public reference record is
missing but the canonical source still contains that use. Ordinary quoted paths
and Markdown links do not gain this policy.

PNG support remains the existing bounded non-interlaced format with verified
chunk lengths, CRCs and decoded scanline layout. JPEG support is a conservative
baseline JFIF subset with verified marker, table, frame, scan, Huffman block and
terminal padding structure. Metadata such as COM or EXIF, unsupported coding
modes, malformed data and trailing bytes do not qualify. Structural validation
does not inspect rendered content, perform OCR, or establish that an image's
meaning is safe.

Complete canonical bytes must match the discovered asset size and suffix. Mixed,
fatal, missing or truncated inspection evidence prevents the exception. Source
corroboration is cached per source and bounded by a shared one-million-byte,
16-source and two-second allowance. Format proofs are cached per asset and
bounded by 256 attempts, 128 MiB encoded input and two seconds, in addition to
each format's own deterministic bounds. Failed attempts consume the allowance.
An exhausted proof preserves AE1 and the original incomplete coverage evidence.

This policy does **not** mark image bytes fully inspected or change raw native
`partial` into another status. Consumers may admit a corroborated passive-image
limitation as an advisory; they must still consider other findings and limits.
Native completion and a consumer's admission decision remain separate signals.
2 changes: 1 addition & 1 deletion docs/scan-completeness.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ flowchart TD

The arrows represent data and decisions, not execution of inspected instructions. Existing ZIP handling can extract a selected `.zip` or inspect nested/renamed ZIP members; the **unsupported-format header check itself never extracts or decompresses anything**. Optional LLM analysis is separate from this reconstruction; all validation described here explicitly disables live providers.

The required-content boundary applies to the selected standalone filename and to cached, in-profile basenames exactly `SKILL.md` or `skill.md`, including `pkg/SKILL.md` and `bundle.dat!/pkg/SKILL.md`. It does not depend on an archive's extension. An ordinary `image.png` remains governed by existing asset/reference policy. This is intentionally conservative: even an in-profile example named `SKILL.md` receives instruction-file treatment. It does not infer that every arbitrary binary file is a primary instruction.
The required-content boundary applies to the selected standalone filename and to cached, in-profile basenames exactly `SKILL.md` or `skill.md`, including `pkg/SKILL.md` and `bundle.dat!/pkg/SKILL.md`. It does not depend on an archive's extension. An ordinary `image.png` remains governed by [passive documentation image policy](passive-documentation-images.md); that policy retains unsupported image coverage while separating format-only reference findings from independent risks. This is intentionally conservative: even an in-profile example named `SKILL.md` receives instruction-file treatment. It does not infer that every arbitrary binary file is a primary instruction.

The exclusion audit can also inventory metadata for paths under generated/dependency or VCS directories such as `node_modules` and `.git`. That inventory does not make all of those bytes part of ordinary source analysis. Unreferenced, non-executable instructions under those policy exclusions retain the existing exclusion rules and can coexist with a complete result; this PR does not certify their interpretation. Explicitly selecting such an instruction file still makes it required. Excluded executable content and references have separate incompleteness rules. When required-content failure and excluded-executable evidence coexist, both ledger reasons must survive finalization.

Expand Down
2 changes: 2 additions & 0 deletions src/skillspector/artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class ReferenceKind(StrEnum):
"""Syntactic use that produced one intra-bundle reference."""

MARKDOWN_IMAGE = "markdown_image"
DOCUMENTATION_IMAGE = "documentation_image"
MARKDOWN_LINK = "markdown_link"
INLINE_COMMAND = "inline_command"
QUOTED_OR_CODE = "quoted_or_code"
Expand Down Expand Up @@ -80,6 +81,7 @@ class BundleReference(TypedDict):
status: str
disposition: ArtifactDisposition
reference_kind: ReferenceKind
source_sha256: NotRequired[str]


@dataclass(frozen=True)
Expand Down
177 changes: 174 additions & 3 deletions src/skillspector/nodes/finalize_inspection_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import math
import re
import time
import zlib
from collections.abc import Mapping, Sequence
from pathlib import PurePosixPath
Expand All @@ -28,6 +29,12 @@
)
from skillspector.models import Finding
from skillspector.nodes.analyzers import ANALYZER_LOAD_ERRORS, ANALYZER_MODULES
from skillspector.passive_jpeg import is_structurally_valid_passive_jpeg
from skillspector.references import (
MAX_REFERENCE_RUNTIME_SECONDS,
MAX_REFERENCE_SOURCE_BYTES,
documentation_image_provenance,
)
from skillspector.semantic_runtime import (
has_semantic_runtime_event,
semantic_runtime_intent,
Expand All @@ -47,6 +54,10 @@
}
_MAX_PASSIVE_PNG_CHUNKS = 4_096
_MAX_REFERENCE_REASONS = 16
_MAX_PASSIVE_REFERENCE_SOURCES = 16
_MAX_PASSIVE_RASTER_PROOFS = 256
_MAX_PASSIVE_RASTER_TOTAL_BYTES = 128 * 1024 * 1024
_MAX_PASSIVE_RASTER_SECONDS = 2.0
_REFERENCE_CAVEAT_REASONS = (LedgerReason.REFERENCE_MISSING, LedgerReason.REFERENCE_UNRESOLVED)
_REFERENCE_LIMIT_FIELDS = frozenset(
f"{prefix}_{unit}"
Expand Down Expand Up @@ -281,7 +292,7 @@ def _has_verified_passive_png(
inventory_item: Mapping[str, object] | None,
raw_file_cache: Mapping[str, object],
) -> bool:
"""Require complete canonical bytes for the one proven-passive image format."""
"""Require complete canonical bytes for the existing passive PNG subset."""
if PurePosixPath(target_path).suffix.lower() != ".png" or not inventory_item:
return False
raw = raw_file_cache.get(target_path)
Expand All @@ -294,14 +305,37 @@ def _has_verified_passive_png(
)


def _has_verified_passive_raster(
target_path: str,
inventory_item: Mapping[str, object] | None,
raw_file_cache: Mapping[str, object],
) -> bool:
if PurePosixPath(target_path).suffix.lower() == ".png":
return _has_verified_passive_png(target_path, inventory_item, raw_file_cache)
if PurePosixPath(target_path).suffix.lower() not in {".jpg", ".jpeg"} or not inventory_item:
return False
raw = raw_file_cache.get(target_path)
size_bytes = inventory_item.get("size_bytes")
return (
isinstance(raw, bytes)
and type(size_bytes) is int
and size_bytes == len(raw)
and is_structurally_valid_passive_jpeg(raw)
)


def _has_only_format_limitations(
target_path: str,
inventory_item: Mapping[str, object] | None,
events: list[Mapping[str, object]],
raw_file_cache: Mapping[str, object],
*,
format_verified: bool | None = None,
) -> bool:
"""Recognize unsupported content without hiding other coverage failures."""
if not _has_verified_passive_png(target_path, inventory_item, raw_file_cache):
if format_verified is None:
format_verified = _has_verified_passive_raster(target_path, inventory_item, raw_file_cache)
if not format_verified:
return False
if not inventory_item or str(inventory_item.get("content_kind")) not in {
ContentKind.BINARY,
Expand Down Expand Up @@ -339,6 +373,84 @@ def _has_only_format_limitations(
)


def _verified_documentation_image_references(
state: SkillspectorState,
inventory: Mapping[str, Mapping[str, object]],
raw_file_cache: Mapping[str, object],
) -> set[tuple[str, int, int, str, str, str]]:
"""Corroborate declarations and newly supported rendered JPEG references.

The provenance label is not authority: bind it to canonical source bytes,
exact syntax/position and target. All sources share one byte/time allowance;
failed proofs are cached too. Existing image coverage remains partial.
"""
resolution = state.get("reference_resolution")
if (
not isinstance(resolution, dict)
or resolution.get("complete") is not True
or resolution.get("limitations") != []
):
return set()
deadline = time.monotonic() + MAX_REFERENCE_RUNTIME_SECONDS
remaining_bytes = MAX_REFERENCE_SOURCE_BYTES
proofs: dict[str, tuple[str, frozenset[tuple[int, int, str, str]]] | None] = {}
verified: set[tuple[str, int, int, str, str, str]] = set()
for reference in state.get("artifact_references", []):
if not isinstance(reference, dict):
continue
kind = reference.get("reference_kind")
if kind != ReferenceKind.DOCUMENTATION_IMAGE and not (
kind == ReferenceKind.MARKDOWN_IMAGE
and isinstance(reference.get("target_path"), str)
and PurePosixPath(reference["target_path"]).suffix.lower() in {".jpg", ".jpeg"}
):
continue
source_path = reference.get("source_path")
if (
not _path_is_canonical(source_path)
or PurePosixPath(source_path).suffix.lower() != ".md"
):
continue
if source_path not in proofs:
if len(proofs) >= _MAX_PASSIVE_REFERENCE_SOURCES or time.monotonic() >= deadline:
continue
proofs[source_path] = None
raw = raw_file_cache.get(source_path)
source_item = inventory.get(source_path)
if (
not isinstance(raw, bytes)
or not source_item
or type(source_item.get("size_bytes")) is not int
or source_item.get("size_bytes") != len(raw)
or str(source_item.get("content_kind")) != ContentKind.TEXT
or source_item.get("misleading_extension") is True
or len(raw) > remaining_bytes
):
continue
remaining_bytes -= len(raw)
proofs[source_path] = documentation_image_provenance(
source_path, raw, deadline=deadline
)
proof = proofs[source_path]
if proof is None:
continue
digest, identities = proof
line, column, target = (
reference.get("line"),
reference.get("column"),
reference.get("target_path"),
)
if (
type(line) is int
and type(column) is int
and isinstance(target, str)
and reference.get("source_sha256") == digest
and (line, column, target, str(kind)) in identities
):
verified.add((source_path, line, column, target, digest, str(kind)))
return verified


def _ledger_evidence_was_truncated(events: Sequence[Mapping[str, object]]) -> bool:
"""Return whether a ledger cap may have hidden a non-format limitation."""
for event in events:
Expand Down Expand Up @@ -575,6 +687,37 @@ def _reference_coverage_findings(
and canonicalization_succeeded
and not _ledger_evidence_was_truncated(ledger_events)
)
documentation_references = (
_verified_documentation_image_references(state, inventory, raw_file_cache)
if ledger_evidence_complete and not duplicate_inventory_paths
else set()
)
raster_proofs: dict[str, bool] = {}
raster_bytes_remaining = _MAX_PASSIVE_RASTER_TOTAL_BYTES
raster_deadline = time.monotonic() + _MAX_PASSIVE_RASTER_SECONDS

def format_is_verified(target_path: str, inventory_item: Mapping[str, object] | None) -> bool:
nonlocal raster_bytes_remaining
if target_path in raster_proofs:
return raster_proofs[target_path]
if len(raster_proofs) >= _MAX_PASSIVE_RASTER_PROOFS:
return False
raster_proofs[target_path] = False
raw = raw_file_cache.get(target_path)
if (
not isinstance(raw, bytes)
or len(raw) > raster_bytes_remaining
or time.monotonic() >= raster_deadline
):
return False
# Charge attempted work before decoding; malformed bytes are not free.
raster_bytes_remaining -= len(raw)
raster_proofs[target_path] = (
_has_verified_passive_raster(target_path, inventory_item, raw_file_cache)
and time.monotonic() < raster_deadline
)
return raster_proofs[target_path]

findings: list[Finding] = []
seen_locations: set[tuple[str, int, str]] = set()
diagnostics_by_target: dict[tuple[str, str], tuple[dict[str, object], str]] = {}
Expand Down Expand Up @@ -605,6 +748,27 @@ def _reference_coverage_findings(
)
if final_disposition not in {"partial", "failed", "out_of_scope"}:
continue
documentation_reference_verified = (
str(reference.get("reference_kind"))
in {
ReferenceKind.DOCUMENTATION_IMAGE,
ReferenceKind.MARKDOWN_IMAGE,
}
and isinstance(reference.get("source_path"), str)
and type(reference.get("line")) is int
and type(reference.get("column")) is int
and isinstance(reference.get("target_path"), str)
and isinstance(reference.get("source_sha256"), str)
and (
reference["source_path"],
reference["line"],
reference["column"],
reference["target_path"],
reference["source_sha256"],
str(reference["reference_kind"]),
)
in documentation_references
)
# The all-reasons check is the safety boundary: executables, concealed
# content, read failures, and any mixed limitation must retain AE1.
if (
Expand All @@ -613,12 +777,19 @@ def _reference_coverage_findings(
and target_path not in invalid_status_paths
and target_path not in duplicate_inventory_paths
and reference_disposition == disposition
and str(reference.get("reference_kind", "")) == ReferenceKind.MARKDOWN_IMAGE
and (
(
str(reference.get("reference_kind", "")) == ReferenceKind.MARKDOWN_IMAGE
and PurePosixPath(target_path).suffix.lower() == ".png"
)
or documentation_reference_verified
)
and _has_only_format_limitations(
target_path,
inventory_item,
target_events,
raw_file_cache,
format_verified=format_is_verified(target_path, inventory_item),
)
):
continue
Expand Down
Loading