Documentation

Everything you need to scan builds from the dashboard or your pipeline. One idea runs through all of it: DeltaWard tells you what this build introduced — not a 200-item dump of everything that was ever true.

Getting started

DeltaWard analyzes the artifact your CI already produces — the final .apk or .ipa — and diffs every scan against your previous build of the same app.

  1. Sign in at app.deltaward.com. (DeltaWard is in closed alpha — sign-ups are invite-only for now.)
  2. Upload a build from the dashboard. The first scan of an app becomes its baseline: everything it finds is new by definition.
  3. Upload the next build and the report becomes a diff — new, fixed, persisting — with plain-language explanations and concrete fixes.

iOS note: upload developer-exported IPAs (from your own build or an ad-hoc/development export). Store-downloaded IPAs are encrypted and can't be analyzed.

Scans & the delta view

Every finding in a scan carries a delta state relative to your previous build:

StateMeaning
newIntroduced by this build. The default view, and the only thing that can fail a CI gate.
fixedPresent in the previous build, gone in this one.
persistingKnown from earlier builds. Reported, never re-raised as urgent — and never breaks a build.

Dismissals carry forward. Dismiss a finding once (false positive, accepted risk) and it stays dismissed across future builds — in the dashboard, in the CLI gate, and in the CI API. Findings come only from deterministic analyzers; severities are never set by an LLM.

Upload keys

CI uploads authenticate with an upload key instead of your account credentials. Mint one on an app space's page in the dashboard.

  • Keys look like dw_ak_… and are shown once, at creation. DeltaWard stores only a SHA-256 hash — a lost key is replaced, never recovered.
  • A key is scoped to one app space: scans it uploads land in that space, and it can read nothing else — not your other spaces, not dashboard uploads.
  • Revocation is immediate. Revoke a key on the space page and every request with it fails from that moment.
  • Requests are rate-limited per key. Spread very chatty pipelines across spaces, or contact us.

Store the key as a CI secret (DELTAWARD_KEY). It never needs to be written to disk.

The CLI

The deltaward npm package uploads a build, waits for the scan, prints a delta-first report, and exits with a code your pipeline can act on. Node 20+, no install required:

DELTAWARD_KEY=dw_ak_… npx deltaward scan app-release.apk --wait --fail-on high

Gate semantics: the exit code is non-zero only when this build introduced findings — new, non-dismissed, at or above --fail-on. Persisting findings never re-break a build, and dismissals made in the dashboard are honored in CI.

Flags

FlagDefaultWhat it does
--fail-on <severity>highLowest severity of a NEW finding that fails the build (critical | high | medium | low | info | none). "none" disables the gate — the scan still runs and reports.
--waitPoll until the scan finishes, print the delta report, and apply the gate. Without it the CLI uploads, registers, prints the scan URL and exits 0.
--jsonMachine-readable output: one JSON document on stdout, nothing else.
--sarif <file>Write the findings as SARIF 2.1.0 to <file> (for GitHub code scanning upload). Requires --wait.
--timeout <seconds>600Maximum time to wait for the scan to complete (only with --wait).
--helpShow usage and exit.
--versionPrint the CLI version and exit.

Environment

VariableRequiredWhat it does
DELTAWARD_KEYyesUpload key for one app space (dw_ak_…), minted on the space page in the dashboard. Pass it as a CI secret — it is never written to disk or logs.
DELTAWARD_API_URLnoScanner API base URL. Default: https://scanner.deltaward.com
DELTAWARD_APP_URLnoDashboard base URL used in finding deep links. Default: https://app.deltaward.com

Exit codes

CodeNameMeaning
0passScan completed and no new, non-dismissed findings meet --fail-on (or --wait was not given and the upload was accepted).
1gate failedNew, non-dismissed findings at or above --fail-on. Persisting findings never cause this — only what this build introduced.
2scan failedThe analysis errored or did not complete within --timeout.
3usage / auth errorBad arguments, unreadable file, missing or rejected DELTAWARD_KEY.

CI snippets

GitHub Actions:

- name: DeltaWard security gate
  run: npx deltaward scan app/build/outputs/apk/release/app-release.apk --wait --fail-on high
  env:
    DELTAWARD_KEY: ${{ secrets.DELTAWARD_KEY }}

On GitHub, the GitHub Action wraps this with a sticky PR comment and SARIF upload.

GitLab CI:

deltaward:
  stage: test
  image: node:20
  script:
    - npx deltaward scan app-release.apk --wait --fail-on high
  variables:
    DELTAWARD_KEY: $DELTAWARD_KEY

Bitrise:

- script@1:
    title: DeltaWard security gate
    inputs:
      - content: npx deltaward scan $BITRISE_APK_PATH --wait --fail-on high

CI API

The CLI is a thin client over a small HTTP API. Use the API directly from any environment that can speak HTTPS. Base URL: https://scanner.deltaward.com; authenticate every request with Authorization: Bearer dw_ak_….

The flow is three steps, in order:

  1. POST /ci/uploads with your build's filename — returns a scan_id and a one-time signed upload URL.
  2. PUT the binary to the signed URL (plain HTTP PUT, no auth header — the URL itself is the credential).
  3. POST /ci/scans with the scan_id — registers the scan and starts the pipeline. Then poll GET /ci/scans/{id} and fetch GET /ci/scans/{id}/findings when it completes.

Endpoints

POST/ci/uploads

Mint a scan_id and a one-time signed upload URL for a build.

No scan exists yet: PUT the binary to upload_url (plain HTTP PUT, no auth header — the URL itself is the credential), then register it with POST /ci/scans. The filename's extension (.apk or .ipa) selects the analysis platform; anything else is rejected with 422.

POST/ci/scans

Register an uploaded build and start the scan.

Verifies the binary actually landed in storage (409 if it didn't), creates the queued scan in the key's app space, and starts the pipeline. 409 if the scan_id was already registered. Optional filename is kept as the build's display name.

GET/ci/scans/{scan_id}

Status and delta counts for a scan the key can see.

Poll until status is completed or failed. Once completed, new_count / fixed_count / persisting_count carry the diff against the app's previous build. 404 for scans outside the key's space.

GET/ci/scans/{scan_id}/findings

Findings for a completed scan (409 while it is still running).

Each finding carries delta (new | persisting) and state (open | dismissed). A faithful CI gate fails the build only on findings with delta == "new" and state == "open" at or above its severity threshold — dismissals made in the dashboard are honored here.

Gating on the response

Each finding in the findings response carries delta (new | persisting) and state (open | dismissed). A faithful gate fails the build only on findings where delta == "new" and state == "open" at or above your severity threshold — that's exactly what the CLI's --fail-on does.

GitHub Action

A composite action wrapping the CLI: scan the artifact your workflow just built, keep one sticky PR comment updated with the delta summary, optionally upload SARIF to GitHub code scanning, and fail the job only when the gate fails. Same exit codes as the CLI; the comment and SARIF upload always run before the job fails.

Marketplace listing pending. The action ships to the GitHub Marketplace alongside the CLI's npm release. The CLI snippet above works in any workflow today.

Usage

name: deltaward
on: pull_request

permissions:
  contents: read
  pull-requests: write     # sticky PR comment
  security-events: write   # only if upload-sarif: true

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: ./gradlew assembleRelease
      - uses: deltaward/scan-action@v1
        with:
          artifact: app/build/outputs/apk/release/app-release.apk
          key: ${{ secrets.DELTAWARD_KEY }}
          fail-on: high
          upload-sarif: "true"

Inputs

InputDefaultWhat it does
artifact(required)Path to the built .apk or .ipa to scan.
key(required)DeltaWard upload key (dw_ak_…) — pass a repository secret, never a literal.
fail-onhighLowest severity of a NEW finding that fails the build (critical | high | medium | low | info | none).
commenttruePost (and keep updating) a sticky PR comment with the delta summary. Needs pull-requests: write.
upload-sariffalseUpload the SARIF log to GitHub code scanning. Needs security-events: write (and GitHub Advanced Security on private repos).
timeout600Maximum seconds to wait for the scan to complete.
cli-versionlatestdeltaward npm version to run (passed to npx).

Outputs

OutputWhat it is
new-countFindings this build introduced.
fixed-countFindings gone since the previous build.
persisting-countFindings carried over from previous builds (never gate).
report-urlDashboard URL of the full scan report.
gateGate verdict — passed, failed or off (--fail-on none).

Code scanning (SARIF)

With upload-sarif: "true" here — or the CLI's --sarif <file> flag in any other CI — the full findings snapshot is written as SARIF 2.1.0: severity maps to level, MASVS groups ride along as rule tags, and every alert deep-links back to its dashboard finding. DeltaWard's stable fingerprints go into partialFingerprints so alert identity survives across builds, and dashboard dismissals arrive as suppressions. GitHub diffs consecutive uploads itself — alerts open and close with your builds. Private repos need GitHub Advanced Security for code scanning.

Data handling

  • Binaries are deleted after every scan — the uploaded artifact is removed from storage as soon as analysis finishes, success or failure. Findings are kept; your APKs and IPAs are not.
  • Analysis is static and runs in an isolated, per-scan sandbox. Your binary is treated as untrusted input.
  • Findings come only from deterministic analyzers. The AI explains findings and writes fixes — it never invents findings and never changes a severity.