@pretense/cli 0.6.4 → 0.6.5
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/dist/chunk-TWMRHZGM.js +0 -0
- package/dist/ed25519-XYI7WRQL.js +0 -0
- package/dist/index.js +13 -447
- package/dist/postinstall.js +0 -0
- package/package.json +18 -19
package/dist/chunk-TWMRHZGM.js
CHANGED
|
File without changes
|
package/dist/ed25519-XYI7WRQL.js
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
@@ -10385,7 +10385,6 @@ function listPresetIds() {
|
|
|
10385
10385
|
|
|
10386
10386
|
// ../packages/scanner/dist/index.js
|
|
10387
10387
|
import { gunzipSync } from "zlib";
|
|
10388
|
-
import { createRequire } from "module";
|
|
10389
10388
|
var SECRET_LABEL_TAILS = [
|
|
10390
10389
|
// password family
|
|
10391
10390
|
"password",
|
|
@@ -13279,259 +13278,6 @@ function resolveSafeSpan(original, map, viewStart, viewEnd, value, cost) {
|
|
|
13279
13278
|
if (!spanContainsValue(slice, value, cost)) return null;
|
|
13280
13279
|
return { start, end, value: slice };
|
|
13281
13280
|
}
|
|
13282
|
-
var _native;
|
|
13283
|
-
function loadNative() {
|
|
13284
|
-
if (_native !== void 0) return _native;
|
|
13285
|
-
try {
|
|
13286
|
-
const require2 = createRequire(import.meta.url);
|
|
13287
|
-
const mod = require2("@pretense/scanner-rs");
|
|
13288
|
-
if (typeof mod?.semanticScan !== "function") {
|
|
13289
|
-
_native = null;
|
|
13290
|
-
} else {
|
|
13291
|
-
_native = mod;
|
|
13292
|
-
}
|
|
13293
|
-
} catch {
|
|
13294
|
-
_native = null;
|
|
13295
|
-
}
|
|
13296
|
-
return _native;
|
|
13297
|
-
}
|
|
13298
|
-
var _astProbe;
|
|
13299
|
-
function isAstCoreAvailable() {
|
|
13300
|
-
if (_astProbe !== void 0) return _astProbe;
|
|
13301
|
-
const native = loadNative();
|
|
13302
|
-
if (!native) {
|
|
13303
|
-
_astProbe = false;
|
|
13304
|
-
return false;
|
|
13305
|
-
}
|
|
13306
|
-
try {
|
|
13307
|
-
native.semanticScan("", "typescript");
|
|
13308
|
-
_astProbe = true;
|
|
13309
|
-
} catch {
|
|
13310
|
-
_astProbe = false;
|
|
13311
|
-
}
|
|
13312
|
-
return _astProbe;
|
|
13313
|
-
}
|
|
13314
|
-
function makeByteToChar(text) {
|
|
13315
|
-
let ascii = true;
|
|
13316
|
-
for (let i = 0; i < text.length; i++) {
|
|
13317
|
-
if (text.charCodeAt(i) > 127) {
|
|
13318
|
-
ascii = false;
|
|
13319
|
-
break;
|
|
13320
|
-
}
|
|
13321
|
-
}
|
|
13322
|
-
if (ascii) return (b) => b;
|
|
13323
|
-
const totalBytes = Buffer.byteLength(text, "utf8");
|
|
13324
|
-
const table = new Int32Array(totalBytes + 1);
|
|
13325
|
-
let byte = 0;
|
|
13326
|
-
for (let charIdx = 0; charIdx < text.length; ) {
|
|
13327
|
-
const cp = text.codePointAt(charIdx);
|
|
13328
|
-
const unitLen = cp > 65535 ? 2 : 1;
|
|
13329
|
-
const byteLen = Buffer.byteLength(String.fromCodePoint(cp), "utf8");
|
|
13330
|
-
for (let k = 0; k < byteLen; k++) table[byte + k] = charIdx;
|
|
13331
|
-
byte += byteLen;
|
|
13332
|
-
charIdx += unitLen;
|
|
13333
|
-
}
|
|
13334
|
-
table[totalBytes] = text.length;
|
|
13335
|
-
return (b) => {
|
|
13336
|
-
if (b <= 0) return 0;
|
|
13337
|
-
if (b >= totalBytes) return text.length;
|
|
13338
|
-
return table[b];
|
|
13339
|
-
};
|
|
13340
|
-
}
|
|
13341
|
-
function isInsideAnySpan(start, end, spans) {
|
|
13342
|
-
for (const s of spans) {
|
|
13343
|
-
if (s.start > start) break;
|
|
13344
|
-
if (start >= s.start && end <= s.end) return true;
|
|
13345
|
-
}
|
|
13346
|
-
return false;
|
|
13347
|
-
}
|
|
13348
|
-
var CREDENTIAL_NAME = /(?:^|[._-]|(?<=[a-z0-9]))(?:api[_-]?key|api[_-]?secret|secret|secrets|token|password|passwd|pwd|access[_-]?key|access[_-]?token|private[_-]?key|client[_-]?secret|auth[_-]?token|bearer[_-]?token|session[_-]?secret|encryption[_-]?key|signing[_-]?key)s?$/i;
|
|
13349
|
-
var CREDENTIAL_SETTER = /^(?:set|with|configure|use|update|put|load|init)[_-]?(?:api[_-]?key|api[_-]?secret|secret|token|password|passwd|pwd|access[_-]?key|access[_-]?token|private[_-]?key|client[_-]?secret|auth[_-]?token|bearer[_-]?token|credential|credentials|key)s?$/i;
|
|
13350
|
-
function looksLikeCredentialName(name) {
|
|
13351
|
-
return CREDENTIAL_NAME.test(name);
|
|
13352
|
-
}
|
|
13353
|
-
function looksLikeCredentialSetter(callee) {
|
|
13354
|
-
const last = callee.split(".").pop() ?? callee;
|
|
13355
|
-
return CREDENTIAL_SETTER.test(last);
|
|
13356
|
-
}
|
|
13357
|
-
function unquoteLiteral(literal) {
|
|
13358
|
-
const t = literal.trim();
|
|
13359
|
-
if (t.length < 2) return null;
|
|
13360
|
-
const open = t[0];
|
|
13361
|
-
const close = t[t.length - 1];
|
|
13362
|
-
if (open === '"' && close === '"' || open === "'" && close === "'" || open === "`" && close === "`") {
|
|
13363
|
-
return t.slice(1, -1);
|
|
13364
|
-
}
|
|
13365
|
-
const firstQuote = t.search(/["'`]/);
|
|
13366
|
-
if (firstQuote >= 0) {
|
|
13367
|
-
const q = t[firstQuote];
|
|
13368
|
-
const lastQuote = t.lastIndexOf(q);
|
|
13369
|
-
if (lastQuote > firstQuote) return t.slice(firstQuote + 1, lastQuote);
|
|
13370
|
-
}
|
|
13371
|
-
return null;
|
|
13372
|
-
}
|
|
13373
|
-
var STRING_LITERAL_RX = /^\s*["'`]/;
|
|
13374
|
-
function dataFlowMatches(text, sem, nextId2) {
|
|
13375
|
-
const matches = [];
|
|
13376
|
-
const findings = [];
|
|
13377
|
-
for (const edge of sem.edges) {
|
|
13378
|
-
const isAssign = edge.kind === "Assignment" && looksLikeCredentialName(edge.from);
|
|
13379
|
-
const isSetter = edge.kind === "Call" && looksLikeCredentialSetter(edge.from);
|
|
13380
|
-
if (!isAssign && !isSetter) continue;
|
|
13381
|
-
if (!STRING_LITERAL_RX.test(edge.to)) continue;
|
|
13382
|
-
const inner = unquoteLiteral(edge.to);
|
|
13383
|
-
if (inner === null || inner.length === 0) continue;
|
|
13384
|
-
const span = locateLiteral(text, edge.to, edge.line);
|
|
13385
|
-
const start = span && span.start >= 0 ? span.start : 0;
|
|
13386
|
-
const end = span && span.end >= 0 ? span.end : 0;
|
|
13387
|
-
const severity = "high";
|
|
13388
|
-
const category = "secret";
|
|
13389
|
-
const action = "redact";
|
|
13390
|
-
const type = "dataflow-credential";
|
|
13391
|
-
const id = nextId2();
|
|
13392
|
-
const masked = maskCredential(inner);
|
|
13393
|
-
matches.push({ id, category, type, value: inner, masked, start, end, severity, action, confidence: 0.85 });
|
|
13394
|
-
findings.push({
|
|
13395
|
-
ruleId: type,
|
|
13396
|
-
value: inner,
|
|
13397
|
-
masked,
|
|
13398
|
-
severity,
|
|
13399
|
-
category,
|
|
13400
|
-
line: edge.line,
|
|
13401
|
-
column: span?.column ?? 0,
|
|
13402
|
-
start,
|
|
13403
|
-
end,
|
|
13404
|
-
action,
|
|
13405
|
-
confidence: 0.85
|
|
13406
|
-
});
|
|
13407
|
-
}
|
|
13408
|
-
return { matches, findings };
|
|
13409
|
-
}
|
|
13410
|
-
function maskCredential(value) {
|
|
13411
|
-
if (value.length <= 8) return "***";
|
|
13412
|
-
return value.slice(0, 4) + "..." + value.slice(-4);
|
|
13413
|
-
}
|
|
13414
|
-
function locateLiteral(text, literalSource, line) {
|
|
13415
|
-
let lineStart = 0;
|
|
13416
|
-
let curLine = 1;
|
|
13417
|
-
for (let i = 0; i < text.length && curLine < line; i++) {
|
|
13418
|
-
if (text.charCodeAt(i) === 10) {
|
|
13419
|
-
curLine++;
|
|
13420
|
-
lineStart = i + 1;
|
|
13421
|
-
}
|
|
13422
|
-
}
|
|
13423
|
-
let idx = text.indexOf(literalSource, lineStart);
|
|
13424
|
-
if (idx < 0) idx = text.indexOf(literalSource);
|
|
13425
|
-
if (idx < 0) return null;
|
|
13426
|
-
const lineNlBefore = text.lastIndexOf("\n", idx);
|
|
13427
|
-
const column = lineNlBefore < 0 ? idx : idx - lineNlBefore - 1;
|
|
13428
|
-
return { start: idx, end: idx + literalSource.length, column };
|
|
13429
|
-
}
|
|
13430
|
-
var SEVERITY_RANK = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
13431
|
-
function rebuildFindings(matches, findingsByMatchId) {
|
|
13432
|
-
const out = [];
|
|
13433
|
-
for (const m of matches) {
|
|
13434
|
-
const f = findingsByMatchId.get(m.id);
|
|
13435
|
-
if (f) out.push(f);
|
|
13436
|
-
}
|
|
13437
|
-
return out;
|
|
13438
|
-
}
|
|
13439
|
-
function dedupeMatches(matches) {
|
|
13440
|
-
const sorted = [...matches].sort(
|
|
13441
|
-
(a, b) => a.start - b.start || SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity] || b.confidence - a.confidence
|
|
13442
|
-
);
|
|
13443
|
-
const kept = [];
|
|
13444
|
-
for (const m of sorted) {
|
|
13445
|
-
const overlap = kept.find(
|
|
13446
|
-
(k) => m.start < k.end && k.start < m.end && !(m.start === 0 && m.end === 0)
|
|
13447
|
-
);
|
|
13448
|
-
if (!overlap) {
|
|
13449
|
-
kept.push(m);
|
|
13450
|
-
}
|
|
13451
|
-
}
|
|
13452
|
-
const seenZero = /* @__PURE__ */ new Set();
|
|
13453
|
-
return kept.filter((m) => {
|
|
13454
|
-
if (m.start === 0 && m.end === 0) {
|
|
13455
|
-
const key = `${m.type}:${m.value}`;
|
|
13456
|
-
if (seenZero.has(key)) return false;
|
|
13457
|
-
seenZero.add(key);
|
|
13458
|
-
}
|
|
13459
|
-
return true;
|
|
13460
|
-
});
|
|
13461
|
-
}
|
|
13462
|
-
async function scanLevel2(text, language, opts = {}) {
|
|
13463
|
-
const lang = language ?? opts.language ?? "typescript";
|
|
13464
|
-
const base = opts.presidioUrl ? await scanWithPresidio(text, opts.presidioUrl, opts) : scan(text, opts);
|
|
13465
|
-
const native = loadNative();
|
|
13466
|
-
if (!native) return base;
|
|
13467
|
-
let sem;
|
|
13468
|
-
try {
|
|
13469
|
-
sem = native.semanticScan(text, lang);
|
|
13470
|
-
} catch {
|
|
13471
|
-
return base;
|
|
13472
|
-
}
|
|
13473
|
-
const byteToChar = makeByteToChar(text);
|
|
13474
|
-
const commentSpans = [];
|
|
13475
|
-
for (const node of sem.nodes) {
|
|
13476
|
-
if (node.class === "Comment") {
|
|
13477
|
-
commentSpans.push({ start: byteToChar(node.start_byte), end: byteToChar(node.end_byte) });
|
|
13478
|
-
}
|
|
13479
|
-
}
|
|
13480
|
-
commentSpans.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
13481
|
-
const findingsById = /* @__PURE__ */ new Map();
|
|
13482
|
-
base.matches.forEach((m, i) => findingsById.set(m.id, base.findings[i] ?? toFinding(text, m)));
|
|
13483
|
-
const survivingMatches = base.matches.filter(
|
|
13484
|
-
(m) => m.start === m.end || !isInsideAnySpan(m.start, m.end, commentSpans)
|
|
13485
|
-
);
|
|
13486
|
-
let dfMatches = [];
|
|
13487
|
-
if (opts.dataFlow !== false) {
|
|
13488
|
-
let counter = 0;
|
|
13489
|
-
const nextId2 = () => `l2df_${++counter}`;
|
|
13490
|
-
const df = dataFlowMatches(text, sem, nextId2);
|
|
13491
|
-
df.matches.forEach((m, i) => findingsById.set(m.id, df.findings[i] ?? toFinding(text, m)));
|
|
13492
|
-
dfMatches = df.matches.filter((m) => !isInsideAnySpan(m.start, m.end, commentSpans));
|
|
13493
|
-
}
|
|
13494
|
-
const deduped = dedupeMatches([...survivingMatches, ...dfMatches]).sort(
|
|
13495
|
-
(a, b) => a.start - b.start || SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity]
|
|
13496
|
-
);
|
|
13497
|
-
const dedupedFindings = rebuildFindings(deduped, findingsById).sort(
|
|
13498
|
-
(a, b) => a.start - b.start || SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity]
|
|
13499
|
-
);
|
|
13500
|
-
const clean = deduped.filter((m) => m.action === "block" || m.action === "redact").length === 0;
|
|
13501
|
-
return {
|
|
13502
|
-
clean,
|
|
13503
|
-
matches: deduped,
|
|
13504
|
-
findings: dedupedFindings,
|
|
13505
|
-
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13506
|
-
durationMs: base.durationMs,
|
|
13507
|
-
patternCount: base.patternCount,
|
|
13508
|
-
// Bounded-work state is a property of the underlying scan and MUST survive
|
|
13509
|
-
// this merge — dropping it here would re-create the silent-incomplete hole
|
|
13510
|
-
// one layer up.
|
|
13511
|
-
incomplete: base.incomplete,
|
|
13512
|
-
limits: base.limits
|
|
13513
|
-
};
|
|
13514
|
-
}
|
|
13515
|
-
function toFinding(text, m) {
|
|
13516
|
-
const before = text.slice(0, m.start);
|
|
13517
|
-
const line = (before.match(/\n/g) ?? []).length + 1;
|
|
13518
|
-
const lastNl = before.lastIndexOf("\n");
|
|
13519
|
-
const column = lastNl === -1 ? m.start : m.start - lastNl - 1;
|
|
13520
|
-
return {
|
|
13521
|
-
ruleId: m.type,
|
|
13522
|
-
value: m.value,
|
|
13523
|
-
masked: m.masked,
|
|
13524
|
-
severity: m.severity,
|
|
13525
|
-
category: m.category,
|
|
13526
|
-
line,
|
|
13527
|
-
column,
|
|
13528
|
-
start: m.start,
|
|
13529
|
-
end: m.end,
|
|
13530
|
-
action: m.action,
|
|
13531
|
-
confidence: m.confidence,
|
|
13532
|
-
...m.entropy !== void 0 ? { entropy: m.entropy } : {}
|
|
13533
|
-
};
|
|
13534
|
-
}
|
|
13535
13281
|
var MIN_SPAN_VERIFY_CHARS = 1024 * 1024;
|
|
13536
13282
|
var MAX_SPAN_VERIFY_CHARS = 256 * 1024 * 1024;
|
|
13537
13283
|
var SPAN_VERIFY_CHARS_PER_CHAR = 64;
|
|
@@ -13878,60 +13624,6 @@ function scan(text, opts = {}) {
|
|
|
13878
13624
|
limits
|
|
13879
13625
|
};
|
|
13880
13626
|
}
|
|
13881
|
-
async function scanWithPresidio(text, presidioUrl, opts = {}) {
|
|
13882
|
-
const { presidio: _presidio, ...syncOpts } = opts;
|
|
13883
|
-
const base = scan(text, syncOpts);
|
|
13884
|
-
const nlIndex = buildNewlineIndex(text);
|
|
13885
|
-
try {
|
|
13886
|
-
const resp = await fetch(`${presidioUrl}/analyze`, {
|
|
13887
|
-
method: "POST",
|
|
13888
|
-
headers: { "Content-Type": "application/json" },
|
|
13889
|
-
body: JSON.stringify({ text, language: "en" }),
|
|
13890
|
-
signal: AbortSignal.timeout(2e3)
|
|
13891
|
-
});
|
|
13892
|
-
if (!resp.ok) return base;
|
|
13893
|
-
const presidioResults = await resp.json();
|
|
13894
|
-
for (const pr of presidioResults) {
|
|
13895
|
-
const alreadyCovered = base.matches.some((m) => m.start <= pr.start && m.end >= pr.end);
|
|
13896
|
-
if (!alreadyCovered && pr.score >= 0.7) {
|
|
13897
|
-
const value = text.slice(pr.start, pr.end);
|
|
13898
|
-
const type = `presidio-${pr.entity_type.toLowerCase()}`;
|
|
13899
|
-
const severity = pr.score >= 0.9 ? "high" : "medium";
|
|
13900
|
-
base.matches.push({
|
|
13901
|
-
id: nextId(),
|
|
13902
|
-
category: "pii",
|
|
13903
|
-
type,
|
|
13904
|
-
value,
|
|
13905
|
-
masked: "***",
|
|
13906
|
-
start: pr.start,
|
|
13907
|
-
end: pr.end,
|
|
13908
|
-
severity,
|
|
13909
|
-
action: "redact",
|
|
13910
|
-
confidence: pr.score
|
|
13911
|
-
});
|
|
13912
|
-
const { line, column } = lineColFromOffset(text, pr.start, nlIndex);
|
|
13913
|
-
base.findings.push({
|
|
13914
|
-
ruleId: type,
|
|
13915
|
-
value,
|
|
13916
|
-
masked: "***",
|
|
13917
|
-
severity,
|
|
13918
|
-
category: "pii",
|
|
13919
|
-
line,
|
|
13920
|
-
column,
|
|
13921
|
-
start: pr.start,
|
|
13922
|
-
end: pr.end,
|
|
13923
|
-
action: "redact",
|
|
13924
|
-
confidence: pr.score
|
|
13925
|
-
});
|
|
13926
|
-
}
|
|
13927
|
-
}
|
|
13928
|
-
base.matches.sort((a, b) => a.start - b.start);
|
|
13929
|
-
base.findings.sort((a, b) => a.start - b.start);
|
|
13930
|
-
base.clean = base.matches.filter((m) => m.action === "block" || m.action === "redact").length === 0;
|
|
13931
|
-
} catch {
|
|
13932
|
-
}
|
|
13933
|
-
return base;
|
|
13934
|
-
}
|
|
13935
13627
|
|
|
13936
13628
|
// ../packages/mutator/dist/index.js
|
|
13937
13629
|
init_esm_shims();
|
|
@@ -17028,71 +16720,13 @@ function filterExisting(files) {
|
|
|
17028
16720
|
}
|
|
17029
16721
|
});
|
|
17030
16722
|
}
|
|
17031
|
-
function langForFile(filePath) {
|
|
17032
|
-
switch (filePath ? extname2(filePath).toLowerCase() : "") {
|
|
17033
|
-
case ".py":
|
|
17034
|
-
return "python";
|
|
17035
|
-
case ".go":
|
|
17036
|
-
return "go";
|
|
17037
|
-
case ".java":
|
|
17038
|
-
return "java";
|
|
17039
|
-
case ".rb":
|
|
17040
|
-
return "ruby";
|
|
17041
|
-
case ".rs":
|
|
17042
|
-
return "rust";
|
|
17043
|
-
case ".cs":
|
|
17044
|
-
return "csharp";
|
|
17045
|
-
case ".js":
|
|
17046
|
-
case ".jsx":
|
|
17047
|
-
return "javascript";
|
|
17048
|
-
case ".tsx":
|
|
17049
|
-
case ".ts":
|
|
17050
|
-
return "typescript";
|
|
17051
|
-
default:
|
|
17052
|
-
return "typescript";
|
|
17053
|
-
}
|
|
17054
|
-
}
|
|
17055
|
-
function resolveScanLevel(opts) {
|
|
17056
|
-
if (opts.ast) return 2;
|
|
17057
|
-
const raw2 = opts.level;
|
|
17058
|
-
if (raw2 === void 0 || raw2 === "1") return 1;
|
|
17059
|
-
if (raw2 === "2") return 2;
|
|
17060
|
-
console.error(chalk3.red(`
|
|
17061
|
-
x Invalid --level: "${raw2}". Use 1 (regex) or 2 (AST/data-flow).
|
|
17062
|
-
`));
|
|
17063
|
-
process.exit(1);
|
|
17064
|
-
}
|
|
17065
|
-
var _astFallbackNoticePrinted = false;
|
|
17066
|
-
function resolveLevelWithAst(opts) {
|
|
17067
|
-
const level = resolveScanLevel(opts);
|
|
17068
|
-
if (level === 1) return { level, astUsed: false };
|
|
17069
|
-
const astUsed = isAstCoreAvailable();
|
|
17070
|
-
if (!astUsed && !_astFallbackNoticePrinted) {
|
|
17071
|
-
console.error(
|
|
17072
|
-
chalk3.yellow("\n ! AST core (@pretense/scanner-rs) unavailable \u2014 falling back to L1 (regex).") + chalk3.dim("\n Build the native core to enable L2 data-flow detection.\n")
|
|
17073
|
-
);
|
|
17074
|
-
_astFallbackNoticePrinted = true;
|
|
17075
|
-
}
|
|
17076
|
-
return { level, astUsed };
|
|
17077
|
-
}
|
|
17078
|
-
function effectiveScanLevel(requested, astUsed) {
|
|
17079
|
-
return requested === 2 && astUsed ? 2 : 1;
|
|
17080
|
-
}
|
|
17081
|
-
function levelLabel(level, astUsed) {
|
|
17082
|
-
if (level === 1) return "L1 \xB7 regex";
|
|
17083
|
-
return astUsed ? "L2 \xB7 AST + regex" : "L2 \u2192 L1 (AST unavailable)";
|
|
17084
|
-
}
|
|
17085
16723
|
async function scanContent(content, opts, filePath) {
|
|
17086
|
-
|
|
16724
|
+
return scan(content, {
|
|
17087
16725
|
contextAware: true,
|
|
17088
16726
|
entropyAnalysis: opts.entropy !== false,
|
|
17089
16727
|
...filePath ? { filePath } : {},
|
|
17090
16728
|
...opts.presetName ? { presetName: opts.presetName } : {}
|
|
17091
|
-
};
|
|
17092
|
-
if (resolveScanLevel(opts) === 2) {
|
|
17093
|
-
return await scanLevel2(content, langForFile(filePath), base);
|
|
17094
|
-
}
|
|
17095
|
-
return scan(content, base);
|
|
16729
|
+
});
|
|
17096
16730
|
}
|
|
17097
16731
|
async function scanFile(filePath, opts) {
|
|
17098
16732
|
const content = readFileSync5(filePath, "utf-8");
|
|
@@ -17183,7 +16817,6 @@ function resolveFormat(opts, defaultSarif = false) {
|
|
|
17183
16817
|
return "text";
|
|
17184
16818
|
}
|
|
17185
16819
|
async function outputMultiFileResults(label, files, opts, { defaultSarif = false, ciMode = false } = {}) {
|
|
17186
|
-
const { level, astUsed } = resolveLevelWithAst(opts);
|
|
17187
16820
|
const sourceFiles = files.filter(isSupportedFile);
|
|
17188
16821
|
if (sourceFiles.length === 0) {
|
|
17189
16822
|
const fmt3 = resolveFormat(opts, defaultSarif);
|
|
@@ -17231,7 +16864,7 @@ async function outputMultiFileResults(label, files, opts, { defaultSarif = false
|
|
|
17231
16864
|
console.log(chalk3.cyan(`
|
|
17232
16865
|
Pretense Scan \u2014 ${label}
|
|
17233
16866
|
`));
|
|
17234
|
-
console.log(chalk3.dim(` Files: ${sourceFiles.length} | Patterns: ${getPatternCount()}
|
|
16867
|
+
console.log(chalk3.dim(` Files: ${sourceFiles.length} | Patterns: ${getPatternCount()}
|
|
17235
16868
|
`));
|
|
17236
16869
|
for (const { file, result } of allResults) printFindings(file, result);
|
|
17237
16870
|
console.log(chalk3.dim(`
|
|
@@ -17240,7 +16873,7 @@ async function outputMultiFileResults(label, files, opts, { defaultSarif = false
|
|
|
17240
16873
|
process.exit(findingsExitCode);
|
|
17241
16874
|
}
|
|
17242
16875
|
function addScanOpts(cmd) {
|
|
17243
|
-
return cmd.option("--json", "Output results as JSON (alias for --format json)", false).option("--format <fmt>", "Output format: text, json, sarif, csv (default: text)").option("--severity <lvl>", "Minimum severity to report: low, medium, high, critical").option("--no-entropy", "Disable entropy-based detection", false).option(
|
|
16876
|
+
return cmd.option("--json", "Output results as JSON (alias for --format json)", false).option("--format <fmt>", "Output format: text, json, sarif, csv (default: text)").option("--severity <lvl>", "Minimum severity to report: low, medium, high, critical").option("--no-entropy", "Disable entropy-based detection", false).option(
|
|
17244
16877
|
"--policy <preset>",
|
|
17245
16878
|
`Apply a compliance preset (${COMPLIANCE_PRESET_IDS.join(", ")}); overrides scan.compliancePreset in pretense.yaml`
|
|
17246
16879
|
);
|
|
@@ -17334,7 +16967,7 @@ function pathSubcommand() {
|
|
|
17334
16967
|
});
|
|
17335
16968
|
}
|
|
17336
16969
|
function scanCommand() {
|
|
17337
|
-
const cmd = new Command3("scan").description("Scan for secrets and PII (file, directory, or git-aware modes)").argument("[file]", "File to scan (omit to read from stdin)").option("--json", "Output results as JSON (alias for --format json)", false).option("--format <fmt>", "Output format: text, json, sarif, csv (default: text)").option("--severity <lvl>", "Minimum severity to report: low, medium, high, critical").option("--no-entropy", "Disable entropy-based detection", false).option(
|
|
16970
|
+
const cmd = new Command3("scan").description("Scan for secrets and PII (file, directory, or git-aware modes)").argument("[file]", "File to scan (omit to read from stdin)").option("--json", "Output results as JSON (alias for --format json)", false).option("--format <fmt>", "Output format: text, json, sarif, csv (default: text)").option("--severity <lvl>", "Minimum severity to report: low, medium, high, critical").option("--no-entropy", "Disable entropy-based detection", false).option(
|
|
17338
16971
|
"--policy <preset>",
|
|
17339
16972
|
`Apply a compliance preset (${COMPLIANCE_PRESET_IDS.join(", ")}); overrides scan.compliancePreset in pretense.yaml`
|
|
17340
16973
|
).action(async (file, opts = {}) => {
|
|
@@ -17379,14 +17012,12 @@ function scanCommand() {
|
|
|
17379
17012
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
17380
17013
|
content = Buffer.concat(chunks).toString("utf-8");
|
|
17381
17014
|
}
|
|
17382
|
-
const { level, astUsed } = resolveLevelWithAst(opts);
|
|
17383
|
-
const effectiveLevel = effectiveScanLevel(level, astUsed);
|
|
17384
17015
|
const rawResult = await scanContent(content, opts, file);
|
|
17385
17016
|
const filteredMatches = filterBySeverity(rawResult.matches, opts?.severity);
|
|
17386
17017
|
const result = { ...rawResult, matches: filteredMatches, clean: filteredMatches.length === 0 };
|
|
17387
17018
|
const fmt2 = resolveFormat(opts ?? {});
|
|
17388
17019
|
if (fmt2 === "json") {
|
|
17389
|
-
console.log(JSON.stringify(
|
|
17020
|
+
console.log(JSON.stringify(result, null, 2));
|
|
17390
17021
|
process.exit(result.clean ? 0 : 1);
|
|
17391
17022
|
}
|
|
17392
17023
|
if (fmt2 === "sarif") {
|
|
@@ -17402,7 +17033,7 @@ function scanCommand() {
|
|
|
17402
17033
|
console.log(chalk3.cyan(`
|
|
17403
17034
|
Pretense Scan \u2014 ${file ?? "stdin"}
|
|
17404
17035
|
`));
|
|
17405
|
-
console.log(chalk3.dim(` Patterns: ${getPatternCount()} |
|
|
17036
|
+
console.log(chalk3.dim(` Patterns: ${getPatternCount()} | Duration: ${result.durationMs}ms
|
|
17406
17037
|
`));
|
|
17407
17038
|
if (result.clean) {
|
|
17408
17039
|
console.log(chalk3.green(" \u2713 No secrets or PII detected\n"));
|
|
@@ -18836,7 +18467,7 @@ import { Command as Command14 } from "commander";
|
|
|
18836
18467
|
import chalk13 from "chalk";
|
|
18837
18468
|
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, chmodSync as chmodSync6 } from "fs";
|
|
18838
18469
|
import { createHash as createHash3 } from "crypto";
|
|
18839
|
-
import { resolve as resolve4
|
|
18470
|
+
import { resolve as resolve4 } from "path";
|
|
18840
18471
|
|
|
18841
18472
|
// src/secret-crypto.ts
|
|
18842
18473
|
init_esm_shims();
|
|
@@ -18932,81 +18563,16 @@ var REVERSAL_MAP_VERSION = 2;
|
|
|
18932
18563
|
function sha256Hex2(s) {
|
|
18933
18564
|
return createHash3("sha256").update(s, "utf8").digest("hex");
|
|
18934
18565
|
}
|
|
18935
|
-
function
|
|
18936
|
-
switch (extname4(filePath).toLowerCase()) {
|
|
18937
|
-
case ".py":
|
|
18938
|
-
return "python";
|
|
18939
|
-
case ".go":
|
|
18940
|
-
return "go";
|
|
18941
|
-
case ".java":
|
|
18942
|
-
return "java";
|
|
18943
|
-
case ".rb":
|
|
18944
|
-
return "ruby";
|
|
18945
|
-
case ".rs":
|
|
18946
|
-
return "rust";
|
|
18947
|
-
case ".cs":
|
|
18948
|
-
return "csharp";
|
|
18949
|
-
case ".js":
|
|
18950
|
-
case ".jsx":
|
|
18951
|
-
return "javascript";
|
|
18952
|
-
case ".tsx":
|
|
18953
|
-
case ".ts":
|
|
18954
|
-
return "typescript";
|
|
18955
|
-
default:
|
|
18956
|
-
return "typescript";
|
|
18957
|
-
}
|
|
18958
|
-
}
|
|
18959
|
-
function resolveMutateLevel(opts) {
|
|
18960
|
-
if (opts.ast) return 2;
|
|
18961
|
-
const raw2 = opts.level;
|
|
18962
|
-
if (raw2 === void 0 || raw2 === "1") return 1;
|
|
18963
|
-
if (raw2 === "2") return 2;
|
|
18964
|
-
console.error(chalk13.red(`
|
|
18965
|
-
x Invalid --level: "${raw2}". Use 1 (regex) or 2 (AST/data-flow).
|
|
18966
|
-
`));
|
|
18967
|
-
process.exit(1);
|
|
18968
|
-
}
|
|
18969
|
-
function overlaps2(aOff, aLen, bOff, bLen) {
|
|
18970
|
-
return aOff < bOff + bLen && bOff < aOff + aLen;
|
|
18971
|
-
}
|
|
18972
|
-
function innerValueFinding(content, m) {
|
|
18973
|
-
if (m.end <= m.start || m.value.length === 0) return null;
|
|
18974
|
-
const raw2 = content.slice(m.start, m.end);
|
|
18975
|
-
const idx = raw2.indexOf(m.value);
|
|
18976
|
-
if (idx < 0) return null;
|
|
18977
|
-
return { value: m.value, offset: m.start + idx, length: m.value.length, type: m.type };
|
|
18978
|
-
}
|
|
18979
|
-
async function collectMutationFindings(content, file, level) {
|
|
18566
|
+
async function collectMutationFindings(content, file) {
|
|
18980
18567
|
const l1 = scan(content, { contextAware: true, entropyAnalysis: true, filePath: file });
|
|
18981
|
-
|
|
18568
|
+
return l1.matches.map((m) => ({
|
|
18982
18569
|
value: m.value,
|
|
18983
18570
|
offset: m.start,
|
|
18984
18571
|
length: m.end - m.start,
|
|
18985
18572
|
type: m.type
|
|
18986
18573
|
}));
|
|
18987
|
-
if (level === 2) {
|
|
18988
|
-
if (!isAstCoreAvailable()) {
|
|
18989
|
-
console.error(
|
|
18990
|
-
chalk13.yellow(" ! AST core (@pretense/scanner-rs) unavailable \u2014 mutating with L1 (regex).")
|
|
18991
|
-
);
|
|
18992
|
-
}
|
|
18993
|
-
const l2 = await scanLevel2(content, langForFile2(file), {
|
|
18994
|
-
contextAware: true,
|
|
18995
|
-
entropyAnalysis: true,
|
|
18996
|
-
filePath: file
|
|
18997
|
-
});
|
|
18998
|
-
for (const m of l2.matches) {
|
|
18999
|
-
if (m.type !== "dataflow-credential") continue;
|
|
19000
|
-
const f = innerValueFinding(content, m);
|
|
19001
|
-
if (!f) continue;
|
|
19002
|
-
if (findings.some((e) => overlaps2(f.offset, f.length, e.offset, e.length))) continue;
|
|
19003
|
-
findings.push(f);
|
|
19004
|
-
}
|
|
19005
|
-
}
|
|
19006
|
-
return findings;
|
|
19007
18574
|
}
|
|
19008
18575
|
async function runMutate(file, opts = {}) {
|
|
19009
|
-
const level = resolveMutateLevel(opts);
|
|
19010
18576
|
let raw2;
|
|
19011
18577
|
try {
|
|
19012
18578
|
raw2 = readFileSync14(file);
|
|
@@ -19027,7 +18593,7 @@ async function runMutate(file, opts = {}) {
|
|
|
19027
18593
|
);
|
|
19028
18594
|
return 1;
|
|
19029
18595
|
}
|
|
19030
|
-
const findings = await collectMutationFindings(content, file
|
|
18596
|
+
const findings = await collectMutationFindings(content, file);
|
|
19031
18597
|
if (findings.length === 0) {
|
|
19032
18598
|
console.error(chalk13.dim(` No secrets found in ${file} \u2014 nothing to mutate.`));
|
|
19033
18599
|
if (!opts.inPlace && !opts.output) process.stdout.write(content);
|
|
@@ -19093,7 +18659,7 @@ async function runMutate(file, opts = {}) {
|
|
|
19093
18659
|
return 0;
|
|
19094
18660
|
}
|
|
19095
18661
|
function mutateCommand() {
|
|
19096
|
-
return new Command14("mutate").description("Mutate secrets in a file to deterministic synthetics").argument("<file>", "File to mutate").option("-i, --in-place", "Write back to the input file").option("-o, --output <path>", "Write mutated content to a specific file").option("--map <path>", "Write the encrypted reversal map (for `pretense reverse`) to a file").option("--
|
|
18662
|
+
return new Command14("mutate").description("Mutate secrets in a file to deterministic synthetics").argument("<file>", "File to mutate").option("-i, --in-place", "Write back to the input file").option("-o, --output <path>", "Write mutated content to a specific file").option("--map <path>", "Write the encrypted reversal map (for `pretense reverse`) to a file").option("--show-map", "Print mutation map JSON to stderr").action(async (file, opts) => {
|
|
19097
18663
|
const code = await runMutate(file, opts);
|
|
19098
18664
|
process.exit(code);
|
|
19099
18665
|
});
|
|
@@ -19604,7 +19170,7 @@ function findClosestCommand(input, knownCommands) {
|
|
|
19604
19170
|
return bestDist <= 3 ? best : null;
|
|
19605
19171
|
}
|
|
19606
19172
|
var program = new Command20();
|
|
19607
|
-
program.name("pretense").description(chalk19.bold("AI firewall") + " \u2014 mutates proprietary code before LLM API calls").version("0.6.
|
|
19173
|
+
program.name("pretense").description(chalk19.bold("AI firewall") + " \u2014 mutates proprietary code before LLM API calls").version("0.6.5", "-v, --version").addHelpText("after", `
|
|
19608
19174
|
${chalk19.bold("Quick start:")}
|
|
19609
19175
|
${chalk19.cyan("pretense scan")} ${chalk19.dim("<file|dir>")} Scan for secrets and PII
|
|
19610
19176
|
${chalk19.cyan("pretense mutate")} ${chalk19.dim("<file>")} Preview code mutations
|
package/dist/postinstall.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pretense/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "Local-first AI firewall that mutates proprietary code before sending to LLM APIs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,14 +11,6 @@
|
|
|
11
11
|
"README.md",
|
|
12
12
|
"LICENSE"
|
|
13
13
|
],
|
|
14
|
-
"scripts": {
|
|
15
|
-
"build": "tsup",
|
|
16
|
-
"dev": "tsx src/index.ts",
|
|
17
|
-
"prepublishOnly": "pnpm build",
|
|
18
|
-
"postinstall": "node -e \"fs.existsSync('dist/postinstall.js') && import('./dist/postinstall.js')\" || true",
|
|
19
|
-
"test": "vitest run",
|
|
20
|
-
"lint": "tsc --noEmit"
|
|
21
|
-
},
|
|
22
14
|
"dependencies": {
|
|
23
15
|
"better-sqlite3": "^11.0.0",
|
|
24
16
|
"chalk": "^5.3.0",
|
|
@@ -26,19 +18,19 @@
|
|
|
26
18
|
"yaml": "^2.4.0"
|
|
27
19
|
},
|
|
28
20
|
"devDependencies": {
|
|
29
|
-
"@pretense/billing": "workspace:*",
|
|
30
|
-
"@pretense/compliance-engine": "workspace:*",
|
|
31
|
-
"@pretense/protocol": "workspace:*",
|
|
32
|
-
"@pretense/learner": "workspace:*",
|
|
33
|
-
"@pretense/mutator": "workspace:*",
|
|
34
|
-
"@pretense/proxy": "workspace:*",
|
|
35
|
-
"@pretense/scanner": "workspace:*",
|
|
36
|
-
"@pretense/store": "workspace:*",
|
|
37
21
|
"@types/node": "^20.0.0",
|
|
38
22
|
"tsup": "^8.0.0",
|
|
39
23
|
"tsx": "^4.0.0",
|
|
40
24
|
"typescript": "^5.4.0",
|
|
41
|
-
"vitest": "^1.0.0"
|
|
25
|
+
"vitest": "^1.0.0",
|
|
26
|
+
"@pretense/protocol": "0.1.0",
|
|
27
|
+
"@pretense/learner": "0.2.0",
|
|
28
|
+
"@pretense/mutator": "0.2.0",
|
|
29
|
+
"@pretense/scanner": "0.2.0",
|
|
30
|
+
"@pretense/store": "0.2.0",
|
|
31
|
+
"@pretense/proxy": "0.1.0",
|
|
32
|
+
"@pretense/compliance-engine": "0.1.0",
|
|
33
|
+
"@pretense/billing": "0.1.0"
|
|
42
34
|
},
|
|
43
35
|
"publishConfig": {
|
|
44
36
|
"access": "public",
|
|
@@ -78,5 +70,12 @@
|
|
|
78
70
|
},
|
|
79
71
|
"engines": {
|
|
80
72
|
"node": ">=20.0.0"
|
|
73
|
+
},
|
|
74
|
+
"scripts": {
|
|
75
|
+
"build": "tsup",
|
|
76
|
+
"dev": "tsx src/index.ts",
|
|
77
|
+
"postinstall": "node -e \"fs.existsSync('dist/postinstall.js') && import('./dist/postinstall.js')\" || true",
|
|
78
|
+
"test": "vitest run",
|
|
79
|
+
"lint": "tsc --noEmit"
|
|
81
80
|
}
|
|
82
|
-
}
|
|
81
|
+
}
|