securevibe 0.1.14 → 0.1.15
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 +8 -3
- package/dist/index.js +1 -1
- package/dist/remote-limit.js +83 -0
- package/dist/repl.js +1 -1
- package/dist/ui/usage.js +1 -1
- package/dist/usage.js +11 -6
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,8 +17,9 @@ securevibe fix <path> --apply # apply + back up originals (asks per change)
|
|
|
17
17
|
From a clone of this repo instead: `pnpm install && pnpm build`, then
|
|
18
18
|
`node packages/cli/dist/index.js scan <path>` (`pnpm demo` scans the bundled sample).
|
|
19
19
|
|
|
20
|
-
The free beta includes a daily usage limit
|
|
21
|
-
|
|
20
|
+
The free beta includes a daily usage limit, reset at local midnight (the current count and
|
|
21
|
+
limit are shown after each run; the limit itself is remotely adjustable and not a fixed
|
|
22
|
+
number, see below). The `--staged` commit guard is never limited. See [`LICENSE`](LICENSE).
|
|
22
23
|
|
|
23
24
|
## Commands
|
|
24
25
|
|
|
@@ -147,7 +148,11 @@ against a local copy of the [OSV](https://osv.dev) advisory database, and report
|
|
|
147
148
|
no network. `db update` and `update` are the only commands that **download** anything (OSV
|
|
148
149
|
advisory dumps, or the CLI's own next version, respectively). `pr-comment` is the only command
|
|
149
150
|
that **uploads** anything, and only a findings summary already computed locally — never your
|
|
150
|
-
code or package list.
|
|
151
|
+
code or package list. Most other commands also make one small background check: a newer
|
|
152
|
+
version notice (the npm registry, cached a day, exempt under `--json`/`--sarif`/`--staged`)
|
|
153
|
+
and the current daily usage limit (a public read only endpoint, cached an hour, exempt under
|
|
154
|
+
`--staged`/`--diff`). Both are tiny GET requests for a version string or a number, and never
|
|
155
|
+
send your code, your findings, or anything else about your project.
|
|
151
156
|
- **Honest framing.** A clean result reads "no known advisories as of `<db date>`", not "secure" —
|
|
152
157
|
it means nothing matched the local database on that date, which is not a proof of safety.
|
|
153
158
|
|
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ async function meter(opts = {}) {
|
|
|
38
38
|
const { recordUse, limitMessage } = await import("./usage.js");
|
|
39
39
|
const r = await recordUse();
|
|
40
40
|
if (!r.allowed) {
|
|
41
|
-
process.stderr.write(limitMessage() + "\n");
|
|
41
|
+
process.stderr.write(limitMessage(r.limit) + "\n");
|
|
42
42
|
process.exit(1);
|
|
43
43
|
}
|
|
44
44
|
return r;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote, admin editable override for the daily usage limit. Same
|
|
3
|
+
* fetch/cache/TTL/fail-open shape as update-check.ts, checking a small
|
|
4
|
+
* public endpoint instead of the npm registry. Never throws — offline, a
|
|
5
|
+
* down endpoint, an invalid value, or a corrupt cache all resolve to the
|
|
6
|
+
* last known good cached value, or ultimately to the caller supplied
|
|
7
|
+
* fallback (usage.ts's DAILY_LIMIT).
|
|
8
|
+
*/
|
|
9
|
+
import { promises as fs } from "node:fs";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
const LIMITS_URL = "https://limits-config.vercel.app/api/limits";
|
|
13
|
+
const CACHE_TTL_MS = 60 * 60 * 1000;
|
|
14
|
+
const FETCH_TIMEOUT_MS = 1500;
|
|
15
|
+
export function cacheFilePath() {
|
|
16
|
+
return path.join(os.homedir(), ".securevibe", "remote-limit.json");
|
|
17
|
+
}
|
|
18
|
+
/** A remote value is only trusted if it's "unlimited" or a finite positive number. */
|
|
19
|
+
function isValidLimit(value) {
|
|
20
|
+
return value === "unlimited" || (typeof value === "number" && Number.isFinite(value) && value > 0);
|
|
21
|
+
}
|
|
22
|
+
async function readCache(file) {
|
|
23
|
+
try {
|
|
24
|
+
const raw = JSON.parse(await fs.readFile(file, "utf8"));
|
|
25
|
+
if (typeof raw?.checkedAt === "number" && isValidLimit(raw?.dailyLimit))
|
|
26
|
+
return raw;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
/* missing or corrupt → treated as no cache */
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
async function writeCache(file, state) {
|
|
34
|
+
try {
|
|
35
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
36
|
+
await fs.writeFile(file, JSON.stringify(state) + "\n", "utf8");
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
/* best effort — a failed write must never break the command */
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export async function fetchRemoteLimit(fetchImpl) {
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
45
|
+
try {
|
|
46
|
+
const res = await fetchImpl(LIMITS_URL, { signal: controller.signal });
|
|
47
|
+
if (!res.ok)
|
|
48
|
+
return null;
|
|
49
|
+
const json = await res.json();
|
|
50
|
+
return isValidLimit(json?.dailyLimit) ? json.dailyLimit : null;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Returns the effective daily limit: a fresh or cached remote override when
|
|
61
|
+
* one is known and valid, else the caller supplied fallback. Never throws.
|
|
62
|
+
*/
|
|
63
|
+
export async function resolveDailyLimit(opts) {
|
|
64
|
+
const file = opts.file ?? cacheFilePath();
|
|
65
|
+
const now = opts.now ?? Date.now();
|
|
66
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
67
|
+
const fallback = opts.fallback;
|
|
68
|
+
try {
|
|
69
|
+
const cached = await readCache(file);
|
|
70
|
+
if (cached && now - cached.checkedAt < CACHE_TTL_MS) {
|
|
71
|
+
return cached.dailyLimit;
|
|
72
|
+
}
|
|
73
|
+
const fetched = await fetchRemoteLimit(fetchImpl);
|
|
74
|
+
if (fetched !== null) {
|
|
75
|
+
await writeCache(file, { checkedAt: now, dailyLimit: fetched });
|
|
76
|
+
return fetched;
|
|
77
|
+
}
|
|
78
|
+
return cached ? cached.dailyLimit : fallback;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return fallback;
|
|
82
|
+
}
|
|
83
|
+
}
|
package/dist/repl.js
CHANGED
|
@@ -35,7 +35,7 @@ async function meterOrPrint(io) {
|
|
|
35
35
|
const { recordUse, limitMessage } = await import("./usage.js");
|
|
36
36
|
const r = await recordUse();
|
|
37
37
|
if (!r.allowed) {
|
|
38
|
-
write(io, limitMessage() + "\n");
|
|
38
|
+
write(io, limitMessage(r.limit) + "\n");
|
|
39
39
|
return undefined;
|
|
40
40
|
}
|
|
41
41
|
return r;
|
package/dist/ui/usage.js
CHANGED
|
@@ -7,6 +7,6 @@ function bar(fraction, width = 20) {
|
|
|
7
7
|
return color("█".repeat(filled)) + pc.dim("░".repeat(width - filled));
|
|
8
8
|
}
|
|
9
9
|
export function renderUsageLine(usage) {
|
|
10
|
-
const fraction = usage.limit
|
|
10
|
+
const fraction = usage.limit === "unlimited" || usage.limit <= 0 ? 0 : usage.used / usage.limit;
|
|
11
11
|
return pc.dim(" Free beta usage today ") + bar(fraction) + pc.dim(` ${usage.used}/${usage.limit}`);
|
|
12
12
|
}
|
package/dist/usage.js
CHANGED
|
@@ -4,10 +4,14 @@
|
|
|
4
4
|
* design — there is no account system yet — so this establishes the metering
|
|
5
5
|
* UX honestly without pretending to be tamper-proof. The --staged commit guard
|
|
6
6
|
* is never metered: a quota must not block someone's commit.
|
|
7
|
+
*
|
|
8
|
+
* The enforced limit is resolved through remote-limit.ts on every call: an
|
|
9
|
+
* admin editable value if one is known and valid, else this file's DAILY_LIMIT.
|
|
7
10
|
*/
|
|
8
11
|
import { promises as fs } from "node:fs";
|
|
9
12
|
import os from "node:os";
|
|
10
13
|
import path from "node:path";
|
|
14
|
+
import { resolveDailyLimit } from "./remote-limit.js";
|
|
11
15
|
export const DAILY_LIMIT = 200;
|
|
12
16
|
export function usageFilePath() {
|
|
13
17
|
return path.join(os.homedir(), ".securevibe", "usage.json");
|
|
@@ -19,10 +23,10 @@ export function localDay(now = new Date()) {
|
|
|
19
23
|
const d = String(now.getDate()).padStart(2, "0");
|
|
20
24
|
return `${y}-${m}-${d}`;
|
|
21
25
|
}
|
|
22
|
-
/** Pure state transition: same day increments, a new day resets, limit blocks. */
|
|
26
|
+
/** Pure state transition: same day increments, a new day resets, limit blocks — "unlimited" never blocks. */
|
|
23
27
|
export function nextUsage(state, today, limit = DAILY_LIMIT) {
|
|
24
28
|
const base = state && state.date === today ? state : { date: today, count: 0 };
|
|
25
|
-
if (base.count >= limit) {
|
|
29
|
+
if (limit !== "unlimited" && base.count >= limit) {
|
|
26
30
|
return { state: base, result: { allowed: false, used: base.count, limit } };
|
|
27
31
|
}
|
|
28
32
|
const next = { date: today, count: base.count + 1 };
|
|
@@ -42,12 +46,13 @@ async function readState(file) {
|
|
|
42
46
|
}
|
|
43
47
|
/**
|
|
44
48
|
* Record one use against today's quota. Returns whether the command may run.
|
|
45
|
-
* Metering failures (unwritable home, etc.) never
|
|
46
|
-
* error the use is allowed.
|
|
49
|
+
* Metering failures (unwritable home, unreachable remote limit, etc.) never
|
|
50
|
+
* break the tool: on any IO error the use is allowed.
|
|
47
51
|
*/
|
|
48
|
-
export async function recordUse(file = usageFilePath(), now = new Date()) {
|
|
52
|
+
export async function recordUse(file = usageFilePath(), now = new Date(), resolveLimit = () => resolveDailyLimit({ fallback: DAILY_LIMIT })) {
|
|
49
53
|
try {
|
|
50
|
-
const
|
|
54
|
+
const limit = await resolveLimit();
|
|
55
|
+
const { state, result } = nextUsage(await readState(file), localDay(now), limit);
|
|
51
56
|
if (result.allowed) {
|
|
52
57
|
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
53
58
|
await fs.writeFile(file, JSON.stringify(state) + "\n", "utf8");
|
package/dist/version.js
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
// Single source of truth for the CLI's own version, so it can't drift from
|
|
3
3
|
// what commander reports and what the update-check compares against.
|
|
4
4
|
// Kept in sync with the "version" field in package.json by hand at release time.
|
|
5
|
-
export const VERSION = "0.1.
|
|
5
|
+
export const VERSION = "0.1.15";
|
package/package.json
CHANGED