Scan on the customer’s runners, not mine
Why
It is the only way to promise the code never leaves GitHub: nothing
is cloned or stored on my side. It also means scanning capacity grows
with the number of users at no cost to me.
The cost
Scans spend the customer’s Actions minutes, take as long as a CI job,
and I cannot see anything beyond the artifact the run produces.
Add the workflow through a pull request
Why
The owner reads exactly what will run before anything runs, and
stopping is as simple as deleting one file. That consent step is what
makes a stranger comfortable installing the app.
The cost
There is an extra step before the first result, and some people
never merge. The API has to track PR state and handle repos where the
workflow already exists.
Pull the report instead of letting the Action push it
Why
If the Action posted results to my API, every repo would need a
BOMWatcher secret to authenticate with. Instead the Action only uploads
an artifact, GitHub tells me the run finished, and I fetch the artifact
with a token GitHub issues to the app. No secret lives in user repos.
The cost
It adds a round trip, depends on webhook delivery, and relies on the
artifact still existing. Artifacts are kept for 30 days.
Reply first, ingest second
Why
GitHub expects a webhook response within a few seconds. Verifying the
signature and answering 202 before downloading anything means a slow
artifact never makes GitHub mark the delivery as failed.
The cost
The ingest runs as a FastAPI background task in the same process. If
the process restarts mid-ingest, that scan is left marked as running.
Use the workflow run ID as the scan ID
Why
GitHub sends several workflow_run events per run and can redeliver
them. With the run ID as the primary key, every event for a run lands on
the same row, so handling them is idempotent.
The cost
Re-running a workflow keeps the same run ID, so the newest attempt
overwrites the previous one rather than adding a second scan.
Detect AI models with patterns, not a model
Why
Regular expressions for model names, SDK imports and Hugging Face
loaders run anywhere Python 3 does, need no dependencies, and catch the
common case: a model name written in the code.
The cost
Names built at runtime or read from environment variables are
missed, and each new model family needs a pattern.