From 2b5eed33640b53c7d1ca9c91873e0040c1d34c79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Sandstr=C3=B6m?= Date: Wed, 24 Jun 2026 16:39:36 +0200 Subject: [PATCH] =?UTF-8?q?feat(check):=20[scan]=20guarddog=20config=20fla?= =?UTF-8?q?g=20=E2=80=94=20persistent=20opt-in=20for=20the=20local=20malwa?= =?UTF-8?q?re=20scan=20(#105)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GuardDog was env-only (KIT_GUARDDOG=1). kit check now also honors `guarddog = true` under [scan] in .kit.toml (best-effort config read) so the opt-in is committed project config, not an ephemeral env var. Verified: config flag bypasses the opt-in skip. Skip message points at both. Foundation for a kit setup prompt. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 6 ++++++ package-lock.json | 4 ++-- package.json | 2 +- src/check-security.ts | 17 ++++++++++++++--- src/config.ts | 7 +++++-- 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de22ffd7..159c1d8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). ## [Unreleased] +## [1.29.0] - 2026-06-24 + +### Added + +- **`[scan] guarddog = true` — persistent project opt-in for the local malware scan.** GuardDog (#105) was enabled only via the ephemeral `KIT_GUARDDOG=1` env var. `kit check` now also honors a `guarddog = true` flag under `[scan]` in `.kit.toml` (best-effort config read; env var still works and takes precedence in spirit — either enables it). So the choice lives in committed project config, not a per-shell env var. The skip message points at both. (Foundation for an interactive `kit setup` prompt to write the flag — a follow-up.) + ## [1.28.2] - 2026-06-24 ### Changed diff --git a/package-lock.json b/package-lock.json index d5559204..60861262 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sandstream-kit", - "version": "1.28.2", + "version": "1.29.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sandstream-kit", - "version": "1.28.2", + "version": "1.29.0", "license": "MIT", "workspaces": [ "packages/*" diff --git a/package.json b/package.json index f1371350..445f3350 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sandstream-kit", - "version": "1.28.2", + "version": "1.29.0", "description": "developer kit. zero LLM, local-first, multi-vault. one command from git clone to working dev environment.", "license": "MIT", "funding": "https://buymeacoffee.com/sandstream", diff --git a/src/check-security.ts b/src/check-security.ts index 5a583bf2..4c47bf1a 100644 --- a/src/check-security.ts +++ b/src/check-security.ts @@ -678,14 +678,25 @@ async function checkSocket(): Promise { */ async function checkGuardDog(): Promise { const base = { category: "supply-chain", name: "guarddog (malware)" } as const; - const enabled = ["1", "true", "yes", "on"].includes( + const envEnabled = ["1", "true", "yes", "on"].includes( (process.env.KIT_GUARDDOG ?? "").trim().toLowerCase(), ); - if (!enabled) { + // Persistent project opt-in via `.kit.toml [scan] guarddog = true` (best-effort + // config read) — so the choice lives in config, not just an ephemeral env var. + let cfgEnabled = false; + try { + const { loadConfig } = await import("./config.js"); + const cfg = await loadConfig(resolve(process.cwd(), ".kit.toml")); + cfgEnabled = cfg.scan?.guarddog === true; + } catch { + // no/invalid config → env var is the only switch + } + if (!envEnabled && !cfgEnabled) { return { ...base, status: "skip", - detail: "opt-in — set KIT_GUARDDOG=1 to run local malware heuristics (needs semgrep)", + detail: + "opt-in — set `guarddog = true` under [scan] in .kit.toml (or KIT_GUARDDOG=1) to run local malware heuristics (needs semgrep)", }; } diff --git a/src/config.ts b/src/config.ts index 9280d3c3..2f08d49f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -298,8 +298,10 @@ export interface kitConfig { context?: ContextConfig; /** Install-time supply-chain triage settings (`kit supply-chain`). */ supply_chain?: { internal_scopes?: string[] }; - /** `kit scan` settings — a tooling Infisical project to resolve scanner tokens (SNYK_TOKEN, …) from. */ - scan?: { tooling?: { project_id?: string; env?: string } }; + /** `kit scan` / `kit check` scanner settings. `tooling` = an Infisical project to + * resolve scanner tokens (SNYK_TOKEN, …) from; `guarddog` = enable the local + * behavioral-malware scan in `kit check` (persistent alt to KIT_GUARDDOG=1). */ + scan?: { tooling?: { project_id?: string; env?: string }; guarddog?: boolean }; /** * No-egress / air-gapped posture (declarative; see docs/AIR_GAP.md). Equivalent * to the `KIT_*` env vars, but checked in so the enclave config is reproducible. @@ -583,6 +585,7 @@ const kitConfigSchema = z .object({ project_id: z.string().optional(), env: z.string().optional() }) .passthrough() .optional(), + guarddog: z.boolean().optional(), }) .passthrough() .optional(),