Skip to main content

Offline Mode and Air-Gapped Deployment

This page documents what the ssg CLI and daemon do when there is no network, what degrades, and how you can confirm zero egress during evaluation on your own machine. Every claim on this page is derived directly from the shipped source.


What works fully offline

Rule evaluation is fully local and does not depend on the network. The ssg eval engine (pure-Go in-process, go/internal/goengine) loads rules from the local SQLite database at ~/.sigmashake/rules.db and evaluates every tool call in-process. No external binary or shared library is required — the Go engine runs entirely within the ssg process with no subprocesses on the eval hot path.

Nothing in this chain requires a network call. A DENY, ALLOW, or ASK decision is computed locally and returned to the calling agent before any network I/O could occur.

What you can verify yourself. With the daemon running, run a dry-run probe with all network interfaces disabled (or while capturing traffic — see the self-test section below):

ssg probe hook --tool=Bash --input='{"command":"ls -la"}'

The command returns a governance decision. If it returns a decision, local eval is working. If the binary itself is unavailable, see the degraded-mode section.

The daemon (ssg daemon) runs fully offline. It loads rules from disk, evaluates tool calls over a Unix socket, and writes audit rows to local SQLite. None of those operations touch the network.

The dashboard (ssg serve) serves the governance UI at 127.0.0.1:5599. The dashboard SPA and all its assets are embedded in the binary. Loading the dashboard does not make external network calls. The dashboard itself reads state from the local daemon over loopback, so it works offline as long as the daemon is running.


What makes background network calls and when

The daemon runs several background tickers. All are fire-and-forget and non-fatal — a network failure never blocks or fails an evaluation.

License heartbeat (licensed tier only)

  • Endpoint: https://accounts.sigmashake.com/api/v1/license/heartbeat
  • Cadence: every 6 hours
  • Payload fields: jwt (your license JWT), version, daily_eval_count
  • Trigger condition: only fires when a license JWT exists at ~/.sigmashake/license.jwt (i.e., you have a Pro or Enterprise license)
  • What happens offline: the POST fails silently. The daemon continues with the last successfully verified tier.

Tier behavior when heartbeat cannot reach the server. The license JWT already contains your tier claim. The daemon caches the plan in memory and re- reads the JWT on restart. As long as the JWT has not expired, your tier is preserved locally without a successful heartbeat. When the JWT is within 7 days of expiry, the daemon attempts a proactive refresh on the same heartbeat tick; if the refresh fails because the network is unreachable, the JWT remains on disk and the expiry grace period (LICENSE_EXP_GRACE_MS = 7 days) continues to apply. Revocation requires a successful heartbeat response with status: "revoked" to take effect — it is never applied speculatively offline.

Usage heartbeat (unlicensed / all tiers)

  • Endpoint: https://accounts.sigmashake.com/api/v1/usage/heartbeat
  • Cadence: approximately every 2 minutes (120 s ± 10 % jitter), deduplicated on unchanged count + date
  • Payload fields: fingerprint, version, daily_eval_count
  • Trigger condition: fires from all installs (licensed and unlicensed) to keep the accounts dashboard "Today's Evaluations" tile current; skipped when daily_eval_count is 0 or unchanged since the last successful send
  • What happens offline: the POST fails silently. Eval is unaffected.

Telemetry (opt-in, default OFF)

  • Endpoint: https://hub.sigmashake.com/api/telemetry
  • Default state: disabled. On first ssg eval, the CLI prompts on /dev/tty: "Help improve ssg? Send anonymous usage data to the team. [y/N]". The default answer is No. Non-interactive environments (CI, piped stdin) default to opted out. The consent state is saved at ~/.sigmashake/telemetry.json.
  • Payload fields when opted in: event, install_id (random UUID, not your account identity), version, os, arch, decision, tool_type, rule_id — never tool input content, file paths, or your JWT.
  • Cadence: fire-and-forget on each eval, 2-second timeout.
  • What happens offline: the POST is dropped silently.

To confirm your current opt-in state:

cat ~/.sigmashake/telemetry.json
# opted_in: false means no telemetry is sent regardless of network state.

To opt out explicitly:

# Edit the file and set opted_in to false, or delete the file to be re-prompted.

Audit sync (Pro/Enterprise only, when configured)

  • Endpoint (hub): https://hub.sigmashake.com/api/v1/audit/sync
  • Endpoint (fleet): https://<fleet-host>/api/v1/orgs/<org>/audit/ingest
  • Endpoint (override): SSG_SYNC_ENDPOINT env var
  • Cadence: every 5 minutes
  • Trigger condition: only fires when getRetentionDays() > 0, which is true for Pro (7-day retention) and Enterprise (90-day retention) tiers, and when a valid auth token is present.
  • Payload fields: batches of audit rows, each containing timestamp, tool, capability, decision, rule_id, input_summary (truncated to 200 characters — never the full tool input), client, ssg_version.
  • What happens offline: the POST fails silently. Unsynced rows accumulate locally with synced = 0 in the SQLite audit log and are retried on the next 5-minute cycle.

Update check

  • Endpoint: https://download.sigmashake.com/cli/latest/<asset> (CDN passthrough to GitHub Releases)
  • Cadence: once every 24 hours; skipped entirely when SSG_NO_UPDATE_CHECK=1 is set
  • Timeout: 3 seconds (fire-and-forget; does not block evaluation)
  • What happens offline: fails silently. No upgrade prompt is shown.

Hub manifest cache

  • Endpoint: https://raw.githubusercontent.com/sigmashakeinc/rules/main/manifest.json
  • Trigger: ssg hub pull and hub-related dashboard operations
  • What happens offline: hub pulls fail with an error. Rules already installed on disk continue to work.

What degrades when offline

FeatureOffline behavior
Rule evaluation (eval/probe)Fully functional — no network dependency
Daemon and dashboardFully functional
License tier (licensed)Preserved from cached JWT until JWT expires
License heartbeatSilent failure; does not affect eval
Usage counter syncSilent failure; counter still increments locally
TelemetrySilent failure (only sends if you opted in)
Audit cloud syncRows queue locally; synced when network returns
Hub rule pullsFail with an error; installed rules are unaffected
Update checksSilent failure; no upgrade prompt shown
ssg auth loginRequires network (browser OAuth / device flow)
Fleet policy pullCannot pull new policy bundle; last cached bundle applies

Self-test: prove zero egress during evaluation

Example procedure — adapt to your environment. The commands below were not executed in producing this page; they are provided as a reproducible template for independent verification on your own machine. The ssg probe hook command itself (the subject of the test) was verified by the author.

Step 1 — capture baseline traffic

Open a terminal and start a packet capture or proxy. On Linux with tcpdump:

# Capture all non-loopback TCP traffic to a file, run in background
sudo tcpdump -i any -w /tmp/ssg-offline-check.pcap 'not (host 127.0.0.1 or host ::1)' &
TCPDUMP_PID=$!

On macOS, replace -i any with -i en0 (or whichever interface has your default route). Alternatively, use mitmproxy or any HTTPS proxy you trust — set HTTPS_PROXY to its address before the probe command.

Step 2 — run the eval probe

ssg probe hook --tool=Bash --input='{"command":"echo hello"}'

Expected output: a governance decision (allow, deny, or ask) with no network activity visible in the capture.

Step 3 — stop the capture and inspect

# Stop tcpdump
kill $TCPDUMP_PID

# Inspect captured packets (should show zero rows to external hosts)
tcpdump -r /tmp/ssg-offline-check.pcap -n 2>/dev/null | grep -v '^$' | head -20

If no packets appear (or only loopback packets appear), the eval made no external network calls.

Why no network calls happen during eval. The eval path reads rules from the local SQLite database, runs them through the pure-Go in-process engine, and writes the result to the local audit log. There is no IPC to a remote service or subprocess on the critical path. The background heartbeat tickers run on their own intervals and are not triggered by an eval call.

Verifying telemetry is off

# Confirm opted_in is false before the capture
python3 -c "import json; d=json.load(open('$HOME/.sigmashake/telemetry.json')); print('opted_in:', d['opted_in'])"

If opted_in is false, no telemetry POST fires regardless of what eval commands you run.


Air-gapped installation runbook

These steps document a working installation for a machine with no internet access after the initial binary download.

On an internet-connected machine:

# 1. Install ssg on an internet-connected machine and verify it
# (subscribe at sigmashake.com/pricing first; see /getting-started for install instructions)
ssg --version

# 2. Copy the ssg binary to offline media
# The ssg binary is a single self-contained Go executable — no native library required.
# On Linux: ssg is at $(which ssg)
which ssg

On the air-gapped machine:

# 3. Place the ssg binary on PATH
# (e.g. /usr/local/bin/ or ~/.local/bin/)

# 4. Initialise rules from files you transferred
# Create ~/.sigmashake/rules/ and place your .rules files there.
mkdir -p ~/.sigmashake/rules

# 5. Run the daemon
ssg daemon

# 6. Verify eval works offline
ssg probe hook --tool=Bash --input='{"command":"ls"}'

What is unavailable on an air-gapped machine:

  • ssg auth login cannot complete (requires accounts.sigmashake.com)
  • Hub rule pulls are unavailable (requires GitHub raw content)
  • License heartbeats never reach the server; the JWT tier applies as long as the JWT has not expired
  • Update checks never complete

What works on an air-gapped machine:

  • All rule evaluation
  • The daemon and dashboard (loopback only)
  • All rules you transferred and installed from disk

Honest boundary

This document describes the network behavior of the ssg CLI and daemon as implemented in the TypeScript source (src/) and the Go runtime (go/). The desktop applications (SigmaShake Desktop for Linux, macOS, and Windows) embed ssg serve and the ssg binary but add their own update-check calls and desktop-platform integrations. Those behaviors are documented in the respective desktop app guides and are not covered here.

The claims on this page can be verified by reading the source files listed below. No claim is asserted without a corresponding code reference.

Source references for every claim on this page:

ClaimSource file
Eval is fully localsigmashake-gov/go/internal/goengine/engine.goEngine / Engine.Evaluate (pure-Go in-process; no subprocess on eval hot path)
License heartbeat endpoint and payloadsigmashake-gov/src/commands/daemon/heartbeats.tsstartLicenseHeartbeat()
Usage heartbeat endpoint and payloadsigmashake-gov/src/commands/daemon/heartbeats.tsstartEvalCountPush()
Telemetry default-off, opt-in promptsigmashake-gov/src/shared/telemetry.tscheckAndPromptTelemetry()
Telemetry payload fields (no input content)sigmashake-gov/src/shared/telemetry.tsTelemetryEvent interface
Telemetry endpoint and 2-second timeoutsigmashake-gov/src/shared/constants.tsTELEMETRY_ENDPOINT, TELEMETRY_TIMEOUT_MS
Audit sync endpoint, cadence, payloadsigmashake-gov/src/shared/sync-client.tssyncAuditLog(), toHubAuditEntry()
Audit sync gated on plan retention dayssigmashake-gov/src/shared/sync-client.tsgetSyncEndpoint()
Heartbeat intervalssigmashake-gov/src/shared/constants.tsLICENSE_HEARTBEAT_INTERVAL_MS, EVAL_COUNT_PUSH_INTERVAL_MS, SYNC_INTERVAL_MS
Grace period on license expirysigmashake-gov/src/shared/constants.tsLICENSE_EXP_GRACE_MS = 7 days
Update check endpoint, 24h cadence, 3s timeout, silent failuresigmashake-gov/src/shared/update-check.tsscheduleUpdateCheck(), fetchPlatformLatestVersion(); sigmashake-gov/src/shared/constants.tsUPDATE_CHECK_INTERVAL_MS, UPDATE_CHECK_TIMEOUT_MS, CDN_DOWNLOAD_URL