nulladrift.

Docs

Connect the app.

The CLI reads your servers in CI. The app decides, and keeps the record.

How it fits

Three places, one reading.

Your CI

npx nulladrift push

Reads every server and sends one reading.

The control plane

compare · policy · verdict

Decides, where no branch can edit the rules.

app.nulladrift.com

a person rules

Accept or reject, with a name on it.

Your organization

Everything in the app belongs to one.

  1. 1. Sign up at app.nulladrift.com.
  2. 2. Name your organization. That is the whole setup.

Collector token

What a pipeline authenticates with. Issued by an owner or admin.

In the app: Settings → Collector tokens. Name it, choose Push readings, and copy it. It is shown once. Only its hash is kept, so losing it means issuing another.

Push readings
What a CI pipeline needs: push a reading, read back the verdict. Nothing else.
Read only
Reads the inventory and the queue. Cannot push a reading or change anything.
Full access
Everything an admin can do over the API, except rule on a change.

No token, whatever its scope, can rule on a change. Only a person can.

Push from CI

Swap check for push. The verdict now comes from the app.

Add the token as a secret, NULLADRIFT_TOKEN, and the control plane's API address as NULLADRIFT_API_URL.

.github/workflows/nulladrift.yml
name: nulladrift
on: [push, pull_request]
permissions:
  contents: read
  id-token: write    # sign each reading
jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npx nulladrift push
        env:
          NULLADRIFT_API_URL: ${{ vars.NULLADRIFT_API_URL }}
          NULLADRIFT_TOKEN: ${{ secrets.NULLADRIFT_TOKEN }}
--token <token>or NULLADRIFT_TOKEN
The collector token. Keep it a CI secret.
--api <url>or NULLADRIFT_API_URL
The control plane's API address to push to.
--project <name>
The project to report under. Taken from GITHUB_REPOSITORY or CI_PROJECT_PATH otherwise.
--watch
Marks a scheduled reading, not one a build asked for.

What comes back

The control plane decides the verdict and the exit code.

exit 0clean
Nothing moved since the last approval.
exit 1drift
Something changed. It waits for a person, and the build fails until then.
exit 1blocked
Your policy blocks it. The reading records why.
exit 2could not audit
A server was unreachable, or the control plane was.

Anything but clean prints a link to the change in the app.

Rule on a change

Open Drift, pick the change, read what moved.

Accept into the baseline
The new surface becomes the approved one, with your name on it. It stops failing the build.
Reject
The baseline stays. The next build reports the change again.

Owners, admins and reviewers can rule. Every ruling is written to the audit log.

Policy

Evaluated on every reading, in the app, not in the repo.

Rules
What fails a build. The default blocks high severity and holds the rest as drift.
Exemptions
Wave a server or a kind through, always with an expiry.
Trusted builds
Readings your CI signs. Add id-token: write to the job, then list the builds you trust.

A policy change applies from the next reading and is recorded against a name.

Notifications

Settings → Organization.

Email
Everyone who can rule on a change hears when one is found.
Slack
The same changes, in a channel, through an incoming webhook.
Silent projects
A project that stops pushing is marked silent, not left green.

To catch a change between builds, run npx nulladrift push --watch on a schedule.

Roles

Who can do what in an organization.

Owner
Everything an admin can do, and cannot be removed by one.
Admin
Changes policy, issues and revokes collector tokens, manages people.
Reviewer
Rules on drift and approves servers, but changes no settings.
Member
Reads the inventory, the queue and the audit log.