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" } ]