Airgap vetting
Claude Code plugin marketplace — 58 installable reference skills across vLLM/SGLang inference, Kubernetes & Harvester, GPU host bring-up, observability, security, and agent workflows.
npx -y skills add air-gapped/skills --skill airgap-vettingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 3 stars3 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Vet an open-source product for air-gap readiness BEFORE adoption. Answers eight questions — telemetry, does-opt-out-actually-work, proxy-in-disguise over a hosted API, runtime downloads, custom-CA support, feature-level content egress / offline degradation, day-two sustainment (feed mirroring, staleness), and a 4-grade verdict (air-gap-native / possible-with-mirror / proxy-in-disguise / no-go). Two-pass: static grep of source + container image (bundled fingerprint tables work offline), then an optional dynamic harness (--network=none, egress deny+log, mitmproxy CA injection, faketime). Writes AIRGAP-VETTING.json + .md. Use for any "should we adopt X?" question in a disconnected environment, not just when the user says "air gap".
SKILL.md
15.5 KB, as published. Nobody here has run it
airgap-vetting
Rapidly vet a candidate OSS product for air-gap readiness before adoption. Answers eight questions and assigns one of four grades. The organizing insight from the research behind this skill: "documents an offline flag" ≠ "works offline" — several tools fall back to downloading even with their own offline flag set, and the decisive distinction (air-gap-native vs proxy-in-disguise) is only provable by denying egress after activation and watching whether the product keeps working.
Invoke with /airgap-vetting <target> [--dynamic] [--repo PATH] [--image REF] [--stakes low|high].
Arguments (parse from $ARGUMENTS):
- target (first positional, required): a product name (
meilisearch), a local source checkout, a git URL, or a container image ref. Resolve whatever else is needed in Phase 0. --dynamic: run the Phase-2 dynamic harness (requires Docker and, for the deny+log step, root/iptables on a disposable host or VM). Without it the vet is static-only and the verdict says so.--repo PATH/--image REF: explicit source checkout / image when the positional target is ambiguous.--stakes low|high(defaultlow):highmandates the dynamic pass for any candidate that survives static (refuse to emit a final grade without it);lowallows a static-only provisional grade.
The eight questions:
| # | Question | Reference |
|---|---|---|
| 1 | Sends telemetry to the internet? | references/telemetry.md |
| 2 | Can telemetry be turned off — and does off mean off? | references/telemetry.md |
| 3 | Actually a proxy over a hosted API? | references/downloads-and-proxy.md |
| 4 | Requires internet at runtime for downloads? | references/downloads-and-proxy.md |
| 5 | Supports custom CA / private TLS trust? | references/ca-trust.md |
| 6 | Ships user content out / degrades offline? | references/content-egress.md |
| 7 | Can it be kept alive offline (day-two)? | references/sustainment.md |
| 8 | Overall verdict grade | rubric below |
Two cross-cutting sub-check clusters feed several questions:
time/revocation/offline signature verification (references/verification-time.md)
and identity/inbound/connectivity-probes (tail of references/content-egress.md).
Read each reference file at its question — they carry the exact hostnames, env vars, grep patterns, and known counter-examples. Do not answer a question from general knowledge when the reference has a table for it: the tables encode verified, sourced fingerprints, including several cases where docs and behavior disagree.
Phase 0: Scope the target
- Check prior art. Read
references/known-products.md. If the candidate (or its direct dependency) already has a row, say so up front — the row may answer the whole request, or at least pin expectations. - Resolve the target. Get a local source checkout (clone if needed;
prefer an existing clone under
~/projects/github.com/<org>/<repo>) and, when a container distribution exists, the image ref. Record versions: git tag/commit and image tag@digest. The verdict is version-specific — pin what was actually vetted. - Identify the deployment shape (CLI, server, K8s chart/operator, desktop app). This decides which static checks apply (Helm image enumeration only for charts; browser-console check only for web UIs).
- Locate the docs' own air-gap story. Search the repo/docs for pages titled "air-gap", "offline", "disconnected", "self-hosted". Their existence is a green flag (Q8) and their claims become hypotheses for the static pass to check — never conclusions.
Phase 1: Static pass (always)
Work through Questions 1–7 in order. For each, record in working state:
answer (yes/no/partial/unknown), evidence[] (exact grep hit with
file:line, hostname, or doc URL), and flags[] (red/green signals for
grading). Every claim in the final report carries evidence — a verdict row
without a grep hit or citation is a guess, and guesses get unknown, not
no.
Q1 — Telemetry. Read references/telemetry.md §Fingerprints. Run BOTH
grep layers (SDK packages AND hostnames) over source and — if an image is
in scope — strings over its binaries, docker inspect ENV, entrypoint
scripts, and a CNAME check of the registry host (Scarf Gateway hides in the
distribution channel). One layer alone misses what the other catches.
Q2 — Opt-out semantics. Read references/telemetry.md §Opt-out. Grep
the candidate for kill-switch-shaped tokens (telemetry-adjacent substrings,
not an exact-name list) — the mere presence of opt-out handling is
evidence telemetry exists. Then check the
six pitfalls (opt-out-by-default, phones-home-before-opt-out,
value-vs-presence bugs, reduce-not-eliminate, separate update-check
channel, destination drift) against the code that actually reads the
variable. Verify polarity in source, not docs.
Q3 — Proxy-in-disguise. Read references/downloads-and-proxy.md §Proxy.
Repo topology, hardcoded base URLs, BYOK-routing docs, pricing-page
"self-hosted = Enterprise" tells, mandatory-login issues.
Q4 — Runtime downloads. Same file, §Downloads. Build-time vs runtime is
the organizing principle. For Helm charts, enumerate images from rendered
output (helm template | grep -oE 'image: *"?[^"]+' | sort -u), never
values.yaml alone. Runtime-computed image names are an automatic no-go.
Q5 — Custom CA. Read references/ca-trust.md. Identify the TLS stack
per ecosystem, check additive-vs-replacement semantics, and hunt the two
hard blockers (compiled-in webpki-roots, certificate pinning) plus the
anti-pattern of verification-off switches offered instead of a CA option.
Q6 — Content egress & offline degradation. Read
references/content-egress.md. Inventory features that send user content
(AI assist, gravatar, map tiles/GeoIP, link previews) with their kill
switches, plus CDN-loaded frontend assets. Also run the identity and
inbound sub-checks at the tail of that file. Output here is a per-feature
table: feature → destination → default on/off → kill switch → what's lost
offline.
Q7 — Sustainment. Read references/sustainment.md. How do upgrades and
content feeds (vuln DBs, rules, models) cross the gap, and what does the
product do when a feed is stale — fail-closed, warn, or silently report
green? Find the age-validation code (or its absence). Count the gap
crossings an upgrade path requires.
Signature-verification and time sub-checks (feed Q4/Q5/Q7): if the
product or its install path verifies signatures (cosign, Notation, GPG) or
carries an expiring license, read references/verification-time.md and run
its greps — provenance verification is itself a common hidden egress.
Static-pass greps run offline; only Phase 0 target resolution and optional
gh issue-searches need network. When vetting from inside a gap, skip the
gh checks and note them as not-run in the report.
Gate: static → dynamic
Run the dynamic pass when any of:
--dynamicwas passed, or--stakes highand the candidate survived static;- Q3 is unresolved (docs market "offline" but static found license-ping or
activation endpoints) — the post-activation egress-deny test is the
mandated tie-breaker between
possible-with-mirrorandproxy-in-disguise; do not guess this boundary statically; - Q2 found opt-out flags whose actual behavior can't be confirmed in source (compiled binary, obfuscated build);
- Q5 hinges on runtime behavior (does the binary honor
HTTPS_PROXY+ custom CA?).
If the gate fires but --dynamic was not passed and stakes are low, emit
the static-only report with grade suffix (provisional — dynamic pass not run) and list exactly which dynamic steps would settle which questions.
If stakes are high but the dynamic pass cannot run (no Docker, no
disposable host), do NOT emit a final grade — that is the whole point of
high. Report the static findings, set grade_provisional: true with the
grade left as the static best-guess, and state plainly that a final verdict
requires running the listed dynamic steps on a disposable host.
Phase 2: Dynamic pass (gated)
Read references/dynamic-harness.md and run its ladder on a disposable
host/VM: (1) --network=none first-run, (2) iptables DOCKER-USER
deny+log with REJECT, (3) passive DNS/egress enumeration, (4) mitmproxy
with CA injection — this one test settles Q5 and inventories Q1
destinations, (5) post-activation egress-deny (the Q3 tie-breaker),
(6) browser-console check under deny for web UIs (Q6), (7) stale-feed run
for security tools (Q7), (8) faketime skew runs (+30 days, +13 months)
for time dependencies. Capture in two windows — first-run AND 10–30 min
steady-state; timer-based heartbeats don't show up in a 30-second capture.
Phase 3: Grade (Question 8)
Assign exactly one grade. When evidence splits across grades, the worse grade wins — the cost of over-promising air-gap readiness (a dead product inside the gap) far exceeds the cost of extra mirroring work.
air-gap-native — complete published artifact enumeration (image list /
relatedImages / SBOM); digest-pinnable references; ONE global registry
override covering ALL images including init-containers and operator-pulled
ones; license validated locally via cryptographic signature with zero
callbacks ever; every outbound feature listed with a documented kill switch
or local-mirror path; custom-CA injection works; offline docs artifact;
installer completes with egress fully denied; content feeds published as
mirrorable artifacts (OCI/HTTPS) with override flags and non-silent
staleness handling; a published feature-degradation table.
possible-with-mirror — all artifacts enumerable and mirrorable (possibly with effort); registry overrides exist but per-image rather than global; offline license path exists but may be approval-gated; some features degrade offline in a documented way; requires standing mirror infrastructure and periodic content sync. Also the cap for: default-on content-egress features that DO have a self-hosted path, and security feeds that go silently stale (state the silent-stale warning explicitly in the verdict).
proxy-in-disguise — vendor markets "offline/disconnected" but operation requires allowlisted egress to specific endpoints: license-ping servers, telemetry gateways, activation callbacks, update checks that gate functionality, or login that requires the vendor's cloud with no offline workaround. Works behind an HTTP proxy, dies behind a true gap. Decisive objective test: does the product survive a permanent egress-deny applied after activation? Note: air-gap sold only as a negotiated license tier is a proxy-in-disguise trigger even when technically mirrorable.
no-go — hardcoded URLs with no override; image/artifact names computed at runtime (defeats all mirroring); install scripts that fetch mid-run with no offline flag; license enforcement that shuts the product down absent connectivity with no offline tier; no way to enumerate what to mirror; TLS certificate pinning or compiled-in-roots-only on endpoints that must be mirrored.
Phase 4: Output
4a. Write ./AIRGAP-VETTING.json
{
"vetting_completed": true,
"candidate": {
"name": "...",
"version": "git tag/commit and/or image@digest actually vetted",
"source": "repo URL", "image": "ref or null",
"deployment_shape": "cli|server|k8s-chart|k8s-operator|desktop"
},
"mode": "static-only|static+dynamic",
"stakes": "low|high",
"grade": "air-gap-native|possible-with-mirror|proxy-in-disguise|no-go",
"grade_provisional": false,
"questions": [
{
"id": 1,
"question": "Sends telemetry to internet?",
"answer": "yes|no|partial|unknown",
"evidence": ["exact grep hit with file:line, hostname, or captured destination"],
"flags": ["red: ...", "green: ..."],
"kill_switches": ["ENV_VAR=value (polarity verified in source at file:line)"],
"dynamic_confirmed": false
}
],
"egress_inventory": ["hostname — purpose — first-seen (static grep | dns capture | mitm)"],
"mirror_list": ["everything that must cross the gap: images@digest, feeds, models, docs"],
"sustainment": {
"feeds": [{"feed": "...", "mechanism": "...", "staleness_behavior": "fail-closed|warn|silent"}],
"upgrade_gap_crossings": "packages/artifacts per upgrade hop",
"sync_cadence_required": "..."
},
"not_run": ["checks skipped and why"]
}
Every question appears exactly once, unknown answers stay unknown, and
not_run lists what a follow-up should do. Do not print this JSON to the
terminal; write to file only.
4b. Write ./AIRGAP-VETTING.md
Reviewer-facing, in this order: title + one-line verdict; the grade with
its two or three decisive pieces of evidence; the eight-question table
(question | answer | strongest evidence); the per-feature content-egress
table (Q6); the mirror list; the sustainment summary; Not run / next steps. Keep it under ~120 lines — the JSON carries the full detail.
4c. Append to the known-products log
Append one row to ./AIRGAP-KNOWN-PRODUCTS.md in the working directory
(create with header if absent, same columns as
references/known-products.md). If the skill directory itself is writable
(in-repo development, not an installed plugin), ALSO append the row to
references/known-products.md so the skill compounds across runs.
4d. Terminal summary
Under ~10 lines: grade (+ provisional marker), the single most decisive finding, count of red/green flags per question, where the reports were written, and — if static-only — the one dynamic test most worth running.
Maintenance
Smoke tests (expected grades for three known-products rows), design
rationale, composition with sibling skills, and provenance live in
references/maintenance.md. Re-verify the version-gated facts periodically
— cosign v2 vs v3 flags, DCT shutdown 2026-12-08, and the dated
known-products rows all drift.