Skip to content

v1.2.1 — Broader install-time behavior detection and path exclusion

Latest

Choose a tag to compare

@JAAAACCCCCCKKKK JAAAACCCCCCKKKK released this 10 Aug 10:27
· 2 commits to main since this release
1c21e9b

SafeSC v1.2.1

Broader install-time behavior detection (Rust and Python join npm), a way to exclude
paths from discovery, and the first end-to-end tests that prove the pipeline detects real
attack patterns.

pip install --upgrade "safesc[agent,anthropic]"   # or [agent,openai]

⚠️ Read before upgrading

This release can surface findings on dependencies that previously reported nothing.
Two new Stage-3 signals were added, so an audit that passed on v1.2.0 may now report
new gray-zone findings and spend LLM calls on them.

Both new signals are MEDIUM, which means they land in the gray zone for LLM
verification and cannot fail your CI gate on their own (fail_threshold is HIGH).
An audit's exit code should not change from these signals alone. What can change:

  • new findings in your JSON/Markdown/SARIF reports,
  • a modest increase in Stage-4 LLM calls (and therefore cost).

The one pre-existing HIGH behavior signal — npm's hasInstallScript — is unchanged.

New runtime dependency: pathspec>=0.12 is now a core dependency (it powers
.safescignore matching). It is small, pure-Python, and dependency-free.


Added

Exclude paths from discovery — .safescignore and --exclude

SafeSC walks the whole target directory, which meant no way to skip paths that
look like dependency files but shouldn't be audited — vendored copies, generated
trees, or security-test fixtures.

Drop a gitignore-syntax .safescignore at the scanned root. It is auto-discovered by
every entrypoint (index, scan, safesc audit, safesc query) with no flags,
because they all funnel through one discovery function:

# .safescignore
tests/fixtures/**
vendor/

For one-off exclusions, a repeatable --exclude flag layers on top of (never replaces)
that file:

safesc audit . --exclude "tests/fixtures/**"
index discover . --exclude "vendor/**"
scan signals . --exclude "vendor/**"

The GitHub Action exposes the same thing as an exclude input, one pattern per line:

- uses: JAAAACCCCCCKKKK/SafeSC@v1
  with:
    llm-api-key: ${{ secrets.SAFESC_LLM_API_KEY }}
    llm-provider: anthropic
    exclude: |
      tests/fixtures/**
      vendor/

Matching uses pathspec for real gitignore semantics (negation, **, anchoring,
directory pruning) rather than hand-rolled globbing.

Rust build-script detection (Stage 3, MEDIUM)

Crates that declare a Cargo links key are now flagged as having a build script.
crates.io exposes this as lib_links, and Cargo requires a build script whenever
links is set — so this is a zero-false-positive proxy that needs no download.

It is deliberately narrow: a build.rs used purely for codegen (no links key)
is not flagged. That is intentional, not an oversight. Build scripts are the norm in
Rust — serde, libc, anyhow and proc-macro2 all ship one — so a "has a build.rs
at all" signal would fire on roughly two-thirds of every Rust dependency tree and be
useless as an escalation trigger. Content inspection of any build.rs still happens at
Stage 4 for crates that reach it.

Python install-time build detection (Stage 3, MEDIUM)

A release that publishes an sdist but no wheel must be built from source at install
time, executing the project's PEP 517 backend / setup.py. A wheel is only unpacked and
runs no project code, so wheel-bearing releases are never flagged.

Measured before shipping: 0 of 45 real-world dependencies flagged, while correctly
firing on genuinely sdist-only releases (pycparser==2.14,
python-Levenshtein==0.12.0). It costs no extra HTTP requests — the data comes from
a registry response SafeSC already fetches.

End-to-end attack-pattern test coverage

tests/fixtures/attacks/ adds four synthetic, inert fixtures — a build-script
payload, a poisoned install hook, a typosquat, and a clean negative control — driven by
tests/test_attack_fixtures.py through the real spine, gate, specialists, and scorer.

These are pattern fixtures, not incident replays: no malware is vendored, and every
payload is harmless. They exist to prove the pipeline detects an actual attack shape end
to end, and — via the clean baseline — that it still passes an ordinary project.

Clearer errors for missing provider SDKs

Running the CI tier without the SDK for your configured SAFESC_LLM_PROVIDER now fails
with an actionable install hint instead of a raw traceback.


Fixed

  • Rust audits no longer report a spurious "0 dependencies" warning. Cargo.toml is
    discovered but intentionally parses to nothing (it is a manifest, not a lockfile), yet
    it was not on the manifest-only allowlist — so every normal Rust project (Cargo.toml
    and Cargo.lock) was permanently marked "incomplete analysis".
  • Module-presence checks no longer misfire on injected test doubles.

Release engineering

The publish workflow now verifies that the git tag matches the version in
pyproject.toml, so a mistagged release fails before anything reaches PyPI.


Notes for maintainers

Two decisions in this release were made on measured evidence rather than intuition, and
are recorded in CLAUDE.md (§9, v2.9–v2.11) with the numbers behind them:

  1. Rust's signal is MEDIUM, not HIGH. It shipped as HIGH during development, which
    would have failed the gate for every consumer of openssl-sys/ring/libz-sys
    irrecoverably, since LLM signals may escalate but never downgrade. The governing rule
    is now explicit and test-pinned: only opted-in code execution may reach the gate.
    An npm lifecycle hook qualifies; a Cargo links key and an sdist-only release are
    routine build-system facts and do not.
  2. Rust detection was deliberately not broadened beyond links, because completeness
    would have destroyed the signal's selectivity.

Full changelog: v1.2.0...v1.2.1