@grafana/plugin-ui 0.17.3 → 0.17.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.
Files changed (56) hide show
  1. package/NOTICE +18 -0
  2. package/dist/cjs/index.cjs +3 -3
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/cjs/secret-scanner.cjs +3032 -0
  5. package/dist/cjs/secret-scanner.cjs.map +1 -0
  6. package/dist/cjs/secret-scanner.d.cts +273 -0
  7. package/dist/esm/node_modules/js-yaml/dist/js-yaml.js +3 -3
  8. package/dist/esm/node_modules/js-yaml/dist/js-yaml.js.map +1 -1
  9. package/dist/esm/secret-scanner/core/constants.js +4 -0
  10. package/dist/esm/secret-scanner/core/constants.js.map +1 -0
  11. package/dist/esm/secret-scanner/core/detection/embedded.js +80 -0
  12. package/dist/esm/secret-scanner/core/detection/embedded.js.map +1 -0
  13. package/dist/esm/secret-scanner/core/detection/entropy.js +19 -0
  14. package/dist/esm/secret-scanner/core/detection/entropy.js.map +1 -0
  15. package/dist/esm/secret-scanner/core/detection/highEntropy.js +24 -0
  16. package/dist/esm/secret-scanner/core/detection/highEntropy.js.map +1 -0
  17. package/dist/esm/secret-scanner/core/detection/literals.js +135 -0
  18. package/dist/esm/secret-scanner/core/detection/literals.js.map +1 -0
  19. package/dist/esm/secret-scanner/core/detection/nameDetector.js +48 -0
  20. package/dist/esm/secret-scanner/core/detection/nameDetector.js.map +1 -0
  21. package/dist/esm/secret-scanner/core/detection/placeholders.js +10 -0
  22. package/dist/esm/secret-scanner/core/detection/placeholders.js.map +1 -0
  23. package/dist/esm/secret-scanner/core/detection/position.js +39 -0
  24. package/dist/esm/secret-scanner/core/detection/position.js.map +1 -0
  25. package/dist/esm/secret-scanner/core/detection/ruleDetectors.js +59 -0
  26. package/dist/esm/secret-scanner/core/detection/ruleDetectors.js.map +1 -0
  27. package/dist/esm/secret-scanner/core/k6Rewriter.js +44 -0
  28. package/dist/esm/secret-scanner/core/k6Rewriter.js.map +1 -0
  29. package/dist/esm/secret-scanner/core/memoizeLatest.js +18 -0
  30. package/dist/esm/secret-scanner/core/memoizeLatest.js.map +1 -0
  31. package/dist/esm/secret-scanner/core/scanCode.js +70 -0
  32. package/dist/esm/secret-scanner/core/scanCode.js.map +1 -0
  33. package/dist/esm/secret-scanner/core/scanText.js +51 -0
  34. package/dist/esm/secret-scanner/core/scanText.js.map +1 -0
  35. package/dist/esm/secret-scanner/core/secretScanner.utils.js +33 -0
  36. package/dist/esm/secret-scanner/core/secretScanner.utils.js.map +1 -0
  37. package/dist/esm/secret-scanner/monaco/installSecretScanner.js +117 -0
  38. package/dist/esm/secret-scanner/monaco/installSecretScanner.js.map +1 -0
  39. package/dist/esm/secret-scanner/panel/SecretReferenceModal.js +21 -0
  40. package/dist/esm/secret-scanner/panel/SecretReferenceModal.js.map +1 -0
  41. package/dist/esm/secret-scanner/panel/SecretScannerPanel.js +75 -0
  42. package/dist/esm/secret-scanner/panel/SecretScannerPanel.js.map +1 -0
  43. package/dist/esm/secret-scanner/react/useIgnoredSecrets.js +25 -0
  44. package/dist/esm/secret-scanner/react/useIgnoredSecrets.js.map +1 -0
  45. package/dist/esm/secret-scanner/react/useLocalStorageList.js +38 -0
  46. package/dist/esm/secret-scanner/react/useLocalStorageList.js.map +1 -0
  47. package/dist/esm/secret-scanner/react/useMonacoSecretScanner.js +28 -0
  48. package/dist/esm/secret-scanner/react/useMonacoSecretScanner.js.map +1 -0
  49. package/dist/esm/secret-scanner/react/useSecretScanner.js +57 -0
  50. package/dist/esm/secret-scanner/react/useSecretScanner.js.map +1 -0
  51. package/dist/esm/secret-scanner/rules/rules.generated.js +2083 -0
  52. package/dist/esm/secret-scanner/rules/rules.generated.js.map +1 -0
  53. package/dist/esm/secret-scanner.d.ts +273 -0
  54. package/dist/esm/secret-scanner.js +15 -0
  55. package/dist/esm/secret-scanner.js.map +1 -0
  56. package/package.json +38 -3
@@ -0,0 +1,4 @@
1
+ const DEFAULT_DEBOUNCE_MS = 1500;
2
+
3
+ export { DEFAULT_DEBOUNCE_MS };
4
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sources":["../../../../src/secret-scanner/core/constants.ts"],"sourcesContent":["/**\n * How long to wait after the last edit before re-scanning. A scan runs every\n * detector over the whole document (and, for code scans, re-parses it), so\n * running it on every keystroke blocks typing. Debouncing coalesces a burst of\n * edits into one scan.\n */\nexport const DEFAULT_DEBOUNCE_MS = 1500;\n"],"names":[],"mappings":"AAMO,MAAM,mBAAA,GAAsB;;;;"}
@@ -0,0 +1,80 @@
1
+ import { RULE_DETECTORS } from './ruleDetectors.js';
2
+ import { shannonEntropy } from './entropy.js';
3
+
4
+ const isTokenChar = (ch) => ch !== "" && /[A-Za-z0-9]/.test(ch);
5
+ function better(next, best) {
6
+ if (best === null) {
7
+ return true;
8
+ }
9
+ return next.index < best.index || next.index === best.index && next.rank < best.rank;
10
+ }
11
+ function* iterateEmbeddedMatches(text) {
12
+ var _a, _b, _c, _d;
13
+ const lower = text.toLowerCase();
14
+ for (let i = 0; i < RULE_DETECTORS.length; i++) {
15
+ const rule = RULE_DETECTORS[i];
16
+ if (rule.keywords.length > 0 && !rule.keywords.some((k) => lower.includes(k))) {
17
+ continue;
18
+ }
19
+ const { regex } = rule;
20
+ regex.lastIndex = 0;
21
+ let m;
22
+ while ((m = regex.exec(text)) !== null) {
23
+ const groupIndex = m.indices && m.indices[1] ? 1 : 0;
24
+ const span = (_a = m.indices) == null ? void 0 : _a[groupIndex];
25
+ const secret = (_b = m[groupIndex]) != null ? _b : m[0];
26
+ if (!span || secret.length === 0) {
27
+ if (m.index === regex.lastIndex) {
28
+ regex.lastIndex += 1;
29
+ }
30
+ continue;
31
+ }
32
+ const [start, end] = span;
33
+ if (rule.entropy != null && shannonEntropy(secret) < rule.entropy) {
34
+ continue;
35
+ }
36
+ if (isTokenChar((_c = text[start - 1]) != null ? _c : "") || isTokenChar((_d = text[end]) != null ? _d : "")) {
37
+ continue;
38
+ }
39
+ yield {
40
+ type: rule.type,
41
+ label: rule.label,
42
+ confidence: rule.confidence,
43
+ secret,
44
+ index: start,
45
+ length: end - start,
46
+ rank: i
47
+ };
48
+ }
49
+ }
50
+ }
51
+ function detectEmbedded(text) {
52
+ let best = null;
53
+ for (const candidate of iterateEmbeddedMatches(text)) {
54
+ if (better(candidate, best)) {
55
+ best = candidate;
56
+ }
57
+ }
58
+ if (best === null) {
59
+ return null;
60
+ }
61
+ const { rank: _rank, ...match } = best;
62
+ return match;
63
+ }
64
+ function findAllEmbedded(text) {
65
+ const candidates = [...iterateEmbeddedMatches(text)].sort((a, b) => a.index - b.index || a.rank - b.rank);
66
+ const accepted = [];
67
+ let lastEnd = -1;
68
+ for (const candidate of candidates) {
69
+ if (candidate.index < lastEnd) {
70
+ continue;
71
+ }
72
+ const { rank: _rank, ...match } = candidate;
73
+ accepted.push(match);
74
+ lastEnd = candidate.index + candidate.length;
75
+ }
76
+ return accepted;
77
+ }
78
+
79
+ export { detectEmbedded, findAllEmbedded };
80
+ //# sourceMappingURL=embedded.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedded.js","sources":["../../../../../src/secret-scanner/core/detection/embedded.ts"],"sourcesContent":["import { RULE_DETECTORS } from './ruleDetectors';\nimport { shannonEntropy } from './entropy';\nimport type { DetectionResult } from './types';\n\nexport interface EmbeddedMatch extends DetectionResult {\n index: number;\n length: number;\n}\n\nconst isTokenChar = (ch: string): boolean => ch !== '' && /[A-Za-z0-9]/.test(ch);\n\ninterface Candidate extends EmbeddedMatch {\n rank: number;\n}\n\nfunction better(next: Candidate, best: Candidate | null): boolean {\n if (best === null) {\n return true;\n }\n return next.index < best.index || (next.index === best.index && next.rank < best.rank);\n}\n\n/**\n * Every value-shape match in `text`, unordered and possibly overlapping. Shared\n * by `detectEmbedded` and `findAllEmbedded` so the matching rules — group\n * selection, entropy guard, token-boundary guard — live in one place.\n */\nfunction* iterateEmbeddedMatches(text: string): Generator<Candidate> {\n const lower = text.toLowerCase();\n\n for (let i = 0; i < RULE_DETECTORS.length; i++) {\n const rule = RULE_DETECTORS[i]!;\n if (rule.keywords.length > 0 && !rule.keywords.some((k) => lower.includes(k))) {\n continue;\n }\n const { regex } = rule;\n regex.lastIndex = 0;\n let m: RegExpExecArray | null;\n while ((m = regex.exec(text)) !== null) {\n const groupIndex = m.indices && m.indices[1] ? 1 : 0;\n const span = m.indices?.[groupIndex];\n const secret = m[groupIndex] ?? m[0];\n if (!span || secret.length === 0) {\n if (m.index === regex.lastIndex) {\n regex.lastIndex += 1;\n }\n continue;\n }\n const [start, end] = span;\n if (rule.entropy != null && shannonEntropy(secret) < rule.entropy) {\n continue;\n }\n if (isTokenChar(text[start - 1] ?? '') || isTokenChar(text[end] ?? '')) {\n continue;\n }\n yield {\n type: rule.type,\n label: rule.label,\n confidence: rule.confidence,\n secret,\n index: start,\n length: end - start,\n rank: i,\n };\n }\n }\n}\n\n/**\n * The leftmost secret in `text`, ties broken by detector priority (custom rules\n * first, then the gitleaks).\n */\nexport function detectEmbedded(text: string): EmbeddedMatch | null {\n let best: Candidate | null = null;\n for (const candidate of iterateEmbeddedMatches(text)) {\n if (better(candidate, best)) {\n best = candidate;\n }\n }\n\n if (best === null) {\n return null;\n }\n const { rank: _rank, ...match } = best;\n return match;\n}\n\n/**\n * Every secret in `text`, non-overlapping and in source order.\n */\nexport function findAllEmbedded(text: string): EmbeddedMatch[] {\n const candidates = [...iterateEmbeddedMatches(text)].sort((a, b) => a.index - b.index || a.rank - b.rank);\n\n const accepted: EmbeddedMatch[] = [];\n let lastEnd = -1;\n for (const candidate of candidates) {\n if (candidate.index < lastEnd) {\n continue;\n }\n const { rank: _rank, ...match } = candidate;\n accepted.push(match);\n lastEnd = candidate.index + candidate.length;\n }\n return accepted;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ function shannonEntropy(value) {
2
+ var _a;
3
+ if (value.length === 0) {
4
+ return 0;
5
+ }
6
+ const counts = /* @__PURE__ */ new Map();
7
+ for (const char of value) {
8
+ counts.set(char, ((_a = counts.get(char)) != null ? _a : 0) + 1);
9
+ }
10
+ let entropy = 0;
11
+ for (const count of counts.values()) {
12
+ const p = count / value.length;
13
+ entropy -= p * Math.log2(p);
14
+ }
15
+ return entropy;
16
+ }
17
+
18
+ export { shannonEntropy };
19
+ //# sourceMappingURL=entropy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entropy.js","sources":["../../../../../src/secret-scanner/core/detection/entropy.ts"],"sourcesContent":["/** Shannon entropy in bits per character — high for random tokens. */\nexport function shannonEntropy(value: string): number {\n if (value.length === 0) {\n return 0;\n }\n\n const counts = new Map<string, number>();\n for (const char of value) {\n counts.set(char, (counts.get(char) ?? 0) + 1);\n }\n\n let entropy = 0;\n for (const count of counts.values()) {\n const p = count / value.length;\n entropy -= p * Math.log2(p);\n }\n\n return entropy;\n}\n"],"names":[],"mappings":"AACO,SAAS,eAAe,KAAA,EAAuB;AADtD,EAAA,IAAA,EAAA;AAEE,EAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACtB,IAAA,OAAO,CAAA;AAAA,EACT;AAEA,EAAA,MAAM,MAAA,uBAAa,GAAA,EAAoB;AACvC,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,MAAA,CAAO,GAAA,CAAI,QAAO,EAAA,GAAA,MAAA,CAAO,GAAA,CAAI,IAAI,CAAA,KAAf,IAAA,GAAA,EAAA,GAAoB,KAAK,CAAC,CAAA;AAAA,EAC9C;AAEA,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,KAAA,MAAW,KAAA,IAAS,MAAA,CAAO,MAAA,EAAO,EAAG;AACnC,IAAA,MAAM,CAAA,GAAI,QAAQ,KAAA,CAAM,MAAA;AACxB,IAAA,OAAA,IAAW,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA;AAAA,EAC5B;AAEA,EAAA,OAAO,OAAA;AACT;;;;"}
@@ -0,0 +1,24 @@
1
+ import { shannonEntropy } from './entropy.js';
2
+
3
+ function detectHighEntropy(value) {
4
+ if (!/^[A-Za-z0-9+/=]{24,}$/.test(value)) {
5
+ return null;
6
+ }
7
+ const hasDigit = /\d/.test(value);
8
+ const hasLetter = /[A-Za-z]/.test(value);
9
+ if (!hasDigit || !hasLetter) {
10
+ return null;
11
+ }
12
+ if (shannonEntropy(value) < 3.6) {
13
+ return null;
14
+ }
15
+ return {
16
+ type: "high-entropy",
17
+ label: "HIGH ENTROPY STRING",
18
+ confidence: "low",
19
+ secret: value
20
+ };
21
+ }
22
+
23
+ export { detectHighEntropy };
24
+ //# sourceMappingURL=highEntropy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"highEntropy.js","sources":["../../../../../src/secret-scanner/core/detection/highEntropy.ts"],"sourcesContent":["import { shannonEntropy } from './entropy';\nimport type { DetectionResult } from './types';\n\n// The prefix-shaped detectors live as data in ../../rules/*.toml. This heuristic\n// is algorithmic — an entropy threshold, not a fixed pattern — so it can't be\n// expressed as a rule regex and stays here.\n\nexport function detectHighEntropy(value: string): DetectionResult | null {\n if (!/^[A-Za-z0-9+/=]{24,}$/.test(value)) {\n return null;\n }\n const hasDigit = /\\d/.test(value);\n const hasLetter = /[A-Za-z]/.test(value);\n if (!hasDigit || !hasLetter) {\n return null;\n }\n if (shannonEntropy(value) < 3.6) {\n return null;\n }\n return {\n type: 'high-entropy',\n label: 'HIGH ENTROPY STRING',\n confidence: 'low',\n secret: value,\n };\n}\n"],"names":[],"mappings":";;AAOO,SAAS,kBAAkB,KAAA,EAAuC;AACvE,EAAA,IAAI,CAAC,uBAAA,CAAwB,IAAA,CAAK,KAAK,CAAA,EAAG;AACxC,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,IAAA,CAAK,KAAK,CAAA;AAChC,EAAA,MAAM,SAAA,GAAY,UAAA,CAAW,IAAA,CAAK,KAAK,CAAA;AACvC,EAAA,IAAI,CAAC,QAAA,IAAY,CAAC,SAAA,EAAW;AAC3B,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,cAAA,CAAe,KAAK,CAAA,GAAI,GAAA,EAAK;AAC/B,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,cAAA;AAAA,IACN,KAAA,EAAO,qBAAA;AAAA,IACP,UAAA,EAAY,KAAA;AAAA,IACZ,MAAA,EAAQ;AAAA,GACV;AACF;;;;"}
@@ -0,0 +1,135 @@
1
+ import { Parser } from 'acorn';
2
+ import * as acornLoose from 'acorn-loose';
3
+ import { tsPlugin } from 'acorn-typescript';
4
+
5
+ const TsParser = Parser.extend(tsPlugin());
6
+ const PARSE_OPTIONS = { ecmaVersion: "latest", sourceType: "module" };
7
+ function parseScript(script) {
8
+ try {
9
+ return Parser.parse(script, PARSE_OPTIONS);
10
+ } catch (e) {
11
+ try {
12
+ return TsParser.parse(script, PARSE_OPTIONS);
13
+ } catch (e2) {
14
+ try {
15
+ return acornLoose.parse(script, PARSE_OPTIONS);
16
+ } catch (e3) {
17
+ return null;
18
+ }
19
+ }
20
+ }
21
+ }
22
+ function isNode(value) {
23
+ return typeof value === "object" && value !== null && typeof value.type === "string";
24
+ }
25
+ function collectStringLiterals(node, parent, out) {
26
+ var _a, _b, _c;
27
+ if (Array.isArray(node)) {
28
+ for (const item of node) {
29
+ collectStringLiterals(item, parent, out);
30
+ }
31
+ return;
32
+ }
33
+ if (!isNode(node)) {
34
+ return;
35
+ }
36
+ if (node.type === "Literal" && typeof node.value === "string") {
37
+ out.push({
38
+ body: node.value.trim(),
39
+ literalStart: node.start,
40
+ literalEnd: node.end,
41
+ contentStart: node.start + 1,
42
+ contentEnd: node.end - 1,
43
+ kind: "string",
44
+ name: resolveAssignedName(node, parent)
45
+ });
46
+ } else if (node.type === "TemplateLiteral" && Array.isArray(node.expressions)) {
47
+ const quasis = node.quasis;
48
+ if (node.expressions.length === 0) {
49
+ const cooked = (_b = (_a = quasis[0]) == null ? void 0 : _a.value.cooked) != null ? _b : "";
50
+ out.push({
51
+ body: cooked.trim(),
52
+ literalStart: node.start,
53
+ literalEnd: node.end,
54
+ contentStart: node.start + 1,
55
+ contentEnd: node.end - 1,
56
+ kind: "template",
57
+ name: resolveAssignedName(node, parent)
58
+ });
59
+ } else {
60
+ for (const quasi of quasis) {
61
+ const cooked = (_c = quasi.value.cooked) != null ? _c : "";
62
+ if (cooked.length === 0) {
63
+ continue;
64
+ }
65
+ out.push({
66
+ body: cooked.trim(),
67
+ literalStart: quasi.start,
68
+ literalEnd: quasi.end,
69
+ contentStart: quasi.start,
70
+ contentEnd: quasi.end,
71
+ kind: "template",
72
+ embeddedOnly: true
73
+ });
74
+ }
75
+ }
76
+ }
77
+ for (const key of Object.keys(node)) {
78
+ collectStringLiterals(node[key], node, out);
79
+ }
80
+ }
81
+ function identifierName(id) {
82
+ return isNode(id) && id.type === "Identifier" ? id.name : void 0;
83
+ }
84
+ function memberName(key) {
85
+ if (!isNode(key)) {
86
+ return void 0;
87
+ }
88
+ if (key.type === "Identifier") {
89
+ return key.name;
90
+ }
91
+ if (key.type === "Literal" && typeof key.value === "string") {
92
+ return key.value;
93
+ }
94
+ return void 0;
95
+ }
96
+ function assignmentTargetName(target) {
97
+ if (!isNode(target)) {
98
+ return void 0;
99
+ }
100
+ if (target.type === "Identifier") {
101
+ return target.name;
102
+ }
103
+ if (target.type === "MemberExpression") {
104
+ return memberName(target.property);
105
+ }
106
+ return void 0;
107
+ }
108
+ function resolveAssignedName(node, parent) {
109
+ if (!parent) {
110
+ return void 0;
111
+ }
112
+ switch (parent.type) {
113
+ case "VariableDeclarator":
114
+ return parent.init === node ? identifierName(parent.id) : void 0;
115
+ case "Property":
116
+ case "PropertyDefinition":
117
+ return parent.value === node ? memberName(parent.key) : void 0;
118
+ case "AssignmentExpression":
119
+ return parent.right === node ? assignmentTargetName(parent.left) : void 0;
120
+ default:
121
+ return void 0;
122
+ }
123
+ }
124
+ function iterateStringLiterals(script) {
125
+ const ast = parseScript(script);
126
+ if (!ast) {
127
+ return [];
128
+ }
129
+ const literals = [];
130
+ collectStringLiterals(ast, null, literals);
131
+ return literals;
132
+ }
133
+
134
+ export { iterateStringLiterals };
135
+ //# sourceMappingURL=literals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"literals.js","sources":["../../../../../src/secret-scanner/core/detection/literals.ts"],"sourcesContent":["import { Parser, type Options } from 'acorn';\nimport * as acornLoose from 'acorn-loose';\nimport { tsPlugin } from 'acorn-typescript';\n\n// acorn extended with a TypeScript-aware plugin\nconst TsParser = Parser.extend(tsPlugin() as unknown as Parameters<typeof Parser.extend>[0]);\n\nconst PARSE_OPTIONS: Options = { ecmaVersion: 'latest', sourceType: 'module' };\n\n/**\n * Parses the script into an offset-accurate AST, preferring fidelity but never\n * throwing.\n *\n * Three tiers, fastest first:\n *\n * 1. Plain acorn. It is ~5x faster than the TS-aware parser and succeeds on\n * plain-JS scripts (the common case for k6). Crucially, plain JS has no\n * type positions, so there is no type-position literal to mis-flag — a file\n * acorn accepts is genuinely JS. Any TypeScript syntax makes it throw, which\n * just falls through to tier 2.\n * 2. The TS-aware parser (`acorn` + `acorn-typescript`). Handles type\n * annotations and distinguishes type-position literals (`type K = 'AKIA…'`,\n * `x: 'live' | 'test'`) from runtime values, so it won't flag a\n * secret-shaped string that only appears in a type. It rejects the whole\n * file on a construct `acorn-typescript` doesn't implement (e.g.\n * `satisfies`) or on a mid-edit syntax error.\n * 3. `acorn-loose`. Recovers around bad tokens and always returns a best-effort\n * tree with source-accurate offsets, so one unsupported construct or a\n * transient syntax error no longer blanks every finding in the file. The\n * loose tree is not TS-aware, so a type-position literal in a recovered file\n * may be flagged; a low-confidence false positive in an already-unusual file\n * is preferable to missing a secret.\n *\n * Returns null only if even the loose parse throws, which it is not expected to.\n */\nfunction parseScript(script: string): AstNode | null {\n try {\n return Parser.parse(script, PARSE_OPTIONS) as unknown as AstNode;\n } catch {\n try {\n return TsParser.parse(script, PARSE_OPTIONS) as unknown as AstNode;\n } catch {\n try {\n return acornLoose.parse(script, PARSE_OPTIONS) as unknown as AstNode;\n } catch {\n return null;\n }\n }\n }\n}\n\nexport interface StringLiteral {\n body: string;\n literalStart: number;\n literalEnd: number;\n contentStart: number;\n contentEnd: number;\n kind: 'string' | 'template';\n name?: string;\n embeddedOnly?: boolean;\n}\n\ninterface AstNode {\n type: string;\n start: number;\n end: number;\n [key: string]: unknown;\n}\n\nfunction isNode(value: unknown): value is AstNode {\n return typeof value === 'object' && value !== null && typeof (value as AstNode).type === 'string';\n}\n\n/**\n * Depth-first walk that appends every string / template literal to `out`.\n *\n * A plain recursive function rather than a generator: on a large AST the\n * `yield*` delegation and per-node iterator allocation of a generator walk cost\n * more than the traversal itself. This visits the same nodes with none of that\n * overhead.\n */\nfunction collectStringLiterals(node: unknown, parent: AstNode | null, out: StringLiteral[]): void {\n if (Array.isArray(node)) {\n for (const item of node) {\n collectStringLiterals(item, parent, out);\n }\n return;\n }\n if (!isNode(node)) {\n return;\n }\n\n if (node.type === 'Literal' && typeof node.value === 'string') {\n out.push({\n body: node.value.trim(),\n literalStart: node.start,\n literalEnd: node.end,\n contentStart: node.start + 1,\n contentEnd: node.end - 1,\n kind: 'string',\n name: resolveAssignedName(node, parent),\n });\n } else if (node.type === 'TemplateLiteral' && Array.isArray(node.expressions)) {\n const quasis = node.quasis as Array<{ start: number; end: number; value: { cooked?: string | null } }>;\n if (node.expressions.length === 0) {\n const cooked = quasis[0]?.value.cooked ?? '';\n out.push({\n body: cooked.trim(),\n literalStart: node.start,\n literalEnd: node.end,\n contentStart: node.start + 1,\n contentEnd: node.end - 1,\n kind: 'template',\n name: resolveAssignedName(node, parent),\n });\n } else {\n for (const quasi of quasis) {\n const cooked = quasi.value.cooked ?? '';\n if (cooked.length === 0) {\n continue;\n }\n out.push({\n body: cooked.trim(),\n literalStart: quasi.start,\n literalEnd: quasi.end,\n contentStart: quasi.start,\n contentEnd: quasi.end,\n kind: 'template',\n embeddedOnly: true,\n });\n }\n }\n }\n\n for (const key of Object.keys(node)) {\n collectStringLiterals(node[key], node, out);\n }\n}\n\nfunction identifierName(id: unknown): string | undefined {\n return isNode(id) && id.type === 'Identifier' ? (id.name as string) : undefined;\n}\n\n/** Name of an object/class member key: `foo`, `'foo'` or `['foo']`. */\nfunction memberName(key: unknown): string | undefined {\n if (!isNode(key)) {\n return undefined;\n }\n if (key.type === 'Identifier') {\n return key.name as string;\n }\n if (key.type === 'Literal' && typeof key.value === 'string') {\n return key.value;\n }\n return undefined;\n}\n\n/** Name an assignment targets: `foo = …` or `obj.foo = …`. */\nfunction assignmentTargetName(target: unknown): string | undefined {\n if (!isNode(target)) {\n return undefined;\n }\n if (target.type === 'Identifier') {\n return target.name as string;\n }\n if (target.type === 'MemberExpression') {\n return memberName(target.property);\n }\n return undefined;\n}\n\nfunction resolveAssignedName(node: AstNode, parent: AstNode | null): string | undefined {\n if (!parent) {\n return undefined;\n }\n switch (parent.type) {\n case 'VariableDeclarator':\n return parent.init === node ? identifierName(parent.id) : undefined;\n case 'Property':\n case 'PropertyDefinition':\n return parent.value === node ? memberName(parent.key) : undefined;\n case 'AssignmentExpression':\n return parent.right === node ? assignmentTargetName(parent.left) : undefined;\n default:\n return undefined;\n }\n}\n\n/**\n * Every string / template literal in the script, in source order, with its\n * decoded body and original offsets. An interpolated template yields one entry\n * per static chunk (`embeddedOnly`) rather than a single whole-value entry.\n */\nexport function iterateStringLiterals(script: string): StringLiteral[] {\n const ast = parseScript(script);\n if (!ast) {\n return [];\n }\n\n const literals: StringLiteral[] = [];\n collectStringLiterals(ast, null, literals);\n return literals;\n}\n"],"names":["e"],"mappings":";;;;AAKA,MAAM,QAAA,GAAW,MAAA,CAAO,MAAA,CAAO,QAAA,EAA4D,CAAA;AAE3F,MAAM,aAAA,GAAyB,EAAE,WAAA,EAAa,QAAA,EAAU,YAAY,QAAA,EAAS;AA4B7E,SAAS,YAAY,MAAA,EAAgC;AACnD,EAAA,IAAI;AACF,IAAA,OAAO,MAAA,CAAO,KAAA,CAAM,MAAA,EAAQ,aAAa,CAAA;AAAA,EAC3C,CAAA,CAAA,OAAQ,CAAA,EAAA;AACN,IAAA,IAAI;AACF,MAAA,OAAO,QAAA,CAAS,KAAA,CAAM,MAAA,EAAQ,aAAa,CAAA;AAAA,IAC7C,SAAQA,EAAAA,EAAA;AACN,MAAA,IAAI;AACF,QAAA,OAAO,UAAA,CAAW,KAAA,CAAM,MAAA,EAAQ,aAAa,CAAA;AAAA,MAC/C,SAAQA,EAAAA,EAAA;AACN,QAAA,OAAO,IAAA;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;AAoBA,SAAS,OAAO,KAAA,EAAkC;AAChD,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,UAAU,IAAA,IAAQ,OAAQ,MAAkB,IAAA,KAAS,QAAA;AAC3F;AAUA,SAAS,qBAAA,CAAsB,IAAA,EAAe,MAAA,EAAwB,GAAA,EAA4B;AAjFlG,EAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAkFE,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,EAAG;AACvB,IAAA,KAAA,MAAW,QAAQ,IAAA,EAAM;AACvB,MAAA,qBAAA,CAAsB,IAAA,EAAM,QAAQ,GAAG,CAAA;AAAA,IACzC;AACA,IAAA;AAAA,EACF;AACA,EAAA,IAAI,CAAC,MAAA,CAAO,IAAI,CAAA,EAAG;AACjB,IAAA;AAAA,EACF;AAEA,EAAA,IAAI,KAAK,IAAA,KAAS,SAAA,IAAa,OAAO,IAAA,CAAK,UAAU,QAAA,EAAU;AAC7D,IAAA,GAAA,CAAI,IAAA,CAAK;AAAA,MACP,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,IAAA,EAAK;AAAA,MACtB,cAAc,IAAA,CAAK,KAAA;AAAA,MACnB,YAAY,IAAA,CAAK,GAAA;AAAA,MACjB,YAAA,EAAc,KAAK,KAAA,GAAQ,CAAA;AAAA,MAC3B,UAAA,EAAY,KAAK,GAAA,GAAM,CAAA;AAAA,MACvB,IAAA,EAAM,QAAA;AAAA,MACN,IAAA,EAAM,mBAAA,CAAoB,IAAA,EAAM,MAAM;AAAA,KACvC,CAAA;AAAA,EACH,CAAA,MAAA,IAAW,KAAK,IAAA,KAAS,iBAAA,IAAqB,MAAM,OAAA,CAAQ,IAAA,CAAK,WAAW,CAAA,EAAG;AAC7E,IAAA,MAAM,SAAS,IAAA,CAAK,MAAA;AACpB,IAAA,IAAI,IAAA,CAAK,WAAA,CAAY,MAAA,KAAW,CAAA,EAAG;AACjC,MAAA,MAAM,UAAS,EAAA,GAAA,CAAA,EAAA,GAAA,MAAA,CAAO,CAAC,MAAR,IAAA,GAAA,MAAA,GAAA,EAAA,CAAW,KAAA,CAAM,WAAjB,IAAA,GAAA,EAAA,GAA2B,EAAA;AAC1C,MAAA,GAAA,CAAI,IAAA,CAAK;AAAA,QACP,IAAA,EAAM,OAAO,IAAA,EAAK;AAAA,QAClB,cAAc,IAAA,CAAK,KAAA;AAAA,QACnB,YAAY,IAAA,CAAK,GAAA;AAAA,QACjB,YAAA,EAAc,KAAK,KAAA,GAAQ,CAAA;AAAA,QAC3B,UAAA,EAAY,KAAK,GAAA,GAAM,CAAA;AAAA,QACvB,IAAA,EAAM,UAAA;AAAA,QACN,IAAA,EAAM,mBAAA,CAAoB,IAAA,EAAM,MAAM;AAAA,OACvC,CAAA;AAAA,IACH,CAAA,MAAO;AACL,MAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,QAAA,MAAM,MAAA,GAAA,CAAS,EAAA,GAAA,KAAA,CAAM,KAAA,CAAM,MAAA,KAAZ,IAAA,GAAA,EAAA,GAAsB,EAAA;AACrC,QAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG;AACvB,UAAA;AAAA,QACF;AACA,QAAA,GAAA,CAAI,IAAA,CAAK;AAAA,UACP,IAAA,EAAM,OAAO,IAAA,EAAK;AAAA,UAClB,cAAc,KAAA,CAAM,KAAA;AAAA,UACpB,YAAY,KAAA,CAAM,GAAA;AAAA,UAClB,cAAc,KAAA,CAAM,KAAA;AAAA,UACpB,YAAY,KAAA,CAAM,GAAA;AAAA,UAClB,IAAA,EAAM,UAAA;AAAA,UACN,YAAA,EAAc;AAAA,SACf,CAAA;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,EAAG;AACnC,IAAA,qBAAA,CAAsB,IAAA,CAAK,GAAG,CAAA,EAAG,IAAA,EAAM,GAAG,CAAA;AAAA,EAC5C;AACF;AAEA,SAAS,eAAe,EAAA,EAAiC;AACvD,EAAA,OAAO,OAAO,EAAE,CAAA,IAAK,GAAG,IAAA,KAAS,YAAA,GAAgB,GAAG,IAAA,GAAkB,MAAA;AACxE;AAGA,SAAS,WAAW,GAAA,EAAkC;AACpD,EAAA,IAAI,CAAC,MAAA,CAAO,GAAG,CAAA,EAAG;AAChB,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI,GAAA,CAAI,SAAS,YAAA,EAAc;AAC7B,IAAA,OAAO,GAAA,CAAI,IAAA;AAAA,EACb;AACA,EAAA,IAAI,IAAI,IAAA,KAAS,SAAA,IAAa,OAAO,GAAA,CAAI,UAAU,QAAA,EAAU;AAC3D,IAAA,OAAO,GAAA,CAAI,KAAA;AAAA,EACb;AACA,EAAA,OAAO,MAAA;AACT;AAGA,SAAS,qBAAqB,MAAA,EAAqC;AACjE,EAAA,IAAI,CAAC,MAAA,CAAO,MAAM,CAAA,EAAG;AACnB,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI,MAAA,CAAO,SAAS,YAAA,EAAc;AAChC,IAAA,OAAO,MAAA,CAAO,IAAA;AAAA,EAChB;AACA,EAAA,IAAI,MAAA,CAAO,SAAS,kBAAA,EAAoB;AACtC,IAAA,OAAO,UAAA,CAAW,OAAO,QAAQ,CAAA;AAAA,EACnC;AACA,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,mBAAA,CAAoB,MAAe,MAAA,EAA4C;AACtF,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,QAAQ,OAAO,IAAA;AAAM,IACnB,KAAK,oBAAA;AACH,MAAA,OAAO,OAAO,IAAA,KAAS,IAAA,GAAO,cAAA,CAAe,MAAA,CAAO,EAAE,CAAA,GAAI,MAAA;AAAA,IAC5D,KAAK,UAAA;AAAA,IACL,KAAK,oBAAA;AACH,MAAA,OAAO,OAAO,KAAA,KAAU,IAAA,GAAO,UAAA,CAAW,MAAA,CAAO,GAAG,CAAA,GAAI,MAAA;AAAA,IAC1D,KAAK,sBAAA;AACH,MAAA,OAAO,OAAO,KAAA,KAAU,IAAA,GAAO,oBAAA,CAAqB,MAAA,CAAO,IAAI,CAAA,GAAI,MAAA;AAAA,IACrE;AACE,MAAA,OAAO,MAAA;AAAA;AAEb;AAOO,SAAS,sBAAsB,MAAA,EAAiC;AACrE,EAAA,MAAM,GAAA,GAAM,YAAY,MAAM,CAAA;AAC9B,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,WAA4B,EAAC;AACnC,EAAA,qBAAA,CAAsB,GAAA,EAAK,MAAM,QAAQ,CAAA;AACzC,EAAA,OAAO,QAAA;AACT;;;;"}
@@ -0,0 +1,48 @@
1
+ const SENSITIVE_NAME_KEYWORDS = /* @__PURE__ */ new Set([
2
+ "password",
3
+ "passwd",
4
+ "passphrase",
5
+ "pass",
6
+ "pwd",
7
+ "secret",
8
+ "token",
9
+ "apikey",
10
+ "key",
11
+ "auth",
12
+ "bearer",
13
+ "credential",
14
+ "credentials",
15
+ "cred",
16
+ "creds",
17
+ "session",
18
+ "signature",
19
+ "salt",
20
+ "certificate",
21
+ "cert"
22
+ ]);
23
+ function splitIdentifierWords(name) {
24
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).map((word) => word.toLowerCase()).filter(Boolean);
25
+ }
26
+ function isSensitiveName(name) {
27
+ return splitIdentifierWords(name).some((word) => SENSITIVE_NAME_KEYWORDS.has(word));
28
+ }
29
+ function isPlausibleNamedSecretValue(value) {
30
+ return value.length >= 4 && value.length <= 200 && !/\s/.test(value) && !value.includes("://");
31
+ }
32
+ function detectFromName(body, name) {
33
+ if (!isPlausibleNamedSecretValue(body)) {
34
+ return null;
35
+ }
36
+ if (!name || !isSensitiveName(name)) {
37
+ return null;
38
+ }
39
+ return {
40
+ type: "named",
41
+ label: "NAMED SECRET",
42
+ confidence: "low",
43
+ secret: body
44
+ };
45
+ }
46
+
47
+ export { detectFromName };
48
+ //# sourceMappingURL=nameDetector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nameDetector.js","sources":["../../../../../src/secret-scanner/core/detection/nameDetector.ts"],"sourcesContent":["// cspell:ignore apikey creds\nimport type { DetectionResult } from './types';\n\n// Variable / property names that, when assigned a string literal, suggest the\n// value is a credential even when the value itself looks unremarkable (e.g.\n// `const pass = 'hunter2'`). Matched per identifier *word* — split on\n// camelCase / snake / kebab boundaries — so `apiKey` matches but `keyboard`,\n// `author` and `monkey` do not.\nconst SENSITIVE_NAME_KEYWORDS = new Set([\n 'password',\n 'passwd',\n 'passphrase',\n 'pass',\n 'pwd',\n 'secret',\n 'token',\n 'apikey',\n 'key',\n 'auth',\n 'bearer',\n 'credential',\n 'credentials',\n 'cred',\n 'creds',\n 'session',\n 'signature',\n 'salt',\n 'certificate',\n 'cert',\n]);\n\nfunction splitIdentifierWords(name: string): string[] {\n return name\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')\n .split(/[^A-Za-z0-9]+/)\n .map((word) => word.toLowerCase())\n .filter(Boolean);\n}\n\nfunction isSensitiveName(name: string): boolean {\n return splitIdentifierWords(name).some((word) => SENSITIVE_NAME_KEYWORDS.has(word));\n}\n\n// A name-based hit is low signal, so only flag compact single-token values that\n// could plausibly be a credential — not sentences, URLs or empty strings.\nfunction isPlausibleNamedSecretValue(value: string): boolean {\n return value.length >= 4 && value.length <= 200 && !/\\s/.test(value) && !value.includes('://');\n}\n\n/**\n * Fallback for values that look unremarkable on their own, so an obvious\n * mistake like `const pass = 'function'` is still surfaced.\n */\nexport function detectFromName(body: string, name: string | undefined): DetectionResult | null {\n if (!isPlausibleNamedSecretValue(body)) {\n return null;\n }\n\n if (!name || !isSensitiveName(name)) {\n return null;\n }\n\n return {\n type: 'named',\n label: 'NAMED SECRET',\n confidence: 'low',\n secret: body,\n };\n}\n"],"names":[],"mappings":"AAQA,MAAM,uBAAA,uBAA8B,GAAA,CAAI;AAAA,EACtC,UAAA;AAAA,EACA,QAAA;AAAA,EACA,YAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,YAAA;AAAA,EACA,aAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,WAAA;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAC,CAAA;AAED,SAAS,qBAAqB,IAAA,EAAwB;AACpD,EAAA,OAAO,IAAA,CACJ,QAAQ,oBAAA,EAAsB,OAAO,EACrC,OAAA,CAAQ,uBAAA,EAAyB,OAAO,CAAA,CACxC,KAAA,CAAM,eAAe,CAAA,CACrB,GAAA,CAAI,CAAC,IAAA,KAAS,IAAA,CAAK,aAAa,CAAA,CAChC,OAAO,OAAO,CAAA;AACnB;AAEA,SAAS,gBAAgB,IAAA,EAAuB;AAC9C,EAAA,OAAO,oBAAA,CAAqB,IAAI,CAAA,CAAE,IAAA,CAAK,CAAC,IAAA,KAAS,uBAAA,CAAwB,GAAA,CAAI,IAAI,CAAC,CAAA;AACpF;AAIA,SAAS,4BAA4B,KAAA,EAAwB;AAC3D,EAAA,OAAO,KAAA,CAAM,MAAA,IAAU,CAAA,IAAK,KAAA,CAAM,UAAU,GAAA,IAAO,CAAC,IAAA,CAAK,IAAA,CAAK,KAAK,CAAA,IAAK,CAAC,KAAA,CAAM,SAAS,KAAK,CAAA;AAC/F;AAMO,SAAS,cAAA,CAAe,MAAc,IAAA,EAAkD;AAC7F,EAAA,IAAI,CAAC,2BAAA,CAA4B,IAAI,CAAA,EAAG;AACtC,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,eAAA,CAAgB,IAAI,CAAA,EAAG;AACnC,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,OAAA;AAAA,IACN,KAAA,EAAO,cAAA;AAAA,IACP,UAAA,EAAY,KAAA;AAAA,IACZ,MAAA,EAAQ;AAAA,GACV;AACF;;;;"}
@@ -0,0 +1,10 @@
1
+ const PLACEHOLDER_PATTERN = /^(your|my|the|some|example|sample|changeme|placeholder|todo|xxx+|test|dummy|<.*>|\{\{.*\}\})/i;
2
+ function isPlaceholder(value) {
3
+ if (PLACEHOLDER_PATTERN.test(value)) {
4
+ return true;
5
+ }
6
+ return /^(.)\1+$/.test(value);
7
+ }
8
+
9
+ export { isPlaceholder };
10
+ //# sourceMappingURL=placeholders.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"placeholders.js","sources":["../../../../../src/secret-scanner/core/detection/placeholders.ts"],"sourcesContent":["// cspell:ignore changeme\n\n// Values that look secret-shaped but are obviously placeholders. Skipping these\n// keeps the warning noise down so the real findings stand out.\nconst PLACEHOLDER_PATTERN =\n /^(your|my|the|some|example|sample|changeme|placeholder|todo|xxx+|test|dummy|<.*>|\\{\\{.*\\}\\})/i;\n\nexport function isPlaceholder(value: string): boolean {\n if (PLACEHOLDER_PATTERN.test(value)) {\n return true;\n }\n\n // A single repeated character (aaaa…, xxxx…, 0000…) is never a real secret.\n return /^(.)\\1+$/.test(value);\n}\n"],"names":[],"mappings":"AAIA,MAAM,mBAAA,GACJ,+FAAA;AAEK,SAAS,cAAc,KAAA,EAAwB;AACpD,EAAA,IAAI,mBAAA,CAAoB,IAAA,CAAK,KAAK,CAAA,EAAG;AACnC,IAAA,OAAO,IAAA;AAAA,EACT;AAGA,EAAA,OAAO,UAAA,CAAW,KAAK,KAAK,CAAA;AAC9B;;;;"}
@@ -0,0 +1,39 @@
1
+ function offsetToPosition(text, offset) {
2
+ let line = 1;
3
+ let column = 1;
4
+ const end = Math.min(offset, text.length);
5
+ for (let i = 0; i < end; i++) {
6
+ if (text[i] === "\n") {
7
+ line++;
8
+ column = 1;
9
+ } else {
10
+ column++;
11
+ }
12
+ }
13
+ return { line, column };
14
+ }
15
+ function createPositionResolver(text) {
16
+ const lineStarts = [0];
17
+ for (let i = 0; i < text.length; i++) {
18
+ if (text[i] === "\n") {
19
+ lineStarts.push(i + 1);
20
+ }
21
+ }
22
+ return function positionAt(offset) {
23
+ const target = Math.min(Math.max(offset, 0), text.length);
24
+ let lo = 0;
25
+ let hi = lineStarts.length - 1;
26
+ while (lo < hi) {
27
+ const mid = lo + hi + 1 >> 1;
28
+ if (lineStarts[mid] <= target) {
29
+ lo = mid;
30
+ } else {
31
+ hi = mid - 1;
32
+ }
33
+ }
34
+ return { line: lo + 1, column: target - lineStarts[lo] + 1 };
35
+ };
36
+ }
37
+
38
+ export { createPositionResolver, offsetToPosition };
39
+ //# sourceMappingURL=position.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"position.js","sources":["../../../../../src/secret-scanner/core/detection/position.ts"],"sourcesContent":["/**\n * Converts a character offset into a 1-based {line, column}.\n */\nexport function offsetToPosition(text: string, offset: number) {\n let line = 1;\n let column = 1;\n const end = Math.min(offset, text.length);\n\n for (let i = 0; i < end; i++) {\n if (text[i] === '\\n') {\n line++;\n column = 1;\n } else {\n column++;\n }\n }\n\n return { line, column };\n}\n\n/**\n * Builds a reusable offset→{line, column} resolver for one document.\n */\nexport function createPositionResolver(text: string) {\n // lineStarts[i] = offset where line (i + 1) begins. Line 1 starts at 0.\n const lineStarts = [0];\n for (let i = 0; i < text.length; i++) {\n if (text[i] === '\\n') {\n lineStarts.push(i + 1);\n }\n }\n\n return function positionAt(offset: number) {\n const target = Math.min(Math.max(offset, 0), text.length);\n let lo = 0;\n let hi = lineStarts.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n if (lineStarts[mid]! <= target) {\n lo = mid;\n } else {\n hi = mid - 1;\n }\n }\n return { line: lo + 1, column: target - lineStarts[lo]! + 1 };\n };\n}\n"],"names":[],"mappings":"AAGO,SAAS,gBAAA,CAAiB,MAAc,MAAA,EAAgB;AAC7D,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,IAAI,MAAA,GAAS,CAAA;AACb,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,MAAA,EAAQ,KAAK,MAAM,CAAA;AAExC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,EAAK,CAAA,EAAA,EAAK;AAC5B,IAAA,IAAI,IAAA,CAAK,CAAC,CAAA,KAAM,IAAA,EAAM;AACpB,MAAA,IAAA,EAAA;AACA,MAAA,MAAA,GAAS,CAAA;AAAA,IACX,CAAA,MAAO;AACL,MAAA,MAAA,EAAA;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,MAAM,MAAA,EAAO;AACxB;AAKO,SAAS,uBAAuB,IAAA,EAAc;AAEnD,EAAA,MAAM,UAAA,GAAa,CAAC,CAAC,CAAA;AACrB,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,QAAQ,CAAA,EAAA,EAAK;AACpC,IAAA,IAAI,IAAA,CAAK,CAAC,CAAA,KAAM,IAAA,EAAM;AACpB,MAAA,UAAA,CAAW,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,IACvB;AAAA,EACF;AAEA,EAAA,OAAO,SAAS,WAAW,MAAA,EAAgB;AACzC,IAAA,MAAM,MAAA,GAAS,KAAK,GAAA,CAAI,IAAA,CAAK,IAAI,MAAA,EAAQ,CAAC,CAAA,EAAG,IAAA,CAAK,MAAM,CAAA;AACxD,IAAA,IAAI,EAAA,GAAK,CAAA;AACT,IAAA,IAAI,EAAA,GAAK,WAAW,MAAA,GAAS,CAAA;AAC7B,IAAA,OAAO,KAAK,EAAA,EAAI;AACd,MAAA,MAAM,GAAA,GAAO,EAAA,GAAK,EAAA,GAAK,CAAA,IAAM,CAAA;AAC7B,MAAA,IAAI,UAAA,CAAW,GAAG,CAAA,IAAM,MAAA,EAAQ;AAC9B,QAAA,EAAA,GAAK,GAAA;AAAA,MACP,CAAA,MAAO;AACL,QAAA,EAAA,GAAK,GAAA,GAAM,CAAA;AAAA,MACb;AAAA,IACF;AACA,IAAA,OAAO,EAAE,MAAM,EAAA,GAAK,CAAA,EAAG,QAAQ,MAAA,GAAS,UAAA,CAAW,EAAE,CAAA,GAAK,CAAA,EAAE;AAAA,EAC9D,CAAA;AACF;;;;"}
@@ -0,0 +1,59 @@
1
+ import { CUSTOM_RULES, GITLEAKS_RULES } from '../../rules/rules.generated.js';
2
+
3
+ function toJsRegex(source) {
4
+ let body = source;
5
+ let flags = "gd";
6
+ if (/\(\?i\)/.test(body)) {
7
+ body = body.replace(/\(\?i\)/g, "");
8
+ flags += "i";
9
+ }
10
+ body = body.replace(/\(\?P</g, "(?<");
11
+ try {
12
+ return new RegExp(body, flags);
13
+ } catch (e) {
14
+ return null;
15
+ }
16
+ }
17
+ function normalizeLabelFromId(id) {
18
+ return id.replace(/-/g, " ").toUpperCase();
19
+ }
20
+ function compile(rule, defaultConfidence) {
21
+ var _a, _b;
22
+ if (typeof rule.regex !== "string") {
23
+ return null;
24
+ }
25
+ const regex = toJsRegex(rule.regex);
26
+ if (regex === null) {
27
+ return null;
28
+ }
29
+ return {
30
+ type: rule.id,
31
+ label: normalizeLabelFromId(rule.id),
32
+ confidence: (_a = rule.confidence) != null ? _a : defaultConfidence,
33
+ regex,
34
+ entropy: rule.entropy,
35
+ keywords: ((_b = rule.keywords) != null ? _b : []).map((k) => k.toLowerCase())
36
+ };
37
+ }
38
+ function build() {
39
+ var _a;
40
+ const custom = CUSTOM_RULES.map((r) => compile(r, "high")).filter((d) => d !== null);
41
+ const covered = custom.flatMap((d) => d.keywords);
42
+ const isCovered = (keywords) => keywords.some((k) => covered.some((p) => k.startsWith(p) || p.startsWith(k)));
43
+ const supplemental = [];
44
+ for (const rule of GITLEAKS_RULES) {
45
+ const keywords = ((_a = rule.keywords) != null ? _a : []).map((k) => k.toLowerCase());
46
+ if (isCovered(keywords)) {
47
+ continue;
48
+ }
49
+ const detector = compile(rule, "medium");
50
+ if (detector !== null) {
51
+ supplemental.push(detector);
52
+ }
53
+ }
54
+ return [...custom, ...supplemental];
55
+ }
56
+ const RULE_DETECTORS = build();
57
+
58
+ export { RULE_DETECTORS };
59
+ //# sourceMappingURL=ruleDetectors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ruleDetectors.js","sources":["../../../../../src/secret-scanner/core/detection/ruleDetectors.ts"],"sourcesContent":["// Value-shape detectors built from data: tuned patterns in custom.toml plus the\n// vendored gitleaks file. Custom rules are authoritative — any gitleaks rule\n// whose keywords overlap a custom rule's is skipped, so the custom pattern wins\n// over gitleaks' file-oriented version of the same secret.\n//\n// The TOML is compiled ahead of time by `yarn gen:secret-rules`, so no TOML\n// parser is bundled. Edit the `.toml` files, then re-run the generator.\nimport { CUSTOM_RULES, GITLEAKS_RULES, type SecretRule } from '../../rules/rules.generated';\nimport type { SecretConfidence } from './types';\n\nexport interface RuleDetector {\n type: string;\n label: string;\n confidence: SecretConfidence;\n regex: RegExp;\n entropy?: number;\n keywords: string[];\n}\n\n/** Rewrites a gitleaks (Go/RE2) pattern into an equivalent the JS engine accepts. */\nfunction toJsRegex(source: string): RegExp | null {\n let body = source;\n let flags = 'gd';\n if (/\\(\\?i\\)/.test(body)) {\n body = body.replace(/\\(\\?i\\)/g, '');\n flags += 'i';\n }\n body = body.replace(/\\(\\?P</g, '(?<');\n try {\n return new RegExp(body, flags);\n } catch {\n return null;\n }\n}\n\nfunction normalizeLabelFromId(id: string): string {\n return id.replace(/-/g, ' ').toUpperCase();\n}\n\nfunction compile(rule: SecretRule, defaultConfidence: SecretConfidence): RuleDetector | null {\n if (typeof rule.regex !== 'string') {\n return null;\n }\n const regex = toJsRegex(rule.regex);\n if (regex === null) {\n return null;\n }\n return {\n type: rule.id,\n label: normalizeLabelFromId(rule.id),\n confidence: rule.confidence ?? defaultConfidence,\n regex,\n entropy: rule.entropy,\n keywords: (rule.keywords ?? []).map((k) => k.toLowerCase()),\n };\n}\n\nfunction build(): RuleDetector[] {\n // custom detectors first — they rank ahead of the gitleaks file.\n const custom = CUSTOM_RULES.map((r) => compile(r, 'high')).filter((d): d is RuleDetector => d !== null);\n\n const covered = custom.flatMap((d) => d.keywords);\n const isCovered = (keywords: string[]) =>\n keywords.some((k) => covered.some((p) => k.startsWith(p) || p.startsWith(k)));\n\n const supplemental: RuleDetector[] = [];\n for (const rule of GITLEAKS_RULES) {\n const keywords = (rule.keywords ?? []).map((k) => k.toLowerCase());\n if (isCovered(keywords)) {\n continue;\n }\n const detector = compile(rule, 'medium');\n if (detector !== null) {\n supplemental.push(detector);\n }\n }\n\n return [...custom, ...supplemental];\n}\n\nexport const RULE_DETECTORS: RuleDetector[] = build();\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ const SECRETS_MODULE = "k6/secrets";
2
+ function findSecretsBinding(script) {
3
+ var _a;
4
+ const match = script.match(/import\s+([A-Za-z_$][\w$]*)\s+from\s+['"]k6\/secrets['"]/);
5
+ return (_a = match == null ? void 0 : match[1]) != null ? _a : null;
6
+ }
7
+ function buildSecretReference(secretName, binding = "secrets") {
8
+ return `await ${binding}.get('${secretName}')`;
9
+ }
10
+ function ensureSecretsImport(script) {
11
+ const existing = findSecretsBinding(script);
12
+ if (existing) {
13
+ return { script, binding: existing, addedImport: false };
14
+ }
15
+ const importLine = `import secrets from '${SECRETS_MODULE}'
16
+ `;
17
+ return {
18
+ script: `${importLine}${script}`,
19
+ binding: "secrets",
20
+ addedImport: true
21
+ };
22
+ }
23
+ function applySecretReference(script, finding, secretName) {
24
+ var _a, _b;
25
+ if (finding.rewrite === void 0 || finding.rewrite === "none") {
26
+ return { text: script, addedImport: false, needsAsync: false, applied: false };
27
+ }
28
+ const target = finding.rewrite === "literal" ? (_a = finding.literal) != null ? _a : finding.range : finding.range;
29
+ if (!script.slice(target.start, target.end).includes(finding.secret)) {
30
+ return { text: script, addedImport: false, needsAsync: false, applied: false };
31
+ }
32
+ const binding = (_b = findSecretsBinding(script)) != null ? _b : "secrets";
33
+ const reference = finding.rewrite === "template-interpolation" ? `\${await ${binding}.get('${secretName}')}` : buildSecretReference(secretName, binding);
34
+ const replaced = script.slice(0, target.start) + reference + script.slice(target.end);
35
+ const { script: withImport, addedImport } = ensureSecretsImport(replaced);
36
+ return { text: withImport, addedImport, needsAsync: true, applied: true };
37
+ }
38
+ const k6SecretRewriter = {
39
+ apply: applySecretReference,
40
+ reference: buildSecretReference
41
+ };
42
+
43
+ export { applySecretReference, buildSecretReference, k6SecretRewriter };
44
+ //# sourceMappingURL=k6Rewriter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"k6Rewriter.js","sources":["../../../../src/secret-scanner/core/k6Rewriter.ts"],"sourcesContent":["/**\n * The k6 {@link SecretRewriter}: rewrites a detected hardcoded secret into a\n * reference to the managed Secrets store, matching the syntax documented in the\n * in-product Secrets guide:\n *\n * import secrets from 'k6/secrets'\n * const value = await secrets.get('my-secret')\n *\n * This is the only k6-specific piece of the library. Everything else — the\n * detectors, the finding shape, the hooks, the panel — is runtime-agnostic.\n */\nimport type { SecretFinding } from './detection/types';\nimport type { ApplyResult, SecretRewriter } from './rewrite';\n\nconst SECRETS_MODULE = 'k6/secrets';\n\nfunction findSecretsBinding(script: string): string | null {\n const match = script.match(/import\\s+([A-Za-z_$][\\w$]*)\\s+from\\s+['\"]k6\\/secrets['\"]/);\n return match?.[1] ?? null;\n}\n\nexport function buildSecretReference(secretName: string, binding = 'secrets'): string {\n return `await ${binding}.get('${secretName}')`;\n}\n\nfunction ensureSecretsImport(script: string): {\n script: string;\n binding: string;\n addedImport: boolean;\n} {\n const existing = findSecretsBinding(script);\n if (existing) {\n return { script, binding: existing, addedImport: false };\n }\n\n const importLine = `import secrets from '${SECRETS_MODULE}'\\n`;\n return {\n script: `${importLine}${script}`,\n binding: 'secrets',\n addedImport: true,\n };\n}\n\nexport function applySecretReference(script: string, finding: SecretFinding, secretName: string): ApplyResult {\n if (finding.rewrite === undefined || finding.rewrite === 'none') {\n return { text: script, addedImport: false, needsAsync: false, applied: false };\n }\n\n const target = finding.rewrite === 'literal' ? (finding.literal ?? finding.range) : finding.range;\n\n // Guard against stale offsets. `finding` is a snapshot; if the script moved\n // under it (auto-format, autosave, or a host migrating several findings from\n // one captured array), the recorded range may no longer contain this secret.\n if (!script.slice(target.start, target.end).includes(finding.secret)) {\n return { text: script, addedImport: false, needsAsync: false, applied: false };\n }\n\n const binding = findSecretsBinding(script) ?? 'secrets';\n const reference =\n finding.rewrite === 'template-interpolation'\n ? `\\${await ${binding}.get('${secretName}')}`\n : buildSecretReference(secretName, binding);\n\n const replaced = script.slice(0, target.start) + reference + script.slice(target.end);\n\n const { script: withImport, addedImport } = ensureSecretsImport(replaced);\n\n return { text: withImport, addedImport, needsAsync: true, applied: true };\n}\n\nexport const k6SecretRewriter: SecretRewriter = {\n apply: applySecretReference,\n reference: buildSecretReference,\n};\n"],"names":[],"mappings":"AAcA,MAAM,cAAA,GAAiB,YAAA;AAEvB,SAAS,mBAAmB,MAAA,EAA+B;AAhB3D,EAAA,IAAA,EAAA;AAiBE,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,0DAA0D,CAAA;AACrF,EAAA,OAAA,CAAO,EAAA,GAAA,KAAA,IAAA,IAAA,GAAA,MAAA,GAAA,KAAA,CAAQ,OAAR,IAAA,GAAA,EAAA,GAAc,IAAA;AACvB;AAEO,SAAS,oBAAA,CAAqB,UAAA,EAAoB,OAAA,GAAU,SAAA,EAAmB;AACpF,EAAA,OAAO,CAAA,MAAA,EAAS,OAAO,CAAA,MAAA,EAAS,UAAU,CAAA,EAAA,CAAA;AAC5C;AAEA,SAAS,oBAAoB,MAAA,EAI3B;AACA,EAAA,MAAM,QAAA,GAAW,mBAAmB,MAAM,CAAA;AAC1C,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,OAAO,EAAE,MAAA,EAAQ,OAAA,EAAS,QAAA,EAAU,aAAa,KAAA,EAAM;AAAA,EACzD;AAEA,EAAA,MAAM,UAAA,GAAa,wBAAwB,cAAc,CAAA;AAAA,CAAA;AACzD,EAAA,OAAO;AAAA,IACL,MAAA,EAAQ,CAAA,EAAG,UAAU,CAAA,EAAG,MAAM,CAAA,CAAA;AAAA,IAC9B,OAAA,EAAS,SAAA;AAAA,IACT,WAAA,EAAa;AAAA,GACf;AACF;AAEO,SAAS,oBAAA,CAAqB,MAAA,EAAgB,OAAA,EAAwB,UAAA,EAAiC;AA3C9G,EAAA,IAAA,EAAA,EAAA,EAAA;AA4CE,EAAA,IAAI,OAAA,CAAQ,OAAA,KAAY,MAAA,IAAa,OAAA,CAAQ,YAAY,MAAA,EAAQ;AAC/D,IAAA,OAAO,EAAE,MAAM,MAAA,EAAQ,WAAA,EAAa,OAAO,UAAA,EAAY,KAAA,EAAO,SAAS,KAAA,EAAM;AAAA,EAC/E;AAEA,EAAA,MAAM,MAAA,GAAS,QAAQ,OAAA,KAAY,SAAA,GAAA,CAAa,aAAQ,OAAA,KAAR,IAAA,GAAA,EAAA,GAAmB,OAAA,CAAQ,KAAA,GAAS,OAAA,CAAQ,KAAA;AAK5F,EAAA,IAAI,CAAC,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,KAAA,EAAO,MAAA,CAAO,GAAG,CAAA,CAAE,QAAA,CAAS,OAAA,CAAQ,MAAM,CAAA,EAAG;AACpE,IAAA,OAAO,EAAE,MAAM,MAAA,EAAQ,WAAA,EAAa,OAAO,UAAA,EAAY,KAAA,EAAO,SAAS,KAAA,EAAM;AAAA,EAC/E;AAEA,EAAA,MAAM,OAAA,GAAA,CAAU,EAAA,GAAA,kBAAA,CAAmB,MAAM,CAAA,KAAzB,IAAA,GAAA,EAAA,GAA8B,SAAA;AAC9C,EAAA,MAAM,SAAA,GACJ,OAAA,CAAQ,OAAA,KAAY,wBAAA,GAChB,CAAA,SAAA,EAAY,OAAO,CAAA,MAAA,EAAS,UAAU,CAAA,GAAA,CAAA,GACtC,oBAAA,CAAqB,UAAA,EAAY,OAAO,CAAA;AAE9C,EAAA,MAAM,QAAA,GAAW,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,MAAA,CAAO,KAAK,CAAA,GAAI,SAAA,GAAY,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,GAAG,CAAA;AAEpF,EAAA,MAAM,EAAE,MAAA,EAAQ,UAAA,EAAY,WAAA,EAAY,GAAI,oBAAoB,QAAQ,CAAA;AAExE,EAAA,OAAO,EAAE,IAAA,EAAM,UAAA,EAAY,aAAa,UAAA,EAAY,IAAA,EAAM,SAAS,IAAA,EAAK;AAC1E;AAEO,MAAM,gBAAA,GAAmC;AAAA,EAC9C,KAAA,EAAO,oBAAA;AAAA,EACP,SAAA,EAAW;AACb;;;;"}
@@ -0,0 +1,18 @@
1
+ function memoizeLatest(compute) {
2
+ let lastInput = null;
3
+ let lastResult = [];
4
+ return (input) => {
5
+ if (!input) {
6
+ return [];
7
+ }
8
+ if (input === lastInput) {
9
+ return lastResult;
10
+ }
11
+ lastInput = input;
12
+ lastResult = compute(input);
13
+ return lastResult;
14
+ };
15
+ }
16
+
17
+ export { memoizeLatest };
18
+ //# sourceMappingURL=memoizeLatest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memoizeLatest.js","sources":["../../../../src/secret-scanner/core/memoizeLatest.ts"],"sourcesContent":["/**\n * Memoizes the latest input only — a scanner sees an evolving document, so anything older is not relevant.\n */\nexport function memoizeLatest<T>(compute: (input: string) => T[]): (input: string) => T[] {\n let lastInput: string | null = null;\n let lastResult: T[] = [];\n\n return (input: string) => {\n if (!input) {\n return [];\n }\n if (input === lastInput) {\n return lastResult;\n }\n lastInput = input;\n lastResult = compute(input);\n return lastResult;\n };\n}\n"],"names":[],"mappings":"AAGO,SAAS,cAAiB,OAAA,EAAyD;AACxF,EAAA,IAAI,SAAA,GAA2B,IAAA;AAC/B,EAAA,IAAI,aAAkB,EAAC;AAEvB,EAAA,OAAO,CAAC,KAAA,KAAkB;AACxB,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,OAAO,EAAC;AAAA,IACV;AACA,IAAA,IAAI,UAAU,SAAA,EAAW;AACvB,MAAA,OAAO,UAAA;AAAA,IACT;AACA,IAAA,SAAA,GAAY,KAAA;AACZ,IAAA,UAAA,GAAa,QAAQ,KAAK,CAAA;AAC1B,IAAA,OAAO,UAAA;AAAA,EACT,CAAA;AACF;;;;"}