@shriyanss/js-recon 1.4.1-alpha.4 → 1.4.1-alpha.6

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 (191) hide show
  1. package/.github/workflows/build-and-prettify.yaml +187 -1
  2. package/.github/workflows/promote-js-recon.yml +116 -0
  3. package/.github/workflows/publish-js-recon.yml +7 -114
  4. package/.github/workflows/rules-smoke-test.yaml +79 -0
  5. package/CHANGELOG.md +46 -0
  6. package/CLAUDE.md +208 -14
  7. package/Dockerfile.release +29 -0
  8. package/README.md +18 -17
  9. package/build/analyze/engine/csMastSEngine.js +129 -0
  10. package/build/analyze/engine/csMastSEngine.js.map +1 -0
  11. package/build/analyze/engine/index.js +15 -0
  12. package/build/analyze/engine/index.js.map +1 -1
  13. package/build/analyze/helpers/engineHelpers/findDirectAssignment.js +2 -1
  14. package/build/analyze/helpers/engineHelpers/findDirectAssignment.js.map +1 -1
  15. package/build/analyze/helpers/engineHelpers/findMemberExpressionAssignment.js +2 -1
  16. package/build/analyze/helpers/engineHelpers/findMemberExpressionAssignment.js.map +1 -1
  17. package/build/analyze/helpers/engineHelpers/resolveFunctionIdentifier.js +2 -1
  18. package/build/analyze/helpers/engineHelpers/resolveFunctionIdentifier.js.map +1 -1
  19. package/build/analyze/helpers/engineHelpers/taintFlow.js +11 -2
  20. package/build/analyze/helpers/engineHelpers/taintFlow.js.map +1 -1
  21. package/build/analyze/helpers/schemas.js +5 -1
  22. package/build/analyze/helpers/schemas.js.map +1 -1
  23. package/build/analyze/helpers/validate.js +3 -3
  24. package/build/analyze/helpers/validate.js.map +1 -1
  25. package/build/completion/index.js +432 -0
  26. package/build/completion/index.js.map +1 -0
  27. package/build/cs_mast/index.js +94 -3
  28. package/build/cs_mast/index.js.map +1 -1
  29. package/build/endpoints/next_js/client_jsFilesHref.js +2 -1
  30. package/build/endpoints/next_js/client_jsFilesHref.js.map +1 -1
  31. package/build/endpoints/next_js/client_jsonParse.js +2 -1
  32. package/build/endpoints/next_js/client_jsonParse.js.map +1 -1
  33. package/build/endpoints/next_js/client_mappedJsonFile.js +2 -1
  34. package/build/endpoints/next_js/client_mappedJsonFile.js.map +1 -1
  35. package/build/endpoints/next_js/client_subsequentRequests.js +2 -1
  36. package/build/endpoints/next_js/client_subsequentRequests.js.map +1 -1
  37. package/build/fingerprint/index.js +56 -33
  38. package/build/fingerprint/index.js.map +1 -1
  39. package/build/globalConfig.js +1 -1
  40. package/build/index.js +67 -6
  41. package/build/index.js.map +1 -1
  42. package/build/lazyLoad/angular/angular_getFromMainJs.js +2 -1
  43. package/build/lazyLoad/angular/angular_getFromMainJs.js.map +1 -1
  44. package/build/lazyLoad/downloadFilesUtil.js +6 -5
  45. package/build/lazyLoad/downloadFilesUtil.js.map +1 -1
  46. package/build/lazyLoad/downloadLoadedJsUtil.js +56 -7
  47. package/build/lazyLoad/downloadLoadedJsUtil.js.map +1 -1
  48. package/build/lazyLoad/downloadQueue.js +9 -8
  49. package/build/lazyLoad/downloadQueue.js.map +1 -1
  50. package/build/lazyLoad/index.js +28 -1
  51. package/build/lazyLoad/index.js.map +1 -1
  52. package/build/lazyLoad/next_js/next_GetLazyResourcesBuildManifestJs.js +36 -34
  53. package/build/lazyLoad/next_js/next_GetLazyResourcesBuildManifestJs.js.map +1 -1
  54. package/build/lazyLoad/next_js/next_GetLazyResourcesWebpackJs.js +63 -15
  55. package/build/lazyLoad/next_js/next_GetLazyResourcesWebpackJs.js.map +1 -1
  56. package/build/lazyLoad/next_js/next_parseLayoutJs.js +80 -68
  57. package/build/lazyLoad/next_js/next_parseLayoutJs.js.map +1 -1
  58. package/build/lazyLoad/next_js/next_promiseResolve.js +22 -16
  59. package/build/lazyLoad/next_js/next_promiseResolve.js.map +1 -1
  60. package/build/lazyLoad/nuxt_js/nuxt_astParse.js +49 -47
  61. package/build/lazyLoad/nuxt_js/nuxt_astParse.js.map +1 -1
  62. package/build/lazyLoad/nuxt_js/nuxt_getFromPageSource.js +3 -1
  63. package/build/lazyLoad/nuxt_js/nuxt_getFromPageSource.js.map +1 -1
  64. package/build/lazyLoad/nuxt_js/nuxt_stringAnalysisJSFiles.js +3 -2
  65. package/build/lazyLoad/nuxt_js/nuxt_stringAnalysisJSFiles.js.map +1 -1
  66. package/build/lazyLoad/react/react_followImports.js +12 -9
  67. package/build/lazyLoad/react/react_followImports.js.map +1 -1
  68. package/build/lazyLoad/react/react_webpackChunkPaths.js +175 -123
  69. package/build/lazyLoad/react/react_webpackChunkPaths.js.map +1 -1
  70. package/build/lazyLoad/svelte/svelte_stringAnalysisJSFiles.js +3 -2
  71. package/build/lazyLoad/svelte/svelte_stringAnalysisJSFiles.js.map +1 -1
  72. package/build/lazyLoad/techDetect/checkAngularJS.js +27 -6
  73. package/build/lazyLoad/techDetect/checkAngularJS.js.map +1 -1
  74. package/build/lazyLoad/techDetect/checkReact.js +8 -0
  75. package/build/lazyLoad/techDetect/checkReact.js.map +1 -1
  76. package/build/lazyLoad/techDetect/index.js +23 -2
  77. package/build/lazyLoad/techDetect/index.js.map +1 -1
  78. package/build/lazyLoad/vue/vue_RuntimeJs.js +2 -1
  79. package/build/lazyLoad/vue/vue_RuntimeJs.js.map +1 -1
  80. package/build/lazyLoad/vue/vue_SingleJsFileOnHome.js +2 -1
  81. package/build/lazyLoad/vue/vue_SingleJsFileOnHome.js.map +1 -1
  82. package/build/lazyLoad/vue/vue_discoverJsFiles.js +22 -10
  83. package/build/lazyLoad/vue/vue_discoverJsFiles.js.map +1 -1
  84. package/build/lazyLoad/vue/vue_getClientSidePaths.js +2 -1
  85. package/build/lazyLoad/vue/vue_getClientSidePaths.js.map +1 -1
  86. package/build/lazyLoad/vue/vue_jsImports.js +2 -1
  87. package/build/lazyLoad/vue/vue_jsImports.js.map +1 -1
  88. package/build/lazyLoad/vue/vue_viteMapDeps.js +62 -54
  89. package/build/lazyLoad/vue/vue_viteMapDeps.js.map +1 -1
  90. package/build/map/angular_js/getAngularConnections.js +1 -1
  91. package/build/map/angular_js/getAngularConnections.js.map +1 -1
  92. package/build/map/graphql/resolveGraphql.js +2 -1
  93. package/build/map/graphql/resolveGraphql.js.map +1 -1
  94. package/build/map/next_js/getExports.js +2 -1
  95. package/build/map/next_js/getExports.js.map +1 -1
  96. package/build/map/next_js/getFetchInstances.js +2 -1
  97. package/build/map/next_js/getFetchInstances.js.map +1 -1
  98. package/build/map/next_js/getTurbopackConnections.js +2 -1
  99. package/build/map/next_js/getTurbopackConnections.js.map +1 -1
  100. package/build/map/next_js/getWebpackConnections.js +2 -1
  101. package/build/map/next_js/getWebpackConnections.js.map +1 -1
  102. package/build/map/next_js/resolveAxios.js +2 -1
  103. package/build/map/next_js/resolveAxios.js.map +1 -1
  104. package/build/map/next_js/resolveAxiosHelpers/directCallsWithoutAssignment.js +2 -1
  105. package/build/map/next_js/resolveAxiosHelpers/directCallsWithoutAssignment.js.map +1 -1
  106. package/build/map/next_js/resolveAxiosHelpers/findAxiosInstance.js +2 -1
  107. package/build/map/next_js/resolveAxiosHelpers/findAxiosInstance.js.map +1 -1
  108. package/build/map/next_js/resolveAxiosHelpers/findCrossChunkParams.js +2 -1
  109. package/build/map/next_js/resolveAxiosHelpers/findCrossChunkParams.js.map +1 -1
  110. package/build/map/next_js/resolveAxiosHelpers/handleAxiosCreate.js +2 -1
  111. package/build/map/next_js/resolveAxiosHelpers/handleAxiosCreate.js.map +1 -1
  112. package/build/map/next_js/resolveAxiosHelpers/handleZDotCreate.js +2 -1
  113. package/build/map/next_js/resolveAxiosHelpers/handleZDotCreate.js.map +1 -1
  114. package/build/map/next_js/resolveAxiosHelpers/interceptorHeaders.js +2 -1
  115. package/build/map/next_js/resolveAxiosHelpers/interceptorHeaders.js.map +1 -1
  116. package/build/map/next_js/resolveAxiosHelpers/processExportedEndpoints.js +2 -1
  117. package/build/map/next_js/resolveAxiosHelpers/processExportedEndpoints.js.map +1 -1
  118. package/build/map/next_js/resolveAxiosHelpers/traceAxiosInstanceExports.js +2 -1
  119. package/build/map/next_js/resolveAxiosHelpers/traceAxiosInstanceExports.js.map +1 -1
  120. package/build/map/next_js/resolveAxiosHelpers/traceBody.js +2 -1
  121. package/build/map/next_js/resolveAxiosHelpers/traceBody.js.map +1 -1
  122. package/build/map/next_js/resolveFetch.js +2 -1
  123. package/build/map/next_js/resolveFetch.js.map +1 -1
  124. package/build/map/next_js/resolveNewRequest.js +2 -1
  125. package/build/map/next_js/resolveNewRequest.js.map +1 -1
  126. package/build/map/next_js/resolveServerActions.js +2 -1
  127. package/build/map/next_js/resolveServerActions.js.map +1 -1
  128. package/build/map/next_js/utils.js +2 -1
  129. package/build/map/next_js/utils.js.map +1 -1
  130. package/build/map/react_js/getReactConnections.js +5 -4
  131. package/build/map/react_js/getReactConnections.js.map +1 -1
  132. package/build/map/vue_js/crossFileResolver.js +3 -2
  133. package/build/map/vue_js/crossFileResolver.js.map +1 -1
  134. package/build/map/vue_js/getViteConnections.js +5 -4
  135. package/build/map/vue_js/getViteConnections.js.map +1 -1
  136. package/build/map/vue_js/taint_utils.js +2 -1
  137. package/build/map/vue_js/taint_utils.js.map +1 -1
  138. package/build/map/vue_js/vue_resolveFetch.js +3 -2
  139. package/build/map/vue_js/vue_resolveFetch.js.map +1 -1
  140. package/build/map/vue_js/vue_resolveHttpClient.js +3 -2
  141. package/build/map/vue_js/vue_resolveHttpClient.js.map +1 -1
  142. package/build/map/vue_js/vue_resolveXhr.js +3 -2
  143. package/build/map/vue_js/vue_resolveXhr.js.map +1 -1
  144. package/build/refactor/index.js +399 -60
  145. package/build/refactor/index.js.map +1 -1
  146. package/build/refactor/next/helpers.js +194 -13
  147. package/build/refactor/next/helpers.js.map +1 -1
  148. package/build/refactor/next/index.js +223 -36
  149. package/build/refactor/next/index.js.map +1 -1
  150. package/build/refactor/next/transform.js +905 -55
  151. package/build/refactor/next/transform.js.map +1 -1
  152. package/build/refactor/react/index.js +3 -3
  153. package/build/refactor/react/index.js.map +1 -1
  154. package/build/refactor/react/transform.js +2 -1
  155. package/build/refactor/react/transform.js.map +1 -1
  156. package/build/refactor/react/validator.js +2 -1
  157. package/build/refactor/react/validator.js.map +1 -1
  158. package/build/refactor/react-vite/index.js +39 -5
  159. package/build/refactor/react-vite/index.js.map +1 -1
  160. package/build/refactor/react-vite/vendor-analyze.js +3 -3
  161. package/build/refactor/react-vite/vendor-analyze.js.map +1 -1
  162. package/build/refactor/remote/config.js +17 -1
  163. package/build/refactor/remote/config.js.map +1 -1
  164. package/build/refactor/remote/hf-client.js +27 -2
  165. package/build/refactor/remote/hf-client.js.map +1 -1
  166. package/build/refactor/remote/version-detect.js +355 -0
  167. package/build/refactor/remote/version-detect.js.map +1 -0
  168. package/build/refactor/vue/index.js +176 -0
  169. package/build/refactor/vue/index.js.map +1 -0
  170. package/build/refactor/vue/vendor-analyze-vue.js +271 -0
  171. package/build/refactor/vue/vendor-analyze-vue.js.map +1 -0
  172. package/build/refactor/vue/vite.js +216 -0
  173. package/build/refactor/vue/vite.js.map +1 -0
  174. package/build/report/utility/genHtml.js +14 -3
  175. package/build/report/utility/genHtml.js.map +1 -1
  176. package/build/run/bundler-detect.js +140 -0
  177. package/build/run/bundler-detect.js.map +1 -0
  178. package/build/run/index.js +91 -5
  179. package/build/run/index.js.map +1 -1
  180. package/build/strings/index.js +6 -2
  181. package/build/strings/index.js.map +1 -1
  182. package/build/strings/trufflehog.js +71 -0
  183. package/build/strings/trufflehog.js.map +1 -0
  184. package/build/utility/makeReq.js +10 -9
  185. package/build/utility/makeReq.js.map +1 -1
  186. package/build/utility/postmanGenerator.js +2 -2
  187. package/build/utility/postmanGenerator.js.map +1 -1
  188. package/build/utility/puppeteerInstance.js +7 -1
  189. package/build/utility/puppeteerInstance.js.map +1 -1
  190. package/package.json +9 -5
  191. package/scripts/smoke-test.js +74 -0
@@ -89,9 +89,30 @@ jobs:
89
89
  git commit -m "chore: prettify code" || echo "No changes to commit"
90
90
  git push
91
91
 
92
- build:
92
+ test:
93
93
  needs: prettier
94
94
  runs-on: ubuntu-latest
95
+ steps:
96
+ - name: Checkout
97
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
98
+ with:
99
+ ref: ${{ github.ref_name }}
100
+
101
+ - name: Set up Node.js
102
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
103
+ with:
104
+ node-version: "22"
105
+ cache: "npm"
106
+
107
+ - name: Install dependencies
108
+ run: npm ci
109
+
110
+ - name: Run unit tests
111
+ run: npm test
112
+
113
+ build:
114
+ needs: test
115
+ runs-on: ubuntu-latest
95
116
  steps:
96
117
  - name: Checkout
97
118
  uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -119,3 +140,168 @@ jobs:
119
140
  ref: ${{ github.ref_name }}
120
141
  - name: Build docker container
121
142
  run: docker build -t shriyanss/js-recon .
143
+
144
+ integration-test-docker:
145
+ needs: build-docker
146
+ runs-on: ubuntu-latest
147
+ timeout-minutes: 30
148
+ steps:
149
+ - name: Checkout js-recon
150
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
151
+ with:
152
+ ref: ${{ github.ref_name }}
153
+
154
+ - name: Checkout js-recon-labs
155
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
156
+ with:
157
+ repository: shriyanss/js-recon-labs
158
+ path: js-recon-labs
159
+
160
+ - name: Checkout js-recon-rules
161
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
162
+ with:
163
+ repository: shriyanss/js-recon-rules
164
+ path: js-recon-rules
165
+
166
+ - name: Set up Node.js
167
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
168
+ with:
169
+ node-version: "22"
170
+ cache: "npm"
171
+
172
+ - name: Build js-recon Docker image
173
+ run: docker build -t shriyanss/js-recon .
174
+
175
+ - name: Install vuln-all-rules app dependencies
176
+ working-directory: js-recon-labs/next_js/vuln-all-rules
177
+ run: npm ci
178
+
179
+ - name: Build vuln-all-rules app
180
+ working-directory: js-recon-labs/next_js/vuln-all-rules
181
+ run: npm run build
182
+
183
+ - name: Start vuln-all-rules app
184
+ working-directory: js-recon-labs/next_js/vuln-all-rules
185
+ run: npm start &
186
+
187
+ - name: Wait for lab app to be ready
188
+ run: |
189
+ echo "Waiting for lab app on port 3001..."
190
+ n=0
191
+ until curl -sf http://localhost:3001 > /dev/null; do
192
+ n=$((n+1))
193
+ [ $n -ge 30 ] && echo "Timed out waiting for lab app" && exit 1
194
+ sleep 2
195
+ done
196
+ echo "Lab app is ready."
197
+
198
+ - name: Run js-recon via Docker against lab app
199
+ run: |
200
+ docker run --name js-recon-run \
201
+ --network host \
202
+ -v "$(pwd)/js-recon-rules:/home/pptruser/js-recon-rules" \
203
+ --entrypoint /bin/bash \
204
+ shriyanss/js-recon \
205
+ -c 'mkdir -p ~/.js-recon && ln -sf /home/pptruser/js-recon-rules ~/.js-recon/rules && printf "http://localhost:3001\n" > /tmp/urls.txt && node build/index.js run -u /tmp/urls.txt -r ./js-recon-rules --no-sandbox -y -k'
206
+ mkdir -p output
207
+ docker cp js-recon-run:/home/pptruser/output/. ./output/ 2>/dev/null || true
208
+ docker rm js-recon-run
209
+
210
+ - name: Verify all rules fired
211
+ run: node scripts/smoke-test.js
212
+
213
+ - name: Upload analyze.json on failure
214
+ if: failure()
215
+ uses: actions/upload-artifact@v4
216
+ with:
217
+ name: analyze-json-docker
218
+ path: output/localhost_3001/analyze.json
219
+ if-no-files-found: ignore
220
+
221
+ integration-test-macos:
222
+ needs: build
223
+ runs-on: macos-latest
224
+ timeout-minutes: 30
225
+ steps:
226
+ - name: Checkout js-recon
227
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
228
+ with:
229
+ ref: ${{ github.ref_name }}
230
+
231
+ - name: Checkout js-recon-labs
232
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
233
+ with:
234
+ repository: shriyanss/js-recon-labs
235
+ path: js-recon-labs
236
+
237
+ - name: Checkout js-recon-rules
238
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
239
+ with:
240
+ repository: shriyanss/js-recon-rules
241
+ path: js-recon-rules
242
+
243
+ - name: Set up Node.js
244
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
245
+ with:
246
+ node-version: "22"
247
+ cache: "npm"
248
+
249
+ - name: Install Chrome
250
+ run: brew install --cask google-chrome
251
+
252
+ - name: Install js-recon dependencies
253
+ run: npm ci
254
+ env:
255
+ PUPPETEER_SKIP_DOWNLOAD: "true"
256
+
257
+ - name: Build js-recon
258
+ run: npm run build
259
+
260
+ - name: Install vuln-all-rules app dependencies
261
+ working-directory: js-recon-labs/next_js/vuln-all-rules
262
+ run: npm ci
263
+
264
+ - name: Build vuln-all-rules app
265
+ working-directory: js-recon-labs/next_js/vuln-all-rules
266
+ run: npm run build
267
+
268
+ - name: Start vuln-all-rules app
269
+ working-directory: js-recon-labs/next_js/vuln-all-rules
270
+ run: npm start &
271
+
272
+ - name: Wait for lab app to be ready
273
+ run: |
274
+ echo "Waiting for lab app on port 3001..."
275
+ n=0
276
+ until curl -sf http://localhost:3001 > /dev/null; do
277
+ n=$((n+1))
278
+ [ $n -ge 30 ] && echo "Timed out waiting for lab app" && exit 1
279
+ sleep 2
280
+ done
281
+ echo "Lab app is ready."
282
+
283
+ - name: Pre-populate js-recon rules cache
284
+ run: |
285
+ mkdir -p ~/.js-recon
286
+ ln -sf "$GITHUB_WORKSPACE/js-recon-rules" ~/.js-recon/rules
287
+
288
+ - name: Run js-recon natively against lab app
289
+ run: |
290
+ echo "http://localhost:3001" > /tmp/smoke-urls.txt
291
+ node --max-old-space-size=4096 build/index.js run \
292
+ -u /tmp/smoke-urls.txt \
293
+ -r ./js-recon-rules \
294
+ -y -k
295
+ env:
296
+ PUPPETEER_EXECUTABLE_PATH: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
297
+
298
+ - name: Verify all rules fired
299
+ run: node scripts/smoke-test.js
300
+
301
+ - name: Upload analyze.json on failure
302
+ if: failure()
303
+ uses: actions/upload-artifact@v4
304
+ with:
305
+ name: analyze-json-macos
306
+ path: output/localhost_3001/analyze.json
307
+ if-no-files-found: ignore
@@ -0,0 +1,116 @@
1
+ # Manually-triggered second phase of a release, run after a staged npm publish
2
+ # (see publish-js-recon.yml) has been approved by a human with 2FA on npmjs.com.
3
+ # Installs js-recon from the published npm registry artifact rather than
4
+ # building from git source, so the Homebrew/Docker/GHCR outputs are provably
5
+ # the same bits that were reviewed and approved on npm.
6
+
7
+ name: Promote JS Recon Release
8
+
9
+ on:
10
+ workflow_dispatch:
11
+ inputs:
12
+ version:
13
+ description: "Published npm version to promote (e.g. 1.4.1-alpha.6, no leading v)"
14
+ required: true
15
+ type: string
16
+
17
+ jobs:
18
+ update-homebrew-tap:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - name: Fetch published tarball and compute SHA256
22
+ id: sha256
23
+ run: |
24
+ npm pack "@shriyanss/js-recon@${VERSION}"
25
+ SHA=$(sha256sum "shriyanss-js-recon-${VERSION}.tgz" | awk '{print $1}')
26
+ echo "sha256=${SHA}" >> "$GITHUB_OUTPUT"
27
+ env:
28
+ VERSION: ${{ inputs.version }}
29
+
30
+ - name: Check out homebrew-tap
31
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
32
+ with:
33
+ repository: shriyanss/homebrew-tap
34
+ token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
35
+ path: homebrew-tap
36
+
37
+ - name: Update formula fields
38
+ working-directory: homebrew-tap
39
+ run: |
40
+ F="Formula/js-recon.rb"
41
+ sed -i "s|^ url \"https://registry\.npmjs\.org/@shriyanss/js-recon/-/js-recon-[^\"]*\"| url \"https://registry.npmjs.org/@shriyanss/js-recon/-/js-recon-${VERSION}.tgz\"|" "${F}"
42
+ sed -i "s|^ sha256 \"[a-f0-9]*\"| sha256 \"${SHA}\"|" "${F}"
43
+ env:
44
+ VERSION: ${{ inputs.version }}
45
+ SHA: ${{ steps.sha256.outputs.sha256 }}
46
+
47
+ - name: Commit and push
48
+ working-directory: homebrew-tap
49
+ run: |
50
+ git config user.name "github-actions[bot]"
51
+ git config user.email "github-actions[bot]@users.noreply.github.com"
52
+ git add Formula/js-recon.rb
53
+ git commit -m "chore: update js-recon formula to ${VERSION}"
54
+ git push
55
+ env:
56
+ VERSION: ${{ inputs.version }}
57
+
58
+ publish-docker:
59
+ runs-on: ubuntu-latest
60
+ steps:
61
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
62
+ - name: Build and push to dockerhub
63
+ run: |
64
+ # login to docker
65
+ echo $DOCKER_SECRET | docker login -u shriyanss --password-stdin
66
+
67
+ # build the image from the published npm artifact
68
+ docker build -f Dockerfile.release --build-arg JS_RECON_VERSION=$VERSION -t shriyanss/js-recon:$VERSION .
69
+
70
+ # check if it is alpha or beta. default to latest
71
+ TAG="latest"
72
+ [[ "$VERSION" == *"beta"* ]] && TAG="beta"
73
+ [[ "$VERSION" == *"alpha"* ]] && TAG="alpha"
74
+
75
+ # tag again with whatever has been set
76
+ docker tag shriyanss/js-recon:$VERSION shriyanss/js-recon:$TAG
77
+
78
+ # push images with both tags
79
+ docker push shriyanss/js-recon:$VERSION
80
+ docker push shriyanss/js-recon:$TAG
81
+
82
+ env:
83
+ DOCKER_SECRET: ${{ secrets.DOCKER_SECRET }}
84
+ VERSION: ${{ inputs.version }}
85
+
86
+ publish-ghcr:
87
+ runs-on: ubuntu-latest
88
+ permissions:
89
+ contents: read
90
+ packages: write
91
+ steps:
92
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
93
+ - uses: docker/login-action@v3
94
+ with:
95
+ registry: ghcr.io
96
+ username: ${{ github.actor }}
97
+ password: ${{ secrets.GITHUB_TOKEN }}
98
+ - name: Build and push to github container registry
99
+ run: |
100
+ # build the image from the published npm artifact
101
+ docker build -f Dockerfile.release --build-arg JS_RECON_VERSION=$VERSION -t ghcr.io/shriyanss/js-recon:$VERSION .
102
+
103
+ # check if it is alpha or beta. default to latest
104
+ TAG="latest"
105
+ [[ "$VERSION" == *"beta"* ]] && TAG="beta"
106
+ [[ "$VERSION" == *"alpha"* ]] && TAG="alpha"
107
+
108
+ # tag again with whatever has been set
109
+ docker tag ghcr.io/shriyanss/js-recon:$VERSION ghcr.io/shriyanss/js-recon:$TAG
110
+
111
+ # push images with both tags
112
+ docker push ghcr.io/shriyanss/js-recon:$VERSION
113
+ docker push ghcr.io/shriyanss/js-recon:$TAG
114
+
115
+ env:
116
+ VERSION: ${{ inputs.version }}
@@ -63,6 +63,9 @@ jobs:
63
63
  publish-npm:
64
64
  needs: build
65
65
  runs-on: ubuntu-latest
66
+ permissions:
67
+ id-token: write
68
+ contents: read
66
69
  steps:
67
70
  - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
68
71
  - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
@@ -71,130 +74,20 @@ jobs:
71
74
  registry-url: https://registry.npmjs.org/
72
75
  - run: npm ci
73
76
  - run: npm run build
74
- - name: Publish to npm
75
- run: |
76
- TAG="latest"
77
- [[ "$VERSION" == *"beta"* ]] && TAG="beta"
78
- [[ "$VERSION" == *"alpha"* ]] && TAG="alpha"
79
- npm publish --tag $TAG
80
- env:
81
- NODE_AUTH_TOKEN: ${{secrets.npm_token}}
82
- VERSION: ${{ github.event.release.tag_name }}
83
-
84
-
85
- update-homebrew-tap:
86
- needs: publish-npm
87
- runs-on: ubuntu-latest
88
- steps:
89
- - name: Get release version
90
- id: version
91
- run: |
92
- RAW="${{ github.event.release.tag_name }}"
93
- echo "version=${RAW#v}" >> "$GITHUB_OUTPUT"
94
-
95
- - name: Compute npm tarball SHA256
96
- id: sha256
97
- run: |
98
- VERSION="${{ steps.version.outputs.version }}"
99
- URL="https://registry.npmjs.org/@shriyanss/js-recon/-/js-recon-${VERSION}.tgz"
100
- SHA=$(curl -fsSL "${URL}" | sha256sum | awk '{print $1}')
101
- echo "sha256=${SHA}" >> "$GITHUB_OUTPUT"
102
-
103
- - name: Check out homebrew-tap
104
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
105
- with:
106
- repository: shriyanss/homebrew-tap
107
- token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
108
- path: homebrew-tap
109
-
110
- - name: Update formula fields
111
- working-directory: homebrew-tap
112
- run: |
113
- VERSION="${{ steps.version.outputs.version }}"
114
- SHA="${{ steps.sha256.outputs.sha256 }}"
115
- F="Formula/js-recon.rb"
116
- sed -i "s|^ version \".*\"| version \"${VERSION}\"|" "${F}"
117
- sed -i "s|^ url \"https://registry\.npmjs\.org/@shriyanss/js-recon/-/js-recon-[^\"]*\"| url \"https://registry.npmjs.org/@shriyanss/js-recon/-/js-recon-${VERSION}.tgz\"|" "${F}"
118
- sed -i "s|^ sha256 \"[a-f0-9]*\"| sha256 \"${SHA}\"|" "${F}"
119
-
120
- - name: Commit and push
121
- working-directory: homebrew-tap
122
- run: |
123
- git config user.name "github-actions[bot]"
124
- git config user.email "github-actions[bot]@users.noreply.github.com"
125
- git add Formula/js-recon.rb
126
- git commit -m "chore: update js-recon formula to ${{ steps.version.outputs.version }}"
127
- git push
128
-
129
-
130
- publish-docker:
131
- needs: build
132
- runs-on: ubuntu-latest
133
- steps:
134
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
135
- - name: Build and push to dockerhub
77
+ - name: Ensure npm CLI supports OIDC trusted publishing
78
+ run: npm install -g npm@latest
79
+ - name: Stage release to npm via OIDC
136
80
  run: |
137
- # login to docker
138
- echo $DOCKER_SECRET | docker login -u shriyanss --password-stdin
139
-
140
- # build the image
141
- docker build -t shriyanss/js-recon:$VERSION .
142
-
143
- # check if it is alpha or beta. default to latest
144
81
  TAG="latest"
145
82
  [[ "$VERSION" == *"beta"* ]] && TAG="beta"
146
83
  [[ "$VERSION" == *"alpha"* ]] && TAG="alpha"
147
-
148
- # tag again with whatever has been set
149
- docker tag shriyanss/js-recon:$VERSION shriyanss/js-recon:$TAG
150
-
151
- # push images with both tags
152
- docker push shriyanss/js-recon:$VERSION
153
- docker push shriyanss/js-recon:$TAG
154
-
84
+ npm stage publish --tag $TAG
155
85
  env:
156
- DOCKER_SECRET: ${{ secrets.DOCKER_SECRET }}
157
86
  VERSION: ${{ github.event.release.tag_name }}
158
87
 
159
-
160
- publish-ghcr:
161
- needs: build
162
- runs-on: ubuntu-latest
163
- permissions:
164
- contents: read
165
- packages: write
166
- steps:
167
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
168
- - uses: docker/login-action@v3
169
- with:
170
- registry: ghcr.io
171
- username: ${{ github.actor }}
172
- password: ${{ secrets.GITHUB_TOKEN }}
173
- - name: Build and push to github container registry
174
- run: |
175
- # build the image
176
- docker build -t ghcr.io/shriyanss/js-recon:$VERSION .
177
-
178
- # check if it is alpha or beta. default to latest
179
- TAG="latest"
180
- [[ "$VERSION" == *"beta"* ]] && TAG="beta"
181
- [[ "$VERSION" == *"alpha"* ]] && TAG="alpha"
182
-
183
- # tag again with whatever has been set
184
- docker tag ghcr.io/shriyanss/js-recon:$VERSION ghcr.io/shriyanss/js-recon:$TAG
185
-
186
- # push images with both tags
187
- docker push ghcr.io/shriyanss/js-recon:$VERSION
188
- docker push ghcr.io/shriyanss/js-recon:$TAG
189
-
190
- env:
191
- VERSION: ${{ github.event.release.tag_name }}
192
-
193
88
  merge_main_and_dev:
194
89
  needs:
195
90
  - publish-npm
196
- - publish-docker
197
- - publish-ghcr
198
91
  runs-on: ubuntu-latest
199
92
  steps:
200
93
  - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -0,0 +1,79 @@
1
+ name: Rules Smoke Test
2
+
3
+ on:
4
+ push:
5
+ branches-ignore:
6
+ - main
7
+
8
+ jobs:
9
+ rules-smoke-test:
10
+ runs-on: ubuntu-latest
11
+ timeout-minutes: 30
12
+
13
+ steps:
14
+ - name: Checkout js-recon
15
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
16
+
17
+ - name: Checkout js-recon-labs
18
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
19
+ with:
20
+ repository: shriyanss/js-recon-labs
21
+ path: js-recon-labs
22
+
23
+ - name: Checkout js-recon-rules
24
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
25
+ with:
26
+ repository: shriyanss/js-recon-rules
27
+ path: js-recon-rules
28
+
29
+ - name: Set up Node.js
30
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
31
+ with:
32
+ node-version: "22"
33
+ cache: "npm"
34
+
35
+ - name: Install js-recon dependencies
36
+ run: npm ci
37
+
38
+ - name: Build js-recon
39
+ run: npm run build
40
+
41
+ - name: Install vulnerable app dependencies
42
+ working-directory: js-recon-labs/next_js/vuln-all-rules
43
+ run: npm ci
44
+
45
+ - name: Build vulnerable app
46
+ working-directory: js-recon-labs/next_js/vuln-all-rules
47
+ run: npm run build
48
+
49
+ - name: Start vulnerable app
50
+ working-directory: js-recon-labs/next_js/vuln-all-rules
51
+ run: npm start &
52
+
53
+ - name: Wait for app to be ready
54
+ run: |
55
+ echo "Waiting for app on port 3001..."
56
+ timeout 60 bash -c 'until curl -sf http://localhost:3001 > /dev/null; do sleep 2; done'
57
+ echo "App is ready."
58
+
59
+ - name: Run js-recon against vulnerable app
60
+ run: |
61
+ echo "http://localhost:3001" > /tmp/smoke-urls.txt
62
+ node --max-old-space-size=4096 build/index.js run \
63
+ -u /tmp/smoke-urls.txt \
64
+ -r ./js-recon-rules \
65
+ --no-sandbox \
66
+ -y -k
67
+ env:
68
+ PUPPETEER_EXECUTABLE_PATH: /usr/bin/google-chrome-stable
69
+
70
+ - name: Verify all rules fired
71
+ run: node scripts/smoke-test.js
72
+
73
+ - name: Upload analyze.json on failure
74
+ if: failure()
75
+ uses: actions/upload-artifact@v4
76
+ with:
77
+ name: analyze-json
78
+ path: output/localhost_3001/analyze.json
79
+ if-no-files-found: ignore
package/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
1
  # Change Log
2
2
 
3
+ ## 1.4.1-alpha.6 - 2026-07-13
4
+
5
+ ### Changed
6
+
7
+ - CI: npm publishing now uses OIDC trusted publishing with staged releases (`npm stage publish`) instead of a classic auth token, since NPM is restricting tokens that bypass MFA. Promoting a staged release to live still requires a manual, 2FA-gated `npm stage approve` — this cannot be automated. The Homebrew tap update and Docker/GHCR image publishing have moved out of `publish-js-recon.yml` into a new manually-triggered workflow, `promote-js-recon.yml`, run after the staged release is approved. That workflow installs js-recon from the published npm registry artifact (`npm pack`/`npm install <pkg>@<version>`) rather than building from git source, as an additional supply-chain safeguard — what ships in the Docker/GHCR images and what Homebrew hashes is provably the same bits that were reviewed and approved on npm. (`ci`)
8
+
9
+ ## 1.4.1-alpha.5 - 2026-07-13
10
+
11
+ ### Fixed
12
+
13
+ - Homebrew install: `puppeteer-extra` now receives the ESM `puppeteer` instance directly via `addExtra()` instead of relying on its internal `require('puppeteer')` call. On newer Node.js versions (22+, as shipped by Homebrew), a synchronous `require()` of an ESM module triggers `ExperimentalWarning` on every run. Using `addExtra()` bypasses the `require()` entirely and silences the warning. (`all`)
14
+ - `lazyload` (tech detect, react): React framework detection now works for Vite dev-mode servers. Vite's `@vitejs/plugin-react` and `@vitejs/plugin-react-swc` inject an inline `<script>` block containing `import { injectIntoGlobalHook } from "/@react-refresh"` into every dev-served page. Previously, none of the `REACT_MARKERS` matched this content, so dev-mode targets returned `unknown`. Two fixes: (1) `@react-refresh` and `injectIntoGlobalHook` are now included in `REACT_MARKERS` so the inline-script branch of `checkReact` fires immediately; (2) `/@react-refresh` is now a fast-path match in `fetchAndCheck` so any script src pointing at the HMR endpoint is also caught; (3) the intercepted-URL fallback in `frameworkDetect` now recognises `/@react-refresh` as a React signal alongside the existing Next.js / Nuxt / Svelte URL patterns. (`lazyload`, `fingerprint`, `run`)
15
+ - `lazyload` (tech detect, angular): Angular framework detection is now more robust. Three HTML-level signals are checked before fetching `main.js` — `data-beasties-container` (Angular's Beasties SSR/prerendering marker), `ng-version` (set by the Angular runtime on the root component element after bootstrapping, visible in Puppeteer-rendered DOM), and `_nghost-*` view-encapsulation attributes. These checks require no extra HTTP request and work even for zoneless Angular apps (Angular 16+) that omit Zone.js and therefore lack the previous `isAngularZone`/`this.ngZone` patterns. The `main.js` src pattern was also broadened from `main-` (hashed production builds only) to also match `main.js` (unhashed development builds). (`lazyload`, `fingerprint`, `run`)
16
+ - `lazyload`, `map`, `strings`: `.mjs` (ES module) files are now fully supported across all framework pipelines. Previously, extension filters in `downloadQueue.ts`, `downloadFilesUtil.ts`, `downloadLoadedJsUtil.ts`, `react_followImports.ts`, `getReactConnections.ts`, `getViteConnections.ts`, `getAngularConnections.ts`, `vue_resolveHttpClient.ts`, `vue_resolveFetch.ts`, `vue_resolveXhr.ts`, `crossFileResolver.ts`, and `strings/index.ts` only matched `.js` files. Sites that bundle all application code as `.mjs` (e.g. Framer/Rolldown) were silently ignored — all chunks were downloaded but zero files were scanned for connections, strings, or secrets. All affected paths now match both `.js` and `.mjs`. (`run`, `lazyload`, `map`, `strings`)
17
+ - `run` (react): The map step now receives the full `outputDir` (all downloaded hosts) rather than only the target host's subdirectory. Previously, when a React site served all its JS from a CDN host, the map step was pointed at an empty target-host directory and reported zero chunks. Now mirrors the Vue pipeline's directory-passing behaviour so CDN-hosted chunks are analysed correctly. (`run`, `map`)
18
+ - `lazyload` (react): `react_followImports` now discovers dynamic imports written as template literals (``import(`./chunk.mjs`)``) in addition to single/double-quoted strings. Bundlers that use Rolldown (e.g. Framer) emit all dynamic import paths as template literals; the previous regex only matched quoted strings, silently missing all lazily-loaded page and collection chunks. (`lazyload`, `run`)
19
+ - `lazyload`: `.mjs.map` sourcemap files are now handled correctly. The filename extraction regex now prefers the `.mjs.map` suffix over the shorter `.mjs` match, preventing sourcemap JSON content from being written with a `.mjs` extension (which caused Prettier's Babel parser to throw a `SyntaxError` and discard the file). The sourcemap write path now uses the JSON parser for both `.js.map` and `.mjs.map` files. (`lazyload`, `run`)
20
+ - `lazyload` (Puppeteer): `downloadLoadedJsUtil` now uses `waitUntil: "networkidle0"` with a 10 s timeout on `page.goto()`. Previously the call had no timeout and no error handling, so pages whose `load` event never fires (deferred JS, service workers, some SPA patterns) caused the lazyload step to hang indefinitely. Navigation errors and timeouts are now caught and the URLs collected up to that point are returned normally. (`lazyload`, `run`)
21
+ - `lazyload` (Puppeteer): `browser.close()` in `downloadLoadedJsUtil` is now wrapped in a try/catch; if it hangs, the Chrome process is force-killed via `SIGKILL`. Previously a stuck `browser.close()` caused the process to hang after download was otherwise complete. (`lazyload`, `run`)
22
+ - `lazyload` (Puppeteer): All three Puppeteer-using modules (`downloadLoadedJsUtil`, `next_GetLazyResourcesWebpackJs`, `techDetect/index`) now abort non-http/s requests (e.g. `mailto:`, `data:`, `blob:`, `chrome-extension:`, `tel:`) in the request interceptor instead of calling `request.continue()`. Calling `continue()` on these schemes throws an unhandled error and can trigger OS protocol handlers. (`lazyload`, `run`)
23
+ - `lazyload` (Puppeteer): `downloadLoadedJsUtil` and `next_GetLazyResourcesWebpackJs` now pass `--disable-external-protocol-dialog` to Chrome at launch and install an `evaluateOnNewDocument` guard that overrides `window.open` and suppresses clicks on non-http/s anchors. This provides a three-layer defence (Chrome flag + JS intercept + request interceptor abort) ensuring that non-http protocol links on target pages never invoke OS handlers (mail client, phone, etc.). (`lazyload`, `run`)
24
+ - `lazyload` (Puppeteer): All three Puppeteer-using modules now issue a `Page.setDownloadBehavior({ behavior: "deny" })` CDP command immediately after page creation. This prevents accidental file downloads triggered by download-link clicks or JavaScript during crawl, which could block the browser and fill the output directory with unexpected files. (`lazyload`, `run`)
25
+ - `lazyload` (tech detect): After `waitUntil: "load"` resolves, if no framework URL has been captured in `interceptedUrls` yet, a conditional `page.waitForNavigation({ timeout: 5000 })` is now issued to wait for a second navigation event. This catches sites that serve a JS proof-of-work bot challenge (e.g. Vercel's `challenge.v2.min.js`) that fires its own `load` event before calling `window.location` to redirect to the real app. Previously, Puppeteer exited on the challenge page's `load` event and tech detection returned null (exit 10). On sites with no redirect the navigation wait times out after 5 s and detection continues normally. (`lazyload`, `run`)
26
+ - `lazyload`: When `frameworkDetect` returns null and `downloadLoadedJs` is used as a fallback, the downloaded file URL paths are now scanned for `/_next/`, `/_nuxt/`, and `/_app/immutable/` signatures as a second-chance tech detection pass. Previously, sites that serve their framework app at a non-root `basePath` (e.g. Next.js at `/app` instead of `/`) caused the initial Puppeteer-based detection to miss the framework because the intercepted JS paths didn't match any framework pattern, resulting in exit 10. (`lazyload`, `run`)
27
+ - `lazyload` (next_js): `next_parseLayoutJs` now applies a 1.5 MB per-file size guard — checked from both the `Content-Length` response header and the fetched content body — before passing layout files to the AST parser. Files exceeding the limit are skipped with a yellow warning. Previously, Next.js App Router sites with 50+ nested pages each loading a large `layout.js` caused cumulative AST memory of ~2 GB, exhausting the V8 heap and crashing with SIGSEGV (exit 139). (`lazyload`, `run`)
28
+ - `cs-mast`: `--scat`, `--sinc`, `--all-scat-permutations`, `--perm-output`, and `--perm-concurrency` flags were declared in the CLI but not wired to the underlying function, causing a TypeScript compile error (`TS2554: Expected 5 arguments, but got 10`). The function now accepts all five additional parameters: `--scat`/`--sinc` override the active CS-MAST config, and `--all-scat-permutations` runs all 511 non-empty scat subsets in parallel batches and writes one collision file per subset to `--perm-output`. (`cs-mast`)
29
+
30
+ - `refactor -t react-vite`: vendor chunks (`vendor-react-*.js`, `rolldown-runtime-*.js`) that are absent from `mapped.json` are now automatically located in the downloaded assets directory and injected before the refactor pass. Previously, `mapped.json` typically contained only app chunks — vendor chunks are excluded from mapping because they hold only third-party code. This left `vendorExportMaps` empty, so the `rewriteVendorImports` pass had nothing to match against and vendor import statements like `import { d as t } from './vendor-react-CLFLfR9F.js'` survived into the output, causing the Vite build check to fail with "Could not resolve". The fix uses the existing `findAssetsDir`/`findVendorChunkFiles` helpers (already used by the webpack branch) to discover the assets directory from `// File Source:` headers in chunk code.
31
+ - `lazyload` (nuxt): `nuxt_getFromPageSource` now prints the number of new JS files discovered (delta from the global URL set) instead of the running total. Previously the count included every URL already known from earlier in the session.
32
+ - `lazyload` (vue): Each discovery method in `vue_discoverJsFiles` now prints only the count of URL that are genuinely new — URLs already known from earlier methods are excluded. Previously `fromImports.length`, `fromStringRefs.length`, etc. could include URLs already in the accumulator and over-count.
33
+ - Error messages emitted by `makeReq` (fetch failures, cache errors, timeout notices, firewall detection) now use `progressError`/`progressLog` instead of `console.error`/`console.log`, so they are routed through the active progress bar's logger and no longer clutter the bar line when errors occur during a scan.
34
+ - `refactor` (`react-webpack`, `react-vite`): new `--detect-version` flag. When set, the tool fetches per-version `reliable_signatures.json` files from the `shriyanss/cs-mast-s-dataset` HuggingFace bucket (path: `version/react/<bundler>/<version>/<scat>/reliable_signatures.json`) and matches them against CS-MAST signatures generated from the target bundle's chunks. The best-matching React version is used to set `react` and `react-dom` version pins in the refactored output's `package.json` instead of the default `^18.3.1`. Signatures are cached under `~/.js-recon/refactor/version_sigs_cache/` with a 7-day TTL. Supports all webpack versions (react-0.12 through react-19) and all Vite versions (react-16 through react-19) available in the dataset. (`refactor`)
35
+
36
+ ### Added
37
+
38
+ - `fingerprint`: output files are now written incrementally — each result is flushed to disk immediately after detection rather than buffered and written at the end. For text, csv, and jsonl formats the result is appended; for json the full array is rewritten. Output files are also created (and csv headers written) before the worker pool starts, so a partial file always exists if the scan is interrupted. (`fingerprint`)
39
+ - CLI: help output is now colorized. Section titles (`Usage:`, `Options:`, `Commands:`) are bold cyan; flag names are yellow; `<required>` argument placeholders are magenta; `[optional]` argument placeholders are cyan; descriptions are dimmed; and `(default: …)` suffixes are dim italic. Applies to every subcommand.
40
+ - `run`: the pipeline now runs an optional `refactor` pass after the report step for React, Vue, Nuxt, and Next.js targets. The bundler (webpack vs vite) is detected automatically using CS-MAST-S signatures sampled from the `shriyanss/cs-mast-s-dataset` HuggingFace bucket — the same bucket and caching layer used by `refactor --remote-collisions`. If the detected match count meets `--cs-mast-tech-detect-threshold` (default `50`), `refactor` is called with the full tech identifier (e.g. `react-webpack`); otherwise the step is silently skipped. Refactored output is written to `refactored/` (single-URL mode) or `<workingDir>/refactored/` (batch mode). Currently only React bundles have bucket entries; Vue, Nuxt, and Next.js gracefully skip until their signatures are added to the bucket. (`run`, `refactor`)
41
+ - `run`: new `--cs-mast-tech-detect-threshold <n>` flag (default `50`). Sets the minimum number of CS-MAST-S signature matches required to consider a bundler detected for the refactor step. Pass `0` to disable refactor entirely. (`run`)
42
+ - `refactor` (`react-vite`, `react-webpack`): new `--remote-collisions <path>` flag. Accepts a HuggingFace bucket path (e.g. `react/vite/large-0.1.8`) and uses it as the signature source instead of the automatic `TECH_TO_BRANCH` mapping. When the path does not exist in the `shriyanss/cs-mast-s-dataset` bucket the tool exits with code 25. The existing caching layer is fully reused — signatures are cached under `~/.js-recon/refactor/signature_cache/` and the file list is cached in `~/.js-recon/refactor/cs-mast-s-list-cache.json`. Feature directories that contain no collision records in the dataset are skipped during intersection rather than collapsing the result to zero.
43
+ - `refactor` (`react-webpack`, `react-vite`): `--detect-version` now uses multiple scat configurations for improved accuracy. Three new flags control the behaviour: `--detect-version-config` (default `dynamic`), `--detect-version-dynamic-threshold` (default `3`), and `--detect-version-dynamic-conf-purge`. In `dynamic` mode the tool automatically selects up to `--detect-version-dynamic-threshold` scat configs that have non-empty reliable signatures across all known React versions, caches the selection in `~/.js-recon/refactor/config.json`, and aggregates match counts across all configs per version for a stronger detection signal. Alternatively, passing comma-separated scat categories (e.g. `lit,decl,loop,cond`) to `--detect-version-config` fixes the config; the tool validates signatures are non-empty for every version and exits with code 26 if not. Use `--detect-version-dynamic-conf-purge` to force recomputation of the cached config.
44
+
45
+ ### Changed
46
+
47
+ - Set `react/webpack/large-0.1.8` as default cs-mast-s dataset for react + webpack refactoring
48
+
3
49
  ## 1.4.1-alpha.4 - 2026-06-29
4
50
 
5
51
  ### Added