From 2ff29ee15768f6ec04329a72e7849e33ddf694fc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 10:33:39 +0000 Subject: [PATCH] Describe the tool as software composition analysis, not supply chain analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Supply chain analysis" oversold what this does. It inventories declared third-party components and checks them against OSV — software composition analysis. It does not look at build provenance, signatures, or what a downloaded artefact actually contains, and the docs should not suggest otherwise. Renamed the category everywhere it describes the tool: README, website hero and site description, package description and CLI help, the bundled agent skill, and the horizontal wordmark (letter-spacing dropped to 1.8 so the longer subtitle sits flush with the wordmark above it). Left "supply chain" where it names a genuine risk — shelling out to `mvn`, taking on dependencies of our own, our own CI — since those statements are still true. Added an explicit scope note to the README and How it works so the boundary is stated rather than implied. Also toned down the docs: dropped the "Find. Resolve. Check. Report." slogan, retitled "The one rule" to "What an empty findings list means", "Built to be scanned as well as to scan" to "The tool's own dependencies", "Real dependency graphs" to "Transitive dependency graphs", and trimmed the rhetorical flourishes ("matters more than it looks", "within a week", "worth knowing") without touching any technical content. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CgjzvMcy8B8yuir8AZBgAW --- CLAUDE.md | 6 +-- README.md | 23 +++++----- pyproject.toml | 14 ++++++- .../.agents/skills/icebergsca/SKILL.md | 4 +- .../icebergsca/references/ci-integration.md | 6 +-- src/icebergsca/__init__.py | 2 +- src/icebergsca/cli/main.py | 2 +- src/icebergsca/core/models.py | 2 +- src/icebergsca/report/cyclonedx.py | 2 +- src/icebergsca/resolve/ranges.py | 4 +- website/README.md | 4 +- website/docs/agents.md | 4 +- website/docs/assets/icebergsca-horizontal.svg | 2 +- website/docs/ecosystems.md | 5 +-- website/docs/how-it-works.md | 8 +++- website/docs/index.md | 42 ++++++++----------- website/docs/output.md | 10 ++--- website/zensical.toml | 7 ++-- 18 files changed, 80 insertions(+), 67 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b9dac8a..c3fae29 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,7 @@ # IcebergSCA — maintenance notes -CLI for supply chain analysis: walk a project, find manifests and lockfiles, resolve the -dependency graph, check every package against OSV, report it. +CLI for software composition analysis: walk a project, find manifests and lockfiles, resolve +the dependency graph, check every package against OSV, report it. ## Commands @@ -126,7 +126,7 @@ project's build to discover its dependencies is itself a supply chain risk. `MavenResolver._backfill` re-reads `pom.xml` to apply BOM-supplied versions to direct dependencies, which the synchronous parser cannot do because BOMs live on Central. -**Ranges, SARIF and CycloneDX are hand-written** rather than pulled from packages — a supply chain +**Ranges, SARIF and CycloneDX are hand-written** rather than pulled from packages — a dependency scanner with a large dependency tree of its own is a poor advertisement. Correctness is held by schema validation in `tests/test_report_formats.py` against the official schemas in `tests/schemas/`. diff --git a/README.md b/README.md index fbaea72..1f11e18 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ # IcebergSCA -Supply chain analysis for software projects. Point it at a directory; it finds every -dependency manifest and lockfile, builds the direct and transitive dependency set, looks each -package up in [OSV](https://osv.dev), and reports what it finds. +Software composition analysis (SCA) for software projects. Point it at a directory; it finds +every dependency manifest and lockfile, builds the direct and transitive dependency set, looks +each package up in [OSV](https://osv.dev), and reports what it finds. + +It inventories the third-party components a project declares; it does not read first-party +source code, and it identifies a package by name and version rather than by what the +downloaded artefact contains. Documentation: @@ -47,8 +51,8 @@ lockfile is present does it fall back to resolving version ranges against the re those findings are labelled `resolved` rather than `pinned`. Java, which has no lockfile, gets its graph reconstructed from Maven Central and is marked `~` for approximate. -**It never claims to be clean when it isn't.** This is the design constraint everything else -bends around: +**A clean result is never implied unless it was earned.** Most of the rest of the design +follows from this: - An empty findings list because the lookup never ran is reported as *"vulnerability lookup did not run"*, never as *"no vulnerabilities found"*. The JSON carries an explicit @@ -60,7 +64,7 @@ bends around: are declared rather than installed. - Skipped files, truncated graphs and unresolved constraints are all counted and shown. -**Findings never fail your build.** +**Findings do not change the exit code.** | Exit code | Meaning | |---|---| @@ -119,10 +123,9 @@ icebergsca/.agents/skills/icebergsca/SKILL.md ``` Agents that glob site-packages for `SKILL.md` will find it automatically after install. It -covers invocation, the JSON schema, exit-code semantics, and — most importantly — the three -fields that must be checked before reporting a project as clean. An empty `findings` array -means "checked and clean", "never checked" or "partially checked", and only the report can say -which. +covers invocation, the JSON schema, exit-code semantics, and the three fields to check before +reporting a project as clean. An empty `findings` array means "checked and clean", "never +checked" or "partially checked", and only the report can say which. ## Caching diff --git a/pyproject.toml b/pyproject.toml index 712cfb5..fe1003f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "icebergsca" version = "0.1.0" -description = "Supply chain analysis for software projects — lockfile-first dependency scanning against OSV" +description = "Software composition analysis for software projects — lockfile-first dependency scanning against OSV" readme = "README.md" requires-python = ">=3.11" # PEP 639: an SPDX expression plus the files to bundle. A "License ::" classifier @@ -13,7 +13,17 @@ requires-python = ">=3.11" license = "Apache-2.0" license-files = ["LICENSE", "NOTICE"] authors = [{ name = "Richard Hope" }] -keywords = ["sca", "supply-chain", "security", "osv", "sbom", "cyclonedx", "vulnerabilities"] +keywords = [ + "sca", + "software-composition-analysis", + "dependency-scanning", + "supply-chain", + "security", + "osv", + "sbom", + "cyclonedx", + "vulnerabilities", +] classifiers = [ "Development Status :: 3 - Alpha", "Environment :: Console", diff --git a/src/icebergsca/.agents/skills/icebergsca/SKILL.md b/src/icebergsca/.agents/skills/icebergsca/SKILL.md index 95172ae..4260855 100644 --- a/src/icebergsca/.agents/skills/icebergsca/SKILL.md +++ b/src/icebergsca/.agents/skills/icebergsca/SKILL.md @@ -1,11 +1,11 @@ --- name: icebergsca -description: IcebergSCA usage and output interpretation. Use when scanning a project for vulnerable dependencies, auditing a supply chain, generating an SBOM, or reading an IcebergSCA report. Covers the JSON schema, exit codes, and the checks required before reporting a project as clean. +description: IcebergSCA usage and output interpretation. Use when scanning a project for vulnerable dependencies, performing software composition analysis, generating an SBOM, or reading an IcebergSCA report. Covers the JSON schema, exit codes, and the checks required before reporting a project as clean. --- # IcebergSCA -Official IcebergSCA skill for running supply chain scans and reading the results correctly. +Official IcebergSCA skill for running dependency scans and reading the results correctly. IcebergSCA walks a project, finds every dependency manifest and lockfile, resolves the dependency graph, checks each package against [OSV](https://osv.dev), and reports findings. diff --git a/src/icebergsca/.agents/skills/icebergsca/references/ci-integration.md b/src/icebergsca/.agents/skills/icebergsca/references/ci-integration.md index 4e9708f..085981e 100644 --- a/src/icebergsca/.agents/skills/icebergsca/references/ci-integration.md +++ b/src/icebergsca/.agents/skills/icebergsca/references/ci-integration.md @@ -55,7 +55,7 @@ Notes on the SARIF we emit: ## Full workflow ```yaml -name: Supply chain +name: Dependency scan on: push: { branches: [main] } @@ -86,8 +86,8 @@ jobs: category: icebergsca ``` -The scheduled run matters more than it looks: most new findings arrive because an advisory was -published, not because the code changed. +The scheduled run matters: most new findings arrive because an advisory was published, not +because the code changed. ## SBOM artefacts diff --git a/src/icebergsca/__init__.py b/src/icebergsca/__init__.py index 4321fe6..132f971 100644 --- a/src/icebergsca/__init__.py +++ b/src/icebergsca/__init__.py @@ -1,3 +1,3 @@ -"""IcebergSCA — supply chain analysis for software projects.""" +"""IcebergSCA — software composition analysis for software projects.""" __version__ = "0.1.0" diff --git a/src/icebergsca/cli/main.py b/src/icebergsca/cli/main.py index c724a9a..e29e5e5 100644 --- a/src/icebergsca/cli/main.py +++ b/src/icebergsca/cli/main.py @@ -48,7 +48,7 @@ class ExitCode(IntEnum): app = typer.Typer( name="icebergsca", - help="Supply chain analysis: find dependencies, check them against OSV.", + help="Software composition analysis: find dependencies, check them against OSV.", no_args_is_help=True, add_completion=False, ) diff --git a/src/icebergsca/core/models.py b/src/icebergsca/core/models.py index 3968e22..cf76a4d 100644 --- a/src/icebergsca/core/models.py +++ b/src/icebergsca/core/models.py @@ -362,7 +362,7 @@ def sources(self) -> tuple[SourceLocation, ...]: class SkippedFile: """A file we recognised but did not scan, and why. - Skips are always reported. A supply chain tool that quietly drops a manifest is + Skips are always reported. A scanner that quietly drops a manifest is indistinguishable from one that found nothing wrong with it. """ diff --git a/src/icebergsca/report/cyclonedx.py b/src/icebergsca/report/cyclonedx.py index c10f41d..3bc2753 100644 --- a/src/icebergsca/report/cyclonedx.py +++ b/src/icebergsca/report/cyclonedx.py @@ -5,7 +5,7 @@ affected by CVE-2021-44228", which would mean shipping the interesting half of the report in a separate file. -Written by hand rather than through ``cyclonedx-python-lib``. A supply chain scanner +Written by hand rather than through ``cyclonedx-python-lib``. A dependency scanner that drags in a large dependency tree of its own is a poor advertisement for itself, and the document is a few nested dictionaries. Correctness is held by validating the output against the published schema in the test suite. diff --git a/src/icebergsca/resolve/ranges.py b/src/icebergsca/resolve/ranges.py index 8043d83..56ee7d6 100644 --- a/src/icebergsca/resolve/ranges.py +++ b/src/icebergsca/resolve/ranges.py @@ -6,8 +6,8 @@ ecosystem's job is reduced to expanding its own shorthand (``^``, ``~>``, ``[1.0,2.0)``) into those bounds. -Implemented here rather than pulled in from a semver package: this is a supply chain -tool, and every dependency it takes on is one more thing its own users have to trust. +Implemented here rather than pulled in from a semver package: every dependency this +tool takes on is one more thing its own users have to trust. Every entry point can return ``None``, meaning "this syntax is beyond us". That is deliberately distinct from "nothing matched" — the caller marks such dependencies diff --git a/website/README.md b/website/README.md index 1cd7801..f2b7e40 100644 --- a/website/README.md +++ b/website/README.md @@ -28,7 +28,7 @@ Notes: captures in `../docs/img/`. Regenerate those first, then re-copy. - Nothing is loaded from a CDN: fonts are self-hosted (`font = false` disables Google Fonts) and the mermaid fence is configured but deliberately unused, - since the theme lazy-loads mermaid from unpkg. A supply chain scanner's own - site should not quietly take a third-party runtime dependency. + since the theme lazy-loads mermaid from unpkg. The site for a dependency + scanner should not quietly take a third-party runtime dependency. - `docs/stylesheets/iceberg.css` carries the family design tokens verbatim from the Iceberg (CTI) sheet. Edit them there first, then copy across. diff --git a/website/docs/agents.md b/website/docs/agents.md index dc2af63..e499de8 100644 --- a/website/docs/agents.md +++ b/website/docs/agents.md @@ -25,7 +25,7 @@ always the version documented. - [`references/ci-integration.md`](https://github.com/IcebergAI/IcebergSCA/blob/main/src/icebergsca/.agents/skills/icebergsca/references/ci-integration.md) — SARIF, SBOM artefacts, gating and caching -## The part that matters +## Before reporting a project clean An empty `findings` array means "checked and clean", "never checked" or "partially checked" — and only the report can say which. The skill exists mostly @@ -87,7 +87,7 @@ if not report["findings"]: - **Escalate `malicious: true`.** An OSV `MAL-` advisory is a package published to attack its consumers, not a bug. The remediation is removal, not upgrade. -## Keeping it honest +## How the skill is kept current The skill is tested, not just written: `tests/test_skill.py` guards its location, frontmatter and links, and asserts that diff --git a/website/docs/assets/icebergsca-horizontal.svg b/website/docs/assets/icebergsca-horizontal.svg index fe0355e..56df91c 100644 --- a/website/docs/assets/icebergsca-horizontal.svg +++ b/website/docs/assets/icebergsca-horizontal.svg @@ -16,4 +16,4 @@ - IcebergSCASupply chain analysis + IcebergSCASoftware composition analysis diff --git a/website/docs/ecosystems.md b/website/docs/ecosystems.md index a49e7ef..80b9e74 100644 --- a/website/docs/ecosystems.md +++ b/website/docs/ecosystems.md @@ -32,7 +32,7 @@ icebergsca scan . --ecosystem pypi,npm reports carry the **OSV** name in `package.ecosystem` and the purl type inside `package.purl`. Correlate on `purl`. -## Caveats worth knowing +## Caveats ### Go reads `go.mod`, never `go.sum` @@ -49,8 +49,7 @@ exclusions. Not modelled: profiles, mirrors, relocation and version ranges. Affected manifests carry `approximate: true`, and the table marks them `~`. IcebergSCA never shells out to `mvn`. Running a project's own build in order to -discover its dependencies is itself a supply chain risk, and a scanner is a -strange place to take it. +discover its dependencies is itself a supply chain risk. Gradle sees only literal declarations — no version catalogues, no computed versions. diff --git a/website/docs/how-it-works.md b/website/docs/how-it-works.md index bc693b1..462e417 100644 --- a/website/docs/how-it-works.md +++ b/website/docs/how-it-works.md @@ -9,6 +9,12 @@ A scan is five stages: **discover → parse → resolve → scan → report**. E records what it could not do, and that record survives all the way into the output. +The analysis is composition analysis: it inventories the third-party components +a project declares and checks each one against known advisories. It does not +read first-party source code, and it identifies a package by name and version +rather than by what the downloaded artefact actually contains — so build +provenance, signatures and typosquats are outside what a report can speak to. + ## The five stages ### 1. Discover @@ -56,7 +62,7 @@ Every package is looked up in [OSV](https://osv.dev), in two passes. `querybatch` returns advisory IDs; `GET /v1/vulns/{id}` returns the detail that makes severity and fix versions possible. -Two behaviours here matter more than they look: +Two behaviours here are worth spelling out: - **Alias merging is required, not cosmetic.** OSV returns one record per database, so a single CVE arrives as both a GHSA and a PYSEC record — rendered diff --git a/website/docs/index.md b/website/docs/index.md index f93d919..86eee77 100644 --- a/website/docs/index.md +++ b/website/docs/index.md @@ -9,15 +9,13 @@ hide: IcebergSCA -

Supply chain analysis

- -Find. Resolve. Check. Report. +

Software composition analysis

Point IcebergSCA at a directory. It finds every dependency manifest and lockfile, builds the direct and transitive dependency set, looks each package up -in **OSV**, and reports what it finds — including, precisely, everything it -could **not** check. +in **OSV**, and reports what it finds — including everything it could **not** +check.

@@ -42,15 +40,11 @@ it to the dependency tree it is meant to be auditing. ![A scan reporting 12 vulnerabilities across three Python packages](assets/scan-table.svg){ .term } -## The one rule - -A scanner is only worth running if you can trust its silence. Most of the design -here follows from a single constraint: **never imply a clean result the tool did -not earn.** +## What an empty findings list means -An empty findings list means one of three very different things — checked and -clean, never checked, or partially checked — and only the report can say which. -So IcebergSCA says which: +An empty findings list means one of three different things — checked and clean, +never checked, or partially checked — and only the report can say which. So the +report says which: - The OSV lookup not running is reported as *"vulnerability lookup did not run"*, never as *"no vulnerabilities found"*. JSON carries an explicit @@ -90,7 +84,7 @@ So IcebergSCA says which: [:octicons-arrow-right-24: Ecosystem support](ecosystems.md) -- :material-file-tree: __Real dependency graphs__ +- :material-file-tree: __Transitive dependency graphs__ --- @@ -111,13 +105,13 @@ So IcebergSCA says which: [:octicons-arrow-right-24: Output and CI](output.md) -- :material-gate-alert: __Findings never fail your build__ +- :material-gate-alert: __Findings never change the exit code__ --- Exit `0` means the scan completed, findings or not. A non-zero exit always - means the tool could not do its job — so nobody ever appends `|| true` and - silences genuine failures along with the noise. + means the tool could not do its job, which is what keeps `|| true` off the + end of the command and genuine failures visible. [:octicons-arrow-right-24: Exit codes](output.md#exit-codes) @@ -125,20 +119,20 @@ So IcebergSCA says which: --- - A skill ships inside the wheel, the way FastAPI, Typer and SQLModel do. - Agents that glob site-packages find it after install: invocation, the JSON - schema, exit-code semantics, and the three fields to check before calling a - project clean. + A skill ships inside the wheel, following the convention FastAPI, Typer and + SQLModel use. Agents that glob site-packages find it after install: + invocation, the JSON schema, exit-code semantics, and the three fields to + check before calling a project clean. [:octicons-arrow-right-24: For AI agents](agents.md)
-## Built to be scanned as well as to scan +## The tool's own dependencies -A supply chain scanner with a large dependency tree of its own is a poor +A dependency scanner with a large dependency tree of its own is a poor advertisement. Ranges, SARIF and CycloneDX are hand-written rather than pulled -from packages, correctness held by schema validation against the official +from packages, with correctness held by schema validation against the official schemas. Maven graphs are reconstructed from Central rather than shelling out to `mvn`, because running a project's build to discover its dependencies is itself a supply chain risk. CI scans this repository with the tool on every run. diff --git a/website/docs/output.md b/website/docs/output.md index 5d74316..f22f3d8 100644 --- a/website/docs/output.md +++ b/website/docs/output.md @@ -109,7 +109,7 @@ one as evidence that anything was checked. | `2` | Usage error: bad flag, unknown format (Click's reserved code) | Fix the invocation | Findings deliberately never change the exit code. A scanner that exits non-zero -on findings gets `|| true` appended within a week, at which point genuine tool +on findings sooner or later gets `|| true` appended, at which point genuine tool failures go unnoticed too. !!! note "Exit 2 is usage, exit 1 is scan failure" @@ -134,7 +134,7 @@ jq -e '(.summary.by_severity.critical // 0) == 0' report.json > /dev/null \ ## A full workflow ```yaml -name: Supply chain +name: Dependency scan on: push: { branches: [main] } @@ -165,11 +165,11 @@ jobs: category: icebergsca ``` -The scheduled run matters more than it looks: most new findings arrive because -an advisory was published, not because the code changed. +The scheduled run matters: most new findings arrive because an advisory was +published, not because the code changed. Deliberately vulnerable test fixtures are a common source of noise — exclude -them, rather than explaining them away every week. +them rather than explaining them away. ### Caching between runs diff --git a/website/zensical.toml b/website/zensical.toml index f4128fc..95ccda1 100644 --- a/website/zensical.toml +++ b/website/zensical.toml @@ -3,7 +3,7 @@ [project] site_name = "IcebergSCA" -site_description = "Supply chain analysis for software projects: lockfile-first dependency scanning against OSV, with a report that never implies a clean result it did not earn." +site_description = "Software composition analysis for software projects: lockfile-first dependency scanning against OSV, with a report that never implies a clean result it did not earn." site_author = "IcebergAI" site_url = "https://icebergai.github.io/IcebergSCA/" @@ -122,8 +122,9 @@ check_paths = true # sites, but no page currently uses one — the theme lazy-loads mermaid.min.js # from **unpkg.com**, and every other asset on this site is self-hosted. Adding # a ```mermaid fence silently reintroduces a third-party CDN dependency at view -# time; render the diagram as a table or an inline SVG instead. On a site for a -# supply chain scanner that would be a particularly poor look. +# time; render the diagram as a table or an inline SVG instead. On the site for +# a tool that exists to find undeclared third-party code, that would be a +# particularly poor look. custom_fences = [ { name = "mermaid", class = "mermaid", format = "pymdownx.superfences.fence_code_format" } ]