sentinel-scanner 1.0.1 → 1.1.0-alpha.2

Sign up to get free protection for your applications and to get access to all the features.
package/.cspell.json CHANGED
@@ -2,16 +2,33 @@
2
2
  "version": "0.2",
3
3
  "language": "en",
4
4
  "words": [
5
+ "acmr",
6
+ "codebases",
7
+ "datetime",
5
8
  "degit",
9
+ "domcontentloaded",
10
+ "dynsrc",
11
+ "dynsrcs",
6
12
  "esbuild",
7
- "webapp-scanner",
13
+ "esportzvio",
14
+ "hrefs",
15
+ "khtml",
16
+ "longdesc",
17
+ "longdescs",
18
+ "lowsrc",
19
+ "lowsrcs",
20
+ "muzaffarpur",
21
+ "networkidle",
8
22
  "octocat",
9
23
  "outdir",
24
+ "rebackk",
10
25
  "rmrf",
11
- "ryansonshine",
12
26
  "socio",
27
+ "srcs",
13
28
  "tsdoc",
14
- "rebackk"
29
+ "webapp-scanner",
30
+ "wordlist",
31
+ "xlink"
15
32
  ],
16
33
  "flagWords": [],
17
34
  "ignorePaths": [
@@ -0,0 +1,86 @@
1
+ name: "check PR"
2
+
3
+ on: [push]
4
+
5
+ permissions: "write-all"
6
+
7
+ jobs:
8
+ dependencies:
9
+ name: 📦 Dependencies
10
+ runs-on: macos-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-node@v4
14
+ with:
15
+ node-version-file: package.json
16
+ cache: "npm"
17
+ - run: npm ci
18
+
19
+ lint:
20
+ name: 🔬 Lint & Format
21
+ runs-on: macos-latest
22
+ needs: [dependencies]
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: actions/setup-node@v4
26
+ with:
27
+ node-version-file: package.json
28
+ cache: "npm"
29
+ - run: npm ci
30
+ - name: 🔬 Lint & Format
31
+ run: node --run lint:check
32
+
33
+ audit:
34
+ name: 🛡️ Audit
35
+ runs-on: macos-latest
36
+ needs: [dependencies]
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+ - uses: actions/setup-node@v4
40
+ with:
41
+ node-version-file: package.json
42
+ cache: "npm"
43
+ - name: 🛡️ Audit
44
+ run: npm audit --audit-level=high
45
+
46
+ spell:
47
+ name: 🈸 Spellcheck
48
+ runs-on: macos-latest
49
+ needs: [dependencies]
50
+ steps:
51
+ - uses: actions/checkout@v4
52
+ - uses: actions/setup-node@v4
53
+ with:
54
+ node-version-file: package.json
55
+ cache: "npm"
56
+ - run: npm ci
57
+ - name: 🈸 Spellcheck
58
+ run: node --run spell:check
59
+
60
+ type:
61
+ name: ʦ Typecheck
62
+ runs-on: macos-latest
63
+ needs: [dependencies]
64
+ steps:
65
+ - uses: actions/checkout@v4
66
+ - uses: actions/setup-node@v4
67
+ with:
68
+ node-version-file: package.json
69
+ cache: "npm"
70
+ - run: npm ci
71
+ - name: ʦ Typecheck
72
+ run: node --run type:check
73
+
74
+ test:
75
+ name: ⚡ Tests
76
+ runs-on: macos-latest
77
+ needs: [dependencies]
78
+ steps:
79
+ - uses: actions/checkout@v4
80
+ - uses: actions/setup-node@v4
81
+ with:
82
+ node-version-file: package.json
83
+ cache: "npm"
84
+ - run: npm ci
85
+ - name: ⚡ Tests
86
+ run: node --run test:coverage
@@ -0,0 +1,66 @@
1
+ name: Welcome Comments
2
+
3
+ permissions: "write-all"
4
+
5
+ on:
6
+ issues:
7
+ types: [opened, closed]
8
+ pull_request_target:
9
+ types: [opened, closed]
10
+
11
+ jobs:
12
+ welcomer:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - name: Auto Welcome on Issues or PRs
16
+ uses: actions/github-script@v6
17
+ with:
18
+ github-token: ${{ secrets.GITHUB_TOKEN }}
19
+ script: |
20
+ const author = context.payload.sender.login;
21
+ const commentBody = (message) => `👋 @${author} 👋\n\n${message}`;
22
+
23
+ if (context.eventName === 'issues') {
24
+ const issue = context.payload.issue;
25
+
26
+ if (context.payload.action === 'opened') {
27
+ const message = `We're thrilled to see you opening an issue! Your input is valuable to us. Don’t forget to fill out our issue template for the best experience. We will look into it soon.`;
28
+ github.rest.issues.createComment({
29
+ issue_number: issue.number,
30
+ owner: context.repo.owner,
31
+ repo: context.repo.repo,
32
+ body: commentBody(message),
33
+ });
34
+ } else if (context.payload.action === 'closed') {
35
+ const message = `Thanks for closing the issue! We appreciate your updates.`;
36
+ github.rest.issues.createComment({
37
+ issue_number: issue.number,
38
+ owner: context.repo.owner,
39
+ repo: context.repo.repo,
40
+ body: commentBody(message),
41
+ });
42
+ }
43
+ } else if (context.eventName === 'pull_request_target') {
44
+ const pr = context.payload.pull_request;
45
+
46
+ if (context.payload.action === 'opened') {
47
+ const message = `We're delighted to have your pull request! Please take a moment to check our contributing guidelines and ensure you've filled out the PR template for a smooth process. We will review it soon.`;
48
+ github.rest.issues.createComment({
49
+ issue_number: pr.number,
50
+ owner: context.repo.owner,
51
+ repo: context.repo.repo,
52
+ body: commentBody(message),
53
+ });
54
+ } else if (context.payload.action === 'closed') {
55
+ const message = pr.merged
56
+ ? `🎉 You've just merged your pull request! We're excited to have you in our community. Keep up the fantastic contributions to the project!`
57
+ : `Thanks for closing the pull request! Your contributions are valuable to us.`;
58
+
59
+ github.rest.issues.createComment({
60
+ issue_number: pr.number,
61
+ owner: context.repo.owner,
62
+ repo: context.repo.repo,
63
+ body: commentBody(message),
64
+ });
65
+ }
66
+ }
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
- ## [1.0.1](https://github.com/RebackkHQ/webapp-scanner/compare/v1.0.0...v1.0.1) (2024-11-07)
1
+ # [1.1.0-alpha.2](https://github.com/RebackkHQ/webapp-scanner/compare/v1.1.0-alpha.1...v1.1.0-alpha.2) (2024-11-15)
2
2
 
3
3
 
4
- ### Bug Fixes
4
+ ### Features
5
5
 
6
- * fixed compatibility with npx ([a778b5a](https://github.com/RebackkHQ/webapp-scanner/commit/a778b5a97140fb67cf503d72dac59adc571316a3))
6
+ * finalized Spider ([17127e9](https://github.com/RebackkHQ/webapp-scanner/commit/17127e99ec82befb4bdf1c2702215d5f9266465e))
package/DISCLAIMER.md ADDED
@@ -0,0 +1,64 @@
1
+ # DISCLAIMER
2
+
3
+ Last Updated: **10/11/2024**
4
+
5
+ ### 1. General Information
6
+
7
+ This web vulnerability scanner (“the Tool”) is provided by Esportzvio Private Limited (“the Company”) under the product name Sentinel. The Tool is designed to identify potential security vulnerabilities in web applications and APIs. It is intended solely for educational and informational purposes to help enhance cybersecurity. By using this Tool, you acknowledge that you have read, understood, and agreed to the terms of this Disclaimer.
8
+
9
+ ### 2. No Guarantee of Security
10
+
11
+ The Tool is designed to detect certain security vulnerabilities; however, it does not guarantee that your website, application, or network will be free from security threats or that all vulnerabilities will be detected. The Company makes no representations or warranties regarding the completeness, accuracy, or effectiveness of the Tool’s scanning capabilities. Security is a complex field, and this Tool should be used as part of a broader security strategy.
12
+
13
+ ### 3. Use at Your Own Risk
14
+
15
+ By using the Tool, you acknowledge that you do so at your own risk. The Company is not responsible for any direct, indirect, incidental, special, or consequential damages, losses, or liabilities resulting from the use of the Tool, including but not limited to data loss, system downtime, unauthorized access, security breaches, or legal implications. It is strongly recommended that you use the Tool only on non-production environments or systems where you have full ownership and authorization.
16
+
17
+ ### 4. Authorization to Scan
18
+
19
+ You must obtain explicit written permission from the website, application, or network owner before using the Tool to scan any system. Unauthorized use of this Tool on systems that you do not own or have explicit permission to test is strictly prohibited and may be illegal, potentially resulting in civil, criminal, or regulatory penalties. The Company disclaims all responsibility for any misuse of the Tool, including any legal consequences that may arise from unauthorized usage.
20
+
21
+ ### 5. Ethical Usage Clause
22
+
23
+ The Tool is intended solely for ethical security testing purposes. By using this Tool, you agree to:
24
+ - Only scan systems for which you have obtained explicit written consent from the system owner.
25
+ - Not use the Tool for any illegal, unethical, or malicious activities, including but not limited to unauthorized hacking, data theft, denial of service attacks, or any form of exploitation.
26
+ - Ensure compliance with all applicable local, national, and international laws and regulations related to cybersecurity, data protection, and privacy.
27
+ - Refrain from sharing, distributing, or using the Tool for purposes that could harm, disrupt, or compromise any system without proper authorization.
28
+
29
+ Any misuse of this Tool for malicious purposes, unethical hacking, or unauthorized access to systems is strictly prohibited and will result in immediate termination of your access to the Tool, along with potential legal action by the Company.
30
+
31
+ ### 6. No Legal or Professional Advice
32
+
33
+ The results and outputs of the Tool are provided for informational purposes only and should not be considered a substitute for professional cybersecurity advice. The Company is not liable for any decisions or actions taken based on the Tool’s findings. Always consult with a qualified cybersecurity professional before implementing any security measures based on the Tool’s results.
34
+
35
+ ### 7. Limitation of Liability
36
+
37
+ To the fullest extent permitted by law, the Company, its affiliates, partners, and licensors shall not be liable for any direct, indirect, incidental, special, consequential, or punitive damages, including but not limited to loss of profits, revenue, data, or other intangible losses arising from or in connection with your use or misuse of the Tool, regardless of whether the Company has been advised of the possibility of such damages.
38
+
39
+ ### 8. Indemnification
40
+
41
+ You agree to indemnify, defend, and hold harmless the Company, its officers, directors, employees, agents, and affiliates from any and all claims, liabilities, damages, losses, or expenses, including reasonable attorneys’ fees, arising out of or in any way connected with:
42
+ - Your access to or use of the Tool.
43
+ - Your violation of any terms of this Disclaimer.
44
+ - Your violation of any rights of a third party, including intellectual property rights.
45
+ - Your violation of any applicable laws or regulations.
46
+
47
+ ### 9. Compliance with Laws
48
+
49
+ By using this Tool, you agree to comply with all applicable laws, regulations, and guidelines, including but not limited to data protection laws, cybersecurity laws, and any other regulations relevant to your jurisdiction. The Company is not responsible for any violations of the law resulting from your use of the Tool.
50
+
51
+ ### 10. Changes to This Disclaimer
52
+
53
+ The Company reserves the right to modify, update, or change this Disclaimer at any time without prior notice. Your continued use of the Tool after any changes are made constitutes your acceptance of the revised Disclaimer. It is your responsibility to review this Disclaimer periodically for updates.
54
+
55
+ ### 11. Governing Law and Jurisdiction
56
+
57
+ This Disclaimer shall be governed by and construed in accordance with the laws of India, without regard to its conflict of laws principles. Any disputes arising out of or related to this Disclaimer or your use of the Tool shall be subject to the exclusive jurisdiction of the courts located in Muzaffarpur, Bihar, India.
58
+
59
+ ### 12. Contact Information
60
+
61
+ If you have any questions, concerns, or require further clarification regarding this Disclaimer, please contact us at:
62
+
63
+ - Email: **legal@esportzvio.com**
64
+ - Address: **Esportzvio Private Limited, Muzaffarpur, Bihar, 842001, India.**
package/LICENSE CHANGED
@@ -1,7 +1,7 @@
1
-
2
- Apache License
1
+ Apache License
3
2
  Version 2.0, January 2004
4
3
  http://www.apache.org/licenses/
4
+ DISCLAIMER: Use of this tool is subject to the terms outlined in ./DISCLAIMER.md.
5
5
 
6
6
  TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
7
 
package/README.md CHANGED
@@ -2,7 +2,14 @@
2
2
  <h1 align="center">
3
3
  Sentinel By Rebackk (Work in progress)
4
4
  <br />
5
- <img src="https://github.com/RebackkHQ/webapp-scanner/actions/workflows/webapp-scanner.yml/badge.svg" style="padding: 15px;" />
5
+ <img src="https://img.shields.io/github/actions/workflow/status/RebackkHQ/webapp-scanner/.github%2Fworkflows%2Fwebapp-scanner.yml" style="padding-top: 15px;" />
6
+ <img src="https://img.shields.io/github/contributors/RebackkHQ/webapp-scanner" style="padding-top: 15px;" />
7
+ <img src="https://img.shields.io/github/issues-raw/RebackkHQ/webapp-scanner" style="padding-top: 15px;" />
8
+ <img src="https://img.shields.io/github/v/release/RebackkHQ/webapp-scanner?include_prereleases" style="padding-top: 15px;" />
9
+ <img src="https://img.shields.io/npm/dw/sentinel-scanner" style="padding-top: 15px;" />
10
+ <div>
11
+ <img src="https://img.shields.io/github/stars/RebackkHQ/webapp-scanner" style="padding-top: 15px;" />
12
+ </div>
6
13
  </h3>
7
14
  <p align="center">
8
15
  <a href="https://www.sentinel.rebackk.xyz">
@@ -50,6 +57,18 @@ Happy Scanning! 🛡️
50
57
  ## Contributing 🤝
51
58
  If you want to help us building the best status page and alerting system, you can check our [contributing guidelines](./CODE_OF_CONDUCT.md)
52
59
 
60
+ ## Disclaimer
61
+
62
+ By using Sentinel, you agree to the terms outlined in our [DISCLAIMER](./DISCLAIMER.md). It is **strongly recommended** that you read the disclaimer thoroughly before using this tool.
63
+
64
+ **Key points include:**
65
+
66
+ - Only scan systems for which you have explicit permission.
67
+ - The tool is provided "as is" without any warranties.
68
+ - The company is not liable for any misuse, damages, or legal issues arising from the use of this tool.
69
+
70
+ You can find the full disclaimer [here](./DISCLAIMER.md).
71
+
53
72
  ---
54
73
 
55
74
  **This is a work in progress project**
package/build/bin.js ADDED
@@ -0,0 +1,376 @@
1
+ #!/usr/bin/env node --no-warnings
2
+
3
+ // src/bin.ts
4
+ import yargs from "yargs";
5
+ import { hideBin } from "yargs/helpers";
6
+
7
+ // src/commands/spider.ts
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+
11
+ // src/modules/spider/index.ts
12
+ import fetch from "isomorphic-fetch";
13
+ import jsdom from "jsdom";
14
+ import UserAgent from "user-agents";
15
+
16
+ // src/utils/index.ts
17
+ import winston from "winston";
18
+ var createLogger = (label) => winston.createLogger({
19
+ levels: {
20
+ error: 0,
21
+ warn: 1,
22
+ info: 2,
23
+ http: 3,
24
+ verbose: 4,
25
+ debug: 5,
26
+ silly: 6
27
+ },
28
+ format: winston.format.combine(
29
+ winston.format.label({ label }),
30
+ winston.format.colorize(),
31
+ winston.format.timestamp({
32
+ format: () => {
33
+ return (/* @__PURE__ */ new Date()).toLocaleString("en-US");
34
+ }
35
+ }),
36
+ winston.format.align(),
37
+ winston.format.printf(
38
+ (info) => `\x1B[34m(${info.label})\x1B[0m \x1B[33m${info.timestamp}\x1B[0m [${info.level}]: ${info.message}`
39
+ )
40
+ ),
41
+ transports: [new winston.transports.Console()]
42
+ });
43
+
44
+ // src/modules/spider/index.ts
45
+ var SpiderScanner = class {
46
+ header = {
47
+ "User-Agent": new UserAgent().toString()
48
+ };
49
+ url;
50
+ logger = createLogger("SpiderScanner");
51
+ depth;
52
+ concurrency;
53
+ retries;
54
+ timeout;
55
+ constructor(url, options = {}) {
56
+ const {
57
+ depth = 250,
58
+ concurrency = 5,
59
+ retries = 3,
60
+ timeout = 5e3
61
+ } = options;
62
+ this.depth = depth;
63
+ this.concurrency = concurrency;
64
+ this.retries = retries;
65
+ this.timeout = timeout;
66
+ try {
67
+ this.url = new URL(url);
68
+ this.logger.info(
69
+ `Initialized with URL: ${url}, User-Agent: ${this.header["User-Agent"]}`
70
+ );
71
+ } catch (error) {
72
+ if (error instanceof TypeError) {
73
+ this.logger.error("Invalid URL");
74
+ throw new Error("Invalid URL");
75
+ }
76
+ this.logger.error(`Unexpected error in constructor: ${error}`);
77
+ throw error;
78
+ }
79
+ }
80
+ normalizeDomain(domain) {
81
+ return domain.startsWith("www.") ? domain.slice(4) : domain;
82
+ }
83
+ convertRelativeUrlToAbsolute(url) {
84
+ return new URL(url, this.url.toString()).toString();
85
+ }
86
+ isInternalLink(url) {
87
+ try {
88
+ const parsedUrl = new URL(url, this.url.href);
89
+ if (!["http:", "https:"].includes(parsedUrl.protocol)) {
90
+ return false;
91
+ }
92
+ const baseDomain = this.normalizeDomain(this.url.hostname);
93
+ const parsedDomain = this.normalizeDomain(parsedUrl.hostname);
94
+ return parsedDomain === baseDomain;
95
+ } catch (error) {
96
+ this.logger.warn(`Error parsing URL: ${url} - ${error}`);
97
+ return false;
98
+ }
99
+ }
100
+ async fetchWithRetries(url, retries) {
101
+ for (let attempt = 1; attempt <= retries; attempt++) {
102
+ const controller = new AbortController();
103
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
104
+ try {
105
+ this.logger.debug(`Fetching URL (Attempt ${attempt}): ${url}`);
106
+ const randomUserAgent = new UserAgent().toString();
107
+ this.logger.info(`Changing User-Agent to: ${randomUserAgent}`);
108
+ this.header["User-Agent"] = randomUserAgent;
109
+ const response = await fetch(url, {
110
+ headers: this.header,
111
+ signal: controller.signal,
112
+ redirect: "follow"
113
+ });
114
+ clearTimeout(timeoutId);
115
+ if (response.ok) {
116
+ this.logger.info(`Successfully fetched URL: ${url}`);
117
+ return await response.text();
118
+ }
119
+ this.logger.warn(`Failed to fetch URL (${response.status}): ${url}`);
120
+ } catch (error) {
121
+ if (error.name === "AbortError") {
122
+ this.logger.warn(`Fetch timed out: ${url}`);
123
+ } else {
124
+ this.logger.error(`Error fetching URL: ${url} - ${error}`);
125
+ }
126
+ }
127
+ }
128
+ return null;
129
+ }
130
+ extractLinks(html) {
131
+ const { JSDOM } = jsdom;
132
+ const dom = new JSDOM(html);
133
+ const links = Array.from(dom.window.document.querySelectorAll("a"));
134
+ const hrefs = links.map((link) => link.href);
135
+ const internalLinks = hrefs.filter((href) => this.isInternalLink(href));
136
+ this.logger.debug(
137
+ `Extracted ${internalLinks.length} internal links from HTML content`
138
+ );
139
+ return internalLinks.map((link) => this.convertRelativeUrlToAbsolute(link));
140
+ }
141
+ async crawl() {
142
+ const visited = /* @__PURE__ */ new Set();
143
+ const queue = /* @__PURE__ */ new Set([this.url.href]);
144
+ const resultLinks = /* @__PURE__ */ new Set();
145
+ const assetExtensions = [
146
+ ".css",
147
+ ".js",
148
+ ".png",
149
+ ".jpg",
150
+ ".jpeg",
151
+ ".gif",
152
+ ".svg",
153
+ ".ico",
154
+ ".webp",
155
+ ".mp4",
156
+ ".mp3",
157
+ ".wav",
158
+ ".avi",
159
+ ".mov",
160
+ ".webm",
161
+ ".pdf",
162
+ ".doc",
163
+ ".docx",
164
+ ".xls",
165
+ ".xlsx",
166
+ ".ppt",
167
+ ".pptx",
168
+ ".zip",
169
+ ".rar",
170
+ ".tar",
171
+ ".gz"
172
+ ];
173
+ const fetchAndExtract = async (currentUrl) => {
174
+ if (visited.has(currentUrl)) {
175
+ this.logger.debug(`Skipping already visited URL: ${currentUrl}`);
176
+ return;
177
+ }
178
+ visited.add(currentUrl);
179
+ this.logger.info(`Visiting URL: ${currentUrl}`);
180
+ const html = await this.fetchWithRetries(currentUrl, this.retries);
181
+ if (!html) return;
182
+ const links = this.extractLinks(html);
183
+ for (const link of links) {
184
+ if (assetExtensions.some((ext) => link.endsWith(ext))) {
185
+ this.logger.debug(`Ignoring asset link: ${link}`);
186
+ continue;
187
+ }
188
+ this.logger.debug(`Found link: ${link}`);
189
+ }
190
+ for (const link of links) {
191
+ if (!visited.has(link) && queue.size < this.depth) {
192
+ queue.add(link);
193
+ this.logger.debug(`Added to queue: ${link}`);
194
+ }
195
+ }
196
+ resultLinks.add(currentUrl);
197
+ };
198
+ const processBatch = async () => {
199
+ const batch = Array.from(queue).slice(0, this.concurrency);
200
+ for (const url of batch) {
201
+ queue.delete(url);
202
+ }
203
+ await Promise.allSettled(batch.map((url) => fetchAndExtract(url)));
204
+ };
205
+ this.logger.info(
206
+ `Starting crawl with depth: ${this.depth}, concurrency: ${this.concurrency}`
207
+ );
208
+ while (queue.size > 0 && visited.size < this.depth) {
209
+ await processBatch();
210
+ }
211
+ this.logger.info(
212
+ `Crawling completed. Total pages visited: ${resultLinks.size}`
213
+ );
214
+ return Array.from(resultLinks);
215
+ }
216
+ };
217
+
218
+ // src/commands/spider.ts
219
+ var cliLogger = createLogger("CLI");
220
+ var spiderCommand = {
221
+ command: "spider",
222
+ describe: "Crawl a website and get an array of URLs which are internal to the website",
223
+ builder: (yargs2) => {
224
+ return yargs2.option("url", {
225
+ alias: "u",
226
+ type: "string",
227
+ description: "The URL of the website to scan",
228
+ demandOption: true,
229
+ coerce: (url) => {
230
+ try {
231
+ new URL(url);
232
+ return url;
233
+ } catch (error) {
234
+ throw new Error(`Invalid URL: ${url}`);
235
+ }
236
+ }
237
+ }).option("depth", {
238
+ alias: "d",
239
+ type: "number",
240
+ description: "The maximum depth to crawl",
241
+ default: 250,
242
+ coerce: (depth) => {
243
+ if (depth < 0) {
244
+ throw new Error("Depth must be a positive number");
245
+ }
246
+ if (depth > 250) {
247
+ throw new Error("Depth must be less than 250");
248
+ }
249
+ return depth;
250
+ }
251
+ }).option("output", {
252
+ alias: "o",
253
+ type: "string",
254
+ description: "The output file to write the results to. Must be a JSON file",
255
+ coerce: (output) => {
256
+ try {
257
+ const resolvedPath = path.resolve(output);
258
+ const parsedPath = path.parse(resolvedPath);
259
+ if (parsedPath.ext !== ".json") {
260
+ throw new Error("Output file must be a JSON file");
261
+ }
262
+ if (fs.existsSync(resolvedPath)) {
263
+ throw new Error("Output file already exists");
264
+ }
265
+ return resolvedPath;
266
+ } catch (error) {
267
+ throw new Error(`Invalid output file: ${output}`);
268
+ }
269
+ },
270
+ default: getDefaultFilePath()
271
+ }).option("concurrency", {
272
+ alias: "c",
273
+ type: "number",
274
+ description: "The number of concurrent requests to make",
275
+ default: 10,
276
+ coerce: (concurrency) => {
277
+ if (concurrency < 1) {
278
+ throw new Error("Concurrency must be a positive number");
279
+ }
280
+ if (concurrency > 20) {
281
+ throw new Error("Concurrency must be less than 20");
282
+ }
283
+ return concurrency;
284
+ }
285
+ }).option("timeout", {
286
+ alias: "t",
287
+ type: "number",
288
+ description: "The timeout for each request in milliseconds",
289
+ default: 5e3,
290
+ coerce: (timeout) => {
291
+ if (timeout < 0) {
292
+ throw new Error("Timeout must be a positive number");
293
+ }
294
+ if (timeout > 25e3) {
295
+ throw new Error("Timeout must be less than 25,000");
296
+ }
297
+ return timeout;
298
+ }
299
+ }).option("retries", {
300
+ alias: "r",
301
+ type: "number",
302
+ description: "The number of retries for each request",
303
+ default: 3,
304
+ coerce: (retries) => {
305
+ if (retries < 0) {
306
+ throw new Error("Retries must be a positive number");
307
+ }
308
+ if (retries > 10) {
309
+ throw new Error("Retries must be less than 10");
310
+ }
311
+ return retries;
312
+ }
313
+ });
314
+ },
315
+ handler: async (args) => {
316
+ try {
317
+ const argData = args;
318
+ const scanner = new SpiderScanner(argData.url, {
319
+ depth: argData.depth ?? 250,
320
+ concurrency: argData.concurrency ?? 10,
321
+ timeout: argData.timeout ?? 5e3,
322
+ retries: argData.retries ?? 3
323
+ });
324
+ cliLogger.info("Starting to crawl website");
325
+ const results = await scanner.crawl();
326
+ if (argData.output) {
327
+ fs.writeFileSync(argData.output, JSON.stringify(results, null, 2));
328
+ cliLogger.info(`Results written to ${argData.output}`);
329
+ } else {
330
+ const resolvedPath = getDefaultFilePath();
331
+ fs.writeFileSync(resolvedPath, JSON.stringify(results, null, 2));
332
+ cliLogger.info(`Results written to ${resolvedPath}`);
333
+ }
334
+ } catch (error) {
335
+ if (error instanceof Error) {
336
+ cliLogger.error(error.message);
337
+ }
338
+ cliLogger.error("Failed to run spider command");
339
+ process.exit(1);
340
+ }
341
+ }
342
+ };
343
+ var getDefaultFilePath = () => {
344
+ try {
345
+ const resolvedDir = path.resolve("sentinel_output");
346
+ if (!fs.existsSync(resolvedDir)) {
347
+ fs.mkdirSync(resolvedDir);
348
+ }
349
+ const resolvedPath = path.resolve(
350
+ `sentinel_output/spider_${Date.now()}.json`
351
+ );
352
+ if (fs.existsSync(resolvedPath)) {
353
+ throw new Error("Output file already exists");
354
+ }
355
+ const parsedPath = path.parse(resolvedPath);
356
+ if (parsedPath.ext !== ".json") {
357
+ throw new Error("Output file must be a JSON file");
358
+ }
359
+ return resolvedPath;
360
+ } catch (error) {
361
+ throw new Error("Invalid output file");
362
+ }
363
+ };
364
+
365
+ // src/bin.ts
366
+ var commandHandler = yargs(hideBin(process.argv));
367
+ commandHandler.demandCommand();
368
+ commandHandler.scriptName("sentinel-scanner");
369
+ commandHandler.usage("Usage: $0 <command> [options]");
370
+ commandHandler.help().alias("help", "h");
371
+ commandHandler.version().alias("version", "v");
372
+ commandHandler.strict();
373
+ commandHandler.showHelpOnFail(true);
374
+ commandHandler.command(spiderCommand);
375
+ commandHandler.parse();
376
+ //# sourceMappingURL=bin.js.map