Integrating with the platform
Cyscan on its own is great for shift-left in one repo. The Cybrium platform adds the layers that only make sense with state: reachability, multi-repo dashboards, historical trend, team triage, and compliance evidence bundles.
The bridge between the two is SARIF — cyscan emits it, the platform ingests it.
How the integration works
┌──────────────── developer / CI ────────────────┐
│ │
│ cyscan scan . --format sarif → out.sarif │
│ │
└──────────────────────────────────────┬──────────┘
│
▼
POST /api/scans/upload-sarif/ │ Authorization: Bearer sk-...
│
┌──────────────── platform ─────────────────────┐
│ │
│ parses SARIF → creates ScanJob │
│ upserts Finding rows (dedup by rule/file/line)│
│ enriches supply-chain with reachability │
│ routes to tenant schema via API-key org binding│
│ │
└────────────────────────────────────────────────┘
1. Get an API key
Platform UI → Settings → API Keys → Create API key
- Name: anything, e.g.
ci-github-actions - Scopes: at minimum
scan:uploadandfindings:write - Expiry: 90 days (recommended) or leave blank for non-expiring
You'll see the raw sk-... key once — copy it now. It's tenant-scoped automatically to the org you're browsing.
Use the Cybrium CLI: cybrium login opens a browser, mints a 90-day key, stores it in ~/.cybrium/config.yml. Then the CLI handles SARIF uploads for you:
cyscan scan . --format sarif | cybrium findings upload -
2. Upload SARIF
cyscan scan . --format sarif > out.sarif
curl -X POST https://app.cybrium.ai/api/scans/upload-sarif/ \
-H "Authorization: Bearer sk-..." \
-F "sarif=@out.sarif" \
-F "title=CI scan on $(git rev-parse --short HEAD)"
Response:
{
"upload_id": "4d775be0-87d6-48d0-bde4-3a168e2cc31f",
"scan_id": "3c647cd6-23c6-45dd-a5d6-446be91d6b9f",
"tool": "cyscan",
"payload_sha256": "ef8c41f46ba3d1cf...",
"findings_created": 5,
"findings_updated": 0,
"findings_skipped": 0,
"idempotent": false,
"created_at": "2026-04-14T14:29:21Z"
}
Idempotency
Re-uploading the same file returns "idempotent": true with no changes — content sha256 is the dedup key. Use this to retry fearlessly in CI.
Attaching to an existing scan
Pass scan_id to append findings to an existing scan rather than creating a new one:
curl -X POST .../upload-sarif/ \
-H "Authorization: Bearer sk-..." \
-F "sarif=@out.sarif" \
-F "scan_id=3c647cd6-23c6-45dd-a5d6-446be91d6b9f"
GitHub Actions recipe
name: Cyscan
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install cyscan
run: |
curl -sSL "https://github.com/cybrium-ai/cyscan/releases/latest/download/cyscan_latest_x86_64-unknown-linux-gnu.tar.gz" \
| tar -xz -C /tmp
sudo mv /tmp/cyscan_*/cyscan /usr/local/bin/
sudo mv /tmp/cyscan_*/rules /opt/cyscan-rules
echo "CYSCAN_RULES=/opt/cyscan-rules" >> $GITHUB_ENV
- name: Scan + upload
env:
CYBRIUM_API_KEY: ${{ secrets.CYBRIUM_API_KEY }}
run: |
cyscan scan . --format sarif > cyscan.sarif
curl --fail -X POST https://app.cybrium.ai/api/scans/upload-sarif/ \
-H "Authorization: Bearer ${CYBRIUM_API_KEY}" \
-F "sarif=@cyscan.sarif" \
-F "title=${GITHUB_REPOSITORY} @ ${GITHUB_SHA:0:7}"
Reachability enrichment (platform-only)
Cyscan's supply-chain findings arrive tagged with packageCoordinate (e.g. event-stream@3.3.6). The platform's reachability engine walks your uploaded call graph and annotates each finding with a verdict:
reached— your code actually calls a vulnerable symbolunreached— the dep is present but no call path existsunknown— not enough data (no call graph yet; common for first upload)
Unreached findings are collapsed under a "hidden by reachability" counter in the UI. Reached ones stay front and centre. Customers typically see a ~20x reduction in apparent CVE noise after the first full-project reachability pass.
This layer is proprietary — the OSS cyscan binary emits the coordinate data, the platform does the graph work.
Scopes reference
Each endpoint requires a specific scope on the API key:
| Endpoint | Required scope |
|---|---|
POST /api/scans/upload-sarif/ | scan:upload |
GET /api/scans/ | scan:read |
GET /api/scans/findings/ | findings:read |
PATCH /api/scans/findings/:id/ | findings:write |
A key with no scopes is legacy full-access; new keys created from the UI default to scan:upload + findings:write + findings:read.
Rate limits
- Login attempts: 10 per 10 minutes per IP
- Device-code activation: 30 per 10 minutes per IP
- SARIF upload: 60 uploads per minute per API key
Hit one and you get a 403 with a plain-English detail. Wait out the window and retry.
Next step
- Cybrium CLI — the higher-level wrapper that handles auth, upload, and more