@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.
|
|
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-
|
|
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.
|
|
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 "
|
|
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
|
|
17310
|
-
|
|
17311
|
-
|
|
17312
|
-
|
|
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];
|
package/dist/postinstall.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pretense/cli",
|
|
3
|
-
"version": "0.6.
|
|
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",
|