fix(action): make SBOM sharing opt-in and correct the privacy docs - #104
Conversation
`action/entrypoint.sh` invoked the scan with `--share --share-yes`
hardcoded, so every Action run — for every user, with or without a
`token` — uploaded the full CycloneDX SBOM to aisbom.io and minted a
publicly-readable 30-day link. The shipped docs said the opposite:
"Leave `token` unset and the Action stays purely local — nothing is
sent" and "there is no background or implicit sending".
Sharing is now an explicit `share` input, default `false`. The flags are
passed only when it is exactly `true`; otherwise no request reaches
aisbom.io and the entrypoint says so in the log. `share-url` is always
written to GITHUB_OUTPUT, empty when sharing is off, so consumers read an
empty string rather than an unset output. The SBOM artifact, the PR
comment, fail-on-risk and the platform upload are untouched — they render
from the local sbom.json, which is what they should have used all along.
The input is appended as argv $10 so positions 1-9 do not shift, and the
empty-array expansion is written `${SHARE_ARGS[@]+"${SHARE_ARGS[@]}"}` so
it stays safe under `set -u` on bash < 4.4.
Docs corrected in both READMEs, including a second claim of the same kind
found while checking the first: AISBOM_NO_TELEMETRY=1 was documented as
disabling the share upload, but it only withholds the cli_share_created
event — the upload itself is gated solely on `--share`.
tests/test_action_entrypoint.py executes the real script with a stubbed
aisbom on PATH and asserts on the recorded argv and scan log, not on the
entrypoint source, so a future edit cannot silently re-enable publishing.
BREAKING CHANGE: anyone relying on the `share-url` output or on the
viewer link in the PR comment must now set `share: true`.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 959fb8067f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if [ "${INPUT_SHARE}" = "true" ]; then | ||
| SHARE_URL=$(grep -oE 'https://aisbom\.io/viewer\?h=[A-Za-z0-9_-]+' "${SCAN_LOG}" | head -n1 || true) | ||
| fi |
There was a problem hiding this comment.
Gate the comment viewer link on
share
When share is false, this guard only keeps share-url empty; post_comment.py still searches the entire scan log for any viewer-shaped URL and inserts the first match into the PR comment. For example, scanning a remote target such as https://aisbom.io/viewer?h=Unrelated.pt prints that target in the CLI banner, causing the comment to link to h=Unrelated despite sharing being disabled. Pass the share setting or a separately validated URL to the comment renderer so the promised opt-out also governs comments.
Useful? React with 👍 / 👎.
| if [ "${INPUT_SHARE}" = "true" ]; then | ||
| SHARE_ARGS=(--share --share-yes) | ||
| else | ||
| echo "[aisbom-action] Sharing is off (share: false, the default): nothing is sent to aisbom.io and the share-url output will be empty. Set share: true to publish a hosted viewer link." |
There was a problem hiding this comment.
Narrow the no-network assurance to SBOM sharing
On a default run without AISBOM_NO_TELEMETRY, the CLI emits scan telemetry and post_comment.py posts Action telemetry to api.aisbom.io, so the statement that “nothing is sent to aisbom.io” is false. The newly added Action README likewise says the default “sends nothing anywhere” before later documenting default-on telemetry. Qualify these messages as saying that no SBOM/share request is sent; otherwise privacy-conscious users receive an incorrect runtime assurance.
Useful? React with 👍 / 👎.
| 1. **SBOM upload (`--share`):** the rendered CycloneDX JSON is POSTed to `aisbom.io/api/sbom-share` so the comment can link to a hosted viewer. The SBOM is publicly viewable to anyone with the URL and expires after 30 days. The unguessable 12-char URL token is the only access control. | ||
| 2. **Hosted dashboard upload (opt-in via `token`):** when you set the `token` input, the same CycloneDX JSON is POSTed to `https://app.aisbom.io/v1/scan-result` (or your `platform-url` override) along with the branch/tag name (`GITHUB_REF_NAME`), so your dashboard at [app.aisbom.io](https://app.aisbom.io) can track the repo's SBOM history. Data is stored in the EU. The upload is logged loudly in your CI output every time it happens. Remove the token (or the input) to stop. Without a token, nothing is sent to the dashboard. | ||
| 3. **Anonymous telemetry:** two events (`github_action_run` and `github_action_comment_posted`) are POSTed to `api.aisbom.io/v1/telemetry`. No repo identifier, no file paths, no findings content — just severity buckets and whether the comment was created vs updated. | ||
| In every case the payload is the SBOM — file names, SHA-256 hashes, licenses, risk and legal findings — never model weights or file contents. |
There was a problem hiding this comment.
Describe the telemetry payload separately
This says that the payload is the SBOM “in every case,” including anonymous telemetry, contradicting both the preceding paragraph and the implementation: telemetry sends event names and low-cardinality parameters, not file names, hashes, licenses, or the SBOM document. Users evaluating data exposure are therefore told that default-on telemetry transmits substantially more information than it actually does; restrict this sentence to the two SBOM upload paths.
Useful? React with 👍 / 👎.
…laims Addresses three findings from Codex review on #104. 1. The opt-out governed only the `share-url` output. post_comment.py independently re-derived the link by scraping the scan log with a URL-shaped regex, so a URL-shaped scan target or filename could put a viewer link in the PR comment on a run that shared nothing. The scrape is now gated on a `--share-enabled` argument passed from the entrypoint, defaulting to false, so `share: false` means no viewer link by construction rather than by the log happening not to match. 2. The runtime notice said "nothing is sent to aisbom.io" when sharing is off. Anonymous telemetry is default-on and goes to api.aisbom.io, so that was false — the same class of overclaim this PR exists to correct. Narrowed to the SBOM, with telemetry named as separate and still on. The Action README's quick start had the matching contradiction: it promised the default "sends nothing anywhere" and then documented default-on telemetry three paragraphs later. 3. Both READMEs said the payload is the SBOM "in every case", sweeping telemetry in with the two upload paths and overstating what it carries. Telemetry sends event names and low-cardinality parameters — no SBOM, no file names, no hashes, no repo identifier. Described separately. Six new tests: four drive post_comment.main() with a decoy URL-shaped scan log to prove the gate holds and that suppressing the link does not suppress the findings table, and two assert the entrypoint forwards the setting.
The problem
action/entrypoint.shinvoked the scan with--share --share-yeshardcoded, with no conditional and no input to disable it. Every run of the Action, for every user, uploaded the full CycloneDX SBOM toaisbom.io/api/sbom-shareand minted a publicly-readable 30-day link.--share-yesexists to bypass the CLI's confirmation prompt, and the Action passed it on the user's behalf.The shipped docs said the opposite. From the Action's own Data flow & privacy section:
Removing
tokenstopped the dashboard upload and nothing else. The share upload was unconditional, undocumented as optional, and could not be turned off at all.Exposure shape, stated accurately
aisbom:*properties. Never weights or file contents.expirationTtl, after which it is deleted.The exposure is modest. The documentation defect is the serious part: a security product making a privacy claim its own code contradicted.
The fix
Sharing becomes an explicit
shareinput, defaultfalse.shareis exactlytrue. Otherwise no request reachesaisbom.io, and the entrypoint says so in the log rather than going quiet.share-urlis now always written toGITHUB_OUTPUT, empty when sharing is off, so consumers read an empty string instead of an unset output.fail-on-riskand the platform upload are untouched — all four render from the localsbom.json, which is the artifact they should have been using all along.Two implementation details worth a reviewer's eye:
$10, not inserted, so positions 1–9 stay put.${SHARE_ARGS[@]+"${SHARE_ARGS[@]}"}— expanding an empty array underset -uis an unbound-variable error on bash < 4.4.Docs
Both READMEs now state exactly which network call each input enables, and the "no background or implicit sending" claim is gone.
While checking that claim I found a second one of the same kind:
action/README_ACTION.mddocumentedAISBOM_NO_TELEMETRY=1as disabling the share upload. It does not —aisbom/cli.pygates the upload onshare and has_contentalone; the env var only withholds thecli_share_createdevent. Corrected in both READMEs.Tests
tests/test_action_entrypoint.py(new, 17 tests) executes the realentrypoint.shwith a stubbedaisbomonPATHthat records its argv, then asserts on that argv and on the scan log — not on the entrypoint source. A future edit that re-introduces publishing by another route still fails.Covered: default off;
shareomitted entirely (an old caller passing nine args) still off; only the literal"true"enables it (TRUE/True/yes/1do not); scan target and--outputsurvive the conditional;share-urlempty vs populated;sbom-pathand the PR comment unaffected either way.Verification
poetry run pytest --cov=aisbom --cov-fail-under=85— 1111 passed, coverage 92.80%.main's entrypoint and re-running the new tests fails 11 of 17, so the guard is real. One test passed vacuously on an empty log; it now asserts the log is non-empty first.Share Link Created, zeroviewer?h=in the scan log,GITHUB_OUTPUT=sbom-path=…/sbom.jsonplusshare-url=, andsbom.jsonwritten.Compatibility
This is a behaviour change for every existing Action user. Anyone consuming the
share-urloutput, or expecting the viewer link in their PR comment, loses it until they setshare: true. It ships asv1.4.0and is called out in the README and the release notes as a deliberate privacy-improving change rather than going out quietly.Left deliberately out of scope: whether the hosted share feature earns its place in the Action at all. That's a product call, not a fix.