guardvibe 3.1.0 → 3.1.1
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/build/cli.js +4 -0
- package/build/index.js +3 -0
- package/build/utils/update-check.d.ts +27 -0
- package/build/utils/update-check.js +125 -0
- package/package.json +1 -1
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.
|
|
3
|
+
"version": "3.1.1",
|
|
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",
|