@js-recon/js-recon 1.4.3 → 2.0.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-sync/sync.sh +1 -1
- package/.github/workflows/build-and-prettify.yaml +55 -25
- package/.github/workflows/codeql.yml +33 -0
- package/.github/workflows/exploit-smoke-test.yaml +160 -0
- package/.github/workflows/framework-detection-smoke-test.yaml +135 -0
- package/.github/workflows/plumber.yml +20 -0
- package/.github/workflows/promote-js-recon.yml +5 -3
- package/.github/workflows/publish-js-recon.yml +6 -1
- package/.github/workflows/rules-smoke-test.yaml +15 -7
- package/.nvmrc +1 -0
- package/.plumber.yaml +151 -0
- package/CHANGELOG.md +54 -0
- package/CONTRIBUTING.md +32 -0
- package/Dockerfile +1 -0
- package/README.md +55 -2
- package/SECURITY.md +1 -1
- package/build/analyze/engine/astEngine.js +10 -9
- package/build/analyze/engine/astEngine.js.map +1 -1
- package/build/analyze/engine/csMastSEngine.js +6 -6
- package/build/analyze/engine/csMastSEngine.js.map +1 -1
- package/build/analyze/engine/index.js +1 -1
- package/build/analyze/engine/index.js.map +1 -1
- package/build/analyze/engine/requestEngine.js +6 -6
- package/build/analyze/engine/requestEngine.js.map +1 -1
- package/build/analyze/helpers/determineCompatibleVersion.js +70 -0
- package/build/analyze/helpers/determineCompatibleVersion.js.map +1 -0
- package/build/analyze/helpers/engineHelpers/findMemberExpressionAssignment.js +9 -3
- package/build/analyze/helpers/engineHelpers/findMemberExpressionAssignment.js.map +1 -1
- package/build/analyze/helpers/initRules.js +42 -24
- package/build/analyze/helpers/initRules.js.map +1 -1
- package/build/analyze/helpers/outputHelper.js +3 -3
- package/build/analyze/helpers/outputHelper.js.map +1 -1
- package/build/analyze/helpers/schemas.js +1 -0
- package/build/analyze/helpers/schemas.js.map +1 -1
- package/build/analyze/helpers/validate.js +9 -5
- package/build/analyze/helpers/validate.js.map +1 -1
- package/build/analyze/index.js +26 -16
- package/build/analyze/index.js.map +1 -1
- package/build/cliProgram.js +710 -0
- package/build/cliProgram.js.map +1 -0
- package/build/completion/index.js +245 -339
- package/build/completion/index.js.map +1 -1
- package/build/completion/introspect.js +17 -0
- package/build/completion/introspect.js.map +1 -0
- package/build/config/applicationConfig.js +494 -0
- package/build/config/applicationConfig.js.map +1 -0
- package/build/cs_mast/index.js +18 -17
- package/build/cs_mast/index.js.map +1 -1
- package/build/endpoints/gen_report/gen_json.js +2 -2
- package/build/endpoints/gen_report/gen_json.js.map +1 -1
- package/build/endpoints/gen_report/utility/iterate_n_store.js +1 -1
- package/build/endpoints/gen_report/utility/iterate_n_store.js.map +1 -1
- package/build/endpoints/index.js +12 -12
- package/build/endpoints/index.js.map +1 -1
- package/build/endpoints/next_js/client_jsFilesHref.js +3 -3
- package/build/endpoints/next_js/client_jsFilesHref.js.map +1 -1
- package/build/endpoints/next_js/client_jsonParse.js +4 -4
- package/build/endpoints/next_js/client_jsonParse.js.map +1 -1
- package/build/endpoints/next_js/client_mappedJsonFile.js +6 -6
- package/build/endpoints/next_js/client_mappedJsonFile.js.map +1 -1
- package/build/endpoints/next_js/client_subsequentRequests.js +5 -5
- package/build/endpoints/next_js/client_subsequentRequests.js.map +1 -1
- package/build/exploit/angular/cve-2026-68945.js +61 -0
- package/build/exploit/angular/cve-2026-68945.js.map +1 -0
- package/build/exploit/index.js +60 -0
- package/build/exploit/index.js.map +1 -0
- package/build/exploit/next/cve-2025-29927.js +65 -0
- package/build/exploit/next/cve-2025-29927.js.map +1 -0
- package/build/exploit/next/cve-2026-64645.js +51 -0
- package/build/exploit/next/cve-2026-64645.js.map +1 -0
- package/build/exploit/nuxt/cve-2026-71315.js +47 -0
- package/build/exploit/nuxt/cve-2026-71315.js.map +1 -0
- package/build/exploit/nuxt/cve-2026-71320.js +114 -0
- package/build/exploit/nuxt/cve-2026-71320.js.map +1 -0
- package/build/exploit/react/cve-2025-55182.js +116 -0
- package/build/exploit/react/cve-2025-55182.js.map +1 -0
- package/build/exploit/registry/cveRegistry.js +115 -0
- package/build/exploit/registry/cveRegistry.js.map +1 -0
- package/build/exploit/types.js +2 -0
- package/build/exploit/types.js.map +1 -0
- package/build/exploit/utility/compareResponses.js +29 -0
- package/build/exploit/utility/compareResponses.js.map +1 -0
- package/build/exploit/utility/outputHelper.js +22 -0
- package/build/exploit/utility/outputHelper.js.map +1 -0
- package/build/exploit/utility/rawRequest.js +54 -0
- package/build/exploit/utility/rawRequest.js.map +1 -0
- package/build/exploit/utility/sandboxRce.js +49 -0
- package/build/exploit/utility/sandboxRce.js.map +1 -0
- package/build/exploit/utility/toEngineOutput.js +28 -0
- package/build/exploit/utility/toEngineOutput.js.map +1 -0
- package/build/exploit/utility/wsClient.js +78 -0
- package/build/exploit/utility/wsClient.js.map +1 -0
- package/build/exploit/vite/cve-2025-30208.js +44 -0
- package/build/exploit/vite/cve-2025-30208.js.map +1 -0
- package/build/exploit/vite/cve-2026-39363.js +60 -0
- package/build/exploit/vite/cve-2026-39363.js.map +1 -0
- package/build/fingerprint/index.js +11 -21
- package/build/fingerprint/index.js.map +1 -1
- package/build/globalConfig.js +2 -2
- package/build/globalConfig.js.map +1 -1
- package/build/index.js +16 -555
- package/build/index.js.map +1 -1
- package/build/lazyLoad/angular/angular_getFromPageSource.js +3 -3
- package/build/lazyLoad/angular/angular_getFromPageSource.js.map +1 -1
- package/build/lazyLoad/downloadFilesUtil.js +51 -40
- package/build/lazyLoad/downloadFilesUtil.js.map +1 -1
- package/build/lazyLoad/downloadQueue.js +154 -50
- package/build/lazyLoad/downloadQueue.js.map +1 -1
- package/build/lazyLoad/downloadResponse.js +113 -0
- package/build/lazyLoad/downloadResponse.js.map +1 -0
- package/build/lazyLoad/generic/generic_crawlPages.js +186 -0
- package/build/lazyLoad/generic/generic_crawlPages.js.map +1 -0
- package/build/lazyLoad/generic/generic_downloadFiles.js +81 -0
- package/build/lazyLoad/generic/generic_downloadFiles.js.map +1 -0
- package/build/lazyLoad/generic/generic_extractLinks.js +79 -0
- package/build/lazyLoad/generic/generic_extractLinks.js.map +1 -0
- package/build/lazyLoad/generic/generic_getScriptTags.js +152 -0
- package/build/lazyLoad/generic/generic_getScriptTags.js.map +1 -0
- package/build/lazyLoad/generic/generic_importMapDiscovery.js +87 -0
- package/build/lazyLoad/generic/generic_importMapDiscovery.js.map +1 -0
- package/build/lazyLoad/generic/generic_resolveScope.js +61 -0
- package/build/lazyLoad/generic/generic_resolveScope.js.map +1 -0
- package/build/lazyLoad/generic/generic_scanAttributesForJs.js +122 -0
- package/build/lazyLoad/generic/generic_scanAttributesForJs.js.map +1 -0
- package/build/lazyLoad/generic/generic_stringsDiscovery.js +103 -0
- package/build/lazyLoad/generic/generic_stringsDiscovery.js.map +1 -0
- package/build/lazyLoad/generic/generic_webpackChunkPaths.js +56 -0
- package/build/lazyLoad/generic/generic_webpackChunkPaths.js.map +1 -0
- package/build/lazyLoad/globals.js +35 -0
- package/build/lazyLoad/globals.js.map +1 -1
- package/build/lazyLoad/index.js +311 -175
- package/build/lazyLoad/index.js.map +1 -1
- package/build/lazyLoad/methodFilter.js +2 -0
- package/build/lazyLoad/methodFilter.js.map +1 -1
- package/build/lazyLoad/next_js/NextJsCrawler.js +39 -14
- package/build/lazyLoad/next_js/NextJsCrawler.js.map +1 -1
- package/build/lazyLoad/next_js/next_GetJSScript.js +3 -3
- package/build/lazyLoad/next_js/next_GetJSScript.js.map +1 -1
- package/build/lazyLoad/next_js/next_GetLazyResourcesBuildManifestJs.js +4 -4
- package/build/lazyLoad/next_js/next_GetLazyResourcesBuildManifestJs.js.map +1 -1
- package/build/lazyLoad/next_js/next_GetLazyResourcesWebpackJs.js +21 -20
- package/build/lazyLoad/next_js/next_GetLazyResourcesWebpackJs.js.map +1 -1
- package/build/lazyLoad/next_js/next_SubsequentRequests.js +100 -99
- package/build/lazyLoad/next_js/next_SubsequentRequests.js.map +1 -1
- package/build/lazyLoad/next_js/next_bruteForceJsFiles.js +1 -1
- package/build/lazyLoad/next_js/next_bruteForceJsFiles.js.map +1 -1
- package/build/lazyLoad/next_js/next_buildId.js +2 -2
- package/build/lazyLoad/next_js/next_buildId.js.map +1 -1
- package/build/lazyLoad/next_js/next_getClientSidePaths.js +2 -2
- package/build/lazyLoad/next_js/next_getClientSidePaths.js.map +1 -1
- package/build/lazyLoad/next_js/next_parseLayoutJs.js +6 -6
- package/build/lazyLoad/next_js/next_parseLayoutJs.js.map +1 -1
- package/build/lazyLoad/next_js/next_promiseResolve.js +1 -1
- package/build/lazyLoad/next_js/next_promiseResolve.js.map +1 -1
- package/build/lazyLoad/next_js/next_routerStateForge.js +341 -0
- package/build/lazyLoad/next_js/next_routerStateForge.js.map +1 -0
- package/build/lazyLoad/next_js/next_scriptTagsSubsequentRequests.js +1 -1
- package/build/lazyLoad/next_js/next_scriptTagsSubsequentRequests.js.map +1 -1
- package/build/lazyLoad/next_js/next_serverActionIdScan.js +77 -0
- package/build/lazyLoad/next_js/next_serverActionIdScan.js.map +1 -0
- package/build/lazyLoad/nuxt_js/nuxt_astParse.js +15 -13
- package/build/lazyLoad/nuxt_js/nuxt_astParse.js.map +1 -1
- package/build/lazyLoad/nuxt_js/nuxt_getBuildsManifest.js +2 -2
- package/build/lazyLoad/nuxt_js/nuxt_getBuildsManifest.js.map +1 -1
- package/build/lazyLoad/nuxt_js/nuxt_getFromPageSource.js +3 -3
- package/build/lazyLoad/nuxt_js/nuxt_getFromPageSource.js.map +1 -1
- package/build/lazyLoad/nuxt_js/nuxt_stringAnalysisJSFiles.js +2 -2
- package/build/lazyLoad/nuxt_js/nuxt_stringAnalysisJSFiles.js.map +1 -1
- package/build/lazyLoad/outputPath.js +92 -0
- package/build/lazyLoad/outputPath.js.map +1 -0
- package/build/lazyLoad/react/react_followImports.js +15 -5
- package/build/lazyLoad/react/react_followImports.js.map +1 -1
- package/build/lazyLoad/react/react_getScriptTags.js +11 -6
- package/build/lazyLoad/react/react_getScriptTags.js.map +1 -1
- package/build/lazyLoad/react/react_sourcemapUrls.js +3 -2
- package/build/lazyLoad/react/react_sourcemapUrls.js.map +1 -1
- package/build/lazyLoad/react/react_webpackChunkPaths.js +12 -203
- package/build/lazyLoad/react/react_webpackChunkPaths.js.map +1 -1
- package/build/lazyLoad/researchUtils.js +15 -0
- package/build/lazyLoad/researchUtils.js.map +1 -0
- package/build/lazyLoad/shared/webpackChunkParsers.js +218 -0
- package/build/lazyLoad/shared/webpackChunkParsers.js.map +1 -0
- package/build/lazyLoad/stagnation/stagnationMonitor.js +63 -0
- package/build/lazyLoad/stagnation/stagnationMonitor.js.map +1 -0
- package/build/lazyLoad/svelte/svelte_discoverPagesFromJs.js +1 -0
- package/build/lazyLoad/svelte/svelte_discoverPagesFromJs.js.map +1 -1
- package/build/lazyLoad/svelte/svelte_getFromPageSource.js +5 -5
- package/build/lazyLoad/svelte/svelte_getFromPageSource.js.map +1 -1
- package/build/lazyLoad/svelte/svelte_getVersionJson.js +2 -2
- package/build/lazyLoad/svelte/svelte_getVersionJson.js.map +1 -1
- package/build/lazyLoad/svelte/svelte_recursivePageCrawl.js +6 -6
- package/build/lazyLoad/svelte/svelte_recursivePageCrawl.js.map +1 -1
- package/build/lazyLoad/svelte/svelte_stringAnalysisJSFiles.js +2 -2
- package/build/lazyLoad/svelte/svelte_stringAnalysisJSFiles.js.map +1 -1
- package/build/lazyLoad/techDetect/checkAngularJS.js +1 -1
- package/build/lazyLoad/techDetect/checkAngularJS.js.map +1 -1
- package/build/lazyLoad/techDetect/checkNuxtJS.js +3 -3
- package/build/lazyLoad/techDetect/checkNuxtJS.js.map +1 -1
- package/build/lazyLoad/techDetect/checkSvelte.js +0 -1
- package/build/lazyLoad/techDetect/checkSvelte.js.map +1 -1
- package/build/lazyLoad/techDetect/checkVueJS.js +6 -6
- package/build/lazyLoad/techDetect/checkVueJS.js.map +1 -1
- package/build/lazyLoad/techDetect/index.js +106 -60
- package/build/lazyLoad/techDetect/index.js.map +1 -1
- package/build/lazyLoad/vue/vue_RuntimeJs.js +20 -15
- package/build/lazyLoad/vue/vue_RuntimeJs.js.map +1 -1
- package/build/lazyLoad/vue/vue_SingleJsFileOnHome.js +10 -5
- package/build/lazyLoad/vue/vue_SingleJsFileOnHome.js.map +1 -1
- package/build/lazyLoad/vue/vue_discoverJsFiles.js +20 -17
- package/build/lazyLoad/vue/vue_discoverJsFiles.js.map +1 -1
- package/build/lazyLoad/vue/vue_getClientSidePaths.js +70 -46
- package/build/lazyLoad/vue/vue_getClientSidePaths.js.map +1 -1
- package/build/lazyLoad/vue/vue_jsImports.js +3 -2
- package/build/lazyLoad/vue/vue_jsImports.js.map +1 -1
- package/build/lazyLoad/vue/vue_pageSrc.js +3 -2
- package/build/lazyLoad/vue/vue_pageSrc.js.map +1 -1
- package/build/lazyLoad/vue/vue_reconstructSourceMaps.js +6 -5
- package/build/lazyLoad/vue/vue_reconstructSourceMaps.js.map +1 -1
- package/build/lazyLoad/vue/vue_recursiveClientSidePathDownload.js +10 -21
- package/build/lazyLoad/vue/vue_recursiveClientSidePathDownload.js.map +1 -1
- package/build/lazyLoad/vue/vue_severalJsFilesHome.js +5 -4
- package/build/lazyLoad/vue/vue_severalJsFilesHome.js.map +1 -1
- package/build/lazyLoad/vue/vue_stringJsFiles.js +28 -25
- package/build/lazyLoad/vue/vue_stringJsFiles.js.map +1 -1
- package/build/lazyLoad/vue/vue_viteMapDeps.js +8 -2
- package/build/lazyLoad/vue/vue_viteMapDeps.js.map +1 -1
- package/build/load/index.js +8 -7
- package/build/load/index.js.map +1 -1
- package/build/map/angular_js/getAngularConnections.js +6 -6
- package/build/map/angular_js/getAngularConnections.js.map +1 -1
- package/build/map/graphql/resolveGraphql.js +6 -6
- package/build/map/graphql/resolveGraphql.js.map +1 -1
- package/build/map/index.js +17 -17
- package/build/map/index.js.map +1 -1
- package/build/map/interactive-mode/commandHandler.js +278 -0
- package/build/map/interactive-mode/commandHandler.js.map +1 -0
- package/build/map/interactive-mode/commandHelpers.js +189 -0
- package/build/map/interactive-mode/commandHelpers.js.map +1 -0
- package/build/map/interactive-mode/esqueryGen.js.map +1 -0
- package/build/map/interactive-mode/helpMenu.js +11 -0
- package/build/map/interactive-mode/helpMenu.js.map +1 -0
- package/build/map/interactive-mode/inputPatch.js.map +1 -0
- package/build/map/interactive-mode/interactive.js +71 -0
- package/build/map/interactive-mode/interactive.js.map +1 -0
- package/build/map/interactive-mode/keybindings.js.map +1 -0
- package/build/map/interactive-mode/printer.js.map +1 -0
- package/build/map/interactive-mode/ui.js.map +1 -0
- package/build/map/next_js/getAxiosInstances.js +5 -5
- package/build/map/next_js/getAxiosInstances.js.map +1 -1
- package/build/map/next_js/getExports.js +4 -4
- package/build/map/next_js/getExports.js.map +1 -1
- package/build/map/next_js/getFetchInstances.js +10 -10
- package/build/map/next_js/getFetchInstances.js.map +1 -1
- package/build/map/next_js/getTurbopackConnections.js +8 -8
- package/build/map/next_js/getTurbopackConnections.js.map +1 -1
- package/build/map/next_js/getWebpackConnections.js +27 -16
- package/build/map/next_js/getWebpackConnections.js.map +1 -1
- package/build/map/next_js/interactive.js +3 -65
- package/build/map/next_js/interactive.js.map +1 -1
- package/build/map/next_js/interactive_helpers/commandHandler.js +38 -290
- package/build/map/next_js/interactive_helpers/commandHandler.js.map +1 -1
- package/build/map/next_js/interactive_helpers/commandHelpers.js +0 -184
- package/build/map/next_js/interactive_helpers/commandHelpers.js.map +1 -1
- package/build/map/next_js/interactive_helpers/helpMenu.js +2 -10
- package/build/map/next_js/interactive_helpers/helpMenu.js.map +1 -1
- package/build/map/next_js/resolveAxios.js +6 -6
- package/build/map/next_js/resolveAxios.js.map +1 -1
- package/build/map/next_js/resolveAxiosHelpers/findAxiosClients.js +4 -4
- package/build/map/next_js/resolveAxiosHelpers/findAxiosClients.js.map +1 -1
- package/build/map/next_js/resolveAxiosHelpers/findAxiosInstance.js +2 -2
- package/build/map/next_js/resolveAxiosHelpers/findAxiosInstance.js.map +1 -1
- package/build/map/next_js/resolveAxiosHelpers/findCrossChunkParams.js +5 -5
- package/build/map/next_js/resolveAxiosHelpers/findCrossChunkParams.js.map +1 -1
- package/build/map/next_js/resolveAxiosHelpers/handleAxiosCreate.js +10 -10
- package/build/map/next_js/resolveAxiosHelpers/handleAxiosCreate.js.map +1 -1
- package/build/map/next_js/resolveAxiosHelpers/handleZDotCreate.js +13 -9
- package/build/map/next_js/resolveAxiosHelpers/handleZDotCreate.js.map +1 -1
- package/build/map/next_js/resolveAxiosHelpers/processAxiosCall.js +12 -8
- package/build/map/next_js/resolveAxiosHelpers/processAxiosCall.js.map +1 -1
- package/build/map/next_js/resolveAxiosHelpers/processDirectAxiosCall.js +12 -8
- package/build/map/next_js/resolveAxiosHelpers/processDirectAxiosCall.js.map +1 -1
- package/build/map/next_js/resolveAxiosHelpers/processExportedEndpoints.js +11 -11
- package/build/map/next_js/resolveAxiosHelpers/processExportedEndpoints.js.map +1 -1
- package/build/map/next_js/resolveAxiosHelpers/traceAxiosInstanceExports.js +16 -16
- package/build/map/next_js/resolveAxiosHelpers/traceAxiosInstanceExports.js.map +1 -1
- package/build/map/next_js/resolveFetch.js +23 -23
- package/build/map/next_js/resolveFetch.js.map +1 -1
- package/build/map/next_js/resolveNewRequest.js +10 -10
- package/build/map/next_js/resolveNewRequest.js.map +1 -1
- package/build/map/next_js/resolveServerActions.js +4 -4
- package/build/map/next_js/resolveServerActions.js.map +1 -1
- package/build/map/next_js/utils.js +8 -2
- package/build/map/next_js/utils.js.map +1 -1
- package/build/map/react_js/getReactConnections.js +21 -14
- package/build/map/react_js/getReactConnections.js.map +1 -1
- package/build/map/svelte_js/interactive.js +3 -54
- package/build/map/svelte_js/interactive.js.map +1 -1
- package/build/map/vue_js/getViteConnections.js +23 -16
- package/build/map/vue_js/getViteConnections.js.map +1 -1
- package/build/map/vue_js/interactive.js +3 -63
- package/build/map/vue_js/interactive.js.map +1 -1
- package/build/map/vue_js/interactive_helpers/commandHandler.js +34 -283
- package/build/map/vue_js/interactive_helpers/commandHandler.js.map +1 -1
- package/build/map/vue_js/interactive_helpers/helpMenu.js +2 -10
- package/build/map/vue_js/interactive_helpers/helpMenu.js.map +1 -1
- package/build/map/vue_js/taint_utils.js +1 -1
- package/build/map/vue_js/taint_utils.js.map +1 -1
- package/build/map/vue_js/vue_resolveFetch.js +12 -12
- package/build/map/vue_js/vue_resolveFetch.js.map +1 -1
- package/build/map/vue_js/vue_resolveHttpClient.js +18 -18
- package/build/map/vue_js/vue_resolveHttpClient.js.map +1 -1
- package/build/map/vue_js/vue_resolveXhr.js +11 -11
- package/build/map/vue_js/vue_resolveXhr.js.map +1 -1
- package/build/mcp/chatOneShot.js +7 -7
- package/build/mcp/chatOneShot.js.map +1 -1
- package/build/mcp/claudeCodeCreds.js +5 -5
- package/build/mcp/claudeCodeCreds.js.map +1 -1
- package/build/mcp/cli.js +36 -35
- package/build/mcp/cli.js.map +1 -1
- package/build/mcp/config.js +4 -4
- package/build/mcp/config.js.map +1 -1
- package/build/mcp/index.js +7 -7
- package/build/mcp/index.js.map +1 -1
- package/build/mcp/mcpServer.js +3 -2
- package/build/mcp/mcpServer.js.map +1 -1
- package/build/mcp/skills.js +1 -0
- package/build/mcp/skills.js.map +1 -1
- package/build/mcp/tools.js +5 -4
- package/build/mcp/tools.js.map +1 -1
- package/build/proxy/checkFeasibility.js +8 -4
- package/build/proxy/checkFeasibility.js.map +1 -1
- package/build/proxy/checkFireWallBlocking.js +23 -13
- package/build/proxy/checkFireWallBlocking.js.map +1 -1
- package/build/proxy/detectBlockedResponse.js +205 -0
- package/build/proxy/detectBlockedResponse.js.map +1 -0
- package/build/proxy/genReq.js +434 -127
- package/build/proxy/genReq.js.map +1 -1
- package/build/proxy/index.js +3 -3
- package/build/proxy/index.js.map +1 -1
- package/build/proxy/oxylabsFallback.js +110 -0
- package/build/proxy/oxylabsFallback.js.map +1 -0
- package/build/proxy/proxyFeasibilityRequest.js +11 -9
- package/build/proxy/proxyFeasibilityRequest.js.map +1 -1
- package/build/refactor/index.js +83 -79
- package/build/refactor/index.js.map +1 -1
- package/build/refactor/next/index.js +69 -26
- package/build/refactor/next/index.js.map +1 -1
- package/build/refactor/next/transform.js +11 -4
- package/build/refactor/next/transform.js.map +1 -1
- package/build/refactor/next/validator.js +7 -7
- package/build/refactor/next/validator.js.map +1 -1
- package/build/refactor/react/index.js +13 -13
- package/build/refactor/react/index.js.map +1 -1
- package/build/refactor/react/validator.js +7 -7
- package/build/refactor/react/validator.js.map +1 -1
- package/build/refactor/react-vite/index.js +3 -3
- package/build/refactor/react-vite/index.js.map +1 -1
- package/build/refactor/remote/hf-client.js +1 -0
- package/build/refactor/remote/hf-client.js.map +1 -1
- package/build/refactor/remote/version-detect.js +12 -12
- package/build/refactor/remote/version-detect.js.map +1 -1
- package/build/refactor/vue/index.js +6 -6
- package/build/refactor/vue/index.js.map +1 -1
- package/build/refactor/vue/vite.js +6 -6
- package/build/refactor/vue/vite.js.map +1 -1
- package/build/report/index.js +30 -8
- package/build/report/index.js.map +1 -1
- package/build/report/swaggerJacker.js +63 -0
- package/build/report/swaggerJacker.js.map +1 -0
- package/build/report/utility/genHtml.js +7 -7
- package/build/report/utility/genHtml.js.map +1 -1
- package/build/report/utility/populateDb/populateAnalysisFindings.js +2 -2
- package/build/report/utility/populateDb/populateAnalysisFindings.js.map +1 -1
- package/build/report/utility/populateDb/populateEndpoints.js +3 -3
- package/build/report/utility/populateDb/populateEndpoints.js.map +1 -1
- package/build/report/utility/populateDb/populateMappedJson.js +2 -2
- package/build/report/utility/populateDb/populateMappedJson.js.map +1 -1
- package/build/report/utility/populateDb/populateMappedOpenapi.js +3 -3
- package/build/report/utility/populateDb/populateMappedOpenapi.js.map +1 -1
- package/build/run/bundler-detect.js +45 -9
- package/build/run/bundler-detect.js.map +1 -1
- package/build/run/cancellableStep.js +27 -0
- package/build/run/cancellableStep.js.map +1 -0
- package/build/run/dashboard/dashboardHtml.js +150 -0
- package/build/run/dashboard/dashboardHtml.js.map +1 -0
- package/build/run/dashboard/fsBrowser.js +43 -0
- package/build/run/dashboard/fsBrowser.js.map +1 -0
- package/build/run/dashboard/server.js +123 -0
- package/build/run/dashboard/server.js.map +1 -0
- package/build/run/dashboard/state.js +97 -0
- package/build/run/dashboard/state.js.map +1 -0
- package/build/run/index.js +239 -204
- package/build/run/index.js.map +1 -1
- package/build/run/interruptHandler.js +18 -0
- package/build/run/interruptHandler.js.map +1 -1
- package/build/run/outputDirectory.js +377 -0
- package/build/run/outputDirectory.js.map +1 -0
- package/build/sourcemaps/index.js +116 -29
- package/build/sourcemaps/index.js.map +1 -1
- package/build/strings/index.js +38 -28
- package/build/strings/index.js.map +1 -1
- package/build/strings/openapi.js +4 -4
- package/build/strings/openapi.js.map +1 -1
- package/build/strings/permutate.js +4 -4
- package/build/strings/permutate.js.map +1 -1
- package/build/strings/secrets.js +1 -0
- package/build/strings/secrets.js.map +1 -1
- package/build/strings/trufflehog.js +66 -19
- package/build/strings/trufflehog.js.map +1 -1
- package/build/strings/trufflehogConfig.js +36 -0
- package/build/strings/trufflehogConfig.js.map +1 -0
- package/build/strings/trufflehogDownload.js +115 -0
- package/build/strings/trufflehogDownload.js.map +1 -0
- package/build/utility/ai.js +33 -4
- package/build/utility/ai.js.map +1 -1
- package/build/utility/banner.js +4 -3
- package/build/utility/banner.js.map +1 -1
- package/build/utility/configureSandbox.js +2 -2
- package/build/utility/configureSandbox.js.map +1 -1
- package/build/utility/downloadProgress.js +99 -0
- package/build/utility/downloadProgress.js.map +1 -0
- package/build/utility/globals.js +19 -11
- package/build/utility/globals.js.map +1 -1
- package/build/utility/heap.js +2 -1
- package/build/utility/heap.js.map +1 -1
- package/build/utility/makeReq.js +490 -257
- package/build/utility/makeReq.js.map +1 -1
- package/build/utility/openapiGenerator.js +2 -2
- package/build/utility/openapiGenerator.js.map +1 -1
- package/build/utility/printMsg.js +53 -0
- package/build/utility/printMsg.js.map +1 -0
- package/build/utility/progressLog.js +68 -7
- package/build/utility/progressLog.js.map +1 -1
- package/build/utility/resolvePath.js +2 -1
- package/build/utility/resolvePath.js.map +1 -1
- package/build/utility/ruleVersionMap.js +73 -0
- package/build/utility/ruleVersionMap.js.map +1 -0
- package/build/utility/targetInputs.js +177 -0
- package/build/utility/targetInputs.js.map +1 -0
- package/build/utility/urlUtils.js +37 -1
- package/build/utility/urlUtils.js.map +1 -1
- package/config.dist.yaml +251 -0
- package/contributing/testing.md +106 -0
- package/eslint.config.js +24 -0
- package/package.json +22 -5
- package/scripts/exploit-smoke-test.js +82 -0
- package/scripts/framework-detect-smoke-test.js +64 -0
- package/scripts/postinstall-completion.mjs +40 -0
- package/scripts/smoke-test.js +12 -0
- package/build/lazyLoad/downloadLoadedJsUtil.js +0 -98
- package/build/lazyLoad/downloadLoadedJsUtil.js.map +0 -1
- package/build/map/next_js/interactive_helpers/esqueryGen.js.map +0 -1
- package/build/map/next_js/interactive_helpers/inputPatch.js.map +0 -1
- package/build/map/next_js/interactive_helpers/keybindings.js.map +0 -1
- package/build/map/next_js/interactive_helpers/printer.js.map +0 -1
- package/build/map/next_js/interactive_helpers/ui.js.map +0 -1
- /package/build/map/{next_js/interactive_helpers → interactive-mode}/esqueryGen.js +0 -0
- /package/build/map/{next_js/interactive_helpers → interactive-mode}/inputPatch.js +0 -0
- /package/build/map/{next_js/interactive_helpers → interactive-mode}/keybindings.js +0 -0
- /package/build/map/{next_js/interactive_helpers → interactive-mode}/printer.js +0 -0
- /package/build/map/{next_js/interactive_helpers → interactive-mode}/ui.js +0 -0
|
@@ -7,15 +7,51 @@
|
|
|
7
7
|
* @param {string} url - The URL to parse.
|
|
8
8
|
* @returns {Object} An object with the host and directory of the URL.
|
|
9
9
|
*/
|
|
10
|
+
export const toOutputHost = (host) => {
|
|
11
|
+
const normalized = host.normalize("NFKC").replace(/[^a-zA-Z0-9._\-[\]]/g, "_");
|
|
12
|
+
const nonTraversing = normalized.length === 0 || /^\.+$/.test(normalized) ? normalized.replace(/\./g, "_") || "_" : normalized;
|
|
13
|
+
const changed = nonTraversing !== host;
|
|
14
|
+
const reservedLiteral = nonTraversing.startsWith(ENCODED_HOST_PREFIX);
|
|
15
|
+
const needsBounding = nonTraversing.length > MAX_OUTPUT_HOST_LENGTH;
|
|
16
|
+
if (!changed && !reservedLiteral && !needsBounding)
|
|
17
|
+
return nonTraversing;
|
|
18
|
+
const category = changed ? "encoded" : needsBounding ? "bounded" : "literal";
|
|
19
|
+
const digest = createHash("sha256").update(host).digest("hex").slice(0, 12);
|
|
20
|
+
const tagged = `${ENCODED_HOST_PREFIX}${category}_${nonTraversing}`;
|
|
21
|
+
return `${tagged.slice(0, MAX_OUTPUT_HOST_LENGTH - digest.length - 1)}-${digest}`;
|
|
22
|
+
};
|
|
23
|
+
const canonicalNetworkHost = (host) => {
|
|
24
|
+
const candidate = host.trim();
|
|
25
|
+
if (!candidate)
|
|
26
|
+
return null;
|
|
27
|
+
try {
|
|
28
|
+
const parsed = new URL(candidate.includes("://") ? candidate : `http://${candidate}`);
|
|
29
|
+
return parsed.host.toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
catch (_a) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
/** Compares canonical network hosts without using lossy filesystem naming. */
|
|
36
|
+
export const hostMatchesScope = (host, scope) => {
|
|
37
|
+
const canonicalHost = canonicalNetworkHost(host);
|
|
38
|
+
if (!canonicalHost)
|
|
39
|
+
return false;
|
|
40
|
+
return scope.some((candidate) => candidate.trim() === "*" || canonicalNetworkHost(candidate) === canonicalHost);
|
|
41
|
+
};
|
|
10
42
|
const getURLDirectory = (url) => {
|
|
11
43
|
const u = new URL(url);
|
|
12
44
|
const pathname = u.pathname;
|
|
13
45
|
// Remove filename (last part after final /) if it ends with .js or any file extension
|
|
14
46
|
const dir = pathname.replace(/\/[^\/?#]+\.[^\/?#]+$/, "");
|
|
15
47
|
return {
|
|
16
|
-
host: u.host
|
|
48
|
+
host: toOutputHost(u.host), // e.g., "vercel.com" or "localhost_3000"
|
|
49
|
+
rawHost: u.host,
|
|
17
50
|
directory: dir, // e.g., "/static/js"
|
|
18
51
|
};
|
|
19
52
|
};
|
|
20
53
|
export { getURLDirectory };
|
|
54
|
+
import { createHash } from "node:crypto";
|
|
55
|
+
const ENCODED_HOST_PREFIX = "__jsr_host_";
|
|
56
|
+
const MAX_OUTPUT_HOST_LENGTH = 180;
|
|
21
57
|
//# sourceMappingURL=urlUtils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"urlUtils.js","sourceRoot":"","sources":["../../src/utility/urlUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,eAAe,GAAG,CAAC,GAAW,EAAE,EAAE;IACpC,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACvB,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;IAE5B,sFAAsF;IACtF,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;IAE1D,OAAO;QACH,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"urlUtils.js","sourceRoot":"","sources":["../../src/utility/urlUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAAY,EAAU,EAAE;IACjD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC;IAC/E,MAAM,aAAa,GACf,UAAU,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC;IAC7G,MAAM,OAAO,GAAG,aAAa,KAAK,IAAI,CAAC;IACvC,MAAM,eAAe,GAAG,aAAa,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;IACtE,MAAM,aAAa,GAAG,aAAa,CAAC,MAAM,GAAG,sBAAsB,CAAC;IACpE,IAAI,CAAC,OAAO,IAAI,CAAC,eAAe,IAAI,CAAC,aAAa;QAAE,OAAO,aAAa,CAAC;IAEzE,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7E,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC5E,MAAM,MAAM,GAAG,GAAG,mBAAmB,GAAG,QAAQ,IAAI,aAAa,EAAE,CAAC;IACpE,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC;AACtF,CAAC,CAAC;AAEF,MAAM,oBAAoB,GAAG,CAAC,IAAY,EAAiB,EAAE;IACzD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAC5B,IAAI,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,SAAS,EAAE,CAAC,CAAC;QACtF,OAAO,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IACrC,CAAC;IAAC,WAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC,CAAC;AAEF,8EAA8E;AAC9E,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAY,EAAE,KAAwB,EAAW,EAAE;IAChF,MAAM,aAAa,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACjD,IAAI,CAAC,aAAa;QAAE,OAAO,KAAK,CAAC;IACjC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,oBAAoB,CAAC,SAAS,CAAC,KAAK,aAAa,CAAC,CAAC;AACpH,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,GAAW,EAAE,EAAE;IACpC,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACvB,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;IAE5B,sFAAsF;IACtF,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;IAE1D,OAAO;QACH,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,yCAAyC;QACrE,OAAO,EAAE,CAAC,CAAC,IAAI;QACf,SAAS,EAAE,GAAG,EAAE,qBAAqB;KACxC,CAAC;AACN,CAAC,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,mBAAmB,GAAG,aAAa,CAAC;AAC1C,MAAM,sBAAsB,GAAG,GAAG,CAAC"}
|
package/config.dist.yaml
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# js-recon application configuration
|
|
2
|
+
#
|
|
3
|
+
# The CLI creates ~/.config/js-recon/config.yaml on first use. Copy this file
|
|
4
|
+
# elsewhere when you want to use: js-recon --config /path/to/config.yaml <command>
|
|
5
|
+
#
|
|
6
|
+
# Precedence (highest first): environment, command-line flag, this YAML file,
|
|
7
|
+
# built-in default. Every command option also has a canonical environment name:
|
|
8
|
+
# JS_RECON_<COMMAND>_<OPTION>, for example JS_RECON_RUN_THREADS.
|
|
9
|
+
#
|
|
10
|
+
# null means "not configured". Keep credential-bearing files mode 0600.
|
|
11
|
+
version: 1
|
|
12
|
+
|
|
13
|
+
oxylabs:
|
|
14
|
+
# Required in an explicitly selected config when oxylabsWafFallback is true.
|
|
15
|
+
# JS_RECON_OXYLABS_USERNAME/PASSWORD/COUNTRY may override these values.
|
|
16
|
+
username: null
|
|
17
|
+
password: null
|
|
18
|
+
country: null
|
|
19
|
+
|
|
20
|
+
commands:
|
|
21
|
+
lazyload:
|
|
22
|
+
# May also be a YAML list of HTTP(S) URLs:
|
|
23
|
+
# url:
|
|
24
|
+
# - https://one.example.com
|
|
25
|
+
# - https://two.example.com
|
|
26
|
+
url: null
|
|
27
|
+
output: output
|
|
28
|
+
strictScope: false
|
|
29
|
+
scope: "*"
|
|
30
|
+
threads: "1"
|
|
31
|
+
subsequentRequests: false
|
|
32
|
+
urlsFile: extracted_urls.json
|
|
33
|
+
proxyConfig: .proxy_config.json
|
|
34
|
+
ignoreProxyEnv: false
|
|
35
|
+
oxylabsWafFallback: false
|
|
36
|
+
oxylabsFallbackMaxRequests: "10"
|
|
37
|
+
oxylabsFallbackMaxTotal: "100"
|
|
38
|
+
oxylabsFallbackMaxOrigins: "25"
|
|
39
|
+
cacheFile: .resp_cache.json
|
|
40
|
+
disableCache: false
|
|
41
|
+
cacheOnly: false
|
|
42
|
+
yes: false
|
|
43
|
+
timeout: "30000"
|
|
44
|
+
insecure: false
|
|
45
|
+
sandbox: true
|
|
46
|
+
buildId: false
|
|
47
|
+
sourcemapDir: extracted
|
|
48
|
+
research: false
|
|
49
|
+
researchOutput: research.json
|
|
50
|
+
maxIterations: "10"
|
|
51
|
+
maxJsSize: "2"
|
|
52
|
+
lazyloadTimeout: "30"
|
|
53
|
+
detectionTimeout: "30"
|
|
54
|
+
maxPages: "200"
|
|
55
|
+
includeMethods: null
|
|
56
|
+
excludeMethods: null
|
|
57
|
+
listMethods: null
|
|
58
|
+
|
|
59
|
+
endpoints:
|
|
60
|
+
url: null
|
|
61
|
+
directory: null
|
|
62
|
+
output: endpoints
|
|
63
|
+
outputFormat: json
|
|
64
|
+
tech: null
|
|
65
|
+
list: false
|
|
66
|
+
mappedJson: null
|
|
67
|
+
|
|
68
|
+
strings:
|
|
69
|
+
directory: null
|
|
70
|
+
output: strings.json
|
|
71
|
+
extractUrls: false
|
|
72
|
+
extractedUrlPath: extracted_urls
|
|
73
|
+
permutate: false
|
|
74
|
+
openapi: false
|
|
75
|
+
scanSecrets: false
|
|
76
|
+
trufflehog: false
|
|
77
|
+
|
|
78
|
+
proxy:
|
|
79
|
+
init: false
|
|
80
|
+
destroy: null
|
|
81
|
+
destroyAll: false
|
|
82
|
+
region: null
|
|
83
|
+
awsAccessKey: null
|
|
84
|
+
awsSecretKey: null
|
|
85
|
+
config: .proxy_config.json
|
|
86
|
+
list: false
|
|
87
|
+
feasibility: false
|
|
88
|
+
feasibilityUrl: null
|
|
89
|
+
proxyMethod: null
|
|
90
|
+
proxy: null
|
|
91
|
+
oxylabsUsername: null
|
|
92
|
+
oxylabsPassword: null
|
|
93
|
+
oxylabsCountry: null
|
|
94
|
+
oxylabsCity: null
|
|
95
|
+
oxylabsSessionId: null
|
|
96
|
+
|
|
97
|
+
map:
|
|
98
|
+
directory: null
|
|
99
|
+
tech: null
|
|
100
|
+
list: false
|
|
101
|
+
output: mapped
|
|
102
|
+
format: json
|
|
103
|
+
interactive: false
|
|
104
|
+
command: []
|
|
105
|
+
ai: null
|
|
106
|
+
aiThreads: "5"
|
|
107
|
+
aiProvider: openai
|
|
108
|
+
aiEndpoint: null
|
|
109
|
+
openaiApiKey: null
|
|
110
|
+
model: gpt-4o-mini
|
|
111
|
+
openapi: false
|
|
112
|
+
openapiOutput: mapped-openapi.json
|
|
113
|
+
openapiChunkTag: false
|
|
114
|
+
graphql: true
|
|
115
|
+
ngql: null
|
|
116
|
+
maxRecursionDepth: "3"
|
|
117
|
+
maxHeap: null
|
|
118
|
+
|
|
119
|
+
refactor:
|
|
120
|
+
mappedJson: mapped.json
|
|
121
|
+
output: output_refactored
|
|
122
|
+
tech: null
|
|
123
|
+
list: false
|
|
124
|
+
collisions: null
|
|
125
|
+
signatureQuality: "100"
|
|
126
|
+
refreshCache: false
|
|
127
|
+
skipCacheChecks: false
|
|
128
|
+
remote: true
|
|
129
|
+
remoteCollisions: null
|
|
130
|
+
scat: null
|
|
131
|
+
detectVersion: false
|
|
132
|
+
detectVersionConfig: null
|
|
133
|
+
detectVersionDynamicThreshold: "3"
|
|
134
|
+
detectVersionDynamicConfPurge: false
|
|
135
|
+
|
|
136
|
+
analyze:
|
|
137
|
+
rules: null
|
|
138
|
+
mappedJson: mapped.json
|
|
139
|
+
tech: null
|
|
140
|
+
openapi: null
|
|
141
|
+
list: false
|
|
142
|
+
validate: false
|
|
143
|
+
output: analyze.json
|
|
144
|
+
disableRulesVersionCheck: false
|
|
145
|
+
|
|
146
|
+
report:
|
|
147
|
+
sqliteDb: js-recon.db
|
|
148
|
+
mappedJson: null
|
|
149
|
+
analyzeJson: null
|
|
150
|
+
endpointsJson: null
|
|
151
|
+
mappedOpenapiJson: null
|
|
152
|
+
output: report
|
|
153
|
+
|
|
154
|
+
run:
|
|
155
|
+
# May also be a YAML list. Repeated -u flags and comma-separated URLs
|
|
156
|
+
# are supported on the command line; a target-file path is also valid.
|
|
157
|
+
# url:
|
|
158
|
+
# - https://one.example.com
|
|
159
|
+
# - https://two.example.com
|
|
160
|
+
url: null
|
|
161
|
+
rules: null
|
|
162
|
+
disableRulesVersionCheck: false
|
|
163
|
+
command: []
|
|
164
|
+
output: output
|
|
165
|
+
outputOverwrite: false
|
|
166
|
+
strictScope: false
|
|
167
|
+
scope: "*"
|
|
168
|
+
threads: "1"
|
|
169
|
+
proxyConfig: .proxy_config.json
|
|
170
|
+
ignoreProxyEnv: false
|
|
171
|
+
proxyWafFallback: false
|
|
172
|
+
oxylabsWafFallback: false
|
|
173
|
+
oxylabsFallbackMaxRequests: "10"
|
|
174
|
+
oxylabsFallbackMaxTotal: "100"
|
|
175
|
+
oxylabsFallbackMaxOrigins: "25"
|
|
176
|
+
cacheFile: .resp_cache.json
|
|
177
|
+
disableCache: false
|
|
178
|
+
cacheOnly: false
|
|
179
|
+
yes: false
|
|
180
|
+
secrets: false
|
|
181
|
+
trufflehog: false
|
|
182
|
+
ai: null
|
|
183
|
+
aiThreads: "5"
|
|
184
|
+
aiProvider: openai
|
|
185
|
+
aiEndpoint: null
|
|
186
|
+
openaiApiKey: null
|
|
187
|
+
model: gpt-4o-mini
|
|
188
|
+
mapOpenapiChunkTag: false
|
|
189
|
+
graphql: true
|
|
190
|
+
ngql: null
|
|
191
|
+
timeout: "30000"
|
|
192
|
+
insecure: false
|
|
193
|
+
sandbox: true
|
|
194
|
+
sourcemapDir: extracted
|
|
195
|
+
research: false
|
|
196
|
+
researchOutput: research.json
|
|
197
|
+
maxIterations: "10"
|
|
198
|
+
maxJsSize: "2"
|
|
199
|
+
lazyloadTimeout: "30"
|
|
200
|
+
detectionTimeout: "30"
|
|
201
|
+
maxHeap: null
|
|
202
|
+
maxPages: "200"
|
|
203
|
+
includeMethods: null
|
|
204
|
+
excludeMethods: null
|
|
205
|
+
listMethods: null
|
|
206
|
+
csMastTechDetectThreshold: "50"
|
|
207
|
+
disableRefactor: false
|
|
208
|
+
|
|
209
|
+
load:
|
|
210
|
+
caido: null
|
|
211
|
+
url: null
|
|
212
|
+
cacheFile: .resp_cache.json
|
|
213
|
+
|
|
214
|
+
fingerprint:
|
|
215
|
+
# May also be a YAML list of HTTP(S) URLs.
|
|
216
|
+
url: null
|
|
217
|
+
output: null
|
|
218
|
+
format: text
|
|
219
|
+
threads: "5"
|
|
220
|
+
timeout: "30000"
|
|
221
|
+
insecure: false
|
|
222
|
+
sandbox: true
|
|
223
|
+
|
|
224
|
+
mcp:
|
|
225
|
+
cli: false
|
|
226
|
+
server: false
|
|
227
|
+
chat: []
|
|
228
|
+
config: null
|
|
229
|
+
apiKey: null
|
|
230
|
+
model: null
|
|
231
|
+
provider: null
|
|
232
|
+
refreshClaudeCreds: true
|
|
233
|
+
claudeClientId: null
|
|
234
|
+
|
|
235
|
+
cs-mast:
|
|
236
|
+
output: output
|
|
237
|
+
collisionTable: false
|
|
238
|
+
minCollisions: "2"
|
|
239
|
+
collisionOutput: null
|
|
240
|
+
collisionFormat: csv
|
|
241
|
+
scat: lit,decl,loop,cond
|
|
242
|
+
sinc: ""
|
|
243
|
+
allScatPermutations: false
|
|
244
|
+
permOutput: null
|
|
245
|
+
permConcurrency: "0"
|
|
246
|
+
|
|
247
|
+
sourcemaps:
|
|
248
|
+
input: null
|
|
249
|
+
output: extracted
|
|
250
|
+
|
|
251
|
+
completion: {}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Writing tests for js-recon
|
|
2
|
+
|
|
3
|
+
New functionality must ship with tests. Which kind depends on what you're adding — see below.
|
|
4
|
+
|
|
5
|
+
## Unit tests (Vitest)
|
|
6
|
+
|
|
7
|
+
Test framework is [Vitest](https://vitest.dev/) — ESM-native, TypeScript-native, no build step.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm test # run all unit tests once
|
|
11
|
+
npm run test:watch # watch mode
|
|
12
|
+
npm run test:coverage # run with coverage report
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**What to test.** Pure functions: anything that takes plain inputs and returns a value without I/O.
|
|
16
|
+
Puppeteer, network calls (`makeRequest`), and file writes are intentionally left untested at the
|
|
17
|
+
unit level — they're validated end-to-end via the `run` subcommand instead (see below). When a
|
|
18
|
+
function mixes I/O with logic, extract the parse/transform step into an exported pure function and
|
|
19
|
+
test that:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// Exported pure function — testable
|
|
23
|
+
export const parseThings = (content: string, baseUrl: string): string[] => {
|
|
24
|
+
/* ... */
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Orchestrator — not unit-tested
|
|
28
|
+
const myModule = async (url: string): Promise<string[]> => {
|
|
29
|
+
const resp = await makeRequest(url);
|
|
30
|
+
const content = await resp.text();
|
|
31
|
+
return parseThings(content, url);
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**File layout.** Tests live in `src/__tests__/<component>/<name>.test.ts`, mirroring the source
|
|
36
|
+
path. Use `describe`/`it` blocks grouped by function name; cover the happy path, edge cases (empty
|
|
37
|
+
input, malformed input), and threshold boundaries.
|
|
38
|
+
|
|
39
|
+
**Local imports use the `.js` extension** (this is an ESM project with `"module": "node16"`):
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { fn } from "../../lazyLoad/next_js/myModule.js";
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Constructing Babel AST nodes for tests.** If a function under test requires a real Babel AST node
|
|
46
|
+
(with `scope`, `path`, correct `start`/`end` offsets), parse a snippet and capture the node via a
|
|
47
|
+
`traverse` visitor rather than building it by hand:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import parser from "@babel/parser";
|
|
51
|
+
import _traverse from "@babel/traverse";
|
|
52
|
+
const traverse = (_traverse.default ?? _traverse) as typeof _traverse.default;
|
|
53
|
+
|
|
54
|
+
function parseExpr(code: string) {
|
|
55
|
+
const src = `const _x = ${code};`;
|
|
56
|
+
const ast = parser.parse(src, { sourceType: "unambiguous", plugins: ["jsx", "typescript"] });
|
|
57
|
+
let node: any;
|
|
58
|
+
traverse(ast, {
|
|
59
|
+
VariableDeclarator(p) {
|
|
60
|
+
node = p.node.init;
|
|
61
|
+
p.stop();
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
return { node, src };
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Avoid GitHub's secret scanner false-positives.** Strings that look like real secrets (webhook
|
|
69
|
+
URLs, API keys) get blocked by push protection even inside test files. Assemble them at runtime:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// BAD — blocked by secret scanner
|
|
73
|
+
const url = "https://hooks.slack.com/services/TABCDEF/BABCDEF/xxxxxxxxxxxx";
|
|
74
|
+
|
|
75
|
+
// GOOD — assembled at runtime
|
|
76
|
+
const parts = ["https://hooks.slack.com/services/T", "ABCDEF/B", "ABCDEF/xxxx"];
|
|
77
|
+
const url = parts.join("");
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Fuzz tests (fast-check)
|
|
81
|
+
|
|
82
|
+
js-recon's job is parsing and analyzing JS bundles pulled from third-party targets — that input is
|
|
83
|
+
fully untrusted. Functions on that boundary (parsing raw bundle text, resolving URLs/strings out of
|
|
84
|
+
it) should get a property-based fuzz test in addition to example-based unit tests, using
|
|
85
|
+
[fast-check](https://fast-check.dev/).
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm run test:fuzz # runs src/**/*.fuzz.test.ts via vitest.fuzz.config.ts
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Fuzz tests live alongside the function's regular unit test, named `<name>.fuzz.test.ts`, and are
|
|
92
|
+
excluded from the default `npm test` run (they're slower — property-based, hundreds of generated
|
|
93
|
+
cases per run). See `src/__tests__/strings/extractStrings.fuzz.test.ts` for the reference shape:
|
|
94
|
+
generate arbitrary/adversarial strings, feed them through the same parse config the real call site
|
|
95
|
+
uses, and assert the function under test never throws uncaught or hangs. If a parse step can
|
|
96
|
+
legitimately throw on malformed input (Babel's `errorRecovery: true` does not guarantee it won't),
|
|
97
|
+
wrap it in try/catch inside the test exactly as the real call site does — a caught, expected error
|
|
98
|
+
is not a fuzz failure; an uncaught throw or a hang is.
|
|
99
|
+
|
|
100
|
+
## Smoke tests (CI)
|
|
101
|
+
|
|
102
|
+
The `rules-smoke-test` GitHub Actions workflow runs js-recon end-to-end against a seeded lab app and
|
|
103
|
+
asserts every expected rule fires. If you're adding a new AST/request rule to `js-recon-rules`, seed
|
|
104
|
+
the corresponding vulnerability in `js-recon-labs`'s `next_js/vuln-all-rules` app and add the rule ID
|
|
105
|
+
to `EXPECTED_RULES` in `scripts/smoke-test.js`. See the root `CLAUDE.md`'s "Rules smoke test (CI)"
|
|
106
|
+
section for details.
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import tseslint from "typescript-eslint";
|
|
2
|
+
import security from "eslint-plugin-security";
|
|
3
|
+
|
|
4
|
+
export default tseslint.config(
|
|
5
|
+
{
|
|
6
|
+
ignores: ["build/**", "output/**", "output_refactored/**", "node_modules/**"],
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
files: ["src/**/*.ts"],
|
|
10
|
+
extends: [...tseslint.configs.recommended],
|
|
11
|
+
plugins: {
|
|
12
|
+
security,
|
|
13
|
+
},
|
|
14
|
+
rules: {
|
|
15
|
+
...security.configs.recommended.rules,
|
|
16
|
+
// These fire on legitimate dynamic-code patterns intrinsic to a bundle-analysis tool
|
|
17
|
+
// (parsing/instrumenting untrusted third-party JS is the entire point of js-recon).
|
|
18
|
+
"security/detect-non-literal-fs-filename": "off",
|
|
19
|
+
"security/detect-object-injection": "off",
|
|
20
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
21
|
+
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@js-recon/js-recon",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.0.1-alpha.1",
|
|
4
4
|
"description": "JS Recon Tool",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -13,9 +13,12 @@
|
|
|
13
13
|
"start": "node --max-old-space-size=8192 build/index.js",
|
|
14
14
|
"test": "vitest run",
|
|
15
15
|
"test:watch": "vitest",
|
|
16
|
+
"test:coverage": "vitest run --coverage",
|
|
17
|
+
"test:fuzz": "vitest run --config vitest.fuzz.config.ts",
|
|
16
18
|
"test:build": "node build/index.js -h",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
+
"lint": "eslint src",
|
|
20
|
+
"postinstall": "patch-package || true; node scripts/postinstall-completion.mjs",
|
|
21
|
+
"cleanup": "rm -rf build output output_refactored .resp_cache.json .resp_cache.json.entries endpoints.json extracted_urls{.txt,.json,-openapi.json} strings.json mapped{-openapi.json,.json,-openapi.postman_collection.json} analyze.json test{.yaml,.js} js-recon-js-recon-*.tgz js-recon.db report.{html,md} js_recon_run_output *_test.js extracted/ research.json results* out.* swagger-jacker-results.json && tsc"
|
|
19
22
|
},
|
|
20
23
|
"keywords": [],
|
|
21
24
|
"author": "Shriyans Sudhi",
|
|
@@ -27,7 +30,7 @@
|
|
|
27
30
|
"@babel/traverse": "^7.28.5",
|
|
28
31
|
"@babel/types": "^7.27.6",
|
|
29
32
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
30
|
-
"@shriyanss/cli-print-img": "^1.1.
|
|
33
|
+
"@shriyanss/cli-print-img": "^1.1.1",
|
|
31
34
|
"@shriyanss/cs-mast": "0.1.8",
|
|
32
35
|
"@types/chalk": "^0.4.31",
|
|
33
36
|
"@types/cli-progress": "^3.11.6",
|
|
@@ -41,6 +44,7 @@
|
|
|
41
44
|
"datatables.net": "^2.3.6",
|
|
42
45
|
"datatables.net-dt": "^2.3.6",
|
|
43
46
|
"esquery": "^1.6.0",
|
|
47
|
+
"express": "^5.2.1",
|
|
44
48
|
"extract-zip": "^2.0.1",
|
|
45
49
|
"fs": "^0.0.2",
|
|
46
50
|
"graphql": "^16.14.1",
|
|
@@ -48,6 +52,7 @@
|
|
|
48
52
|
"inquirer": "^12.6.3",
|
|
49
53
|
"marked": "^16.1.2",
|
|
50
54
|
"md5": "^2.3.0",
|
|
55
|
+
"ohash": "^2.0.11",
|
|
51
56
|
"ollama": "^0.6.3",
|
|
52
57
|
"openai": "^5.8.2",
|
|
53
58
|
"patch-package": "^8.0.1",
|
|
@@ -58,7 +63,10 @@
|
|
|
58
63
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
59
64
|
"readline": "^1.3.0",
|
|
60
65
|
"ses": "^1.13.0",
|
|
61
|
-
"
|
|
66
|
+
"shell-quote": "^1.10.0",
|
|
67
|
+
"tar": "^7.5.22",
|
|
68
|
+
"undici": "^7.29.0",
|
|
69
|
+
"ws": "^8.21.2",
|
|
62
70
|
"yaml": "^2.8.0",
|
|
63
71
|
"zod": "^3.23.8"
|
|
64
72
|
},
|
|
@@ -78,11 +86,20 @@
|
|
|
78
86
|
"@types/babel__traverse": "^7.28.0",
|
|
79
87
|
"@types/better-sqlite3": "^7.6.13",
|
|
80
88
|
"@types/blessed": "^0.1.25",
|
|
89
|
+
"@types/express": "^5.0.6",
|
|
81
90
|
"@types/graphql": "^14.2.3",
|
|
82
91
|
"@types/node": "^24.12.4",
|
|
92
|
+
"@types/shell-quote": "^1.7.5",
|
|
93
|
+
"@types/ws": "^8.18.1",
|
|
94
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
95
|
+
"eslint": "^10.8.0",
|
|
96
|
+
"eslint-plugin-security": "^4.0.1",
|
|
97
|
+
"fast-check": "^4.9.0",
|
|
98
|
+
"patch-package": "^8.0.1",
|
|
83
99
|
"ts-node": "^10.9.2",
|
|
84
100
|
"tsconfig-paths": "^4.2.0",
|
|
85
101
|
"typescript": "^5.9.2",
|
|
102
|
+
"typescript-eslint": "^8.66.0",
|
|
86
103
|
"vitest": "^4.1.10"
|
|
87
104
|
}
|
|
88
105
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Smoke test: verifies that the `exploit` module reported `vulnerable: true` for every CVE
|
|
4
|
+
* fixture in js-recon-labs.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node scripts/exploit-smoke-test.js [results-dir]
|
|
8
|
+
*
|
|
9
|
+
* Expects one `<CVE-ID>.json` (rich exploit.json output shape) per CVE in results-dir, as
|
|
10
|
+
* produced by the `exploit-smoke-test.yaml` workflow's `-o` flag.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync, existsSync } from "fs";
|
|
14
|
+
import { resolve } from "path";
|
|
15
|
+
|
|
16
|
+
const resultsDir = process.argv[2] || "/tmp/exploit-results";
|
|
17
|
+
|
|
18
|
+
const EXPECTED_CVES = [
|
|
19
|
+
"CVE-2025-29927",
|
|
20
|
+
"CVE-2026-64645",
|
|
21
|
+
"CVE-2025-30208",
|
|
22
|
+
"CVE-2026-39363",
|
|
23
|
+
"CVE-2026-71320",
|
|
24
|
+
"CVE-2026-71315",
|
|
25
|
+
"CVE-2026-68945",
|
|
26
|
+
"CVE-2025-55182",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
// RCE-class CVEs must additionally prove their `id` proof output looks like real `id` command
|
|
30
|
+
// output, not just any non-empty string — otherwise a broken payload that returns garbage would
|
|
31
|
+
// still satisfy a loose `vulnerable: true` check.
|
|
32
|
+
const RCE_OUTPUT_PATTERN = /uid=\d+.*gid=\d+/;
|
|
33
|
+
const RCE_CVES = new Set(["CVE-2026-71320", "CVE-2025-55182"]);
|
|
34
|
+
|
|
35
|
+
let failures = [];
|
|
36
|
+
|
|
37
|
+
for (const cveId of EXPECTED_CVES) {
|
|
38
|
+
const filePath = resolve(resultsDir, `${cveId}.json`);
|
|
39
|
+
if (!existsSync(filePath)) {
|
|
40
|
+
failures.push(`${cveId}: no result file at ${filePath}`);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let findings;
|
|
45
|
+
try {
|
|
46
|
+
findings = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
47
|
+
} catch (err) {
|
|
48
|
+
failures.push(`${cveId}: could not parse ${filePath}: ${err.message}`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const finding = findings[0];
|
|
53
|
+
if (!finding || finding.vulnerable !== true) {
|
|
54
|
+
failures.push(`${cveId}: not reported vulnerable (evidence: ${finding?.evidence ?? "none"})`);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (RCE_CVES.has(cveId)) {
|
|
59
|
+
const execOutput = finding.execOutput ?? "";
|
|
60
|
+
if (!RCE_OUTPUT_PATTERN.test(execOutput)) {
|
|
61
|
+
failures.push(
|
|
62
|
+
`${cveId}: vulnerable=true but execOutput doesn't look like real 'id' output: "${execOutput}"`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(`[✓] ${cveId}: vulnerable (${finding.evidence.slice(0, 120)})`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log();
|
|
71
|
+
console.log(`[i] ${EXPECTED_CVES.length - failures.length} / ${EXPECTED_CVES.length} CVEs confirmed vulnerable.`);
|
|
72
|
+
|
|
73
|
+
if (failures.length > 0) {
|
|
74
|
+
console.error();
|
|
75
|
+
console.error("[!] Exploit smoke test FAILED:");
|
|
76
|
+
for (const f of failures) {
|
|
77
|
+
console.error(` - ${f}`);
|
|
78
|
+
}
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log("[✓] Exploit smoke test PASSED.");
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Smoke test: verifies that js-recon's `fingerprint` command detects the
|
|
4
|
+
* correct front-end framework for every lab app in the framework-detection
|
|
5
|
+
* matrix (js-recon-labs/detection/*).
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node scripts/framework-detect-smoke-test.js [path-to-fingerprint-result.json]
|
|
9
|
+
*
|
|
10
|
+
* Default path: /tmp/fingerprint-result.json
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync } from "fs";
|
|
14
|
+
import { resolve } from "path";
|
|
15
|
+
|
|
16
|
+
const resultPath = process.argv[2] || "/tmp/fingerprint-result.json";
|
|
17
|
+
const absolutePath = resolve(resultPath);
|
|
18
|
+
|
|
19
|
+
let results;
|
|
20
|
+
try {
|
|
21
|
+
results = JSON.parse(readFileSync(absolutePath, "utf-8"));
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.error(`[!] Could not read ${absolutePath}: ${err.message}`);
|
|
24
|
+
console.error(" Make sure `js-recon fingerprint -f json` completed successfully before running this script.");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// URL -> expected framework name, matching js-recon-labs/detection/* app ports.
|
|
29
|
+
const EXPECTED = {
|
|
30
|
+
"http://localhost:3010": "next",
|
|
31
|
+
"http://localhost:3011": "vue",
|
|
32
|
+
"http://localhost:3012": "nuxt",
|
|
33
|
+
"http://localhost:3013": "svelte",
|
|
34
|
+
"http://localhost:3014": "angular",
|
|
35
|
+
"http://localhost:3015": "react",
|
|
36
|
+
"http://localhost:3016": "react",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const detected = new Map(results.map((r) => [r.url, r.framework]));
|
|
40
|
+
const rows = [];
|
|
41
|
+
let failed = false;
|
|
42
|
+
|
|
43
|
+
for (const [url, expected] of Object.entries(EXPECTED)) {
|
|
44
|
+
const actual = detected.get(url) ?? "unknown";
|
|
45
|
+
const ok = actual === expected;
|
|
46
|
+
if (!ok) failed = true;
|
|
47
|
+
rows.push({ url, expected, actual, ok });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(`[i] Checked ${rows.length} lab apps.`);
|
|
51
|
+
console.log();
|
|
52
|
+
console.log("URL".padEnd(28), "Expected".padEnd(10), "Detected".padEnd(10), "Result");
|
|
53
|
+
for (const { url, expected, actual, ok } of rows) {
|
|
54
|
+
console.log(url.padEnd(28), expected.padEnd(10), actual.padEnd(10), ok ? "OK" : "MISMATCH");
|
|
55
|
+
}
|
|
56
|
+
console.log();
|
|
57
|
+
|
|
58
|
+
if (failed) {
|
|
59
|
+
const mismatches = rows.filter((r) => !r.ok).length;
|
|
60
|
+
console.error(`[!] ${mismatches} / ${rows.length} apps failed framework detection. Smoke test FAILED.`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(`[✓] All ${rows.length} apps detected correctly. Smoke test PASSED.`);
|