Docs

This page takes you from installing the Ferralon App to reading your first results. It also covers two settings you will come back to: how far back each scan looks, and how to verify the corpus your code is checked against.

Quickstart

  1. Install the Ferralon App. Start at console.ferralon.com/start and sign in with GitHub. You are taken to install the Ferralon GitHub App on the repositories you choose. You can also install it directly from github.com/apps/openferralon.
  2. Merge the setup pull request. For each repository, Ferralon opens a pull request titled "Add Ferralon Assay" from a branch named ferralon-assay. It adds one file, .github/workflows/ferralon-assay.yml. Nothing runs on your default branch until you merge it.
  3. Read your results. The workflow analyzes your entire codebase on every pull request, on every push, daily at 03:17 UTC, and whenever you click Run workflow in the Actions tab. Each pull request gets a comment with its candidates. The repository gets a dashboard issue that stays current. Each run uploads its report (report.json, openvex.json and report.sarif.json) as an artifact named ferralon-assay-report.

After that, sign in to the console with GitHub, or with Google or an email link; the first time you use Google or an email link, the console asks you to connect your GitHub account once. Microsoft sign-in is not offered yet. Signing in takes you to the last organization you used, or to another one you belong to; a GitHub sign-in goes to your own account first if Ferralon is installed there. You see the install page only if none of them has Ferralon installed.

If you belong to more than one organization, the switcher in the console's header moves you between them. The Add a GitHub org card installs Ferralon on another GitHub organization you manage and brings you back to the console in it. If a sign-in fails, you get a page that says so, with Try again.

With FERRALON_LINK_TO_CONSOLE: true in the job's env:, as the setup pull request ships it, results from your default branch also appear in the Ferralon console. Where GitHub code scanning is available, results also appear as code scanning alerts. That means public repositories, and private repositories with GitHub Advanced Security.

What the setup pull request installs

The setup pull request is the whole footprint: one GitHub Actions workflow. The scan runs on your runner, with the workflow's own token, and every action it uses is pinned to a full commit SHA. It keeps its baseline on a separate ref, refs/tegron/state, so your branches never carry Ferralon state.

The workflow asks for these permissions:

permissions:
  contents: write
  actions: write
  issues: write
  pull-requests: write
  id-token: write
  security-events: write

A few kinds of repository need something from you first:

  • No commits yet. There is no branch to add the workflow to. Push a first commit, then reinstall the App on that repository.
  • A fork. GitHub turns Actions off on forks by default, and only you can turn them on. Enable workflows in the repository's Actions settings, and the scan runs from then on.
  • Archived or disabled. GitHub allows no writes, so nothing is added.
  • Already has the workflow. Ferralon never overwrites it. If its Actions state is disabled, Ferralon turns it back on.

Free on its own, or linked to the console

Assay is free either way. One line in the job's env:, FERRALON_LINK_TO_CONSOLE: true, decides whether the workflow contacts Ferralon at all. While it is true, a run makes three requests, to the three addresses written out in the workflow file. None carries your source code:

  • The scan window, at the start of every run: one request to /policy/scan-window on Ferralon's API, sending the run's GitHub OIDC token, which names the repository, its owner and the run. The answer is the window to use and where it came from; see How a window reaches your repository.
  • runs-url, on runs that analyzed your default branch (or the ref a .github/ferralon.yml names under analyze.ref): the assessment. It lists your dependencies, the advisories that matched them, and for a reachable finding, the function names and file:line where your code reaches the affected package.
  • ingest-url, on every run: the repository's owner and name, the commit SHA and the scanner version.

Set it to false, or delete the line, and none of the three is made: nothing in the workflow contacts Ferralon. The scan uses the window written into the file, and the pull-request comment, the dashboard issue, code scanning and the report artifact all keep working. The assay step's link-to-console input reads this same value, so change it in env:, not there. Emptying an address does not switch anything off.

Linking puts your results in the console, and it is how Arc receives the candidates it proves. The uninstall signal arrives on the ingest-url channel, so with linking off the workflow can no longer remove itself. Security & data sets out exactly what reaches Ferralon while linking is on.

Removing Ferralon

Uninstall the Ferralon App and the workflow file removes itself. Deleting the file yourself is also safe. To remove the baseline ref as well, run git push origin --delete refs/tegron/state.

Scan windows

A scan window sets which advisories each scan checks your code against, by when they were published. There are four:

WindowCorpus policyChecks
24h (default)published-24hAdvisories published in the last 24 hours
7dpublished-7dThe last 7 days
30dpublished-30dThe last 30 days
fullfullEvery advisory in the corpus, except withdrawn records

A shorter window checks fewer advisories. It does not mean fewer problems. Use full when you want every published advisory checked against your code as it stands today.

Setting the window

An organization admin sets the window in the console's settings, and other members see it read-only. Until an admin changes it, the window is 24h.

How a window reaches your repository

The workflow asks your console for the window at the start of every scan. Its step Read the scan window from your Ferralon console makes one request and hands the answer to Assay as advisory-corpus-policy. A window you save in the console takes effect on each repository's next scan, with no pull request. The step writes the window it used, and where it came from, to the run's log and summary:

Scan window: 7 days (published-7d), set in your Ferralon console (Customer "…")
Scan window: 24 hours (published-24h), fallback: the request timed out

If the console can't be reached, or the run has no GitHub OIDC token to ask with (a pull request from a fork gets none), the step uses the window written into the file when Ferralon wrote it, says why in a warning, and the scan goes on. The step never fails the job.

One switch stops every call to Ferralon: FERRALON_LINK_TO_CONSOLE: true in the job's env:. Set it to false, or delete the line, and nothing in the workflow contacts Ferralon. The step then uses the window written into the file without asking, and its log says so.

To pin your own window, replace the expression on the advisory-corpus-policy line with a policy id from the table above. A pinned value wins: Ferralon never changes it, and the console shows the repository as pinned, for example Pins 7 days in its workflow. A workflow Ferralon wrote before the console could manage windows passes a fixed value too, so those repositories show as pinned.

To hand a pinned repository to the console, click Let the console manage this beside it, or Let the console manage all N pinned repos to hand over every pinned repository at once. Ferralon opens one pull request per repository that replaces the pinned value with your console setting. Merge it to apply. You do this once: from then on, each window you save reaches the repository on its next scan. If a hand-edited workflow can't take that change, the console says it can't manage the repository, and why.

In the console, each repository reads Uses your console setting. followed by the window its last scan was served, or the one it will use on its next scan; or Pins … in its workflow; or Not known, with the reason.

Verifying corpus signatures

Every advisory Assay and Arc check comes from Ferralon's published corpus, so the corpus is part of what you are trusting. Each release since October 7, 2026 is signed, and you can check the signature yourself.

Each release ships its bundles with an index, bundles.json, that lists every bundle's SHA-256. A signed release adds bundles.json.sigstore.json, a Sigstore signature over that index. Verify the index, then check each bundle's hash against it, and every asset you use traces back to the publisher.

The publisher signs with cosign keyless signing, so there is no long-lived key to steal. What you pin is the signer's identity, and you pin both values exactly:

Certificate identityferralon-intel-publisher@ferralon.iam.gserviceaccount.com
OIDC issuerhttps://accounts.google.com
TAG=corpus-…   # resolve once, then reuse for every asset
base="https://github.com/ferralon-ai/vulnerability-corpus/releases/download/$TAG"
curl -fsSLO "$base/bundles.json"
curl -fsSLO "$base/bundles.json.sigstore.json"

cosign verify-blob --bundle bundles.json.sigstore.json \
  --certificate-identity ferralon-intel-publisher@ferralon.iam.gserviceaccount.com \
  --certificate-oidc-issuer https://accounts.google.com \
  bundles.json

# Then, for each bundle you fetch, its sha256 must equal its bundles[].digest in the
# index you just verified. Hash the downloaded .jsonl.gz itself, never a decompressed copy.

Verified OK with exit code 0 means the index is ours and unchanged since signing. Any other result means do not use the release.

Where the check runs today

Arc's proof machines run this check on every release before they fetch a bundle, and have since October 13, 2026. They fail closed: a missing signature, an index changed after signing, a damaged signature or the wrong signer stops the run before any bundle is fetched.

Assay does not check the signature yet. If you rely on the corpus through Assay, or consume it directly, run the command above on each release you use.

Getting the check right

  • Use cosign 3.x. The publisher and Arc both run v3.1.3.
  • The signature is a single-file Sigstore bundle, not a detached .sig and .pem pair.
  • Use --certificate-identity, not --certificate-identity-regexp. There is one signer with one exact name, and a pattern can match others.
  • Resolve releases/latest once per run and reuse the corpus-… tag it returns, so every asset comes from the same release. Pinning a tag is better still: old tags stay published, so a pinned run is reproducible.
  • Releases cut before signing began carry no signature. Once your pipeline verifies, treat a missing signature as a failure, as Arc does.
  • The signature covers the release assets, not the repository tree or manifest.json on the default branch. It tells you who published a release, not that every advisory in it is correct.

The full consumption protocol is in CONSUMING.md.