@pretense/cli 0.6.12 → 0.6.13

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.
@@ -17,7 +17,7 @@ function versionFromPackageJson() {
17
17
  return void 0;
18
18
  }
19
19
  }
20
- var PRETENSE_VERSION = (true ? "0.6.12" : void 0) ?? versionFromPackageJson() ?? "0.0.0-unknown";
20
+ var PRETENSE_VERSION = (true ? "0.6.13" : void 0) ?? versionFromPackageJson() ?? "0.0.0-unknown";
21
21
 
22
22
  export {
23
23
  PRETENSE_VERSION
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  PRETENSE_VERSION
4
- } from "./chunk-K3L4E6HN.js";
4
+ } from "./chunk-HWSWJ4EC.js";
5
5
  import {
6
6
  __commonJS,
7
7
  __toESM,
@@ -12171,6 +12171,19 @@ var SECRET_PATTERNS = [
12171
12171
  defaultAction: "block",
12172
12172
  pattern: /\bgithub_pat_[A-Za-z0-9_]{22,}\b/g
12173
12173
  },
12174
+ // FIX 1 (v0.6.13) — HuggingFace access tokens. `hf_` user and fine-grained
12175
+ // access tokens are `hf_` + 34+ base62 chars. Confirmed leaking on 0.6.12:
12176
+ // an `hf_` token in bare prose scanned clean (a real credential egressed to
12177
+ // the LLM) because no `hf_`/`huggingface` pattern existed. The `hf_` prefix
12178
+ // plus a 34+ base62 body is a near-zero-false-positive signal. Critical/block
12179
+ // because a live model-registry token grants push access to private models.
12180
+ {
12181
+ name: "huggingface-token",
12182
+ category: "secret",
12183
+ severity: "critical",
12184
+ defaultAction: "block",
12185
+ pattern: /\bhf_[A-Za-z0-9]{34,}\b/g
12186
+ },
12174
12187
  {
12175
12188
  name: "gitlab-token",
12176
12189
  category: "secret",
@@ -12745,8 +12758,9 @@ var ENV_PATTERNS = [
12745
12758
  // variable references (`$VAR`, `${VAR}`, `process.env.X`, `os.environ…`),
12746
12759
  // connection URLs (handled by `database-url`), and values that begin with a
12747
12760
  // recognised structured-secret prefix (Stripe `sk_`/`pk_`/`rk_`, OpenAI
12748
- // `sk-`, Slack `xox*`, GitHub `gh*_`/`glpat-`, AWS `AKIA`, Google `AIza`,
12749
- // Stripe webhook `whsec_`) are deliberately skipped so a generic
12761
+ // `sk-`, Slack `xox*`, GitHub `gh*_`/`glpat-`, HuggingFace `hf_`, AWS
12762
+ // `AKIA`, Google `AIza`, Stripe webhook `whsec_`) are deliberately skipped
12763
+ // so a generic
12750
12764
  // `KEY=variable` reference is not flagged and the dedicated, higher-fidelity
12751
12765
  // patterns above own those matches. Placed last so more-specific patterns
12752
12766
  // win overlap deduplication on ties.
@@ -12760,7 +12774,7 @@ var ENV_PATTERNS = [
12760
12774
  // specific env-var names rather than generic secret-label tails, so they do
12761
12775
  // not belong in the shared vocabulary.
12762
12776
  pattern: new RegExp(
12763
- `(?<![A-Za-z0-9])(?:[A-Z0-9]+[._-])*(?:${ENV_SECRET_LABEL_ALTERNATION}|DATABASE_URL|VAULT_TOKEN)\\s*[=:]\\s*(?!['"\\s])(?!\\$)(?!process\\.env)(?!os\\.environ)(?![A-Za-z][A-Za-z0-9+.-]*:\\/\\/)(?!sk[-_]|pk_|rk_|whsec_|xox[bpors]-|ghp_|gho_|ghs_|glpat-|AKIA|AIza)[A-Za-z0-9/+._@:!#%^&*?~-]{8,}`,
12777
+ `(?<![A-Za-z0-9])(?:[A-Z0-9]+[._-])*(?:${ENV_SECRET_LABEL_ALTERNATION}|DATABASE_URL|VAULT_TOKEN)\\s*[=:]\\s*(?!['"\\s])(?!\\$)(?!process\\.env)(?!os\\.environ)(?![A-Za-z][A-Za-z0-9+.-]*:\\/\\/)(?!sk[-_]|pk_|rk_|whsec_|xox[bpors]-|ghp_|gho_|ghs_|glpat-|hf_|AKIA|AIza)[A-Za-z0-9/+._@:!#%^&*?~-]{8,}`,
12764
12778
  "gi"
12765
12779
  ),
12766
12780
  // PRECISION: reject `PASSWORD=changeme`, `PASSWORD=<your-password>` and
@@ -13827,6 +13841,8 @@ function overlapsAny(m, sorted) {
13827
13841
  }
13828
13842
  async function scanLevel2(text, language, opts = {}) {
13829
13843
  const lang = language ?? opts.language ?? "typescript";
13844
+ const t0 = performance.now();
13845
+ const elapsed = () => Math.round((performance.now() - t0) * 100) / 100;
13830
13846
  const base = opts.presidioUrl ? await scanWithPresidio(text, opts.presidioUrl, opts) : scan(text, opts);
13831
13847
  const l1Matches = base.matches.map((m) => ({ ...m, level: 1 }));
13832
13848
  const l1Findings = base.findings.map((f) => ({ ...f, level: 1 }));
@@ -13834,6 +13850,8 @@ async function scanLevel2(text, language, opts = {}) {
13834
13850
  ...base,
13835
13851
  matches: l1Matches,
13836
13852
  findings: l1Findings,
13853
+ // Real wall time up to this early return, not the L1 sub-phase alone.
13854
+ durationMs: elapsed(),
13837
13855
  l2: status
13838
13856
  });
13839
13857
  const native = loadNative();
@@ -13911,7 +13929,9 @@ async function scanLevel2(text, language, opts = {}) {
13911
13929
  matches: merged,
13912
13930
  findings: mergedFindings,
13913
13931
  scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
13914
- durationMs: base.durationMs,
13932
+ // Real elapsed wall time for the full L1+L2 scan (was base.durationMs, the
13933
+ // L1-only time, which undercounted the AST work ~25x — see FIX 3/TASK-16).
13934
+ durationMs: elapsed(),
13915
13935
  patternCount: base.patternCount,
13916
13936
  // Bounded-work state is a property of the underlying scan and MUST survive
13917
13937
  // this merge — dropping it here would re-create the silent-incomplete hole
@@ -16425,11 +16445,11 @@ function versionFromCliPackageJson() {
16425
16445
  return void 0;
16426
16446
  }
16427
16447
  function productVersion() {
16428
- return override ?? (true ? "0.6.12" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
16448
+ return override ?? (true ? "0.6.13" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
16429
16449
  }
16430
16450
  var UNKNOWN_COMMIT = "unknown";
16431
16451
  function bakedCommitSha() {
16432
- return "cd2bfc0670d3c8aadaa5a163493ba152391489a1".length > 0 ? "cd2bfc0670d3c8aadaa5a163493ba152391489a1" : UNKNOWN_COMMIT;
16452
+ return "a36bf5d80a91cfd8fbf57e6c5a6a9b9114826309".length > 0 ? "a36bf5d80a91cfd8fbf57e6c5a6a9b9114826309" : UNKNOWN_COMMIT;
16433
16453
  }
16434
16454
  var FEATURE_TIERS = {
16435
16455
  compliance_export: "pro",
@@ -18323,9 +18343,24 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
18323
18343
  ".idea",
18324
18344
  ".vscode"
18325
18345
  ]);
18346
+ function isSkippableWalkError(err) {
18347
+ const code = err?.code;
18348
+ return code === "EACCES" || code === "EPERM" || code === "ENOENT" || code === "ELOOP" || code === "ENOTDIR";
18349
+ }
18326
18350
  function walkDir(dir) {
18327
18351
  const files = [];
18328
- for (const entry of readdirSync2(dir, { withFileTypes: true })) {
18352
+ let entries;
18353
+ try {
18354
+ entries = readdirSync2(dir, { withFileTypes: true });
18355
+ } catch (err) {
18356
+ if (isSkippableWalkError(err)) {
18357
+ const code = err.code;
18358
+ console.error(chalk4.yellow(` Skipping unreadable path (${code}): ${dir}`));
18359
+ return files;
18360
+ }
18361
+ throw err;
18362
+ }
18363
+ for (const entry of entries) {
18329
18364
  if (entry.isDirectory()) {
18330
18365
  if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
18331
18366
  files.push(...walkDir(join7(dir, entry.name)));
@@ -18378,6 +18413,18 @@ function l2LanguageFor(filePath) {
18378
18413
  if (!filePath) return void 0;
18379
18414
  return L2_LANGUAGE_BY_EXT[extname2(filePath).toLowerCase()];
18380
18415
  }
18416
+ var L2_LARGE_FILE_WARN_BYTES = 5 * 1024 * 1024;
18417
+ function warnIfLargeForL2(byteLength, filePath) {
18418
+ if (byteLength <= L2_LARGE_FILE_WARN_BYTES) return false;
18419
+ const mb = (byteLength / (1024 * 1024)).toFixed(1);
18420
+ const where = filePath ? ` \u2014 ${basename(filePath)}` : "";
18421
+ console.error(
18422
+ chalk4.yellow(
18423
+ ` Note: L2 AST scanning is slow on very large files (${mb}MB${where}); this may take a while.`
18424
+ )
18425
+ );
18426
+ return true;
18427
+ }
18381
18428
  async function scanContent(content, opts, filePath) {
18382
18429
  return withLocations(await scanContentRaw(content, opts, filePath), content);
18383
18430
  }
@@ -18421,6 +18468,7 @@ async function scanContentRaw(content, opts, filePath) {
18421
18468
  }
18422
18469
  };
18423
18470
  }
18471
+ warnIfLargeForL2(Buffer.byteLength(content, "utf-8"), filePath);
18424
18472
  const result = await scanLevel2(content, language, scanOpts);
18425
18473
  if (opts.level === 2) {
18426
18474
  const only2 = result.matches.filter((m) => m.level === 2);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  PRETENSE_VERSION
4
- } from "./chunk-K3L4E6HN.js";
4
+ } from "./chunk-HWSWJ4EC.js";
5
5
  import {
6
6
  init_esm_shims
7
7
  } from "./chunk-TWMRHZGM.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pretense/cli",
3
- "version": "0.6.12",
3
+ "version": "0.6.13",
4
4
  "description": "Local-first AI firewall that mutates proprietary code before sending to LLM APIs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,11 +23,11 @@
23
23
  "tsx": "^4.0.0",
24
24
  "typescript": "^5.4.0",
25
25
  "vitest": "^1.0.0",
26
- "@pretense/compliance-engine": "0.1.0",
27
- "@pretense/billing": "0.1.0",
28
26
  "@pretense/protocol": "0.1.0",
29
27
  "@pretense/learner": "0.2.0",
28
+ "@pretense/compliance-engine": "0.1.0",
30
29
  "@pretense/mutator": "0.2.0",
30
+ "@pretense/billing": "0.1.0",
31
31
  "@pretense/proxy": "0.1.0",
32
32
  "@pretense/scanner": "0.2.0",
33
33
  "@pretense/scanner-rs": "0.2.0",