@pretense/cli 0.6.18 → 0.6.20

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.18" : void 0) ?? versionFromPackageJson() ?? "0.0.0-unknown";
20
+ var PRETENSE_VERSION = (true ? "0.6.20" : 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-QT5E625F.js";
4
+ } from "./chunk-N2GZIZAD.js";
5
5
  import {
6
6
  __commonJS,
7
7
  __toESM,
@@ -11952,6 +11952,48 @@ var EXTENDED_PATTERNS = [
11952
11952
  defaultAction: "block",
11953
11953
  pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY(?: BLOCK)?-----[ \t]*\r?\n(?:[ \t]*\r?\n|[ \t]*[A-Za-z][A-Za-z0-9_-]{0,40}:[^\r\n]{0,1000}\r?\n){0,40}(?:[ \t]*[A-Za-z0-9+/=]{16,80}[ \t]*(?:\r?\n|$)){1,400}/g
11954
11954
  },
11955
+ // ESCAPED SINGLE-LINE PEM private key — the `.env` / JSON / k8s-secret / CI-var
11956
+ // idiom where the key is stored as ONE line with LITERAL `\n` (backslash-n)
11957
+ // escape sequences instead of real newlines:
11958
+ //
11959
+ // PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIE…\n-----END RSA PRIVATE KEY-----"
11960
+ //
11961
+ // WHY THIS EXISTS (P0 egress, v0.6.19). Both END-anchored PEM detectors above
11962
+ // (`private-key-block`, `private-key-body`) require a REAL newline (`\r?\n`,
11963
+ // 0x0A) after the banner and a base64 body drawn from `[A-Za-z0-9+/=\s]`. In
11964
+ // the escaped form the separators are the two literal characters `\` + `n`,
11965
+ // and a backslash is in NEITHER body charset — so those detectors never match,
11966
+ // and only the banner-only `private-key` detector (patterns.ts) fires. That
11967
+ // spans just `-----BEGIN … KEY-----`; mutate/redact then rewrites the BANNER
11968
+ // and leaves the real base64 body verbatim on the wire, while `audit` reports
11969
+ // `detectorKinds:["private-key"]` — a FALSE assurance that the key was handled.
11970
+ // Confirmed leaking against the shipped 0.6.18 engine (mutate + proxy egress).
11971
+ //
11972
+ // This detector spans the WHOLE escaped block (banner → body → END) so the
11973
+ // mutator's PEM branch replaces the entire key, not just the marker. Being the
11974
+ // LONGER same-severity match it wins interval-dedup over the banner-only
11975
+ // `private-key`, exactly as `private-key-block` does for real multi-line keys.
11976
+ //
11977
+ // PRECISION / no prose-destruction. END-ANCHORED with a `\1` BACKREFERENCE, so
11978
+ // a `BEGIN RSA …` can only close on `-----END RSA …-----` (never a far-away
11979
+ // unrelated END). The body charset is base64 (`[A-Za-z0-9+/=]`) plus the
11980
+ // literal escape sequences `\n`/`\r`/`\t` and space/tab ONLY — ordinary prose
11981
+ // (`.`, `,`, `:`, other punctuation) STOPS the match at the first such char, so
11982
+ // this can never swallow a document. Real MULTI-LINE PEMs contain 0x0A newline
11983
+ // characters, which are in neither branch of the body class, so this detector
11984
+ // does NOT match them — they stay owned by `private-key-block`.
11985
+ //
11986
+ // ReDoS-safe: the two body alternatives are disjoint on their first character
11987
+ // (`\\[rnt]` begins with a backslash; the class excludes backslash), so every
11988
+ // position has exactly one parse; the quantifier is lazy, bounded at 8000, and
11989
+ // terminated by the required `-----END` anchor.
11990
+ {
11991
+ name: "private-key-escaped",
11992
+ category: "secret",
11993
+ severity: "critical",
11994
+ defaultAction: "block",
11995
+ pattern: /-----BEGIN ((?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY(?: BLOCK)?)-----(?:\\[rnt]|[A-Za-z0-9+/= \t]){1,8000}?-----END \1-----/g
11996
+ },
11955
11997
  // AWS *temporary* (STS) access key — `ASIA` + 16. STRICT by design (review
11956
11998
  // fix): the first cut widened the critical/BLOCK `aws-access-key` detector to
11957
11999
  // `(?:AKIA|ASIA)[0-9A-Z]{16}`, which hard-400'd benign traffic because `ASIA`
@@ -12308,6 +12350,32 @@ var SECRET_PATTERNS = [
12308
12350
  defaultAction: "block",
12309
12351
  pattern: /\brk_(?:test|live)_[A-Za-z0-9]{20,}\b/g
12310
12352
  },
12353
+ {
12354
+ // Pretense's OWN API key (`prtns_live_…` / `prtns_test_…`), minted by the
12355
+ // backend `/api/cli/auth/validate`. VALUE-DRIVEN by construction: the
12356
+ // `prtns_(live|test)_` prefix is matched on the LITERAL wherever it appears,
12357
+ // regardless of the assignee's name or the source language.
12358
+ //
12359
+ // WHY THIS DETECTOR EXISTS (P0 egress, v0.6.19). A live `prtns_live_` token
12360
+ // assigned to a variable whose name is NOT a recognised secret-label tail —
12361
+ // `INTERNAL_HMAC_KEY = "prtns_live_…"` (bare `key` and the compound
12362
+ // `hmac_key` are BOTH deliberately absent from SECRET_LABEL_TAILS), or
12363
+ // `InternalHmacKey = …` — matched NO named detector, so it egressed to the
12364
+ // LLM in cleartext and survived `mutate --stdout`. Only tokens that happened
12365
+ // to land in a `…token`/`…secret`/`…password`-suffixed binding were caught,
12366
+ // an inconsistency that depended on the variable name and therefore on the
12367
+ // language's naming convention. A credential must be caught on its own shape.
12368
+ //
12369
+ // Precision: the `prtns_(live|test)_` prefix plus a 16+ base62 body is a
12370
+ // near-zero-false-positive signal — nothing benign carries it — so this is
12371
+ // safe as an unlabelled, value-driven detector on the always-mutate egress
12372
+ // path, exactly like the Stripe `sk_live_` / GitHub `ghp_` prefixes above.
12373
+ name: "pretense-api-key",
12374
+ category: "secret",
12375
+ severity: "critical",
12376
+ defaultAction: "block",
12377
+ pattern: /\bprtns_(?:live|test)_[A-Za-z0-9]{16,}\b/g
12378
+ },
12311
12379
  // Communications
12312
12380
  {
12313
12381
  name: "slack-token",
@@ -12869,7 +12937,7 @@ var ENV_PATTERNS = [
12869
12937
  // specific env-var names rather than generic secret-label tails, so they do
12870
12938
  // not belong in the shared vocabulary.
12871
12939
  pattern: new RegExp(
12872
- `(?<![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,}`,
12940
+ `(?<![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_|prtns_(?:live|test)_|AKIA|AIza)[A-Za-z0-9/+._@:!#%^&*?~-]{8,}`,
12873
12941
  "gi"
12874
12942
  ),
12875
12943
  // PRECISION: reject `PASSWORD=changeme`, `PASSWORD=<your-password>` and
@@ -15633,7 +15701,7 @@ ${end}`;
15633
15701
  }
15634
15702
  const shaped = shapePreservingSynthetic(finding.type, v, digest);
15635
15703
  if (shaped !== null) return shaped;
15636
- const prefix = v.match(/^(?:sk_live_|sk_test_|pk_live_|pk_test_|ghp_|gho_|ghu_|ghs_|ghr_|AKIA|ASIA|AIza|xoxb-|xoxp-|xoxa-|xoxr-|hf_|eyJ)/)?.[0] ?? "";
15704
+ const prefix = v.match(/^(?:sk_live_|sk_test_|pk_live_|pk_test_|prtns_live_|prtns_test_|ghp_|gho_|ghu_|ghs_|ghr_|AKIA|ASIA|AIza|xoxb-|xoxp-|xoxa-|xoxr-|hf_|eyJ)/)?.[0] ?? "";
15637
15705
  const fillerLen = Math.max(1, v.length - prefix.length);
15638
15706
  const filler = digest.repeat(Math.ceil(fillerLen / digest.length)).slice(0, fillerLen);
15639
15707
  return prefix + filler;
@@ -16514,11 +16582,11 @@ function versionFromCliPackageJson() {
16514
16582
  return void 0;
16515
16583
  }
16516
16584
  function productVersion() {
16517
- return override ?? (true ? "0.6.18" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
16585
+ return override ?? (true ? "0.6.20" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
16518
16586
  }
16519
16587
  var UNKNOWN_COMMIT = "unknown";
16520
16588
  function bakedCommitSha() {
16521
- return "220d098b1829361f3d0043fb24275e5fdfb84429".length > 0 ? "220d098b1829361f3d0043fb24275e5fdfb84429" : UNKNOWN_COMMIT;
16589
+ return "69ea69288cc5d8698de1a2bdb4e3c29c002596c6".length > 0 ? "69ea69288cc5d8698de1a2bdb4e3c29c002596c6" : UNKNOWN_COMMIT;
16522
16590
  }
16523
16591
  var FEATURE_TIERS = {
16524
16592
  compliance_export: "pro",
@@ -17133,6 +17201,65 @@ function getTelemetryEmitter(dashboardUrl, pushToken) {
17133
17201
  _emitter = new TelemetryEmitter(url, token);
17134
17202
  return _emitter;
17135
17203
  }
17204
+ var DEFAULT_UPLOAD_URL = "https://api.pretense.ai";
17205
+ var UPLOAD_TIMEOUT_MS = 5e3;
17206
+ var MAX_IN_FLIGHT2 = 64;
17207
+ var _inFlight = 0;
17208
+ var _dropped = 0;
17209
+ function telemetryDisabled() {
17210
+ const v = process.env["PRETENSE_NO_TELEMETRY"]?.trim().toLowerCase();
17211
+ return v === "1" || v === "true" || v === "yes" || v === "on";
17212
+ }
17213
+ function resolveUploadBaseUrl() {
17214
+ const raw2 = (process.env["PRETENSE_DASHBOARD_URL"] ?? DEFAULT_UPLOAD_URL).trim();
17215
+ if (!raw2) return DEFAULT_UPLOAD_URL;
17216
+ const normalized = /^https?:\/\//i.test(raw2) ? raw2 : `https://${raw2}`;
17217
+ try {
17218
+ const u = new URL(normalized);
17219
+ const path2 = u.pathname === "/" ? "" : u.pathname.replace(/\/$/, "");
17220
+ return `${u.origin}${path2}`;
17221
+ } catch {
17222
+ return DEFAULT_UPLOAD_URL;
17223
+ }
17224
+ }
17225
+ function resolveUploadKey(requestKey) {
17226
+ const dashboardKey = process.env["PRETENSE_DASHBOARD_API_KEY"]?.trim();
17227
+ if (dashboardKey) return dashboardKey;
17228
+ const rk = requestKey?.trim();
17229
+ return rk && rk.length > 0 ? rk : null;
17230
+ }
17231
+ function clampCount(n) {
17232
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
17233
+ }
17234
+ function uploadProxyUsage(event) {
17235
+ if (telemetryDisabled()) return;
17236
+ const key = resolveUploadKey(event.apiKey);
17237
+ if (!key) return;
17238
+ if (_inFlight >= MAX_IN_FLIGHT2) {
17239
+ _dropped++;
17240
+ return;
17241
+ }
17242
+ const url = `${resolveUploadBaseUrl()}/api/cli/log`;
17243
+ const signal = typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(UPLOAD_TIMEOUT_MS) : void 0;
17244
+ _inFlight++;
17245
+ void Promise.resolve().then(
17246
+ () => fetch(url, {
17247
+ method: "POST",
17248
+ headers: { "content-type": "application/json", authorization: `Bearer ${key}` },
17249
+ // COUNTS + METADATA ONLY — never a secret value or request body.
17250
+ body: JSON.stringify({
17251
+ file_count: 0,
17252
+ identifiers_mutated: clampCount(event.identifiersMutated),
17253
+ secrets_blocked: clampCount(event.secretsBlocked),
17254
+ llm_provider: event.provider
17255
+ }),
17256
+ ...signal ? { signal } : {}
17257
+ })
17258
+ ).catch(() => {
17259
+ }).finally(() => {
17260
+ _inFlight--;
17261
+ });
17262
+ }
17136
17263
  var riskScorer = new RiskScorer();
17137
17264
  function detectUpstream(path2, overrideHeader) {
17138
17265
  if (overrideHeader) return overrideHeader;
@@ -18008,6 +18135,15 @@ ${_upgradeTier === "enterprise" ? "Enterprise offers unlimited mutations \u2014
18008
18135
  ...skippedUnscannable > 0 ? { findings: ["skipped_unscannable_field"] } : {},
18009
18136
  sourceIp: c.req.header("x-forwarded-for") ?? c.req.header("x-real-ip")
18010
18137
  }));
18138
+ if (anyTokenized) {
18139
+ const usageKey = c.req.header("x-pretense-api-key") ?? c.req.header("authorization")?.replace(/^Bearer\s+/i, "");
18140
+ uploadProxyUsage({
18141
+ apiKey: usageKey,
18142
+ identifiersMutated: mutationsApplied,
18143
+ secretsBlocked: secretsTokenized,
18144
+ provider
18145
+ });
18146
+ }
18011
18147
  const reverseMap = /* @__PURE__ */ new Map();
18012
18148
  for (const [orig, syn] of globalMap.entries()) {
18013
18149
  if (!secretMap.has(syn)) reverseMap.set(orig, syn);
@@ -21249,6 +21385,67 @@ function recordLocalAudit(rec, dbPath = localAuditDbPath()) {
21249
21385
  }
21250
21386
  }
21251
21387
 
21388
+ // src/usage-upload.ts
21389
+ init_esm_shims();
21390
+ var UPLOAD_TIMEOUT_MS2 = 3e3;
21391
+ function telemetryDisabled2() {
21392
+ const v = process.env["PRETENSE_NO_TELEMETRY"]?.trim().toLowerCase();
21393
+ return v === "1" || v === "true" || v === "yes" || v === "on";
21394
+ }
21395
+ function resolveUploadBaseUrl2() {
21396
+ const raw2 = process.env["PRETENSE_DASHBOARD_URL"]?.trim();
21397
+ if (!raw2) return getConfiguredApiRoot();
21398
+ const normalized = /^https?:\/\//i.test(raw2) ? raw2 : `https://${raw2}`;
21399
+ try {
21400
+ const u = new URL(normalized);
21401
+ const path2 = u.pathname === "/" ? "" : u.pathname.replace(/\/$/, "");
21402
+ return `${u.origin}${path2}`;
21403
+ } catch {
21404
+ return getConfiguredApiRoot();
21405
+ }
21406
+ }
21407
+ function resolveUploadKey2() {
21408
+ const dashboardKey = process.env["PRETENSE_DASHBOARD_API_KEY"]?.trim();
21409
+ if (dashboardKey) return dashboardKey;
21410
+ return readApiKey();
21411
+ }
21412
+ function clampCount2(n) {
21413
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
21414
+ }
21415
+ async function uploadUsage(event) {
21416
+ try {
21417
+ if (telemetryDisabled2()) return "skipped";
21418
+ const key = resolveUploadKey2();
21419
+ if (!key) return "skipped";
21420
+ const url = `${resolveUploadBaseUrl2()}/api/cli/log`;
21421
+ const controller = new AbortController();
21422
+ const timer = setTimeout(() => controller.abort(), UPLOAD_TIMEOUT_MS2);
21423
+ try {
21424
+ const resp = await fetch(url, {
21425
+ method: "POST",
21426
+ headers: {
21427
+ "content-type": "application/json",
21428
+ authorization: `Bearer ${key}`
21429
+ },
21430
+ // COUNTS + METADATA ONLY — never a secret value, an identifier name, or
21431
+ // file content. Field names match the `/api/cli/log` contract.
21432
+ body: JSON.stringify({
21433
+ file_count: clampCount2(event.fileCount),
21434
+ identifiers_mutated: clampCount2(event.identifiersMutated),
21435
+ secrets_blocked: clampCount2(event.secretsBlocked),
21436
+ llm_provider: event.llmProvider
21437
+ }),
21438
+ signal: controller.signal
21439
+ });
21440
+ return resp.ok ? "sent" : "failed";
21441
+ } finally {
21442
+ clearTimeout(timer);
21443
+ }
21444
+ } catch {
21445
+ return "failed";
21446
+ }
21447
+ }
21448
+
21252
21449
  // src/commands/mutate.ts
21253
21450
  var REVERSAL_MAP_VERSION = 3;
21254
21451
  function preflightReversalMap(mapPath, absFile, content, contentSha, force) {
@@ -21550,6 +21747,12 @@ async function runMutate(file, opts = {}) {
21550
21747
  detectorKinds: [...new Set(result.mutations.map((m) => m.type))],
21551
21748
  file: absFile
21552
21749
  });
21750
+ await uploadUsage({
21751
+ fileCount: 1,
21752
+ identifiersMutated: result.mutations.length,
21753
+ secretsBlocked,
21754
+ llmProvider: "cli-mutate"
21755
+ });
21553
21756
  }
21554
21757
  if (opts.showMap) {
21555
21758
  console.error(JSON.stringify(result.mutations, null, 2));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  PRETENSE_VERSION
4
- } from "./chunk-QT5E625F.js";
4
+ } from "./chunk-N2GZIZAD.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.18",
3
+ "version": "0.6.20",
4
4
  "description": "Local-first AI firewall that mutates proprietary code before sending to LLM APIs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,13 +25,13 @@
25
25
  "vitest": "^1.0.0",
26
26
  "@pretense/billing": "0.1.0",
27
27
  "@pretense/protocol": "0.1.0",
28
- "@pretense/compliance-engine": "0.1.0",
29
28
  "@pretense/learner": "0.2.0",
30
29
  "@pretense/mutator": "0.2.0",
31
- "@pretense/proxy": "0.1.0",
30
+ "@pretense/compliance-engine": "0.1.0",
32
31
  "@pretense/scanner": "0.2.0",
33
32
  "@pretense/scanner-rs": "0.2.0",
34
- "@pretense/store": "0.2.0"
33
+ "@pretense/store": "0.2.0",
34
+ "@pretense/proxy": "0.1.0"
35
35
  },
36
36
  "publishConfig": {
37
37
  "access": "public",