guardvibe 3.1.0 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -64,7 +64,7 @@ GuardVibe is purpose-built for the AI coding workflow. Traditional tools are exc
64
64
  npx guardvibe init claude
65
65
  ```
66
66
 
67
- Creates `.claude.json` MCP config, `.claude/settings.json` auto-scan hooks, and `CLAUDE.md` security rules. Restart Claude Code after setup.
67
+ Creates `.mcp.json` MCP config (pinned to current version), `.claude/settings.json` auto-scan hooks, and `CLAUDE.md` security rules. Restart Claude Code after setup.
68
68
 
69
69
  ### Cursor
70
70
 
@@ -286,6 +286,13 @@ npx guardvibe doctor --scope host # + shell profiles, global MCP configs
286
286
  npx guardvibe doctor --scope full # + home dir configs
287
287
  npx guardvibe doctor --format json # JSON output
288
288
 
289
+ # LLM-powered deep scan (IDOR, business logic, race conditions, auth bypass)
290
+ npx guardvibe deep-scan <file> # Default: Haiku 4.5, all focus areas
291
+ npx guardvibe deep-scan <file> --focus idor # Narrow to IDOR
292
+ npx guardvibe deep-scan <file> --model sonnet # Deeper analysis (more expensive)
293
+ npx guardvibe deep-scan <file> --max-bytes 5000 # Truncate input for cost control
294
+ # Requires ANTHROPIC_API_KEY or OPENAI_API_KEY env var
295
+
289
296
  # Setup
290
297
  npx guardvibe init <platform> # Setup MCP server (claude, cursor, gemini, all)
291
298
  npx guardvibe hook install # Install pre-commit hook
package/build/cli/init.js CHANGED
@@ -14,6 +14,18 @@ const GUARDVIBE_MCP_CONFIG = {
14
14
  command: "npx",
15
15
  args: ["-y", `guardvibe@${pkg.version}`],
16
16
  };
17
+ /** Extract a pinned version from an existing MCP server config (`{ args: ["-y", "guardvibe@X.Y.Z"] }`). */
18
+ function extractPinnedVersion(config) {
19
+ const args = config?.args;
20
+ if (!Array.isArray(args))
21
+ return null;
22
+ for (const arg of args) {
23
+ if (typeof arg === "string" && arg.startsWith("guardvibe@")) {
24
+ return arg.slice("guardvibe@".length);
25
+ }
26
+ }
27
+ return null;
28
+ }
17
29
  const platforms = {
18
30
  claude: {
19
31
  path: join(process.cwd(), ".mcp.json"),
@@ -177,12 +189,27 @@ function setupPlatform(name) {
177
189
  if (!existing.mcpServers) {
178
190
  existing.mcpServers = {};
179
191
  }
180
- if (existing.mcpServers["guardvibe"]) {
181
- console.log(` [OK] GuardVibe already configured in ${platform.description}`);
192
+ const servers = existing.mcpServers;
193
+ if (servers["guardvibe"]) {
194
+ const existingPin = extractPinnedVersion(servers["guardvibe"]);
195
+ if (existingPin && existingPin !== pkg.version) {
196
+ servers["guardvibe"] = GUARDVIBE_MCP_CONFIG;
197
+ writeJsonFile(platform.path, existing);
198
+ console.log(` [OK] Upgraded GuardVibe pin in ${platform.description} (${existingPin} → ${pkg.version})`);
199
+ }
200
+ else if (!existingPin) {
201
+ // Existing config has no pin (legacy unpinned form) — overwrite to pin.
202
+ servers["guardvibe"] = GUARDVIBE_MCP_CONFIG;
203
+ writeJsonFile(platform.path, existing);
204
+ console.log(` [OK] Pinned GuardVibe in ${platform.description} (was unpinned → ${pkg.version})`);
205
+ }
206
+ else {
207
+ console.log(` [OK] GuardVibe already up-to-date in ${platform.description} (v${pkg.version})`);
208
+ }
182
209
  setupSecurityGuide(name);
183
210
  return true;
184
211
  }
185
- existing.mcpServers["guardvibe"] = GUARDVIBE_MCP_CONFIG;
212
+ servers["guardvibe"] = GUARDVIBE_MCP_CONFIG;
186
213
  writeJsonFile(platform.path, existing);
187
214
  }
188
215
  else {
package/build/cli.js CHANGED
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "module";
3
+ import { checkForUpdate } from "./utils/update-check.js";
3
4
  const require = createRequire(import.meta.url);
4
5
  const pkg = require("../package.json");
6
+ // Fire-and-forget update notification (writes to stderr if newer version exists).
7
+ // Skipped when GUARDVIBE_NO_UPDATE_CHECK=1, NO_UPDATE_NOTIFIER=1, or CI=true.
8
+ checkForUpdate(pkg.version);
5
9
  // ── Scan entry point detection ──────────────────────────────────────
6
10
  const SCAN_SCRIPT_DETECTED = process.argv[1]?.endsWith("guardvibe-scan") ||
7
11
  process.argv[1]?.endsWith("guardvibe-scan.js");
package/build/index.js CHANGED
@@ -998,6 +998,9 @@ export async function startMcpServer() {
998
998
  return main();
999
999
  }
1000
1000
  async function main() {
1001
+ // Fire-and-forget npm update check (writes a banner to stderr if newer version exists).
1002
+ const { checkForUpdate } = await import("./utils/update-check.js");
1003
+ checkForUpdate(pkg.version);
1001
1004
  // Load plugins
1002
1005
  const config = loadConfig(process.cwd());
1003
1006
  const plugins = await discoverPlugins(process.cwd(), config.plugins);
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Non-blocking npm update notification.
3
+ *
4
+ * On startup of the CLI or MCP server, fires an async GET against
5
+ * https://registry.npmjs.org/guardvibe/latest. Result is cached for 24h
6
+ * in ~/.cache/guardvibe/version-check.json so we never hit npm twice in
7
+ * a day from one machine. If a newer version is available, a 5-line
8
+ * banner is written to stderr — never stdout, so the MCP JSON-RPC stream
9
+ * is untouched.
10
+ *
11
+ * Disable with GUARDVIBE_NO_UPDATE_CHECK=1, NO_UPDATE_NOTIFIER=1,
12
+ * or CI=true (CI runners don't need version banners).
13
+ */
14
+ /**
15
+ * Compare two semver strings. Returns true if `latest` is strictly newer.
16
+ * Handles plain MAJOR.MINOR.PATCH (no pre-release / build metadata).
17
+ */
18
+ export declare function isNewer(latest: string, current: string): boolean;
19
+ /**
20
+ * Fire-and-forget version check. Never throws, never blocks.
21
+ *
22
+ * Behavior:
23
+ * - If env var disables it → no-op.
24
+ * - If cache is fresh (< 24h) and indicates newer version → announce immediately.
25
+ * - If cache is stale → fire async fetch, update cache, announce if newer.
26
+ */
27
+ export declare function checkForUpdate(currentVersion: string): void;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Non-blocking npm update notification.
3
+ *
4
+ * On startup of the CLI or MCP server, fires an async GET against
5
+ * https://registry.npmjs.org/guardvibe/latest. Result is cached for 24h
6
+ * in ~/.cache/guardvibe/version-check.json so we never hit npm twice in
7
+ * a day from one machine. If a newer version is available, a 5-line
8
+ * banner is written to stderr — never stdout, so the MCP JSON-RPC stream
9
+ * is untouched.
10
+ *
11
+ * Disable with GUARDVIBE_NO_UPDATE_CHECK=1, NO_UPDATE_NOTIFIER=1,
12
+ * or CI=true (CI runners don't need version banners).
13
+ */
14
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
15
+ import { join, dirname } from "node:path";
16
+ import { homedir, tmpdir } from "node:os";
17
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24h
18
+ const NPM_URL = "https://registry.npmjs.org/guardvibe/latest";
19
+ const FETCH_TIMEOUT_MS = 2000;
20
+ function cachePath() {
21
+ const home = process.env.HOME ?? homedir();
22
+ const baseDir = home && home.length > 0 ? join(home, ".cache", "guardvibe") : join(tmpdir(), "guardvibe");
23
+ return join(baseDir, "version-check.json");
24
+ }
25
+ function readCache() {
26
+ try {
27
+ const raw = readFileSync(cachePath(), "utf-8");
28
+ return JSON.parse(raw);
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
34
+ function writeCache(data) {
35
+ try {
36
+ const path = cachePath();
37
+ mkdirSync(dirname(path), { recursive: true });
38
+ writeFileSync(path, JSON.stringify(data));
39
+ }
40
+ catch {
41
+ // Cache write failure is non-fatal; silently swallow.
42
+ }
43
+ }
44
+ /**
45
+ * Compare two semver strings. Returns true if `latest` is strictly newer.
46
+ * Handles plain MAJOR.MINOR.PATCH (no pre-release / build metadata).
47
+ */
48
+ export function isNewer(latest, current) {
49
+ const parse = (v) => v.split(".").map(n => parseInt(n, 10));
50
+ const la = parse(latest);
51
+ const ca = parse(current);
52
+ for (let i = 0; i < 3; i++) {
53
+ const l = la[i] ?? 0;
54
+ const c = ca[i] ?? 0;
55
+ if (l !== c)
56
+ return l > c;
57
+ }
58
+ return false;
59
+ }
60
+ async function fetchLatest() {
61
+ try {
62
+ const ctrl = new AbortController();
63
+ const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
64
+ // guardvibe-ignore VG120 — NPM_URL is a hardcoded module-level constant, not user input
65
+ const res = await fetch(NPM_URL, { signal: ctrl.signal });
66
+ clearTimeout(timer);
67
+ if (!res.ok)
68
+ return null;
69
+ const data = (await res.json());
70
+ return typeof data.version === "string" ? data.version : null;
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }
76
+ function announceUpdate(latest, current) {
77
+ const lines = [
78
+ "",
79
+ " ┌──────────────────────────────────────────────────────────┐",
80
+ ` │ GuardVibe ${current} → ${latest} available`,
81
+ " │ Upgrade: re-run `npx guardvibe init <host>` to pin the new",
82
+ " │ version into your .mcp.json (or `npx guardvibe@latest`)",
83
+ " │ Silence: set GUARDVIBE_NO_UPDATE_CHECK=1",
84
+ " └──────────────────────────────────────────────────────────┘",
85
+ "",
86
+ ];
87
+ process.stderr.write(lines.join("\n"));
88
+ }
89
+ function isDisabled() {
90
+ return (process.env.GUARDVIBE_NO_UPDATE_CHECK === "1" ||
91
+ process.env.NO_UPDATE_NOTIFIER === "1" ||
92
+ process.env.CI === "true" ||
93
+ process.env.CI === "1");
94
+ }
95
+ /**
96
+ * Fire-and-forget version check. Never throws, never blocks.
97
+ *
98
+ * Behavior:
99
+ * - If env var disables it → no-op.
100
+ * - If cache is fresh (< 24h) and indicates newer version → announce immediately.
101
+ * - If cache is stale → fire async fetch, update cache, announce if newer.
102
+ */
103
+ export function checkForUpdate(currentVersion) {
104
+ if (isDisabled())
105
+ return;
106
+ const cache = readCache();
107
+ const now = Date.now();
108
+ if (cache && cache.latest && now - cache.checkedAt < CACHE_TTL_MS) {
109
+ if (isNewer(cache.latest, currentVersion)) {
110
+ announceUpdate(cache.latest, currentVersion);
111
+ }
112
+ return;
113
+ }
114
+ // Cache missing or stale — refresh in background, don't await.
115
+ fetchLatest()
116
+ .then(latest => {
117
+ writeCache({ checkedAt: now, latest });
118
+ if (latest && isNewer(latest, currentVersion)) {
119
+ announceUpdate(latest, currentVersion);
120
+ }
121
+ })
122
+ .catch(() => {
123
+ // Non-fatal.
124
+ });
125
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardvibe",
3
- "version": "3.1.0",
3
+ "version": "3.1.2",
4
4
  "mcpName": "io.github.goklab/guardvibe",
5
5
  "description": "Security MCP for vibe coding. 390 rules, 36 tools, CLI + doctor. Host security, auth coverage mapping, LLM-powered deep scan (IDOR/business logic), taint analysis, +25 AI-native rules (MCP supply-chain, RAG/vector poisoning, agent loop DoS, public-prefix LLM keys, sandbox bypass). Plus Next.js, Supabase, Clerk, Stripe, Prisma, tRPC, Hono, GraphQL, Convex, Turso, Uploadthing, AI SDK, and the full AI-generated stack.",
6
6
  "type": "module",