@sikkuumiii/defuse 0.0.0-stage → 0.5.0

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 (236) hide show
  1. package/LICENSE +235 -0
  2. package/README.md +561 -2
  3. package/dist/src/cli.d.ts +20 -0
  4. package/dist/src/cli.js +619 -0
  5. package/dist/src/cli.js.map +1 -0
  6. package/dist/src/core/analyze.d.ts +178 -0
  7. package/dist/src/core/analyze.js +834 -0
  8. package/dist/src/core/analyze.js.map +1 -0
  9. package/dist/src/core/coverage.d.ts +87 -0
  10. package/dist/src/core/coverage.js +181 -0
  11. package/dist/src/core/coverage.js.map +1 -0
  12. package/dist/src/core/finding.d.ts +196 -0
  13. package/dist/src/core/finding.js +178 -0
  14. package/dist/src/core/finding.js.map +1 -0
  15. package/dist/src/core/licence.d.ts +50 -0
  16. package/dist/src/core/licence.js +56 -0
  17. package/dist/src/core/licence.js.map +1 -0
  18. package/dist/src/core/paths.d.ts +21 -0
  19. package/dist/src/core/paths.js +58 -0
  20. package/dist/src/core/paths.js.map +1 -0
  21. package/dist/src/core/score.d.ts +137 -0
  22. package/dist/src/core/score.js +232 -0
  23. package/dist/src/core/score.js.map +1 -0
  24. package/dist/src/dast/targets.d.ts +30 -0
  25. package/dist/src/dast/targets.js +79 -0
  26. package/dist/src/dast/targets.js.map +1 -0
  27. package/dist/src/dast/zap.d.ts +42 -0
  28. package/dist/src/dast/zap.js +184 -0
  29. package/dist/src/dast/zap.js.map +1 -0
  30. package/dist/src/engine/scan.d.ts +80 -0
  31. package/dist/src/engine/scan.js +129 -0
  32. package/dist/src/engine/scan.js.map +1 -0
  33. package/dist/src/engine/shapes.d.ts +47 -0
  34. package/dist/src/engine/shapes.js +328 -0
  35. package/dist/src/engine/shapes.js.map +1 -0
  36. package/dist/src/engine/walk.d.ts +60 -0
  37. package/dist/src/engine/walk.js +156 -0
  38. package/dist/src/engine/walk.js.map +1 -0
  39. package/dist/src/parse/astDump.d.ts +31 -0
  40. package/dist/src/parse/astDump.js +66 -0
  41. package/dist/src/parse/astDump.js.map +1 -0
  42. package/dist/src/parse/languages.d.ts +70 -0
  43. package/dist/src/parse/languages.js +174 -0
  44. package/dist/src/parse/languages.js.map +1 -0
  45. package/dist/src/parse/location.d.ts +17 -0
  46. package/dist/src/parse/location.js +38 -0
  47. package/dist/src/parse/location.js.map +1 -0
  48. package/dist/src/parse/node-grammars.d.ts +12 -0
  49. package/dist/src/parse/node-grammars.js +35 -0
  50. package/dist/src/parse/node-grammars.js.map +1 -0
  51. package/dist/src/parse/parser.d.ts +126 -0
  52. package/dist/src/parse/parser.js +202 -0
  53. package/dist/src/parse/parser.js.map +1 -0
  54. package/dist/src/parse/tree-store.d.ts +139 -0
  55. package/dist/src/parse/tree-store.js +236 -0
  56. package/dist/src/parse/tree-store.js.map +1 -0
  57. package/dist/src/platform/dependency-rule.d.ts +27 -0
  58. package/dist/src/platform/dependency-rule.js +21 -0
  59. package/dist/src/platform/dependency-rule.js.map +1 -0
  60. package/dist/src/platform/deps.d.ts +86 -0
  61. package/dist/src/platform/deps.js +304 -0
  62. package/dist/src/platform/deps.js.map +1 -0
  63. package/dist/src/platform/php-facts.d.ts +49 -0
  64. package/dist/src/platform/php-facts.js +67 -0
  65. package/dist/src/platform/php-facts.js.map +1 -0
  66. package/dist/src/platform/runtime-version.d.ts +78 -0
  67. package/dist/src/platform/runtime-version.js +573 -0
  68. package/dist/src/platform/runtime-version.js.map +1 -0
  69. package/dist/src/report/colors.d.ts +50 -0
  70. package/dist/src/report/colors.js +97 -0
  71. package/dist/src/report/colors.js.map +1 -0
  72. package/dist/src/report/coverage-table.d.ts +90 -0
  73. package/dist/src/report/coverage-table.js +291 -0
  74. package/dist/src/report/coverage-table.js.map +1 -0
  75. package/dist/src/report/html-report.d.ts +66 -0
  76. package/dist/src/report/html-report.js +489 -0
  77. package/dist/src/report/html-report.js.map +1 -0
  78. package/dist/src/report/human.d.ts +44 -0
  79. package/dist/src/report/human.js +575 -0
  80. package/dist/src/report/human.js.map +1 -0
  81. package/dist/src/report/json.d.ts +24 -0
  82. package/dist/src/report/json.js +178 -0
  83. package/dist/src/report/json.js.map +1 -0
  84. package/dist/src/report/mark.d.ts +17 -0
  85. package/dist/src/report/mark.js +39 -0
  86. package/dist/src/report/mark.js.map +1 -0
  87. package/dist/src/report/pricing.d.ts +33 -0
  88. package/dist/src/report/pricing.js +343 -0
  89. package/dist/src/report/pricing.js.map +1 -0
  90. package/dist/src/report/sarif.d.ts +39 -0
  91. package/dist/src/report/sarif.js +219 -0
  92. package/dist/src/report/sarif.js.map +1 -0
  93. package/dist/src/report/site.d.ts +87 -0
  94. package/dist/src/report/site.js +742 -0
  95. package/dist/src/report/site.js.map +1 -0
  96. package/dist/src/rules/code-injection.d.ts +47 -0
  97. package/dist/src/rules/code-injection.js +326 -0
  98. package/dist/src/rules/code-injection.js.map +1 -0
  99. package/dist/src/rules/command-injection.d.ts +30 -0
  100. package/dist/src/rules/command-injection.js +286 -0
  101. package/dist/src/rules/command-injection.js.map +1 -0
  102. package/dist/src/rules/contract.d.ts +198 -0
  103. package/dist/src/rules/contract.js +39 -0
  104. package/dist/src/rules/contract.js.map +1 -0
  105. package/dist/src/rules/format-string.d.ts +46 -0
  106. package/dist/src/rules/format-string.js +146 -0
  107. package/dist/src/rules/format-string.js.map +1 -0
  108. package/dist/src/rules/hardcoded-secret.d.ts +38 -0
  109. package/dist/src/rules/hardcoded-secret.js +440 -0
  110. package/dist/src/rules/hardcoded-secret.js.map +1 -0
  111. package/dist/src/rules/lib/guards.d.ts +168 -0
  112. package/dist/src/rules/lib/guards.js +1179 -0
  113. package/dist/src/rules/lib/guards.js.map +1 -0
  114. package/dist/src/rules/lib/html-safety.d.ts +75 -0
  115. package/dist/src/rules/lib/html-safety.js +755 -0
  116. package/dist/src/rules/lib/html-safety.js.map +1 -0
  117. package/dist/src/rules/lib/local-build.d.ts +59 -0
  118. package/dist/src/rules/lib/local-build.js +320 -0
  119. package/dist/src/rules/lib/local-build.js.map +1 -0
  120. package/dist/src/rules/lib/strings.d.ts +208 -0
  121. package/dist/src/rules/lib/strings.js +1970 -0
  122. package/dist/src/rules/lib/strings.js.map +1 -0
  123. package/dist/src/rules/path-traversal.d.ts +33 -0
  124. package/dist/src/rules/path-traversal.js +96 -0
  125. package/dist/src/rules/path-traversal.js.map +1 -0
  126. package/dist/src/rules/registry.d.ts +19 -0
  127. package/dist/src/rules/registry.js +77 -0
  128. package/dist/src/rules/registry.js.map +1 -0
  129. package/dist/src/rules/sql-injection.d.ts +36 -0
  130. package/dist/src/rules/sql-injection.js +299 -0
  131. package/dist/src/rules/sql-injection.js.map +1 -0
  132. package/dist/src/rules/ssrf.d.ts +39 -0
  133. package/dist/src/rules/ssrf.js +111 -0
  134. package/dist/src/rules/ssrf.js.map +1 -0
  135. package/dist/src/rules/unbounded-copy.d.ts +37 -0
  136. package/dist/src/rules/unbounded-copy.js +147 -0
  137. package/dist/src/rules/unbounded-copy.js.map +1 -0
  138. package/dist/src/rules/unsafe-deserialization.d.ts +43 -0
  139. package/dist/src/rules/unsafe-deserialization.js +276 -0
  140. package/dist/src/rules/unsafe-deserialization.js.map +1 -0
  141. package/dist/src/rules/weak-hash.d.ts +37 -0
  142. package/dist/src/rules/weak-hash.js +197 -0
  143. package/dist/src/rules/weak-hash.js.map +1 -0
  144. package/dist/src/rules/xss.d.ts +38 -0
  145. package/dist/src/rules/xss.js +527 -0
  146. package/dist/src/rules/xss.js.map +1 -0
  147. package/dist/src/taint/content-type.d.ts +19 -0
  148. package/dist/src/taint/content-type.js +20 -0
  149. package/dist/src/taint/content-type.js.map +1 -0
  150. package/dist/src/taint/dictionaries.d.ts +23 -0
  151. package/dist/src/taint/dictionaries.js +1778 -0
  152. package/dist/src/taint/dictionaries.js.map +1 -0
  153. package/dist/src/taint/fold.d.ts +59 -0
  154. package/dist/src/taint/fold.js +1177 -0
  155. package/dist/src/taint/fold.js.map +1 -0
  156. package/dist/src/taint/java-scope.d.ts +61 -0
  157. package/dist/src/taint/java-scope.js +212 -0
  158. package/dist/src/taint/java-scope.js.map +1 -0
  159. package/dist/src/taint/local-map.d.ts +79 -0
  160. package/dist/src/taint/local-map.js +603 -0
  161. package/dist/src/taint/local-map.js.map +1 -0
  162. package/dist/src/taint/project.d.ts +172 -0
  163. package/dist/src/taint/project.js +617 -0
  164. package/dist/src/taint/project.js.map +1 -0
  165. package/dist/src/taint/tracer.d.ts +265 -0
  166. package/dist/src/taint/tracer.js +3817 -0
  167. package/dist/src/taint/tracer.js.map +1 -0
  168. package/dist/src/taint/types.d.ts +484 -0
  169. package/dist/src/taint/types.js +47 -0
  170. package/dist/src/taint/types.js.map +1 -0
  171. package/dist/src/taint/visibility.d.ts +25 -0
  172. package/dist/src/taint/visibility.js +96 -0
  173. package/dist/src/taint/visibility.js.map +1 -0
  174. package/docs/examples/defuse.json +20 -0
  175. package/docs/examples/dvwa-urls.json +14 -0
  176. package/docs/examples/github-code-scanning.yml +54 -0
  177. package/docs/examples/tiny.js +4 -0
  178. package/package.json +73 -4
  179. package/src/cli.ts +747 -0
  180. package/src/core/analyze.ts +1072 -0
  181. package/src/core/coverage.ts +269 -0
  182. package/src/core/finding.ts +311 -0
  183. package/src/core/licence.ts +61 -0
  184. package/src/core/paths.ts +59 -0
  185. package/src/core/score.ts +353 -0
  186. package/src/dast/targets.ts +121 -0
  187. package/src/dast/zap.ts +246 -0
  188. package/src/engine/scan.ts +200 -0
  189. package/src/engine/shapes.ts +348 -0
  190. package/src/engine/walk.ts +196 -0
  191. package/src/parse/astDump.ts +85 -0
  192. package/src/parse/languages.ts +232 -0
  193. package/src/parse/location.ts +44 -0
  194. package/src/parse/node-grammars.ts +41 -0
  195. package/src/parse/parser.ts +262 -0
  196. package/src/parse/tree-store.ts +301 -0
  197. package/src/platform/dependency-rule.ts +33 -0
  198. package/src/platform/deps.ts +374 -0
  199. package/src/platform/php-facts.ts +85 -0
  200. package/src/platform/runtime-version.ts +710 -0
  201. package/src/report/colors.ts +107 -0
  202. package/src/report/coverage-table.ts +359 -0
  203. package/src/report/html-report.ts +601 -0
  204. package/src/report/human.ts +811 -0
  205. package/src/report/json.ts +189 -0
  206. package/src/report/mark.ts +47 -0
  207. package/src/report/pricing.ts +374 -0
  208. package/src/report/sarif.ts +235 -0
  209. package/src/report/site.ts +760 -0
  210. package/src/rules/code-injection.ts +346 -0
  211. package/src/rules/command-injection.ts +296 -0
  212. package/src/rules/contract.ts +220 -0
  213. package/src/rules/format-string.ts +158 -0
  214. package/src/rules/hardcoded-secret.ts +485 -0
  215. package/src/rules/lib/guards.ts +1235 -0
  216. package/src/rules/lib/html-safety.ts +793 -0
  217. package/src/rules/lib/local-build.ts +329 -0
  218. package/src/rules/lib/strings.ts +2034 -0
  219. package/src/rules/path-traversal.ts +101 -0
  220. package/src/rules/registry.ts +90 -0
  221. package/src/rules/sql-injection.ts +311 -0
  222. package/src/rules/ssrf.ts +118 -0
  223. package/src/rules/unbounded-copy.ts +162 -0
  224. package/src/rules/unsafe-deserialization.ts +291 -0
  225. package/src/rules/weak-hash.ts +226 -0
  226. package/src/rules/xss.ts +572 -0
  227. package/src/taint/content-type.ts +20 -0
  228. package/src/taint/dictionaries.ts +1855 -0
  229. package/src/taint/fold.ts +1206 -0
  230. package/src/taint/java-scope.ts +200 -0
  231. package/src/taint/local-map.ts +594 -0
  232. package/src/taint/project.ts +774 -0
  233. package/src/taint/tracer.ts +4298 -0
  234. package/src/taint/types.ts +511 -0
  235. package/src/taint/visibility.ts +100 -0
  236. package/tsconfig.json +61 -0
@@ -0,0 +1,146 @@
1
+ /**
2
+ * FORMAT STRING INJECTION (CWE-134)
3
+ *
4
+ * WHAT THE BUG IS.
5
+ *
6
+ * printf(name); // `name` came from the user
7
+ *
8
+ * looks like printing a string and is not. printf reads its first argument as a
9
+ * FORMAT, so whoever controls that string controls the conversions too:
10
+ *
11
+ * %x %x %x %x walks up the stack and prints whatever is sitting there
12
+ * %s treats a stack value as a pointer - crash, or a leak
13
+ * %n WRITES the number of bytes printed so far to a pointer
14
+ * argument, which turns a logging call into an arbitrary
15
+ * memory write
16
+ *
17
+ * That last conversion is why this is not a cosmetic bug. It is the standard
18
+ * route from "the program logs a username" to "the attacker runs code".
19
+ *
20
+ * WHY THIS RULE IS THE BEST ARGUMENT FOR POINTING A TAINT ENGINE AT C.
21
+ *
22
+ * Most famous C bugs are SIZE bugs - is this buffer big enough, is this integer
23
+ * about to wrap - and answering those needs an analysis of sizes, allocations
24
+ * and aliasing that this engine does not have. The unbounded-copy rule next
25
+ * door is careful to claim much less than its name suggests for exactly that
26
+ * reason.
27
+ *
28
+ * A format string bug needs none of it. The entire question is "is this ONE
29
+ * argument attacker-controlled?", which is the only question this engine
30
+ * answers, and it answers it with a printed path. So a flow-verified finding
31
+ * here means precisely what it says, with no hedge underneath it.
32
+ *
33
+ * THE TRAP THIS RULE MUST NOT FALL INTO.
34
+ *
35
+ * printf(name) dangerous
36
+ * printf("%s", name) correct
37
+ *
38
+ * Same function, one extra argument - the identical shape as a parameterised
39
+ * SQL query versus a concatenated one. A rule that fired on "printf with a
40
+ * tainted argument" would report the FIX as the bug, which this project treats
41
+ * as worse than a miss: it teaches people that hardening does not help. Only
42
+ * the format argument counts, and its index differs per function, so the table
43
+ * below is the whole rule.
44
+ */
45
+ import { asCall } from './contract.js';
46
+ import { isStringLiteral } from './lib/strings.js';
47
+ /**
48
+ * Function name -> which argument is the FORMAT.
49
+ *
50
+ * Getting an index wrong here does not just miss a bug, it reports correct
51
+ * code, so each one is written out rather than inferred from the name.
52
+ */
53
+ const FORMAT_ARGUMENT = new Map([
54
+ ['printf', 0],
55
+ ['vprintf', 0],
56
+ ['fprintf', 1],
57
+ ['sprintf', 1],
58
+ ['vfprintf', 1],
59
+ ['vsprintf', 1],
60
+ ['dprintf', 1],
61
+ ['asprintf', 1],
62
+ ['syslog', 1],
63
+ ['snprintf', 2],
64
+ ['vsnprintf', 2],
65
+ ]);
66
+ const C_FAMILY = new Set(['c', 'cpp']);
67
+ const NOT_C = {
68
+ status: 'not-implemented',
69
+ note: 'Format string injection is a C-family bug. Other languages either take the format ' +
70
+ 'as a constant by construction or have no %n equivalent, so there is nothing here to ' +
71
+ 'match and this rule never fires. Stated rather than left as a blank column.',
72
+ };
73
+ export const formatStringRule = {
74
+ id: 'format-string',
75
+ name: 'Attacker-controlled format string',
76
+ cwe: 'CWE-134',
77
+ owasp: 'A05:2025 Injection',
78
+ severity: 'high',
79
+ summary: 'The format string is not fixed; if it comes from outside, it can read or even write memory.',
80
+ fix: 'Make the format a fixed string and pass the value separately: printf("%s", name), not printf(name).',
81
+ explanation: 'A format string is a small program: %x reads, %s dereferences, and %n WRITES. When ' +
82
+ 'the format itself comes from the user, they are writing that program. The fix is ' +
83
+ 'always the same shape and always cheap - make the format a constant and pass the ' +
84
+ 'value as data: printf("%s", name) instead of printf(name).',
85
+ limitations: 'A signature-based finding here means only that the format argument is not a literal ' +
86
+ 'in this call - it may be a constant defined elsewhere, which we cannot see because ' +
87
+ 'the preprocessor is not run and constants are not folded. A flow-verified finding ' +
88
+ 'means the value was traced from an attacker-controlled source into the format ' +
89
+ 'position, and that claim carries its path.',
90
+ shapes: ['call'],
91
+ support: {
92
+ c: {
93
+ status: 'implemented',
94
+ note: 'Covers the printf family (printf, fprintf, sprintf, snprintf, dprintf, asprintf, ' +
95
+ 'syslog and their v- variants), with the format argument index recorded per ' +
96
+ 'function so that the correct form - a constant format with the value passed as ' +
97
+ 'data - is never reported. A format built by a macro is missed: the preprocessor ' +
98
+ 'is not run.',
99
+ },
100
+ cpp: {
101
+ status: 'implemented',
102
+ note: 'Identical to C. std::format and iostreams are type-safe and cannot carry this ' +
103
+ 'bug, so they are deliberately not matched; C++ code that calls printf still can, ' +
104
+ 'and still does.',
105
+ },
106
+ javascript: NOT_C,
107
+ typescript: NOT_C,
108
+ python: NOT_C,
109
+ java: NOT_C,
110
+ go: NOT_C,
111
+ php: NOT_C,
112
+ },
113
+ check(shape, ctx) {
114
+ if (!C_FAMILY.has(ctx.language))
115
+ return null;
116
+ const call = asCall(shape);
117
+ if (!call)
118
+ return null;
119
+ const index = FORMAT_ARGUMENT.get(call.calleeName);
120
+ if (index === undefined)
121
+ return null;
122
+ const formatArgument = call.args[index];
123
+ // No argument in the format position at all - nothing to say about it.
124
+ if (!formatArgument)
125
+ return null;
126
+ /*
127
+ * A literal format is the SAFE form and the whole point of the fix. It is
128
+ * checked first and returns null, so `printf("%s", name)` can never be
129
+ * reported however dirty `name` is.
130
+ */
131
+ if (isStringLiteral(formatArgument, ctx.language))
132
+ return null;
133
+ const argumentText = ctx.text(formatArgument);
134
+ return {
135
+ node: call.node,
136
+ message: `${call.calleeName}() is called with a non-constant format string (\`${argumentText}\`)`,
137
+ reasoning: `The format argument of \`${call.calleeName}()\` is \`${argumentText}\`, which is not a ` +
138
+ `string literal. A format string is executable: \`%x\` reads values off the stack, ` +
139
+ `\`%s\` dereferences one as a pointer, and \`%n\` writes to memory. If that value ` +
140
+ `can be influenced by a user, this is a memory-write primitive rather than a print ` +
141
+ `statement. The fix is to make the format constant and pass the value as data: ` +
142
+ `\`${call.calleeName}(${index === 0 ? '' : '..., '}"%s", ${argumentText})\`.`,
143
+ };
144
+ },
145
+ };
146
+ //# sourceMappingURL=format-string.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format-string.js","sourceRoot":"","sources":["../../../src/rules/format-string.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * HARDCODED CREDENTIALS (CWE-798, OWASP A07:2025 - Authentication
3
+ * Authentication Failures; also A05 Security Misconfiguration)
4
+ *
5
+ * WHAT THE BUG IS, in plain language:
6
+ * A password, API key or private key written directly into source code is a
7
+ * secret that has already partly escaped. It is in your git history forever
8
+ * (deleting the line does not remove it - `git log -p` still has it), it is on
9
+ * every laptop that ever cloned the repo, it is in your CI logs and your
10
+ * backups, and every contractor who read the code knows it. You also cannot
11
+ * rotate it without a code change and a deploy.
12
+ *
13
+ * This is the least glamorous vulnerability class and, empirically, one of the
14
+ * most exploited: attackers scan public repositories for key formats
15
+ * continuously, and a leaked cloud key can become a bill or a breach in minutes.
16
+ *
17
+ * THE FIX:
18
+ * Keep secrets outside the code - environment variables, a secrets manager
19
+ * (Vault, AWS Secrets Manager, Doppler), or a config file that is gitignored.
20
+ * The code should read a NAME, never a VALUE. And if a secret was ever
21
+ * committed, rotate it; scrubbing history is not enough, because you cannot
22
+ * un-copy something.
23
+ *
24
+ * HOW THIS RULE DECIDES - three signals, deliberately separate:
25
+ * 1. KNOWN FORMAT - the text matches a real vendor's key shape
26
+ * (AKIA..., ghp_..., sk_live_...). Very strong.
27
+ * 2. SUSPICIOUS NAME - the variable is called `password`, `apiKey`,
28
+ * `secret`... and holds a literal string. Strong.
29
+ * 3. HIGH ENTROPY - a long, random-looking literal with no meaningful
30
+ * name. Weak - this is the one that produces false
31
+ * positives, so it gets lower severity and the finding
32
+ * says explicitly that it is an entropy guess.
33
+ * Reporting all three at the same confidence would be exactly the dishonesty
34
+ * this project rejects, so they carry different severities and different
35
+ * reasoning text.
36
+ */
37
+ import { type Rule } from './contract.js';
38
+ export declare const hardcodedSecretRule: Rule;
@@ -0,0 +1,440 @@
1
+ /**
2
+ * HARDCODED CREDENTIALS (CWE-798, OWASP A07:2025 - Authentication
3
+ * Authentication Failures; also A05 Security Misconfiguration)
4
+ *
5
+ * WHAT THE BUG IS, in plain language:
6
+ * A password, API key or private key written directly into source code is a
7
+ * secret that has already partly escaped. It is in your git history forever
8
+ * (deleting the line does not remove it - `git log -p` still has it), it is on
9
+ * every laptop that ever cloned the repo, it is in your CI logs and your
10
+ * backups, and every contractor who read the code knows it. You also cannot
11
+ * rotate it without a code change and a deploy.
12
+ *
13
+ * This is the least glamorous vulnerability class and, empirically, one of the
14
+ * most exploited: attackers scan public repositories for key formats
15
+ * continuously, and a leaked cloud key can become a bill or a breach in minutes.
16
+ *
17
+ * THE FIX:
18
+ * Keep secrets outside the code - environment variables, a secrets manager
19
+ * (Vault, AWS Secrets Manager, Doppler), or a config file that is gitignored.
20
+ * The code should read a NAME, never a VALUE. And if a secret was ever
21
+ * committed, rotate it; scrubbing history is not enough, because you cannot
22
+ * un-copy something.
23
+ *
24
+ * HOW THIS RULE DECIDES - three signals, deliberately separate:
25
+ * 1. KNOWN FORMAT - the text matches a real vendor's key shape
26
+ * (AKIA..., ghp_..., sk_live_...). Very strong.
27
+ * 2. SUSPICIOUS NAME - the variable is called `password`, `apiKey`,
28
+ * `secret`... and holds a literal string. Strong.
29
+ * 3. HIGH ENTROPY - a long, random-looking literal with no meaningful
30
+ * name. Weak - this is the one that produces false
31
+ * positives, so it gets lower severity and the finding
32
+ * says explicitly that it is an entropy guess.
33
+ * Reporting all three at the same confidence would be exactly the dishonesty
34
+ * this project rejects, so they carry different severities and different
35
+ * reasoning text.
36
+ */
37
+ import { asAssignment, asCall } from './contract.js';
38
+ import { isLikelyPlaceholder, looksLikeSql, looksLikeProse, echoesItsOwnName, isStructuralIdentifier, isStringLiteral, isTestPath, looksLikeToken, matchKnownSecret, shannonEntropy, unquote, } from './lib/strings.js';
39
+ /** Variable/field names that suggest the value is a credential. */
40
+ const SECRET_NAME = new RegExp([
41
+ 'pass(word|wd|phrase)?',
42
+ 'secret',
43
+ 'token',
44
+ 'api[_-]?key',
45
+ 'apikey',
46
+ 'access[_-]?key',
47
+ 'secret[_-]?key',
48
+ 'private[_-]?key',
49
+ 'client[_-]?secret',
50
+ 'encryption[_-]?key',
51
+ 'signing[_-]?key',
52
+ // The lookahead matters: without it, "auth" matches inside "author",
53
+ // "authorize" and Gin's "AuthUserKey". Found by scanning real repositories.
54
+ 'auth[_-]?(token|key|header|secret)?(?![A-Za-z])',
55
+ 'credential',
56
+ 'bearer',
57
+ 'jwt[_-]?secret',
58
+ 'session[_-]?secret',
59
+ 'connection[_-]?string',
60
+ 'conn[_-]?str',
61
+ 'dsn',
62
+ ].join('|'), 'i');
63
+ /**
64
+ * Names that MATCH the pattern above but are not secrets: they hold the name of
65
+ * a secret, its location, or a boolean about it. Checked first.
66
+ *
67
+ * `prefix`, `suffix` and `source` came from Django, where all three were HIGH
68
+ * findings: SECRET_KEY_INSECURE_PREFIX = "django-insecure-" is the prefix the
69
+ * code checks keys FOR, and token_source = "POST" is where a token is read
70
+ * from (tests/fixtures/safe/secret-shaped-names.py).
71
+ */
72
+ const NOT_A_SECRET = /(_?(name|field|label|header|type|env|var|path|file|url|uri|prompt|placeholder|examples?|samples?|hash|algo|algorithm|regex|pattern|length|expiry|ttl|enabled|required|prefix|suffix|source)$)|^(has|is|use|should|allow|require)/i;
73
+ /**
74
+ * A VALUE THAT NAMES A KEY SLOT, not one that fills it:
75
+ *
76
+ * INTERNAL_RESET_SESSION_TOKEN = "_password_reset_token"
77
+ *
78
+ * is the session key Django stores the reset token UNDER. Lowercase
79
+ * snake_case, letters only, at least two words, one of which is a slot noun.
80
+ * Anything with a digit or a capital still reports - generated values carry
81
+ * one or the other - and so does snake_case without a slot word
82
+ * (`DB_PASSWORD = "admin_pass"`, tests/fixtures/vulnerable/secret-near-misses.py).
83
+ * The cost: a human-chosen secret spelled like a key name, `my_secret_token`,
84
+ * is not reported, and the rule's limitations say so.
85
+ */
86
+ const KEY_SLOT_NAME = /^_?[a-z]+(?:_[a-z]+)+$/;
87
+ const SLOT_NOUNS = new Set(['password', 'passwd', 'token', 'secret', 'key', 'session', 'csrf', 'cookie', 'nonce']);
88
+ function namesAKeySlot(value) {
89
+ if (!KEY_SLOT_NAME.test(value))
90
+ return false;
91
+ return value.split('_').some((word) => SLOT_NOUNS.has(word));
92
+ }
93
+ /**
94
+ * A STATEMENT OR TEMPLATE THE SECRET IS SUBSTITUTED INTO:
95
+ *
96
+ * set_password = 'ALTER USER %(user)s IDENTIFIED BY "%(password)s"'
97
+ *
98
+ * from Django's Oracle backend. The password is the placeholder, filled in
99
+ * later; the string is SQL. A Spring `{noop}hunter22` is not a template - the
100
+ * brace form is not read here at all.
101
+ */
102
+ /**
103
+ * A LEXER'S TOKEN, NOT AN AUTH TOKEN.
104
+ *
105
+ * Django's template engine builds operator tokens for `{% if %}` -
106
+ * `token = "is not"`, `token = "not in"` in django/template/smartif.py - and
107
+ * both were reported as hardcoded credentials because of the variable name.
108
+ *
109
+ * The test is narrow on purpose. The name's only credential word must be
110
+ * `token` (`sessionToken` is a credential slot whatever it holds), and EVERY
111
+ * word of the value must be a programming-language keyword or an operator.
112
+ * One ordinary word - "manny is cool" - and it is a passphrase again.
113
+ */
114
+ const LANGUAGE_WORDS = new Set([
115
+ 'is', 'not', 'in', 'and', 'or', 'if', 'else', 'elif', 'for', 'while', 'def', 'class',
116
+ 'return', 'true', 'false', 'none', 'null', 'nil', 'undefined', 'new', 'of', 'as', 'with',
117
+ 'from', 'import', 'lambda', 'yield', 'await', 'async', 'try', 'except', 'finally', 'raise',
118
+ 'throw', 'catch', 'case', 'switch', 'default', 'break', 'continue', 'pass', 'do', 'then',
119
+ 'end', 'endif', 'endfor', 'block', 'endblock', 'let', 'var', 'const', 'typeof', 'instanceof',
120
+ ]);
121
+ const OTHER_CREDENTIAL_WORD = /pass|secret|key|auth|credential|bearer|jwt|session|access|refresh|csrf|api/i;
122
+ function isLanguageToken(name, value) {
123
+ if (!/token/i.test(name) || OTHER_CREDENTIAL_WORD.test(name))
124
+ return false;
125
+ const words = value.trim().split(/\s+/).filter(Boolean);
126
+ return words.length > 0 && words.every((w) => LANGUAGE_WORDS.has(w.toLowerCase()) || /^[^\w\s]+$/.test(w));
127
+ }
128
+ function isStatementOrTemplate(value) {
129
+ return looksLikeSql(value) || /%\([A-Za-z_]\w*\)[sdr]/.test(value);
130
+ }
131
+ /** Entropy threshold above which a long literal starts looking machine-generated. */
132
+ const ENTROPY_THRESHOLD = 4.0;
133
+ const MIN_ENTROPY_LENGTH = 24;
134
+ function literalOf(node, language) {
135
+ if (!isStringLiteral(node, language))
136
+ return null;
137
+ const text = unquote((node.text ?? '').trim());
138
+ return { node, text };
139
+ }
140
+ /**
141
+ * PUBLIC BY DESIGN: a blockchain address, or a file name.
142
+ *
143
+ * Juice Shop's Web3 pages produced twenty "credentials": contract and wallet
144
+ * ADDRESSES - `BeeTokenAddress = '0x3643...'`, Bitcoin `address: '1AbK...'` -
145
+ * which are published on a public ledger and are the opposite of secret, and a
146
+ * map of Solidity compiler file names. An address is only let off when the
147
+ * name says address or does not say credential at all: `secretKey = '0x' + 40
148
+ * hex digits` still reports. A wallet's PRIVATE key is 64 hex digits, not 40,
149
+ * so it never matches here (tests/fixtures/vulnerable/secret-arguments.js).
150
+ */
151
+ const ETH_ADDRESS = /^0x[0-9a-fA-F]{40}$/;
152
+ const BASE58_ADDRESS = /^(?:[13][1-9A-HJ-NP-Za-km-z]{25,34}|X[1-9A-HJ-NP-Za-km-z]{33}|(?:bc|tb)1[02-9ac-hj-np-z]{11,71})$/;
153
+ const ADDRESS_NAME = /address|addr|wallet|contract|recipient|payee/i;
154
+ const FILE_NAME = /^[\w.+@-]+\.(?:js|mjs|cjs|json|wasm|css|map|html?|png|jpe?g|gif|svg|ico|txt|md|xml|ya?ml|zip|gz|tgz|tar|pdf|woff2?|ttf)$/i;
155
+ function isPublicIdentifier(name, value) {
156
+ if (FILE_NAME.test(value))
157
+ return true;
158
+ const addressShaped = ETH_ADDRESS.test(value) || BASE58_ADDRESS.test(value);
159
+ return addressShaped && (ADDRESS_NAME.test(name) || !SECRET_NAME.test(name));
160
+ }
161
+ const JS_KEY_ARGUMENTS = [
162
+ { callee: 'createHmac', index: 1 },
163
+ { callee: 'createCipheriv', index: 1 },
164
+ { callee: 'createDecipheriv', index: 1 },
165
+ { callee: 'createSecretKey', index: 0 },
166
+ { callee: 'sign', receiver: /^(jwt|jsonwebtoken)$/i, index: 1 },
167
+ { callee: 'verify', receiver: /^(jwt|jsonwebtoken)$/i, index: 1 },
168
+ ];
169
+ const KEY_ARGUMENTS = {
170
+ javascript: JS_KEY_ARGUMENTS,
171
+ typescript: JS_KEY_ARGUMENTS,
172
+ python: [
173
+ { callee: 'new', receiver: /^hmac$/, index: 0 },
174
+ { callee: 'encode', receiver: /^jwt$/, index: 1 },
175
+ { callee: 'decode', receiver: /^jwt$/, index: 1 },
176
+ ],
177
+ php: [
178
+ { callee: 'hash_hmac', index: 2 },
179
+ { callee: 'openssl_encrypt', index: 2 },
180
+ { callee: 'openssl_decrypt', index: 2 },
181
+ { callee: 'encode', receiver: /(^|\\)JWT$/, index: 1 },
182
+ ],
183
+ };
184
+ const TEMPLATE_REFERENCE = /^\$\{?\w+\}?$|^%\(\w+\)s$|^<[^>]+>$|^\{\{.*\}\}$/;
185
+ export const hardcodedSecretRule = {
186
+ id: 'hardcoded-secret',
187
+ name: 'Credential written directly into source code',
188
+ cwe: 'CWE-798',
189
+ owasp: 'A07:2025 Authentication Failures',
190
+ severity: 'high',
191
+ summary: 'A password or key is written in the code, so anyone with the code or its git history has it.',
192
+ fix: 'Move it to an environment variable or secrets manager, read it by name, and rotate the exposed one.',
193
+ explanation: 'A password or API key written into source code lives in git history forever, on ' +
194
+ 'every machine that cloned the repo, and in every CI log. It cannot be rotated ' +
195
+ 'without a deploy, and public repositories are scanned for key formats ' +
196
+ 'continuously. Secrets belong in environment variables or a secrets manager; the ' +
197
+ 'code should reference a name, never a value.',
198
+ limitations: 'UNVERIFIED (signature-based): we matched a name pattern, a known vendor key format, ' +
199
+ 'or a randomness score. We cannot tell a real production key from a test fixture, ' +
200
+ 'a rotated-and-dead key, or a deliberately fake example. Entropy-only hits in ' +
201
+ 'particular are a guess about how random text looks, nothing more. ' +
202
+ 'DELIBERATE GAP: values matching a vendor format but ending in EXAMPLE, or with ' +
203
+ 'the random portion filled by 0000/1234567890, are treated as published ' +
204
+ 'documentation placeholders (AWS prints AKIAIOSFODNN7EXAMPLE in its own docs) and ' +
205
+ 'are NOT reported. A real key that happens to end in EXAMPLE would be missed. ' +
206
+ 'Also not reported: a value under a name ending in prefix, suffix or source; a value ' +
207
+ 'that is an SQL statement or a %(name)s template; and a lowercase snake_case value ' +
208
+ 'that names a key slot (`_password_reset_token`) - so a human-chosen secret spelled ' +
209
+ 'like one, `my_secret_token`, would be missed. Nor a value under a name whose only ' +
210
+ 'credential word is `token` when every word of it is a programming keyword or operator ' +
211
+ '(`token = "is not"` in a template lexer) - a secret made only of such words would be missed. ' +
212
+ 'Nor a blockchain address (`0x` and 40 hex digits, or a Bitcoin-style address) under a name that ' +
213
+ 'says address or does not say credential, nor a value that is a file name (`soljson-v0.8.21.js`): ' +
214
+ 'both are public by design. A key passed straight to a call is reported only for the calls listed ' +
215
+ 'in KEY_ARGUMENTS (createHmac, createCipheriv, jwt.sign / verify, hmac.new, jwt.encode / decode, ' +
216
+ 'hash_hmac, openssl_encrypt / decrypt, JWT::encode); any other function that takes a key is not read.',
217
+ shapes: ['assignment', 'call'],
218
+ support: {
219
+ javascript: {
220
+ status: 'implemented',
221
+ note: 'Covers variable declarations, assignments and object-literal entries ({ apiKey: "..." }).',
222
+ },
223
+ typescript: {
224
+ status: 'implemented',
225
+ note: 'Additionally covers class property definitions (`private token = "..."`).',
226
+ },
227
+ python: {
228
+ status: 'implemented',
229
+ note: 'Covers assignments, dict literals and keyword arguments (connect(password="...")).',
230
+ },
231
+ java: {
232
+ status: 'implemented',
233
+ note: 'Covers field and local variable initialisers, including `static final String`. Values read from .properties files are not analysed - this tool parses code, not config.',
234
+ },
235
+ go: {
236
+ status: 'implemented',
237
+ note: 'Covers :=, var, const and struct literal fields (Config{ApiKey: "..."}).',
238
+ },
239
+ php: {
240
+ status: 'implemented',
241
+ note: 'Covers $var = "..." and class property declarations. NOT covered: define(\'API_KEY\', \'...\') and const declarations, which are a different shape - a documented gap, not a silent one.',
242
+ },
243
+ c: {
244
+ status: 'implemented',
245
+ note: 'String literals and initialisers are analysed exactly as in every other language - C has no interpolation, so a credential is either a literal or it is not. NOT COVERED: a secret assembled by the preprocessor (#define KEY "...") is invisible, because the preprocessor is not run.',
246
+ },
247
+ cpp: {
248
+ status: 'implemented',
249
+ note: 'Every C entry applies unchanged, since C++ inherits the whole libc surface and real code still uses it. Namespace-qualified calls (std::system) and method calls on objects are recognised in addition.',
250
+ },
251
+ },
252
+ check(shape, ctx) {
253
+ const language = ctx.language;
254
+ /**
255
+ * Test and example files get a severity downgrade, not a free pass.
256
+ * Measured on five real repositories, 167 of 179 raw findings were in test
257
+ * trees. Dropping them silently would be dishonest (a committed key is
258
+ * committed wherever it lives) and reporting them as HIGH buries the real
259
+ * ones. So: keep the finding, lower the severity, say why in the reasoning.
260
+ *
261
+ * THE SEVERITY PART OF THIS NOW HAPPENS IN core/analyze.ts, for every rule
262
+ * rather than only this one - see the note there. What stays here is the
263
+ * sentence only a credential rule can write: that the VALUE is likely a
264
+ * stand-in. `eval()` in a test is still a real eval; `password = 'x'` in a
265
+ * test is usually not a real password, and that difference is worth saying.
266
+ */
267
+ const inTests = isTestPath(ctx.file.path);
268
+ const testNote = inTests
269
+ ? ' NOTE: in a test or fixture file the value is usually a stand-in rather than a ' +
270
+ 'live credential. It is still committed to git history, and fixtures do get ' +
271
+ 'copy-pasted into production.'
272
+ : '';
273
+ const downgrade = (severity) => severity;
274
+ /* ---- Any string literal argument that matches a known vendor format ----
275
+ * e.g. client.init("AKIAIOSFODNN7EXAMPLE"). No variable name to go on, but
276
+ * the shape of the value alone is conclusive enough to report. */
277
+ const call = asCall(shape);
278
+ if (call) {
279
+ for (const arg of call.args) {
280
+ const literal = literalOf(arg, language);
281
+ if (!literal)
282
+ continue;
283
+ const known = matchKnownSecret(literal.text);
284
+ if (!known)
285
+ continue;
286
+ return {
287
+ node: arg,
288
+ severity: downgrade('critical'),
289
+ message: `${known.label} passed literally to ${call.calleeText}()`,
290
+ reasoning: `The string passed to \`${call.calleeText}()\` matches the published format of ` +
291
+ `a ${known.label}. Vendor key formats are distinctive enough that automated ` +
292
+ `scrapers search public code for exactly this pattern. Treat this key as ` +
293
+ `already compromised and rotate it, then move it to an environment variable.` +
294
+ testNote,
295
+ };
296
+ }
297
+ for (const slot of KEY_ARGUMENTS[language] ?? []) {
298
+ if (call.calleeName !== slot.callee)
299
+ continue;
300
+ if (slot.receiver && !slot.receiver.test(call.receiverText))
301
+ continue;
302
+ const arg = call.args[slot.index];
303
+ if (!arg || !isStringLiteral(arg, language))
304
+ continue;
305
+ const value = unquote((arg.text ?? '').trim());
306
+ if (value.length < 4 || TEMPLATE_REFERENCE.test(value))
307
+ continue;
308
+ return {
309
+ node: arg,
310
+ severity: downgrade('high'),
311
+ message: `Key written into the code, passed straight to ${call.calleeText}()`,
312
+ reasoning: `Argument ${slot.index + 1} of \`${call.calleeText}()\` is the key it signs, encrypts or ` +
313
+ `verifies with, and here it is a fixed string literal (${value.length} characters) rather ` +
314
+ `than a value read from the environment or a secrets manager. Anyone who can read the ` +
315
+ `code - or its git history - can forge what this key protects.` +
316
+ testNote,
317
+ };
318
+ }
319
+ return null;
320
+ }
321
+ const assignment = asAssignment(shape);
322
+ if (!assignment)
323
+ return null;
324
+ const literal = literalOf(assignment.value, language);
325
+ if (!literal)
326
+ return null; // `password = getSecret()` is the correct pattern.
327
+ const value = literal.text;
328
+ const name = assignment.targetName;
329
+ /* ---- Signal 1: a known vendor key format. Strongest. ---- */
330
+ const known = matchKnownSecret(value);
331
+ if (known) {
332
+ return {
333
+ node: assignment.node,
334
+ severity: downgrade('critical'),
335
+ message: `${known.label} hardcoded in \`${name}\``,
336
+ reasoning: `The literal assigned to \`${name}\` matches the published format of a ` +
337
+ `${known.label}. This is not a name-based guess - the value itself is ` +
338
+ `structurally identifiable as a credential. Rotate it (assume it is already ` +
339
+ `public if this repo has ever been shared) and load it from the environment.` +
340
+ testNote,
341
+ };
342
+ }
343
+ if (isLikelyPlaceholder(value))
344
+ return null;
345
+ /*
346
+ * A SECRET IS NOT A SENTENCE. cal.com stores its user-facing error strings
347
+ * under credential-shaped names - `encryption_key_missing`, `api_key_invalid`
348
+ * - so the name pattern matched and the value was an error message. See
349
+ * looksLikeProse(), which is deliberately word-shaped rather than
350
+ * space-shaped so that a diceware passphrase still reports.
351
+ */
352
+ if (looksLikeProse(name, value))
353
+ return null;
354
+ // A value that only restates its own variable name is a lookup key, not a
355
+ // credential. See echoesItsOwnName() - this was 152 findings on Keycloak.
356
+ if (echoesItsOwnName(name, value))
357
+ return null;
358
+ if (isStatementOrTemplate(value))
359
+ return null;
360
+ if (isLanguageToken(name, value))
361
+ return null;
362
+ if (isPublicIdentifier(name, value))
363
+ return null;
364
+ /* ---- Signal 2: the variable name says "credential". ---- */
365
+ if (SECRET_NAME.test(name) && !NOT_A_SECRET.test(name)) {
366
+ if (value.length < 4)
367
+ return null; // "", "x" - not a real secret
368
+ // A suspicious NAME cannot outvote a value that is structurally not a
369
+ // key. `token: 'delimiter.curly'` is a theme scope, not a credential.
370
+ if (isStructuralIdentifier(value))
371
+ return null;
372
+ if (namesAKeySlot(value))
373
+ return null;
374
+ return {
375
+ node: assignment.node,
376
+ severity: downgrade('high'),
377
+ message: `Credential-looking value hardcoded in \`${name}\``,
378
+ reasoning: `\`${name}\` matches the naming pattern for a credential and is assigned a ` +
379
+ `fixed string literal (${value.length} characters) rather than being read from ` +
380
+ `the environment or a secrets manager. Anything committed here is in git ` +
381
+ `history permanently, even if the line is deleted later.` + testNote,
382
+ };
383
+ }
384
+ /* ---- Signal 3: entropy only. Weakest - and labelled as such. ---- */
385
+ // The structural gate comes FIRST: entropy alone cannot tell a key from a
386
+ // MIME type. See looksLikeToken() for the measurements that motivated this.
387
+ /*
388
+ * AN IDENTIFIER IS NOT A CREDENTIAL, however random it looks.
389
+ *
390
+ * Mattermost's TypeScript produced 266 hardcoded-secret findings and 160
391
+ * came down this path, nearly all of them shaped like
392
+ *
393
+ * id: 'owsyt8n43jfxjpzh9np93mx1wa'
394
+ * group_id: 'cpa9q4w7m2x5c8v1b6n3k0jr5h'
395
+ *
396
+ * in `.test.tsx` fixtures. Those are Mattermost's 26-character record ids.
397
+ * The entropy heuristic is not wrong about them - they ARE random, that is
398
+ * the point of an id - it is just answering a question nobody asked.
399
+ *
400
+ * Unlike the name path, the entropy path had no name check whatsoever: it
401
+ * fired on any variable at all. A name that says "identifier" is the
402
+ * cheapest possible signal that randomness is expected here, and this is
403
+ * the WEAKEST of the three signals to begin with, so a hardcoded session id
404
+ * lost to it costs a `medium` guess rather than a real detection.
405
+ */
406
+ if (/(^|_)(id|ids|uuid|guid|sid|gid|uid)$/i.test(name) || /Id$/.test(name))
407
+ return null;
408
+ /*
409
+ * A PROPERTY NAMED `example` IS DOCUMENTATION, and it is high-entropy on
410
+ * purpose - a sample ID or JWT has to look real to be useful. cal.com's
411
+ * OpenAPI output schemas are built from them. The entropy path had no name
412
+ * check at all until Mattermost's record ids forced one directly above;
413
+ * this is the same idea, and `example` is the OpenAPI keyword rather than a
414
+ * guess at intent.
415
+ */
416
+ if (/(^|[._-])examples?$|(^|[._-])samples?$/i.test(name))
417
+ return null;
418
+ if (value.length >= MIN_ENTROPY_LENGTH && looksLikeToken(value)) {
419
+ const entropy = shannonEntropy(value);
420
+ if (entropy >= ENTROPY_THRESHOLD) {
421
+ return {
422
+ node: assignment.node,
423
+ severity: downgrade('medium'),
424
+ message: `High-entropy literal assigned to \`${name}\` (possible secret)`,
425
+ reasoning: `The value assigned to \`${name}\` is ${value.length} characters long with a ` +
426
+ `Shannon entropy of ${entropy.toFixed(2)} bits/character. Ordinary English text ` +
427
+ `scores around 2.5-3.5; generated keys and tokens usually score above 4. ` +
428
+ `THIS IS A RANDOMNESS HEURISTIC, NOT AN IDENTIFICATION - a hash, a UUID, a ` +
429
+ `base64 test fixture or a minified blob will score the same. Severity is ` +
430
+ `deliberately lower than a name or format match for that reason.` + testNote,
431
+ limitations: 'UNVERIFIED (signature-based, entropy heuristic): this finding rests ONLY on how ' +
432
+ 'random the text looks. There is no name evidence and no format evidence. ' +
433
+ 'Expect false positives and treat it as "worth a glance", not "a secret".',
434
+ };
435
+ }
436
+ }
437
+ return null;
438
+ },
439
+ };
440
+ //# sourceMappingURL=hardcoded-secret.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hardcoded-secret.js","sourceRoot":"","sources":["../../../src/rules/hardcoded-secret.ts"],"names":[],"mappings":"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"}