Skip to content

Commit d3385b6

Browse files
authored
Merge pull request #50 from shriyanss/dev
v1.2.1-beta.2 ### Added - Added tabular view to the report ### Changed - The run module will skip the target if it is invalid, rather than exiting the program ### Fixed - Fix "Attempted to use detached Frame" error by adding try-catch block - Fix "Cannot read properties of undefined (reading 'split')" error in openapi generator by adding try catch
2 parents 9d239ba + a08c350 commit d3385b6

File tree

9 files changed

+550
-37
lines changed

9 files changed

+550
-37
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Change Log
22

3+
## 1.2.1-beta.2 - 2025.08.10
4+
5+
### Added
6+
7+
- Added tabular view to the report
8+
9+
### Changed
10+
11+
- The run module will skip the target if it is invalid, rather than exiting the program
12+
13+
### Fixed
14+
15+
- Fix "Attempted to use detached Frame" error by adding try-catch block
16+
- Fix "Cannot read properties of undefined (reading 'split')" error in openapi generator by adding try catch
17+
318
## 1.2.1-beta.1 - 2025.08.07
419

520
### Added

package-lock.json

Lines changed: 29 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@shriyanss/js-recon",
3-
"version": "1.2.1-beta.1",
3+
"version": "1.2.1-beta.2",
44
"description": "JS Recon Tool",
55
"main": "build/index.js",
66
"type": "module",
@@ -29,6 +29,8 @@
2929
"cheerio": "^1.0.0",
3030
"cli-highlight": "^2.1.11",
3131
"commander": "^14.0.0",
32+
"datatables.net": "^2.3.2",
33+
"datatables.net-dt": "^2.3.2",
3234
"esquery": "^1.6.0",
3335
"fs": "^0.0.2",
3436
"highlight.js": "^11.11.1",

src/globalConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const githubURL = "https://github.com/shriyanss/js-recon";
22
const modulesDocs = "https://js-recon.io/docs/category/modules";
3-
const version = "1.2.1-beta.1";
3+
const version = "1.2.1-beta.2";
44
const toolDesc = "JS Recon Tool";
55
const axiosNonHttpMethods = ["isAxiosError"]; // methods available in axios, which are not for making HTTP requests
66

src/lazyLoad/techDetect/index.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,16 +172,22 @@ const frameworkDetect = async (url: string) => {
172172
args: process.env.IS_DOCKER === "true" ? ["--no-sandbox"] : [],
173173
});
174174
const page = await browser.newPage();
175+
page.setDefaultNavigationTimeout(30000);
176+
let pageSource = "";
175177
try {
176178
await page.goto(url, {
177-
waitUntil: "networkidle0",
179+
waitUntil: "domcontentloaded",
180+
timeout: 30000,
178181
});
182+
// Give client-side frameworks a brief window to render
183+
await page.waitForSelector("html", { timeout: 10000 }).catch(() => {});
184+
await new Promise((resolve) => setTimeout(resolve, 2000));
185+
pageSource = await page.content();
179186
} catch (err) {
180-
console.log(chalk.yellow("[!] Page load timed out, but continuing with current state"));
187+
console.log(chalk.yellow("[!] Page navigation/content failed, falling back to fetch response if available"));
188+
} finally {
189+
await browser.close().catch(() => {});
181190
}
182-
await new Promise((resolve) => setTimeout(resolve, 5000));
183-
const pageSource = await page.content();
184-
await browser.close();
185191

186192
// if (res === null || res === undefined) {
187193
// return;
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import Database from "better-sqlite3";
2+
import hljs from "highlight.js";
3+
4+
interface AnalysisFinding {
5+
ruleId: string;
6+
ruleName: string;
7+
ruleType: string;
8+
ruleDescription: string;
9+
ruleAuthor: string;
10+
ruleTech: string;
11+
severity: string;
12+
message: string;
13+
findingLocation: string;
14+
}
15+
16+
interface MappedData {
17+
id: string;
18+
description: string | null;
19+
containsFetch: number;
20+
isAxiosClient: number;
21+
exports: string | null;
22+
imports: string | null;
23+
file: string;
24+
}
25+
26+
const escapeHtml = (value: unknown): string => {
27+
const str = value === null || value === undefined ? "" : String(value);
28+
return str
29+
.replace(/&/g, "&")
30+
.replace(/</g, "&lt;")
31+
.replace(/>/g, "&gt;")
32+
.replace(/"/g, "&quot;")
33+
.replace(/'/g, "&#39;");
34+
};
35+
36+
const booleanIcon = (b: number | boolean) => (b ? "✅" : "❌");
37+
38+
// Map severity to a sortable rank: info < low < medium < high
39+
const severityRank = (sev: string): number => {
40+
const s = (sev || "").toString().toLowerCase().trim();
41+
switch (s) {
42+
case "info":
43+
return 0;
44+
case "low":
45+
return 1;
46+
case "medium":
47+
return 2;
48+
case "high":
49+
return 3;
50+
default:
51+
return 99; // unknown values sort last
52+
}
53+
};
54+
55+
// Render JS code with highlight.js inside a pre/code block
56+
const renderJsCode = (code: string | null | undefined): string => {
57+
const src = code ?? "";
58+
try {
59+
const highlighted = hljs.highlight(src, { language: "javascript", ignoreIllegals: true }).value;
60+
return `<pre class="code-cell"><code class="hljs language-javascript">${highlighted}</code></pre>`;
61+
} catch {
62+
// Fallback to escaped plain text
63+
return `<pre class="code-cell">${escapeHtml(src)}</pre>`;
64+
}
65+
};
66+
67+
const genDataTablesPage = (db: Database.Database): string => {
68+
const findings = db.prepare(`SELECT * FROM analysis_findings`).all() as AnalysisFinding[];
69+
const mapped = db.prepare(`SELECT * FROM mapped`).all() as MappedData[];
70+
71+
const findingsRows = findings
72+
.map(
73+
(f) => `
74+
<tr>
75+
<td>${escapeHtml(f.ruleId)}</td>
76+
<td>${escapeHtml(f.ruleName)}</td>
77+
<td>${escapeHtml(f.ruleType)}</td>
78+
<td>${escapeHtml(f.ruleDescription)}</td>
79+
<td>${escapeHtml(f.ruleAuthor)}</td>
80+
<td>${escapeHtml(f.ruleTech)}</td>
81+
<td data-order="${severityRank(f.severity)}">${escapeHtml(f.severity)}</td>
82+
<td>${escapeHtml(f.message)}</td>
83+
<td>${renderJsCode(f.findingLocation)}</td>
84+
</tr>`
85+
)
86+
.join("\n");
87+
88+
const mappedRows = mapped
89+
.map(
90+
(m) => `
91+
<tr>
92+
<td>${escapeHtml(m.id)}</td>
93+
<td>${escapeHtml(m.description)}</td>
94+
<td>${booleanIcon(m.containsFetch)}</td>
95+
<td>${booleanIcon(m.isAxiosClient)}</td>
96+
<td>${escapeHtml(m.exports)}</td>
97+
<td>${escapeHtml(m.imports)}</td>
98+
<td>${escapeHtml(m.file)}</td>
99+
</tr>`
100+
)
101+
.join("\n");
102+
103+
const html = `
104+
<section>
105+
<h2>Analyze Findings (Sortable)</h2>
106+
<table id="findings-table" class="display data-table" style="width:100%">
107+
<thead>
108+
<tr>
109+
<th>Rule ID</th>
110+
<th>Name</th>
111+
<th>Type</th>
112+
<th>Description</th>
113+
<th>Author</th>
114+
<th>Tech</th>
115+
<th>Severity</th>
116+
<th>Message</th>
117+
<th>Location</th>
118+
</tr>
119+
</thead>
120+
<tbody>
121+
${findingsRows}
122+
</tbody>
123+
</table>
124+
125+
<h2 style="margin-top: 2rem;">Mapped Data (Sortable)</h2>
126+
<table id="mapped-table" class="display data-table" style="width:100%">
127+
<thead>
128+
<tr>
129+
<th>ID</th>
130+
<th>Description</th>
131+
<th>Contains Fetch</th>
132+
<th>Axios Client</th>
133+
<th>Exports</th>
134+
<th>Imports</th>
135+
<th>File</th>
136+
</tr>
137+
</thead>
138+
<tbody>
139+
${mappedRows}
140+
</tbody>
141+
</table>
142+
</section>`;
143+
144+
return html;
145+
};
146+
147+
export default genDataTablesPage;

0 commit comments

Comments
 (0)