Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ ipython_config.py
# Environments
.env
.venv
.uv-cache/
env/
venv/
ENV/
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
### 2.13.1 (Unreleased)
### Fixes
* Preserve Python and Perl host-language ownership when checking embedded shell text, so ordinary regexes, quoted script text, and report strings do not falsely exhaust the shell parser. (#778, #784, #849)
* Recognize possessive Markdown prose without weakening inspection of executable shell text. (#849)
* Retain incomplete inspection when report-shaped strings can reach recognized shell execution, including through bundled helpers or module initialization.
* Retain conservative shell inspection instead of failing the analyzer when a deeply nested bundled Python expression exceeds the optional import proof's serialization limit.
* Exclude the release workflow's uv build cache from source distributions.
---
### 2.13.0 (Unreleased)
### Security and blocking fixes
* Selected release based on v2.12.0; includes 55 PRs, with #743 included through #734.
Expand Down
71 changes: 71 additions & 0 deletions docs/ANALYSIS_RESOURCE_BOUNDS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Analysis Resource Bounds

## Python string-value and bundled-import bounds

Python string delimiters alone do not prove that later code cannot extend a
command. The optional string-value completeness proof retains the original
whole-source operand fallback around recognized shell execution, dynamic module
access, reflection, and ambiguous subprocess modes.

For bundled helpers, the proof uses only the scanner's in-memory component
inventory and file cache. It follows selected named functions and their local
dependencies, and separately checks module and ancestor-package initialization.
It checks at most 64 files, 16 import levels, 2,000,000 source characters and
40,000 visited syntax nodes per caller proof, sharing the artifact deadline.
Missing or ambiguous local targets, import cycles, unsupported callable shapes,
and exhausted bounds retain the original parser fallback. Interrupted proofs
are not cached. External imports remain outside this bounded context.

SkillSpector applies deterministic resource ceilings to untrusted skill bundles. A ceiling is a
safety boundary, not an allowlist and not evidence that the portion examined was clean. When a
relevant ceiling is reached, the scanner records the limitation and reports partial analysis.
Expand Down Expand Up @@ -123,6 +139,18 @@ The finding and incomplete-analysis gates remain active until the relevant
limitations are resolved. Evidence fields are diagnostic facts, not suppression
instructions.

### Markdown prose apostrophes

In a complete Markdown file, a possessive or contraction such as `reference's`,
`drivers'` or `don't` in a prose line is punctuation, not a shell single quote
that stays open through the rest of the file. This applies only when every quote
on the line is such a word apostrophe, the line has no `$`, backtick, backslash
or double quote, and the line starts a new shell line: the previous line ends in
a newline, not a backslash continuation or a Unicode line separator. Headings,
block quotes, code blocks, HTML blocks, tables, lines inside a pending inline
code span, and an apostrophe inside a quote or substitution that starts earlier
keep the lexical reading.

### Perl literal help text

For `.pl` source, the tool-misuse analyzer recognizes a narrow form of standalone
Expand All @@ -149,6 +177,49 @@ legacy package separators, incomplete fragments, and real parser limits remain
on the conservative analysis path. A complex helper may therefore still need
its particular ledger reason and expression reviewed.

Perl `eval BLOCK` (for example `eval { require $module; 1 } or die $@;`) traps
exceptions in already-compiled code and is not treated as a shell `eval` of a
string. Its statements are still scanned. String forms such as `eval $code`
and `eval "..."` remain on the conservative path.

### Python strings and comments

For a complete `.py` module that the Python parser accepts, each string literal
(including f-strings) and comment owns its bytes. A shell quote or backtick that
is left open inside one of them, such as a Markdown fence in a string or an
apostrophe in a comment, is charged only up to the end of that token, never to
the code that follows. Python code that happens to use a shell wrapper name, as
in `signal.alarm(timeout)`, is not reparsed as a shell command string. A
runtime-selected command named in a comment takes operands only from that
comment. One named in a string literal takes operands only from the rest of
that string's value: the literal's body and the bodies of literals implicitly
or `+`-concatenated after it. A value that code can still extend or rewrite,
with `%`, `.format` or another method call, a subscript, repetition, or a
non-literal operand, keeps the conservative result, also after a closing
parenthesis. An f-string replacement field among the operands may supply the
recursive and force options, so a root target beside it also stays
conservative.

Literal payloads remain visible to every security check. A single string or
comment that itself holds more unresolved shell text than the parser bound,
invalid or fragmentary Python, and source with a non-Python shebang keep the
conservative result.

Declared-marker reconstruction uses the same proof, as it does for validated
JSON strings. When a removal verb is followed later on its line by the closing
quote of a Python string, as in `assert "please omit --hours" in out`, that
quote is not paired with the next literal's opening quote across the code
between them. An explicit declaration, where only a short phrase such as
`the marker` separates the verb from the quote, keeps the lexical reading.
Marker declarations written inside one string or comment are still
reconstructed or reported as `obfuscated_instruction_text`. Invalid or
fragmentary Python and other file types keep the lexical reading.

Both consumers share one parse and tokenization of a module. The two most
recent results, including a failed proof, are reused, and each reuse still
checks the static runtime budget. A proof interrupted by that budget is not
kept.

## Structured skill data

AISOP/AISP structured extraction consumes the already-bounded cache and shares the enclosing
Expand Down
51 changes: 51 additions & 0 deletions docs/release/skillspector-2.13.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# SkillSpector 2.13.1

This patch release is based on the published v2.13.0 maintenance branch. It
corrects false incomplete-analysis reports for ordinary Python report strings,
quoted script text, regular expressions, and possessive Markdown prose.

## Selected changes

- [#778](https://github.com/NVIDIA/SkillSpector/pull/778) establishes bounded
Python and Perl host-language ownership for the shell parser.
- [#784](https://github.com/NVIDIA/SkillSpector/pull/784) shares validated Python
quote ownership with marker reconstruction.
- [#849](https://github.com/NVIDIA/SkillSpector/pull/849) corrects prose and
report-string parsing, using those prerequisites.
- Additional release hardening retains the previous incomplete-inspection
result when a report-shaped string is later assembled into a shell command.
The check covers recognized local execution and a bounded set of bundled
helpers, including import-time initialization. It does not lower findings
or suppress an incomplete scan.
- Deep expressions in bundled Python helpers retain conservative shell bounds
when the optional import proof exceeds Python's serialization recursion limit,
instead of causing an analyzer failure.
- Source distributions exclude the `.uv-cache` directory used by the release
workflow.

The release preserves v2.13.0's third-party dependency lock, provider and model
defaults, and existing publishing workflow. It does not take the remaining
unselected changes from `main`.

## Compatibility and remaining limits

Genuine incomplete inspection and independent security findings remain blocking
signals. A missing-reference advisory is distinct from an uninspected existing
artifact. This patch does not resolve every parser limitation: nested Markdown
table cases and references to otherwise inspected ZIP/XLSX containers retain
their existing limitations. Passive image handling is unchanged.

The bundled-helper check reads only materialized scan inputs. Unsupported,
ambiguous, missing, cyclic, or over-budget local evidence retains the previous
parser fallback. External imports and arbitrary interprocedural execution remain
outside this bounded proof; this release does not claim whole-program analysis.

Exact, version-bound finding baselines must be regenerated and reviewed for
2.13.1; do not relabel a baseline from another scanner version. Consumers should
validate the published wheel against their original inputs and baseline before
activating the upgrade.

Merging a release PR into `yashraj/v2.13.0-base` with the `release:publish` label
already present triggers the existing workflow to publish the GitHub `v2.13.1`
release with wheel and source-distribution assets. Without that label, merging
does not publish a release. This workflow does not publish to PyPI.
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "skillspector"
version = "2.13.0"
version = "2.13.1"
description = "SkillSpector: Security scanner for AI agent skills (Claude Code, Cursor, and similar). Scans skills for vulnerabilities, malicious patterns, and security risks before installation. Supports Git repos, URLs, zips, and local directories; runs static pattern checks and optional LLM semantic analysis; outputs terminal, JSON, and Markdown reports with risk scoring."
readme = "README.md"
license = "Apache-2.0"
Expand Down Expand Up @@ -84,7 +84,7 @@ Documentation = "https://github.com/NVIDIA/skillspector#readme"
Issues = "https://github.com/NVIDIA/skillspector/issues"

[tool.hatch.build]
exclude = [".claude/", ".cursor/", ".agents/"]
exclude = [".claude/", ".cursor/", ".agents/", ".uv-cache/"]

[tool.hatch.build.targets.sdist]
core-metadata-version = "2.4"
Expand Down
Loading
Loading