Two ways to get source into Offense: install the GitHub App and pick one repository, or upload an archive yourself. Both feed the same scan pipeline.
What a code scan is
A scan is a source inspection, not a live assessment. The runner opens a disposable Boat sandbox and runs OSV-Scanner for known dependency vulnerabilities, Gitleaks for committed secrets, and Semgrep for code patterns. The result is findings, a coverage report, a report URL, and ordinary evidence and budget handling.
Repository code and install scripts are never executed. Git submodules and LFS objects are not downloaded.
The GitHub App
Open /app/connections and choose Connect GitHub. That installs the Offense
GitHub App on an organization or personal account and then lets you pick one
repository. Offense never stores a token: it signs a short-lived RS256 JWT with
the App private key and mints installation tokens per request.
What is stored is the installation ID, the account login and type, and the chosen repository with its resolved commit. The App private key never leaves the Worker, and installation tokens are never persisted, rendered, or sent into a sandbox. A cross-site-but-random state cookie binds the callback to the workspace and the pending connection row.
Run sandbox scan resolves the saved ref to a commit, downloads a bounded
archive (20 MiB by default, MAX_SCAN_ARCHIVE_BYTES), and dispatches to
/scans. Redirects are restricted to GitHub's archive host and the installation
token is not forwarded.
App permissions
| Permission | Needed for |
|---|---|
| Contents: Read | reading the archive |
| Metadata: Read | basic repository facts |
| Contents: Write | opening remediation pull requests |
| Pull requests: Write | opening remediation pull requests |
| Checks: Write | posting pull-request check runs |
Set the Setup URL to https://app.offense.sh/app/connections/github/callback
(matching PUBLIC_ORIGIN). For pull-request checks set the webhook URL to
https://app.offense.sh/webhooks/github subscribed to Pull request events.
Required Worker secrets: GITHUB_APP_ID, GITHUB_APP_SLUG,
GITHUB_APP_PRIVATE_KEY (PKCS#1 or PKCS#8 PEM), and GITHUB_APP_WEBHOOK_SECRET
for the check.
Run web/db/migrations/0027-github-app-connection.sql and
0034-github-remediation.sql before deploying.
Uploading an archive yourself
POST /api/v1/scans takes the raw .tar.gz as the request body, not as
multipart form data:
tar -czf src.tgz -C ./my-app .
curl -sX POST https://app.offense.sh/api/v1/scans \
-H "Authorization: Bearer $OFFENSE_API_KEY" \
-H 'content-type: application/gzip' \
-H 'x-offense-repo: acme/app' \
-H 'x-offense-commit: 0f97314' \
--data-binary @src.tgz
Returns 201 (or 200 on an idempotent retry):
{ "scan": { "…assessment fields…" }, "reportUrl": "https://app.offense.sh/app/assessments/…" }
| Status | error |
Meaning |
|---|---|---|
| 400 | archive_required |
body was empty |
| 413 | archive_too_large |
over limitBytes, which is included |
| 402 | monthly_plan_required |
first scan needs a plan; adds checkoutUrl |
| 402 | insufficient_credits |
balance too low |
| 502 | scan_dispatch_failed |
runner refused the job |
x-offense-repo and x-offense-commit are optional labels. GET /api/v1/scans/:id returns { scan, findings, reportUrl } with the scans:read
scope, and 404 not_found unless the assessment is a code scan in your
workspace.
Pre-merge checks in CI
POST /api/v1/checks runs the same policy as the GitHub webhook without GitHub.
It needs the checks:create scope.
Pass a change set directly:
{ "files": [{ "filename": "src/app.js", "patch": "@@ …" }], "advisories": [] }
Or point at a pull request on the connected repository with { "repo": "acme/app", "number": 42 }. With files supplied the analysis is returned inline. With a pull
request number Offense fetches it using the installed App, then posts one check
run and one comment, updated in place.
The policy:
- scans added lines for secrets,
- diffs dependency manifests,
- blocks new critical or high risk,
- warns on dependency changes.
github_not_connected (409) means no App installation is saved;
github_not_configured (503) means the App secrets are missing from the
deployment.
Remediation pull requests
POST /api/v1/assessments/:id/findings/:findingId/remediation with
findings:remediate opens one draft pull request for a confirmed finding.
You supply the fix files; the App commits them on a new branch and opens the
pull request. The App never merges and never writes the base branch. A missing
write scope is reported as its own error rather than a generic failure, and the
finding shows the pull request link once it exists.
When a remediation pull request is merged, Offense retests the findings it
opened through the same fresh-sandbox path as a manual retest and marks the
remediation merged. An unmerged close does nothing. Nothing is auto-merged.
Not implemented
- Automatic scans on push.
- Manifest-driven scanning.
- Submodule and LFS fetching.