@virusis/api-client 0.1.17 → 0.1.19
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/dist/base.d.ts +10 -1
- package/dist/base.js +147 -1
- package/dist/container.d.ts +9 -0
- package/dist/generated/clients/diagnostics-service.d.ts +2 -0
- package/dist/generated/clients/diagnostics-service.js +2 -0
- package/dist/generated/clients/index.d.ts +3 -0
- package/dist/generated/clients/index.js +3 -0
- package/dist/generated/clients/portal-service.d.ts +2 -0
- package/dist/generated/clients/portal-service.js +2 -0
- package/dist/generated/clients/risk-service.d.ts +2 -0
- package/dist/generated/clients/risk-service.js +2 -0
- package/dist/generated/clients-rx/diagnostics-service-rx.d.ts +16 -0
- package/dist/generated/clients-rx/diagnostics-service-rx.js +12 -0
- package/dist/generated/clients-rx/index.d.ts +3 -0
- package/dist/generated/clients-rx/index.js +3 -0
- package/dist/generated/clients-rx/portal-service-rx.d.ts +16 -0
- package/dist/generated/clients-rx/portal-service-rx.js +12 -0
- package/dist/generated/clients-rx/risk-service-rx.d.ts +16 -0
- package/dist/generated/clients-rx/risk-service-rx.js +12 -0
- package/dist/generated/index.d.ts +303 -41
- package/dist/generated/index.js +529 -37
- package/dist/generated/models/access-token-i-data-result.d.ts +4 -0
- package/dist/generated/models/access-token-i-data-result.js +1 -0
- package/dist/generated/models/access-token.d.ts +4 -0
- package/dist/generated/models/access-token.js +1 -0
- package/dist/generated/models/application-click-event-batch-dto.d.ts +4 -0
- package/dist/generated/models/application-click-event-batch-dto.js +1 -0
- package/dist/generated/models/application-click-event-create-dto.d.ts +4 -0
- package/dist/generated/models/application-click-event-create-dto.js +1 -0
- package/dist/generated/models/feedback-category-dto-list-i-data-result.d.ts +4 -0
- package/dist/generated/models/feedback-category-dto-list-i-data-result.js +1 -0
- package/dist/generated/models/feedback-category-dto.d.ts +4 -0
- package/dist/generated/models/feedback-category-dto.js +1 -0
- package/dist/generated/models/index.d.ts +15 -0
- package/dist/generated/models/index.js +15 -0
- package/dist/generated/models/otp-generate-result.d.ts +4 -0
- package/dist/generated/models/otp-generate-result.js +1 -0
- package/dist/generated/models/queue-monitor-workers-response.d.ts +4 -0
- package/dist/generated/models/queue-monitor-workers-response.js +1 -0
- package/dist/generated/models/risk-flag-request-dto.d.ts +4 -0
- package/dist/generated/models/risk-flag-request-dto.js +1 -0
- package/dist/generated/models/risk-signal-avg-dto.d.ts +4 -0
- package/dist/generated/models/risk-signal-avg-dto.js +1 -0
- package/dist/generated/models/risk-signal-client-dto.d.ts +4 -0
- package/dist/generated/models/risk-signal-client-dto.js +1 -0
- package/dist/generated/models/risk-signal-counts-dto.d.ts +4 -0
- package/dist/generated/models/risk-signal-counts-dto.js +1 -0
- package/dist/generated/models/risk-signals-dto.d.ts +4 -0
- package/dist/generated/models/risk-signals-dto.js +1 -0
- package/dist/generated/models/risk-state-dto.d.ts +4 -0
- package/dist/generated/models/risk-state-dto.js +1 -0
- package/dist/generated/models/risk-verify-dto.d.ts +4 -0
- package/dist/generated/models/risk-verify-dto.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/rx.d.ts +18 -0
- package/dist/security/index.d.ts +4 -0
- package/dist/security/index.js +2 -0
- package/dist/security/input-security-policy.d.ts +23 -0
- package/dist/security/input-security-policy.js +5 -0
- package/dist/security/input-security-service.d.ts +3 -0
- package/dist/security/input-security-service.js +153 -0
- package/dist/security/request-sanitizer.d.ts +18 -0
- package/dist/security/request-sanitizer.js +242 -0
- package/package.json +9 -4
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { sanitize } from "./input-security-service.js";
|
|
2
|
+
/**
|
|
3
|
+
* Request-level defensive sanitizer for API client fetch helpers.
|
|
4
|
+
* Validates/sanitizes path, body string fields, headers, and FormData filenames.
|
|
5
|
+
*/
|
|
6
|
+
// ─── Path validation ────────────────────────────────────────────
|
|
7
|
+
const DANGEROUS_PATH_PATTERNS = [
|
|
8
|
+
/\.\.[/\\]/, // path traversal
|
|
9
|
+
/%2e%2e/i, // encoded traversal
|
|
10
|
+
/%2f/i, // encoded slash
|
|
11
|
+
/%5c/i, // encoded backslash
|
|
12
|
+
/[\r\n]/, // CRLF
|
|
13
|
+
/[\x00-\x1F]/, // control chars
|
|
14
|
+
/\/\//, // double slash (protocol-relative)
|
|
15
|
+
];
|
|
16
|
+
export function validatePath(path) {
|
|
17
|
+
for (const p of DANGEROUS_PATH_PATTERNS) {
|
|
18
|
+
if (p.test(path)) {
|
|
19
|
+
return { safe: false, sanitized: "" };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { safe: true, sanitized: path };
|
|
23
|
+
}
|
|
24
|
+
// ─── URL host validation ────────────────────────────────────────
|
|
25
|
+
// ─── Private/reserved IP patterns for SSRF ─────────────────────
|
|
26
|
+
const PRIVATE_IP_PATTERNS = [
|
|
27
|
+
/^127\./,
|
|
28
|
+
/^10\./,
|
|
29
|
+
/^172\.(1[6-9]|2\d|3[01])\./,
|
|
30
|
+
/^192\.168\./,
|
|
31
|
+
/^169\.254\./,
|
|
32
|
+
/^0\./,
|
|
33
|
+
/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./,
|
|
34
|
+
/^198\.1[89]\./,
|
|
35
|
+
/^(2[4-5]\d)\./,
|
|
36
|
+
];
|
|
37
|
+
export function validateAbsoluteUrl(url, trustedHosts) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = new URL(url);
|
|
40
|
+
// Block javascript/data/file schemes
|
|
41
|
+
if (["javascript:", "data:", "file:", "vbscript:", "ftp:", "gopher:", "dict:", "ldap:"].includes(parsed.protocol)) {
|
|
42
|
+
return { safe: false, reason: `Blocked scheme: ${parsed.protocol}` };
|
|
43
|
+
}
|
|
44
|
+
const host = parsed.hostname.toLowerCase();
|
|
45
|
+
// Block embedded credentials
|
|
46
|
+
if (parsed.username || parsed.password) {
|
|
47
|
+
return { safe: false, reason: "URL contains embedded credentials" };
|
|
48
|
+
}
|
|
49
|
+
// Block localhost/loopback
|
|
50
|
+
if (host === "localhost" || host === "127.0.0.1" || host === "[::1]") {
|
|
51
|
+
return { safe: false, reason: `Blocked localhost/loopback: ${host}` };
|
|
52
|
+
}
|
|
53
|
+
// Block metadata hosts
|
|
54
|
+
if (host === "169.254.169.254" || host === "metadata.google.internal" || host === "100.100.100.200") {
|
|
55
|
+
return { safe: false, reason: `Blocked metadata host: ${host}` };
|
|
56
|
+
}
|
|
57
|
+
// Block private/reserved IPs
|
|
58
|
+
for (const pattern of PRIVATE_IP_PATTERNS) {
|
|
59
|
+
if (pattern.test(host)) {
|
|
60
|
+
return { safe: false, reason: `Blocked private/reserved IP: ${host}` };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Check trusted hosts if list is provided
|
|
64
|
+
if (trustedHosts.length > 0) {
|
|
65
|
+
const isTrusted = trustedHosts.some((h) => host === h.toLowerCase() || host.endsWith("." + h.toLowerCase()));
|
|
66
|
+
if (!isTrusted) {
|
|
67
|
+
return { safe: false, reason: `Untrusted host: ${host}` };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { safe: true };
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return { safe: false, reason: "Invalid URL" };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// ─── Body sanitization ─────────────────────────────────────────
|
|
77
|
+
// Property name → policy kind mapping for common API fields
|
|
78
|
+
const FIELD_POLICIES = {
|
|
79
|
+
sha256: "sha256",
|
|
80
|
+
sha1: "sha1",
|
|
81
|
+
md5: "md5",
|
|
82
|
+
url: "url",
|
|
83
|
+
ip: "ip",
|
|
84
|
+
email: "email",
|
|
85
|
+
nickName: "freeTextShort",
|
|
86
|
+
title: "freeTextShort",
|
|
87
|
+
subject: "freeTextShort",
|
|
88
|
+
scanName: "scanName",
|
|
89
|
+
name: "scanDisplayName",
|
|
90
|
+
submissionName: "scanDisplayName",
|
|
91
|
+
description: "freeTextLong",
|
|
92
|
+
message: "freeTextLong",
|
|
93
|
+
fileName: "fileName",
|
|
94
|
+
scanMode: "scanMode",
|
|
95
|
+
scanType: "enum",
|
|
96
|
+
otp: "otp",
|
|
97
|
+
password: "password",
|
|
98
|
+
captchaToken: "captchaToken",
|
|
99
|
+
scanGuid: "guid",
|
|
100
|
+
guid: "guid",
|
|
101
|
+
selectedEngineIdsJson: "engineIdsJson",
|
|
102
|
+
analysisIdsJson: "analysisIdsJson",
|
|
103
|
+
dto: "jsonPayload",
|
|
104
|
+
};
|
|
105
|
+
// ─── Prototype pollution key denylist ────────────────────────────
|
|
106
|
+
const DENIED_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
107
|
+
const MAX_BODY_DEPTH = 8;
|
|
108
|
+
export function sanitizeBody(body, depth = 0, visited = new WeakSet()) {
|
|
109
|
+
const result = {
|
|
110
|
+
body: { ...body },
|
|
111
|
+
modified: false,
|
|
112
|
+
blocked: false,
|
|
113
|
+
threats: [],
|
|
114
|
+
};
|
|
115
|
+
if (depth >= MAX_BODY_DEPTH)
|
|
116
|
+
return result;
|
|
117
|
+
if (visited.has(body))
|
|
118
|
+
return result;
|
|
119
|
+
visited.add(body);
|
|
120
|
+
for (const [key, value] of Object.entries(body)) {
|
|
121
|
+
// Reject prototype pollution keys
|
|
122
|
+
if (DENIED_KEYS.has(key)) {
|
|
123
|
+
result.blocked = true;
|
|
124
|
+
result.threats.push({
|
|
125
|
+
sanitized: "",
|
|
126
|
+
modified: false,
|
|
127
|
+
blocked: true,
|
|
128
|
+
detectedThreats: [{
|
|
129
|
+
type: "prototype-pollution",
|
|
130
|
+
severity: "critical",
|
|
131
|
+
message: `Prototype pollution key rejected: ${key}`,
|
|
132
|
+
}],
|
|
133
|
+
});
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (Array.isArray(value)) {
|
|
137
|
+
// Recursively check array items
|
|
138
|
+
const arrayResult = sanitizeArray(value, depth + 1, visited);
|
|
139
|
+
if (arrayResult.blocked) {
|
|
140
|
+
result.blocked = true;
|
|
141
|
+
result.threats.push(...arrayResult.threats);
|
|
142
|
+
}
|
|
143
|
+
else if (arrayResult.modified) {
|
|
144
|
+
result.body[key] = arrayResult.items;
|
|
145
|
+
result.modified = true;
|
|
146
|
+
result.threats.push(...arrayResult.threats);
|
|
147
|
+
}
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (typeof value === "object" && value !== null) {
|
|
151
|
+
// Recursively check nested objects for prototype pollution keys
|
|
152
|
+
const nested = sanitizeBody(value, depth + 1, visited);
|
|
153
|
+
if (nested.blocked) {
|
|
154
|
+
result.blocked = true;
|
|
155
|
+
result.threats.push(...nested.threats);
|
|
156
|
+
}
|
|
157
|
+
else if (nested.modified) {
|
|
158
|
+
result.body[key] = nested.body;
|
|
159
|
+
result.modified = true;
|
|
160
|
+
result.threats.push(...nested.threats);
|
|
161
|
+
}
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (typeof value !== "string")
|
|
165
|
+
continue;
|
|
166
|
+
const kind = FIELD_POLICIES[key] ?? "freeTextShort";
|
|
167
|
+
const secResult = sanitize(kind, value, key);
|
|
168
|
+
if (secResult.blocked) {
|
|
169
|
+
result.blocked = true;
|
|
170
|
+
result.threats.push(secResult);
|
|
171
|
+
}
|
|
172
|
+
else if (secResult.modified) {
|
|
173
|
+
result.body[key] = secResult.sanitized;
|
|
174
|
+
result.modified = true;
|
|
175
|
+
if (secResult.detectedThreats.length > 0) {
|
|
176
|
+
result.threats.push(secResult);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
function sanitizeArray(items, depth, visited) {
|
|
183
|
+
const result = {
|
|
184
|
+
items: [...items],
|
|
185
|
+
modified: false,
|
|
186
|
+
blocked: false,
|
|
187
|
+
threats: [],
|
|
188
|
+
};
|
|
189
|
+
if (depth >= MAX_BODY_DEPTH)
|
|
190
|
+
return result;
|
|
191
|
+
for (let i = 0; i < items.length; i++) {
|
|
192
|
+
const item = items[i];
|
|
193
|
+
if (item === null || item === undefined)
|
|
194
|
+
continue;
|
|
195
|
+
if (Array.isArray(item)) {
|
|
196
|
+
const nested = sanitizeArray(item, depth + 1, visited);
|
|
197
|
+
if (nested.blocked) {
|
|
198
|
+
result.blocked = true;
|
|
199
|
+
result.threats.push(...nested.threats);
|
|
200
|
+
}
|
|
201
|
+
else if (nested.modified) {
|
|
202
|
+
result.items[i] = nested.items;
|
|
203
|
+
result.modified = true;
|
|
204
|
+
result.threats.push(...nested.threats);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else if (typeof item === "object") {
|
|
208
|
+
const nested = sanitizeBody(item, depth + 1, visited);
|
|
209
|
+
if (nested.blocked) {
|
|
210
|
+
result.blocked = true;
|
|
211
|
+
result.threats.push(...nested.threats);
|
|
212
|
+
}
|
|
213
|
+
else if (nested.modified) {
|
|
214
|
+
result.items[i] = nested.body;
|
|
215
|
+
result.modified = true;
|
|
216
|
+
result.threats.push(...nested.threats);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else if (typeof item === "string") {
|
|
220
|
+
const secResult = sanitize("freeTextShort", item, `[${i}]`);
|
|
221
|
+
if (secResult.blocked) {
|
|
222
|
+
result.blocked = true;
|
|
223
|
+
result.threats.push(secResult);
|
|
224
|
+
}
|
|
225
|
+
else if (secResult.modified) {
|
|
226
|
+
result.items[i] = secResult.sanitized;
|
|
227
|
+
result.modified = true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
// ─── FormData sanitization ──────────────────────────────────────
|
|
234
|
+
export function sanitizeFormDataFileName(filename) {
|
|
235
|
+
const result = sanitize("fileName", filename, "formDataFile");
|
|
236
|
+
return result.blocked ? "sanitized_file" : result.sanitized;
|
|
237
|
+
}
|
|
238
|
+
// ─── Header sanitization ────────────────────────────────────────
|
|
239
|
+
export function sanitizeHeaderValue(value) {
|
|
240
|
+
const result = sanitize("headerValue", value, "header");
|
|
241
|
+
return result.blocked ? "" : result.sanitized;
|
|
242
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@virusis/api-client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -23,13 +23,18 @@
|
|
|
23
23
|
},
|
|
24
24
|
"./package.json": "./package.json"
|
|
25
25
|
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public",
|
|
28
|
+
"registry": "https://registry.npmjs.org/"
|
|
29
|
+
},
|
|
26
30
|
"scripts": {
|
|
27
31
|
"generate": "node scripts/run-nswag.mjs",
|
|
28
32
|
"postgenerate": "node scripts/split-clients.js",
|
|
29
|
-
"build": "tsc -p tsconfig.json",
|
|
30
|
-
"test": "vitest run",
|
|
33
|
+
"build": "node node_modules/typescript/bin/tsc -p tsconfig.json",
|
|
34
|
+
"test": "node node_modules/vitest/vitest.mjs run",
|
|
31
35
|
"ci": "npm run generate && npm run build && npm test",
|
|
32
|
-
"release": "npm run ci && npm publish --access=public",
|
|
36
|
+
"release": "npm run ci && npm publish --access=public --registry=https://registry.npmjs.org/",
|
|
37
|
+
"release:dry": "npm run ci && npm publish --dry-run --access=public --registry=https://registry.npmjs.org/",
|
|
33
38
|
"pack:local": "node -e \"require('fs').mkdirSync('packages',{recursive:true});\" && npm pack --pack-destination ./packages",
|
|
34
39
|
"fetch:spec": "node scripts/fetch-spec.mjs",
|
|
35
40
|
"gen:python": "npm run fetch:spec && npx @openapitools/openapi-generator-cli generate -g python -i specs/openapi.json -o out/python --package-name virusis_api_client",
|