sentinel-scanner 2.4.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/.cspell.json +19 -51
  2. package/.github/ISSUE_TEMPLATE/config.yml +1 -1
  3. package/.github/PULL_REQUEST_TEMPLATE.md +2 -2
  4. package/.github/workflows/stale.yaml +20 -0
  5. package/.github/workflows/webapp-scanner.yml +31 -19
  6. package/.github/workflows/welcome.yaml +9 -55
  7. package/.husky/pre-commit +35 -0
  8. package/.vscode/extensions.json +7 -0
  9. package/.vscode/launch.json +20 -0
  10. package/.vscode/settings.json +32 -0
  11. package/.vscode/tasks.json +24 -0
  12. package/CHANGELOG.md +7 -3
  13. package/CODE_OF_CONDUCT.md +4 -1
  14. package/CONTRIBUTING.md +2 -2
  15. package/README.md +5 -0
  16. package/api-extractor.json +30 -30
  17. package/biome.json +6 -32
  18. package/build/index.d.ts +0 -147
  19. package/build/index.js +111 -2633
  20. package/package.json +69 -102
  21. package/scripts/build.ts +68 -78
  22. package/scripts/test.ts +55 -0
  23. package/src/__tests__/spider.test.ts +44 -0
  24. package/src/commands/spider.ts +61 -126
  25. package/src/index.ts +23 -26
  26. package/src/spider/index.ts +345 -0
  27. package/src/spider/types/index.ts +21 -0
  28. package/src/spider/types/schema.ts +54 -0
  29. package/src/utils/index.ts +199 -3
  30. package/tsconfig.json +19 -18
  31. package/.github/assets/header.png +0 -0
  32. package/.github/dependabot.yml +0 -11
  33. package/.github/workflows/pr.yaml +0 -64
  34. package/.nsprc +0 -3
  35. package/build/bin.js +0 -2679
  36. package/build/xhr-sync-worker.js +0 -59
  37. package/docs/CNAME +0 -1
  38. package/docs/disclaimer.md +0 -68
  39. package/docs/headers/details.md +0 -114
  40. package/docs/headers/index.md +0 -73
  41. package/docs/index.md +0 -82
  42. package/docs/ports/index.md +0 -86
  43. package/docs/scoring.md +0 -91
  44. package/docs/spider/index.md +0 -61
  45. package/docs/sql-injection/details.md +0 -109
  46. package/docs/sql-injection/index.md +0 -73
  47. package/docs/xss/details.md +0 -92
  48. package/docs/xss/index.md +0 -73
  49. package/scripts/extras/document-shim.js +0 -4
  50. package/src/bin.ts +0 -29
  51. package/src/commands/header.ts +0 -150
  52. package/src/commands/ports.ts +0 -175
  53. package/src/commands/sqli.ts +0 -150
  54. package/src/commands/xss.ts +0 -149
  55. package/src/modules/headers/headers.ts +0 -161
  56. package/src/modules/headers/index.ts +0 -179
  57. package/src/modules/ports/index.ts +0 -311
  58. package/src/modules/spider/index.ts +0 -178
  59. package/src/modules/sqli/index.ts +0 -486
  60. package/src/modules/sqli/payloads.json +0 -156
  61. package/src/modules/xss/index.ts +0 -401
  62. package/src/modules/xss/payloads.json +0 -2692
  63. package/src/utils/types.ts +0 -7
@@ -1,175 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import type { ArgumentsCamelCase, CommandModule } from "yargs";
4
- import { PortsScanner } from "../index.js";
5
- import { createLogger } from "../utils/index.js";
6
-
7
- export type PortScannerCLIOpts = {
8
- spiderResults: string;
9
- fromPort?: number;
10
- toPort?: number;
11
- allowList?: Array<number>;
12
- concurrency?: number;
13
- timeout?: number;
14
- output?: string;
15
- };
16
-
17
- const cliLogger = createLogger("CLI");
18
-
19
- export const portsCommand: CommandModule = {
20
- command: "ports",
21
- describe:
22
- "Check a website for Open Port vulnerabilities. Check the ports between the specified range for open ports",
23
- builder: (yargs) => {
24
- return yargs
25
- .option("spiderResults", {
26
- alias: "s",
27
- type: "string",
28
- description:
29
- "The spider results file to use for scanning. It will use the URLs from the spider results to scan for header vulnerabilities",
30
- demandOption: true,
31
- coerce: (url) => {
32
- if (!path.isAbsolute(url)) {
33
- return path.resolve(url);
34
- }
35
- return url;
36
- },
37
- })
38
- .option("output", {
39
- alias: "o",
40
- type: "string",
41
- description:
42
- "The output file to write the results to. Must be a JSON file",
43
- default: () => getDefaultFilePath(),
44
- coerce: (output) => {
45
- const resolvedPath = path.resolve(output);
46
- const { ext } = path.parse(resolvedPath);
47
-
48
- if (ext !== ".json") {
49
- throw new Error("Output file must be a JSON file");
50
- }
51
- return resolvedPath;
52
- },
53
- })
54
- .option("concurrency", {
55
- alias: "c",
56
- type: "number",
57
- description: "The number of concurrent requests to make",
58
- default: 10,
59
- coerce: (concurrency) => {
60
- if (concurrency < 1 || concurrency > 20) {
61
- throw new Error("Concurrency must be between 1 and 20");
62
- }
63
- return concurrency;
64
- },
65
- })
66
- .option("timeout", {
67
- alias: "t",
68
- type: "number",
69
- description: "The timeout for each request in milliseconds",
70
- default: 5000,
71
- coerce: (timeout) => {
72
- if (timeout < 0 || timeout > 25000) {
73
- throw new Error("Timeout must be between 0 and 25,000 ms");
74
- }
75
- return timeout;
76
- },
77
- })
78
- .option("fromPort", {
79
- alias: "fp",
80
- type: "number",
81
- description: "The starting port to scan",
82
- default: 3,
83
- coerce: (fromPort) => {
84
- if (fromPort < 1 || fromPort > 65535) {
85
- throw new Error("Port must be between 1 and 65,535");
86
- }
87
- },
88
- })
89
- .option("toPort", {
90
- alias: "tp",
91
- type: "number",
92
- description: "The ending port to scan",
93
- default: 8080,
94
- coerce: (toPort) => {
95
- if (toPort < 1 || toPort > 65535) {
96
- throw new Error("Port must be between 1 and 65,535");
97
- }
98
- },
99
- })
100
- .option("allowList", {
101
- alias: "al",
102
- type: "array",
103
- description: "A list of ports to allow",
104
- default: [22, 80, 443],
105
- coerce: (allowList) => {
106
- if (!Array.isArray(allowList)) {
107
- throw new Error("Allow list must be an array");
108
- }
109
- return allowList;
110
- },
111
- });
112
- },
113
- handler: async (args) => {
114
- try {
115
- const argData = args as ArgumentsCamelCase<PortScannerCLIOpts>;
116
- const spiderResultsPath = path.resolve(argData.spiderResults);
117
-
118
- // Check if the spider results file exists
119
- if (!(await fileExists(spiderResultsPath))) {
120
- throw new Error(
121
- `Spider results file not found at ${spiderResultsPath}`,
122
- );
123
- }
124
-
125
- const spiderResults = JSON.parse(
126
- await fs.readFile(spiderResultsPath, "utf-8"),
127
- );
128
-
129
- cliLogger.info("Starting Port scan on website");
130
-
131
- const scanner = new PortsScanner({
132
- spiderResults,
133
- fromPort: argData.fromPort ?? 1,
134
- toPort: argData.toPort ?? 65535,
135
- allowList: argData.allowList ?? [22, 80, 443],
136
- concurrency: argData.concurrency ?? 30,
137
- timeout: argData.timeout ?? 10000,
138
- });
139
- const results = await scanner.scan();
140
-
141
- const outputPath = argData.output || getDefaultFilePath();
142
- await fs.writeFile(outputPath, JSON.stringify(results, null, 2));
143
- cliLogger.info(`Results successfully written to ${outputPath}`);
144
- } catch (error) {
145
- if (error instanceof Error) {
146
- cliLogger.error(`Error: ${error.message}`);
147
- }
148
- cliLogger.error("Failed to run Port Scan command");
149
- process.exit(1);
150
- }
151
- },
152
- };
153
-
154
- // Utility function to check if a file exists
155
- const fileExists = async (filePath: string) => {
156
- try {
157
- await fs.access(filePath);
158
- return true;
159
- } catch {
160
- return false;
161
- }
162
- };
163
-
164
- // Utility function to get the default file path
165
- const getDefaultFilePath = () => {
166
- const resolvedDir = path.resolve("sentinel_output");
167
-
168
- // Ensure the directory exists or create it
169
- fs.mkdir(resolvedDir, { recursive: true }).catch((err) => {
170
- cliLogger.error(`Failed to create directory: ${err.message}`);
171
- process.exit(1);
172
- });
173
-
174
- return path.resolve(resolvedDir, `portsResults_${Date.now()}.json`);
175
- };
@@ -1,150 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import type { ArgumentsCamelCase, CommandModule } from "yargs";
4
- import SqliScanner from "../modules/sqli/index.js";
5
- import { createLogger } from "../utils/index.js";
6
-
7
- export type SqliScannerCLIoptions = {
8
- spiderResults: string;
9
- retries?: number;
10
- timeout?: number;
11
- concurrency?: number;
12
- output?: string;
13
- };
14
-
15
- const cliLogger = createLogger("CLI");
16
-
17
- export const sqliCommand: CommandModule = {
18
- command: "sqli",
19
- describe:
20
- "Check a website for SQL Injection vulnerabilities by scanning each page",
21
- builder: (yargs) => {
22
- return yargs
23
- .option("spiderResults", {
24
- alias: "s",
25
- type: "string",
26
- description:
27
- "The spider results file to use for scanning. It will use the URLs from the spider results to scan for SQL Injection vulnerabilities",
28
- demandOption: true,
29
- coerce: (url) => {
30
- if (!path.isAbsolute(url)) {
31
- return path.resolve(url);
32
- }
33
- return url;
34
- },
35
- })
36
- .option("output", {
37
- alias: "o",
38
- type: "string",
39
- description:
40
- "The output file to write the results to. Must be a JSON file",
41
- default: () => getDefaultFilePath(),
42
- coerce: (output) => {
43
- const resolvedPath = path.resolve(output);
44
- const { ext } = path.parse(resolvedPath);
45
-
46
- if (ext !== ".json") {
47
- throw new Error("Output file must be a JSON file");
48
- }
49
- return resolvedPath;
50
- },
51
- })
52
- .option("concurrency", {
53
- alias: "c",
54
- type: "number",
55
- description: "The number of concurrent requests to make",
56
- default: 10,
57
- coerce: (concurrency) => {
58
- if (concurrency < 1 || concurrency > 20) {
59
- throw new Error("Concurrency must be between 1 and 20");
60
- }
61
- return concurrency;
62
- },
63
- })
64
- .option("timeout", {
65
- alias: "t",
66
- type: "number",
67
- description: "The timeout for each request in milliseconds",
68
- default: 5000,
69
- coerce: (timeout) => {
70
- if (timeout < 0 || timeout > 25000) {
71
- throw new Error("Timeout must be between 0 and 25,000 ms");
72
- }
73
- return timeout;
74
- },
75
- })
76
- .option("retries", {
77
- alias: "r",
78
- type: "number",
79
- description: "The number of retries for each request",
80
- default: 3,
81
- coerce: (retries) => {
82
- if (retries < 0 || retries > 10) {
83
- throw new Error("Retries must be between 0 and 10");
84
- }
85
- return retries;
86
- },
87
- });
88
- },
89
- handler: async (args) => {
90
- try {
91
- const argData = args as ArgumentsCamelCase<SqliScannerCLIoptions>;
92
- const spiderResultsPath = path.resolve(argData.spiderResults);
93
-
94
- // Check if the spider results file exists
95
- if (!(await fileExists(spiderResultsPath))) {
96
- throw new Error(
97
- `Spider results file not found at ${spiderResultsPath}`,
98
- );
99
- }
100
-
101
- const spiderResults = JSON.parse(
102
- await fs.readFile(spiderResultsPath, "utf-8"),
103
- );
104
-
105
- cliLogger.info("Starting SQLI scan on website");
106
-
107
- const scanner = new SqliScanner({
108
- spiderResults,
109
- concurrency: argData.concurrency,
110
- timeout: argData.timeout,
111
- retries: argData.retries,
112
- });
113
-
114
- const results = await scanner.scan();
115
-
116
- const outputPath = argData.output || getDefaultFilePath();
117
- await fs.writeFile(outputPath, JSON.stringify(results, null, 2));
118
- cliLogger.info(`Results successfully written to ${outputPath}`);
119
- } catch (error) {
120
- if (error instanceof Error) {
121
- cliLogger.error(`Error: ${error.message}`);
122
- }
123
- cliLogger.error("Failed to run SQLI command");
124
- process.exit(1);
125
- }
126
- },
127
- };
128
-
129
- // Utility function to check if a file exists
130
- const fileExists = async (filePath: string) => {
131
- try {
132
- await fs.access(filePath);
133
- return true;
134
- } catch {
135
- return false;
136
- }
137
- };
138
-
139
- // Utility function to get the default file path
140
- const getDefaultFilePath = () => {
141
- const resolvedDir = path.resolve("sentinel_output");
142
-
143
- // Ensure the directory exists or create it
144
- fs.mkdir(resolvedDir, { recursive: true }).catch((err) => {
145
- cliLogger.error(`Failed to create directory: ${err.message}`);
146
- process.exit(1);
147
- });
148
-
149
- return path.resolve(resolvedDir, `sqliResult_${Date.now()}.json`);
150
- };
@@ -1,149 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import type { ArgumentsCamelCase, CommandModule } from "yargs";
4
- import { XSSScanner } from "../index.js";
5
- import { createLogger } from "../utils/index.js";
6
-
7
- export type XSSScannerCLIoptions = {
8
- spiderResults: string;
9
- retries?: number;
10
- timeout?: number;
11
- concurrency?: number;
12
- output?: string;
13
- };
14
-
15
- const cliLogger = createLogger("CLI");
16
-
17
- export const xssCommand: CommandModule = {
18
- command: "xss",
19
- describe: "Check a website for XSS vulnerabilities by scanning each page",
20
- builder: (yargs) => {
21
- return yargs
22
- .option("spiderResults", {
23
- alias: "s",
24
- type: "string",
25
- description:
26
- "The spider results file to use for scanning. It will use the URLs from the spider results to scan for XSS vulnerabilities",
27
- demandOption: true,
28
- coerce: (url) => {
29
- if (!path.isAbsolute(url)) {
30
- return path.resolve(url);
31
- }
32
- return url;
33
- },
34
- })
35
- .option("output", {
36
- alias: "o",
37
- type: "string",
38
- description:
39
- "The output file to write the results to. Must be a JSON file",
40
- default: () => getDefaultFilePath(),
41
- coerce: (output) => {
42
- const resolvedPath = path.resolve(output);
43
- const { ext } = path.parse(resolvedPath);
44
-
45
- if (ext !== ".json") {
46
- throw new Error("Output file must be a JSON file");
47
- }
48
- return resolvedPath;
49
- },
50
- })
51
- .option("concurrency", {
52
- alias: "c",
53
- type: "number",
54
- description: "The number of concurrent requests to make",
55
- default: 10,
56
- coerce: (concurrency) => {
57
- if (concurrency < 1 || concurrency > 20) {
58
- throw new Error("Concurrency must be between 1 and 20");
59
- }
60
- return concurrency;
61
- },
62
- })
63
- .option("timeout", {
64
- alias: "t",
65
- type: "number",
66
- description: "The timeout for each request in milliseconds",
67
- default: 5000,
68
- coerce: (timeout) => {
69
- if (timeout < 0 || timeout > 25000) {
70
- throw new Error("Timeout must be between 0 and 25,000 ms");
71
- }
72
- return timeout;
73
- },
74
- })
75
- .option("retries", {
76
- alias: "r",
77
- type: "number",
78
- description: "The number of retries for each request",
79
- default: 3,
80
- coerce: (retries) => {
81
- if (retries < 0 || retries > 10) {
82
- throw new Error("Retries must be between 0 and 10");
83
- }
84
- return retries;
85
- },
86
- });
87
- },
88
- handler: async (args) => {
89
- try {
90
- const argData = args as ArgumentsCamelCase<XSSScannerCLIoptions>;
91
- const spiderResultsPath = path.resolve(argData.spiderResults);
92
-
93
- // Check if the spider results file exists
94
- if (!(await fileExists(spiderResultsPath))) {
95
- throw new Error(
96
- `Spider results file not found at ${spiderResultsPath}`,
97
- );
98
- }
99
-
100
- const spiderResults = JSON.parse(
101
- await fs.readFile(spiderResultsPath, "utf-8"),
102
- );
103
-
104
- cliLogger.info("Starting XSS scan on website");
105
-
106
- const scanner = new XSSScanner({
107
- spiderResults,
108
- concurrency: argData.concurrency,
109
- timeout: argData.timeout,
110
- retries: argData.retries,
111
- });
112
-
113
- const results = await scanner.scan();
114
-
115
- const outputPath = argData.output || getDefaultFilePath();
116
- await fs.writeFile(outputPath, JSON.stringify(results, null, 2));
117
- cliLogger.info(`Results successfully written to ${outputPath}`);
118
- } catch (error) {
119
- if (error instanceof Error) {
120
- cliLogger.error(`Error: ${error.message}`);
121
- }
122
- cliLogger.error("Failed to run XSS command");
123
- process.exit(1);
124
- }
125
- },
126
- };
127
-
128
- // Utility function to check if a file exists
129
- const fileExists = async (filePath: string) => {
130
- try {
131
- await fs.access(filePath);
132
- return true;
133
- } catch {
134
- return false;
135
- }
136
- };
137
-
138
- // Utility function to get the default file path
139
- const getDefaultFilePath = () => {
140
- const resolvedDir = path.resolve("sentinel_output");
141
-
142
- // Ensure the directory exists or create it
143
- fs.mkdir(resolvedDir, { recursive: true }).catch((err) => {
144
- cliLogger.error(`Failed to create directory: ${err.message}`);
145
- process.exit(1);
146
- });
147
-
148
- return path.resolve(resolvedDir, `xssResult_${Date.now()}.json`);
149
- };
@@ -1,161 +0,0 @@
1
- import type { HeadersData } from "./index.js";
2
-
3
- const securityChecks: HeadersData[] = [
4
- {
5
- name: "X-Content-Type-Options",
6
- description: "Prevents MIME-type sniffing.",
7
- recommendation: "nosniff",
8
- check: (value: string) => value === "nosniff",
9
- },
10
- {
11
- name: "X-Frame-Options",
12
- description: "Mitigates clickjacking attacks.",
13
- recommendation: "DENY or SAMEORIGIN",
14
- check: (value: string) =>
15
- value === "DENY" ||
16
- value === "SAMEORIGIN" ||
17
- value?.startsWith("ALLOW-FROM"),
18
- },
19
- {
20
- name: "Strict-Transport-Security",
21
- description: "Enforces HTTPS and prevents downgrade attacks.",
22
- recommendation: "max-age=31536000; includeSubDomains; preload",
23
- check: (value: string) =>
24
- value?.includes("max-age=") && value.includes("includeSubDomains"),
25
- },
26
- {
27
- name: "Content-Security-Policy",
28
- description:
29
- "Prevents cross-site scripting (XSS) and data injection attacks.",
30
- recommendation: "script-src 'self'; object-src 'none'",
31
- check: (value: string) => !!value,
32
- },
33
- {
34
- name: "Referrer-Policy",
35
- description:
36
- "Controls how much referrer information is included with requests.",
37
- recommendation: "no-referrer or strict-origin",
38
- check: (value: string) =>
39
- [
40
- "no-referrer",
41
- "strict-origin",
42
- "strict-origin-when-cross-origin",
43
- ].includes(value ?? ""),
44
- },
45
- {
46
- name: "Permissions-Policy",
47
- description: "Manages permissions of APIs (e.g., camera, geolocation).",
48
- recommendation: "default settings for better privacy",
49
- check: (value: string) => !!value,
50
- },
51
- {
52
- name: "Cross-Origin-Embedder-Policy",
53
- description:
54
- "Prevents a document from loading any cross-origin resources that don't explicitly grant permission.",
55
- recommendation: "require-corp",
56
- check: (value: string) => value === "require-corp",
57
- },
58
- {
59
- name: "Cross-Origin-Opener-Policy",
60
- description:
61
- "Prevents other domains from taking control of your context via window.opener.",
62
- recommendation: "same-origin",
63
- check: (value: string) => value === "same-origin",
64
- },
65
- {
66
- name: "Cross-Origin-Resource-Policy",
67
- description: "Prevents your resources from being used by other sites.",
68
- recommendation: "same-origin",
69
- check: (value: string) => value === "same-origin" || value === "same-site",
70
- },
71
- ];
72
-
73
- const informationLeakChecks: HeadersData[] = [
74
- {
75
- name: "Server",
76
- description: "Reveals server software information.",
77
- recommendation: "Remove or obfuscate this header.",
78
- check: (value: string) => !value,
79
- },
80
- {
81
- name: "X-Powered-By",
82
- description:
83
- "Reveals information about the framework (e.g., Express, PHP).",
84
- recommendation: "Remove or set to a generic value.",
85
- check: (value: string) => !value,
86
- },
87
- {
88
- name: "X-AspNet-Version",
89
- description: "Reveals ASP.NET version.",
90
- recommendation: "Remove this header.",
91
- check: (value: string) => !value,
92
- },
93
- {
94
- name: "X-AspNetMvc-Version",
95
- description: "Reveals ASP.NET MVC version.",
96
- recommendation: "Remove this header.",
97
- check: (value: string) => !value,
98
- },
99
- {
100
- name: "X-PHP-Version",
101
- description: "Reveals PHP version.",
102
- recommendation: "Disable or remove this header.",
103
- check: (value: string) => !value,
104
- },
105
- {
106
- name: "X-Generator",
107
- description: "Reveals information about CMS (e.g., WordPress, Joomla).",
108
- recommendation: "Remove this header.",
109
- check: (value: string) => !value,
110
- },
111
- {
112
- name: "X-Drupal-Dynamic-Cache",
113
- description: "Reveals Drupal cache status.",
114
- recommendation: "Remove this header.",
115
- check: (value: string) => !value,
116
- },
117
- {
118
- name: "X-Runtime",
119
- description: "Reveals the application's runtime environment.",
120
- recommendation: "Remove this header.",
121
- check: (value: string) => !value,
122
- },
123
- {
124
- name: "X-Backend-Server",
125
- description: "Leaks information about backend server infrastructure.",
126
- recommendation: "Remove or obfuscate this header.",
127
- check: (value: string) => !value,
128
- },
129
- {
130
- name: "Via",
131
- description: "Reveals intermediate proxies and gateways.",
132
- recommendation: "Remove or obfuscate this header.",
133
- check: (value: string) => !value,
134
- },
135
- {
136
- name: "X-Cache",
137
- description: "Indicates if a resource was served from cache.",
138
- recommendation: "Remove this header.",
139
- check: (value: string) => !value,
140
- },
141
- {
142
- name: "X-CF-Powered-By",
143
- description: "Reveals that the app is behind Cloudflare.",
144
- recommendation: "Remove this header.",
145
- check: (value: string) => !value,
146
- },
147
- {
148
- name: "X-Edge-IP",
149
- description: "Leaks edge server IP addresses.",
150
- recommendation: "Remove or obfuscate this header.",
151
- check: (value: string) => !value,
152
- },
153
- {
154
- name: "X-Edge-Location",
155
- description: "Reveals the physical location of edge servers.",
156
- recommendation: "Remove this header.",
157
- check: (value: string) => !value,
158
- },
159
- ];
160
-
161
- export { securityChecks, informationLeakChecks };