@pretense/cli 0.6.17 → 0.6.19

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.17" : void 0) ?? versionFromPackageJson() ?? "0.0.0-unknown";
20
+ var PRETENSE_VERSION = (true ? "0.6.19" : 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-EFDPBFXJ.js";
4
+ } from "./chunk-XY4NRTOT.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.17" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
16585
+ return override ?? (true ? "0.6.19" : void 0) ?? versionFromCliPackageJson() ?? "0.0.0-unknown";
16518
16586
  }
16519
16587
  var UNKNOWN_COMMIT = "unknown";
16520
16588
  function bakedCommitSha() {
16521
- return "6f467e565f6f035a63fe420acef1019ddf2455f0".length > 0 ? "6f467e565f6f035a63fe420acef1019ddf2455f0" : UNKNOWN_COMMIT;
16589
+ return "79bc3b0f2c6a2ff139ae392d2de37679a06308a6".length > 0 ? "79bc3b0f2c6a2ff139ae392d2de37679a06308a6" : UNKNOWN_COMMIT;
16522
16590
  }
16523
16591
  var FEATURE_TIERS = {
16524
16592
  compliance_export: "pro",
@@ -17269,6 +17337,57 @@ function toSecretFindings(matches) {
17269
17337
  function isBase64Blob(text) {
17270
17338
  return text.length >= 256 && !/\s/.test(text) && /^[A-Za-z0-9+/_=-]+$/.test(text);
17271
17339
  }
17340
+ var BLOB_SECRET_INDICATORS = [
17341
+ /AKIA[0-9A-Z]{16}/,
17342
+ // AWS access key id
17343
+ /ASIA[0-9A-Z]{16}/,
17344
+ // AWS temporary access key id
17345
+ /gh[opsu]_[A-Za-z0-9]{36}/,
17346
+ // GitHub PAT / OAuth / app / refresh
17347
+ /github_pat_[0-9A-Za-z_]{22,}/,
17348
+ // GitHub fine-grained PAT
17349
+ /sk-ant-[A-Za-z0-9_-]{24,}/,
17350
+ // Anthropic API key
17351
+ /sk_live_[A-Za-z0-9]{20,}/,
17352
+ // Stripe live secret key
17353
+ /rk_live_[A-Za-z0-9]{20,}/,
17354
+ // Stripe live restricted key
17355
+ /AIza[0-9A-Za-z_-]{35}/,
17356
+ // Google API key
17357
+ /xox[baprs]-[0-9A-Za-z-]{10,}/,
17358
+ // Slack token
17359
+ /npm_[A-Za-z0-9]{36}/,
17360
+ // npm token
17361
+ /glpat-[0-9A-Za-z_-]{20}/,
17362
+ // GitLab PAT
17363
+ /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/
17364
+ // PEM private key banner
17365
+ ];
17366
+ function blobSecretIndicator(text) {
17367
+ for (const re of BLOB_SECRET_INDICATORS) {
17368
+ if (re.test(text)) return re.source.slice(0, 24);
17369
+ }
17370
+ return null;
17371
+ }
17372
+ var BLOB_DECODE_SCAN_MAX_CHARS = 4 * 1024 * 1024;
17373
+ function decodedTextIfLikelyText(text) {
17374
+ if (text.length > BLOB_DECODE_SCAN_MAX_CHARS) return null;
17375
+ let buf;
17376
+ try {
17377
+ buf = Buffer.from(text.replace(/-/g, "+").replace(/_/g, "/"), "base64");
17378
+ } catch {
17379
+ return null;
17380
+ }
17381
+ if (buf.length === 0) return null;
17382
+ const sample = Math.min(buf.length, 4096);
17383
+ let printable = 0;
17384
+ for (let i = 0; i < sample; i++) {
17385
+ const b = buf[i] ?? 0;
17386
+ if (b === 9 || b === 10 || b === 13 || b >= 32 && b <= 126) printable++;
17387
+ }
17388
+ if (printable / sample < 0.85) return null;
17389
+ return buf.toString("utf8");
17390
+ }
17272
17391
  var CONTROL_CHAR_RE = /[\u0000-\u001f]/;
17273
17392
  var CONTROL_CHAR_RE_G = /[\u0000-\u001f]/g;
17274
17393
  function tokenizeBodyDeep(body, scanText, derivation) {
@@ -17306,11 +17425,39 @@ function tokenizeBodyDeep(body, scanText, derivation) {
17306
17425
  }
17307
17426
  return out;
17308
17427
  };
17309
- const tokenizeLeaf = (text) => {
17310
- if (isBase64Blob(text)) {
17311
- skippedUnscannable++;
17312
- return text;
17428
+ const redactWholeBlob = (text, type) => {
17429
+ const { content, mutations } = mutateSecrets(
17430
+ text,
17431
+ [{ value: text, offset: 0, length: text.length, type }],
17432
+ derivation
17433
+ );
17434
+ for (const mu of mutations) {
17435
+ if (!secretMap.has(mu.replacement)) secretMap.set(mu.replacement, mu.original);
17436
+ }
17437
+ foundTypes.add(type);
17438
+ secretsTokenized++;
17439
+ return content;
17440
+ };
17441
+ const scanBlobLeaf = (text) => {
17442
+ const ind = blobSecretIndicator(text);
17443
+ if (ind) return redactWholeBlob(text, `unscannable-blob:${ind}`);
17444
+ const decoded = decodedTextIfLikelyText(text);
17445
+ if (decoded !== null) {
17446
+ const s = scanText(decoded);
17447
+ if (s.incomplete) {
17448
+ scanIncomplete = true;
17449
+ droppedFindings += s.limits.droppedFindings;
17450
+ for (const r of s.limits.reasons) incompleteReasons.add(r);
17451
+ }
17452
+ if (toSecretFindings(s.matches).length > 0) {
17453
+ return redactWholeBlob(text, "unscannable-blob:encoded-secret");
17454
+ }
17313
17455
  }
17456
+ skippedUnscannable++;
17457
+ return text;
17458
+ };
17459
+ const tokenizeLeaf = (text) => {
17460
+ if (isBase64Blob(text)) return scanBlobLeaf(text);
17314
17461
  return tokenizeText(text);
17315
17462
  };
17316
17463
  const stack = [body];
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  PRETENSE_VERSION
4
- } from "./chunk-EFDPBFXJ.js";
4
+ } from "./chunk-XY4NRTOT.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.17",
3
+ "version": "0.6.19",
4
4
  "description": "Local-first AI firewall that mutates proprietary code before sending to LLM APIs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,15 +23,15 @@
23
23
  "tsx": "^4.0.0",
24
24
  "typescript": "^5.4.0",
25
25
  "vitest": "^1.0.0",
26
+ "@pretense/billing": "0.1.0",
26
27
  "@pretense/compliance-engine": "0.1.0",
27
28
  "@pretense/protocol": "0.1.0",
28
- "@pretense/billing": "0.1.0",
29
29
  "@pretense/learner": "0.2.0",
30
30
  "@pretense/mutator": "0.2.0",
31
31
  "@pretense/proxy": "0.1.0",
32
+ "@pretense/scanner": "0.2.0",
32
33
  "@pretense/scanner-rs": "0.2.0",
33
- "@pretense/store": "0.2.0",
34
- "@pretense/scanner": "0.2.0"
34
+ "@pretense/store": "0.2.0"
35
35
  },
36
36
  "publishConfig": {
37
37
  "access": "public",