@getnightshift/cli
v0.1.2
Published
Find unattended agents that act as the company when no one is on shift.
Maintainers
Readme
Nightshift finds unattended agents that act as the company when no one is on shift. It is not a job scheduler.
A pull request should not quietly add the company, as a bot, with a write token, no human, no sandbox. Nightshift is the check for that shape. $19/seat/month as a GitHub App. The CLI and Action stay free.
npx @getnightshift/cli scan .What we detect
A HIGH or CRITICAL finding when these cluster — all of them, or enough of them that a staff engineer would say “don’t ship that”:
- Actor. A workflow, cron, composite action, Dockerfile
ENTRYPOINT, or script invokes an agent loop (claude,claude-code,hermes,aider,cursor/cursor-agent,codex,opencode, Continue, or a customwhile+ LLM SDK). Apackage.jsonscript or Makefile target that wraps those binaries counts when CI invokes it. An org can list its own names underactors:in.nightshift.ymlor.github/nightshift.yml. Unknown names stay unknown. - Company-as-actor. Triggered by
schedule,repository_dispatch,workflow_dispatchwithout a human gate, issue/PR comment, PagerDuty/webhook,workflow_call,workflow_run, or anif:that runs unattended. A Saturday cron that onlyuses:a reusable agent workflow still fails. A human running Claude Code on a laptop is not this. - Write privilege.
GITHUB_TOKENwith contents / PRs / workflows / packages write, or a PAT / app token / AWS key in env on the agent step.permissions: write-allis worse. A missingpermissions:block is treated as write because repository/org defaults are unknown — the finding says so. - Injection sink. Issue / PR / comment / ticket / pager body interpolated into the prompt or into
claude -p. - No containment. No OpenShell, E2B, Firecracker, gVisor, gh-aw-firewall, safe-outputs split, or explicit netns + deny-egress signal in the job. A string match is a declared control, not a verified sandbox. Declared markers do not downgrade CRITICAL (injection plus a write token still pages). A declared sandbox without injection stays on the inventory as MEDIUM and does not fail default
fail-on high. - No human gate. No verified required reviewers. An
environment:name or mention-ifis a declared gate, not proof that reviewers are required.
The weekend auto-fixer is the whole product:
.github/workflows/auto-fix.yml with cron: + anthropics/claude-code-action + contents: write + ${{ github.event.issue.body }}.
That is CRITICAL. nightshift scan exits non-zero. The PR comment is short on purpose.
What we do not detect
- Broad permissions in
.claude/settings.json. That is AgentShield’s market. - Prompt-injection firewalls. We do not “block jailbreaks.”
- Copilot Studio connectors, SaaS agent inventories, or full AISPM. That is Zenity.
- A runtime. We detect the absence of a sandbox. We do not ship OpenShell.
A human-in-the-loop Claude Code Action on workflow_dispatch with contents: read and no issue-body prompt is a clean exit. So is claude-code-security-review with read-only tokens. pull-requests: write only on a human PR security-review is MEDIUM, not CRITICAL, unless a schedule, issue body, or extra secrets show up.
Default fail-on high pages real sins: untrusted issue/PR/comment title or body interpolated into the agent plus a write token; pull_request_target plus write; schedule (or another unattended trigger) plus contents/PR write plus no containment. It does not fail a report-only workflow_dispatch job whose only token is default GH_TOKEN / github.token, an @claude mention-if / declared human gate, or a job that declares a sandbox (gh-aw firewall, OpenShell, E2B, Firecracker, gVisor, safe-outputs) and does not interpolate untrusted text. Those stay visible as MEDIUM. Passing GH_TOKEN is a different punchline from interpolating a ticket into a write-capable job.
Free forever: CLI + Action
npx @getnightshift/cli scan .
npx @getnightshift/cli scan . --format json
npx @getnightshift/cli scan . --sarif nightshift.sarif --fail-on highCopy examples/nightshift.yml into .github/workflows/nightshift.yml. The Action prints findings, uploads SARIF to code scanning, and ends the job summary with a 90-minute review CTA.
- uses: antonemking/nightshift-action@v1
with:
fail-on: highDefault fail bar is HIGH. MEDIUM does not fail the build.
Semgrep pack (Apache-2.0) for repos that already run Semgrep:
semgrep --config rules/nightshift.yaml .
semgrep --test rules/The CLI does not require Semgrep. It uses deterministic YAML and bash metavariable patterns. No model judges the workflow.
CLI findings are one per job. Semgrep may emit several rule hits for the same workflow. Prefer the CLI (or Action) as the gate; keep the pack if you already run Semgrep.
npx @getnightshift/cli rules prints the packaged Semgrep directory next to the installed CLI, not ./rules in the consumer repo.
Org-named agent binaries (optional):
# .nightshift.yml or .github/nightshift.yml
actors:
- acme-coderSee examples/nightshift-config.yml. The Action workflow example stays examples/nightshift.yml. Those are different files.
Paid: Nightshift GitHub App — $19 / seat / month
The Action tells you about the PR in front of you. The App is the org inventory and the regression gate.
| | Free Action | Nightshift App | |---|---|---| | Price | $0, no card | $19 / seat / month | | Trial | — | 14 days per org, starts at install, no card | | Scope | One repo, one checkout | Every repo the App can see | | Output | Terminal, job summary, SARIF | One tight PR comment, inline YAML annotation, check | | Inventory | No | Night-shift jobs: repo, trigger, token scopes, containment, last run | | Policy | Fail this PR | Baseline existing sins; fail new regressions |
Seat math
A seat is a unique GitHub user who committed to workflows, composite actions, agent Dockerfiles, in-repo crontabs, or Helm CronJobs in the last 90 days.
Not “every org member.” Not “every CI minute.” If twelve people can merge to main but two of them touch automations, you pay for two. Minimum one seat while the App is installed.
Org billing via Stripe. The free Action never asks for a card.
Baseline
Day-one install should not page everyone. The first org scan writes fingerprints and a risk snapshot (severity, write level, injection, extra secrets, containment, gate). Those findings stay in the inventory. They do not fail checks unless the same job gets worse — broader permissions, a new prompt-injection sink, higher severity, or a lost control.
A v1 baseline that stores only fingerprints does not suppress. Rewrite it:
npx @getnightshift/cli scan . --write-baseline nightshift-baseline.json
npx @getnightshift/cli scan . --baseline nightshift-baseline.jsonHow this differs
| | Nightshift | AgentShield | Vanta | Zenity |
|---|---|---|---|---|
| Object | The job that acts as the company | .claude/ MCP, hooks, local perms | Compliance evidence | SaaS / Copilot Studio agents |
| Signal | Actor + unattended trigger + write token + injection + no sandbox + no human | Config allow-list | Policies, people, vendors | Connectors, identity, session |
| Buyer motion | “Don’t merge that workflow” | “Don’t give the laptop agent Bash(*)” | Audit season | Enterprise AISPM |
We compete on actor + token + runtime, not on linting a settings file.
Remediation order
- Don’t. Keep the human. Claude Code locally. The human opens the PR.
- If it must be a service: strip write tokens from the agent job. OIDC, short-lived, scoped. Environment approval.
- If it must write as the company: put the agent in a deny-by-default sandbox (OpenShell, E2B, Firecracker, gVisor). Never put the real model key or the org PAT in the child.
Consulting
A 90-minute Nightshift review of your unattended agents: inventory the jobs, read the token scopes, ask about egress, ask who merges the bot PRs. Script in CONSULTING.md.
Install the App
The paid App lives in apps/web (Next.js). Register it with apps/web/app-manifest.json.
cd apps/web
cp .env.example .env.local
# GITHUB_APP_* and STRIPE_*
npm install
npm run devWebhook: POST /api/webhooks/github. Stripe: POST /api/webhooks/stripe. Checkout quantity is the 90-day workflow-committer seat count. Trial is 14 days from install, no card. After that the org needs an active subscription. The free Action never talks to Stripe.
NIGHTSHIFT_DASHBOARD_SECRET gates /dashboard and /api/checkout. Inventory is not public. Sign in at /dashboard/login.
Release
The Action entrypoint is the committed dist/*.cjs. CI fails if npm run build dirties dist/.
Publishing (maintainers):
- Point strangers at
uses: antonemking/nightshift-action@v1on the public nightshift-action repo. Keep this repository private. - Set repository secret
NPM_TOKEN. - Tag
v0.1.0(or later)..github/workflows/release.ymlpublishes npm, creates the GitHub Release, and moves the floatingv1Action tag.
Until that tag exists, clone the repo and run npm run build && node dist/cli.cjs scan ..
License
- CLI, Action, App: MIT
rules/: Apache-2.0
