@ucptools/validator 1.0.0 → 1.1.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.
- package/dist/auth/config.d.ts +20 -0
- package/dist/auth/config.d.ts.map +1 -0
- package/dist/auth/config.js +114 -0
- package/dist/auth/config.js.map +1 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +17 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware.d.ts +45 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +170 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/service.d.ts +80 -0
- package/dist/auth/service.d.ts.map +1 -0
- package/dist/auth/service.js +298 -0
- package/dist/auth/service.js.map +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +375 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/mock-server.d.ts +20 -0
- package/dist/cli/mock-server.d.ts.map +1 -0
- package/dist/cli/mock-server.js +261 -0
- package/dist/cli/mock-server.js.map +1 -0
- package/dist/compliance/compliance-generator.d.ts +34 -0
- package/dist/compliance/compliance-generator.d.ts.map +1 -0
- package/dist/compliance/compliance-generator.js +320 -0
- package/dist/compliance/compliance-generator.js.map +1 -0
- package/dist/compliance/index.d.ts +8 -0
- package/dist/compliance/index.d.ts.map +1 -0
- package/dist/compliance/index.js +17 -0
- package/dist/compliance/index.js.map +1 -0
- package/dist/compliance/templates.d.ts +34 -0
- package/dist/compliance/templates.d.ts.map +1 -0
- package/{src/compliance/templates.ts → dist/compliance/templates.js} +117 -155
- package/dist/compliance/templates.js.map +1 -0
- package/dist/compliance/types.d.ts +64 -0
- package/dist/compliance/types.d.ts.map +1 -0
- package/dist/compliance/types.js +64 -0
- package/dist/compliance/types.js.map +1 -0
- package/dist/db/index.d.ts +17 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +80 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +3886 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +425 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/utils.d.ts +252 -0
- package/dist/db/utils.d.ts.map +1 -0
- package/dist/db/utils.js +295 -0
- package/dist/db/utils.js.map +1 -0
- package/dist/feed-analyzer/feed-analyzer.d.ts +26 -0
- package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -0
- package/{src/feed-analyzer/feed-analyzer.ts → dist/feed-analyzer/feed-analyzer.js} +856 -726
- package/dist/feed-analyzer/feed-analyzer.js.map +1 -0
- package/dist/feed-analyzer/index.d.ts +8 -0
- package/dist/feed-analyzer/index.d.ts.map +1 -0
- package/dist/feed-analyzer/index.js +19 -0
- package/dist/feed-analyzer/index.js.map +1 -0
- package/dist/feed-analyzer/types.d.ts +285 -0
- package/dist/feed-analyzer/types.d.ts.map +1 -0
- package/dist/feed-analyzer/types.js +175 -0
- package/dist/feed-analyzer/types.js.map +1 -0
- package/{src/generator/index.ts → dist/generator/index.d.ts} +1 -1
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +13 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/key-generator.d.ts +24 -0
- package/dist/generator/key-generator.d.ts.map +1 -0
- package/dist/generator/key-generator.js +144 -0
- package/dist/generator/key-generator.js.map +1 -0
- package/dist/generator/profile-builder.d.ts +15 -0
- package/dist/generator/profile-builder.d.ts.map +1 -0
- package/dist/generator/profile-builder.js +338 -0
- package/dist/generator/profile-builder.js.map +1 -0
- package/dist/hosting/artifacts-generator.d.ts +10 -0
- package/dist/hosting/artifacts-generator.d.ts.map +1 -0
- package/{src/hosting/artifacts-generator.ts → dist/hosting/artifacts-generator.js} +191 -241
- package/dist/hosting/artifacts-generator.js.map +1 -0
- package/{src/hosting/index.ts → dist/hosting/index.d.ts} +1 -1
- package/dist/hosting/index.d.ts.map +1 -0
- package/dist/hosting/index.js +10 -0
- package/dist/hosting/index.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/analytics.d.ts +337 -0
- package/dist/lib/analytics.d.ts.map +1 -0
- package/dist/lib/analytics.js +188 -0
- package/dist/lib/analytics.js.map +1 -0
- package/{src/security/index.ts → dist/security/index.d.ts} +8 -15
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +12 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/security-scanner.d.ts +10 -0
- package/dist/security/security-scanner.d.ts.map +1 -0
- package/dist/security/security-scanner.js +669 -0
- package/dist/security/security-scanner.js.map +1 -0
- package/dist/security/types.d.ts +80 -0
- package/dist/security/types.d.ts.map +1 -0
- package/dist/security/types.js +21 -0
- package/dist/security/types.js.map +1 -0
- package/dist/services/analytics.d.ts +114 -0
- package/dist/services/analytics.d.ts.map +1 -0
- package/dist/services/analytics.js +862 -0
- package/dist/services/analytics.js.map +1 -0
- package/dist/services/badge.d.ts +31 -0
- package/dist/services/badge.d.ts.map +1 -0
- package/dist/services/badge.js +152 -0
- package/dist/services/badge.js.map +1 -0
- package/dist/services/cron.d.ts +125 -0
- package/dist/services/cron.d.ts.map +1 -0
- package/dist/services/cron.js +613 -0
- package/dist/services/cron.js.map +1 -0
- package/dist/services/directory.d.ts +106 -0
- package/dist/services/directory.d.ts.map +1 -0
- package/dist/services/directory.js +351 -0
- package/dist/services/directory.js.map +1 -0
- package/dist/services/email.d.ts +112 -0
- package/dist/services/email.d.ts.map +1 -0
- package/dist/services/email.js +772 -0
- package/dist/services/email.js.map +1 -0
- package/dist/services/hosted-profiles.d.ts +77 -0
- package/dist/services/hosted-profiles.d.ts.map +1 -0
- package/dist/services/hosted-profiles.js +433 -0
- package/dist/services/hosted-profiles.js.map +1 -0
- package/dist/services/latency.d.ts +67 -0
- package/dist/services/latency.d.ts.map +1 -0
- package/dist/services/latency.js +274 -0
- package/dist/services/latency.js.map +1 -0
- package/dist/services/manifest-compliance.d.ts +64 -0
- package/dist/services/manifest-compliance.d.ts.map +1 -0
- package/dist/services/manifest-compliance.js +271 -0
- package/dist/services/manifest-compliance.js.map +1 -0
- package/dist/services/monitoring-diff.d.ts +31 -0
- package/dist/services/monitoring-diff.d.ts.map +1 -0
- package/dist/services/monitoring-diff.js +189 -0
- package/dist/services/monitoring-diff.js.map +1 -0
- package/dist/services/notifications.d.ts +46 -0
- package/dist/services/notifications.d.ts.map +1 -0
- package/dist/services/notifications.js +88 -0
- package/dist/services/notifications.js.map +1 -0
- package/dist/services/stripe.d.ts +93 -0
- package/dist/services/stripe.d.ts.map +1 -0
- package/dist/services/stripe.js +490 -0
- package/dist/services/stripe.js.map +1 -0
- package/dist/services/validation-history.d.ts +99 -0
- package/dist/services/validation-history.d.ts.map +1 -0
- package/dist/services/validation-history.js +344 -0
- package/dist/services/validation-history.js.map +1 -0
- package/dist/services/validation-logging.d.ts +103 -0
- package/dist/services/validation-logging.d.ts.map +1 -0
- package/dist/services/validation-logging.js +210 -0
- package/dist/services/validation-logging.js.map +1 -0
- package/dist/services/validation.d.ts +119 -0
- package/dist/services/validation.d.ts.map +1 -0
- package/dist/services/validation.js +1185 -0
- package/dist/services/validation.js.map +1 -0
- package/dist/simulator/agent-simulator.d.ts +69 -0
- package/dist/simulator/agent-simulator.d.ts.map +1 -0
- package/dist/simulator/agent-simulator.js +870 -0
- package/dist/simulator/agent-simulator.js.map +1 -0
- package/{src/simulator/index.ts → dist/simulator/index.d.ts} +7 -7
- package/dist/simulator/index.d.ts.map +1 -0
- package/dist/simulator/index.js +23 -0
- package/dist/simulator/index.js.map +1 -0
- package/{src/simulator/types.ts → dist/simulator/types.d.ts} +171 -170
- package/dist/simulator/types.d.ts.map +1 -0
- package/dist/simulator/types.js +18 -0
- package/dist/simulator/types.js.map +1 -0
- package/dist/types/acp-validation.d.ts +87 -0
- package/dist/types/acp-validation.d.ts.map +1 -0
- package/dist/types/acp-validation.js +40 -0
- package/dist/types/acp-validation.js.map +1 -0
- package/dist/types/analytics.d.ts +182 -0
- package/dist/types/analytics.d.ts.map +1 -0
- package/dist/types/analytics.js +7 -0
- package/dist/types/analytics.js.map +1 -0
- package/dist/types/generator.d.ts +106 -0
- package/dist/types/generator.d.ts.map +1 -0
- package/dist/types/generator.js +6 -0
- package/dist/types/generator.js.map +1 -0
- package/{src/types/index.ts → dist/types/index.d.ts} +1 -1
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/ucp-profile.d.ts +111 -0
- package/dist/types/ucp-profile.d.ts.map +1 -0
- package/dist/types/ucp-profile.js +45 -0
- package/dist/types/ucp-profile.js.map +1 -0
- package/dist/types/validation.d.ts +76 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +42 -0
- package/dist/types/validation.js.map +1 -0
- package/dist/validator/acp/index.d.ts +31 -0
- package/dist/validator/acp/index.d.ts.map +1 -0
- package/dist/validator/acp/index.js +574 -0
- package/dist/validator/acp/index.js.map +1 -0
- package/dist/validator/index.d.ts +26 -0
- package/dist/validator/index.d.ts.map +1 -0
- package/dist/validator/index.js +161 -0
- package/dist/validator/index.js.map +1 -0
- package/dist/validator/network-validator.d.ts +28 -0
- package/dist/validator/network-validator.d.ts.map +1 -0
- package/dist/validator/network-validator.js +319 -0
- package/dist/validator/network-validator.js.map +1 -0
- package/dist/validator/rules-validator.d.ts +19 -0
- package/dist/validator/rules-validator.d.ts.map +1 -0
- package/dist/validator/rules-validator.js +306 -0
- package/dist/validator/rules-validator.js.map +1 -0
- package/dist/validator/sdk-validator.d.ts +58 -0
- package/dist/validator/sdk-validator.d.ts.map +1 -0
- package/{src/validator/sdk-validator.ts → dist/validator/sdk-validator.js} +273 -330
- package/dist/validator/sdk-validator.js.map +1 -0
- package/dist/validator/structural-validator.d.ts +11 -0
- package/dist/validator/structural-validator.d.ts.map +1 -0
- package/dist/validator/structural-validator.js +549 -0
- package/dist/validator/structural-validator.js.map +1 -0
- package/dist/validator/utils.d.ts +51 -0
- package/dist/validator/utils.d.ts.map +1 -0
- package/dist/validator/utils.js +132 -0
- package/dist/validator/utils.js.map +1 -0
- package/package.json +44 -12
- package/CLAUDE.md +0 -109
- package/api/analyze-feed.js +0 -140
- package/api/badge.js +0 -185
- package/api/benchmark.js +0 -177
- package/api/directory-stats.ts +0 -29
- package/api/directory.ts +0 -73
- package/api/generate-compliance.js +0 -143
- package/api/generate-schema.js +0 -457
- package/api/generate.js +0 -132
- package/api/security-scan.js +0 -133
- package/api/simulate.js +0 -187
- package/api/tsconfig.json +0 -10
- package/api/validate.js +0 -1351
- package/apify-actor/.actor/actor.json +0 -68
- package/apify-actor/.actor/input_schema.json +0 -32
- package/apify-actor/APIFY-STORE-LISTING.md +0 -412
- package/apify-actor/Dockerfile +0 -8
- package/apify-actor/README.md +0 -166
- package/apify-actor/main.ts +0 -111
- package/apify-actor/package.json +0 -17
- package/apify-actor/src/main.js +0 -199
- package/docs/BRAND-IDENTITY.md +0 -238
- package/docs/BRAND-STYLE-GUIDE.md +0 -356
- package/drizzle/0000_black_king_cobra.sql +0 -39
- package/drizzle/meta/0000_snapshot.json +0 -309
- package/drizzle/meta/_journal.json +0 -13
- package/drizzle.config.ts +0 -10
- package/public/.well-known/ucp +0 -25
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/brand.css +0 -321
- package/public/directory.html +0 -701
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/guides/bigcommerce.html +0 -743
- package/public/guides/fastucp.html +0 -838
- package/public/guides/magento.html +0 -779
- package/public/guides/shopify.html +0 -726
- package/public/guides/squarespace.html +0 -749
- package/public/guides/wix.html +0 -747
- package/public/guides/woocommerce.html +0 -733
- package/public/index.html +0 -3835
- package/public/learn.html +0 -396
- package/public/logo.jpeg +0 -0
- package/public/og-image-icon.png +0 -0
- package/public/og-image.png +0 -0
- package/public/robots.txt +0 -6
- package/public/site.webmanifest +0 -31
- package/public/sitemap.xml +0 -69
- package/public/social/linkedin-banner-1128x191.png +0 -0
- package/public/social/temp.PNG +0 -0
- package/public/social/x-header-1500x500.png +0 -0
- package/public/verify.html +0 -410
- package/scripts/generate-favicons.js +0 -44
- package/scripts/generate-ico.js +0 -23
- package/scripts/generate-og-image.js +0 -45
- package/scripts/reset-db.ts +0 -77
- package/scripts/seed-db.ts +0 -71
- package/scripts/setup-benchmark-db.js +0 -70
- package/src/api/server.ts +0 -266
- package/src/cli/index.ts +0 -302
- package/src/compliance/compliance-generator.ts +0 -452
- package/src/compliance/index.ts +0 -28
- package/src/compliance/types.ts +0 -170
- package/src/db/index.ts +0 -28
- package/src/db/schema.ts +0 -84
- package/src/feed-analyzer/index.ts +0 -34
- package/src/feed-analyzer/types.ts +0 -354
- package/src/generator/key-generator.ts +0 -124
- package/src/generator/profile-builder.ts +0 -402
- package/src/index.ts +0 -105
- package/src/security/security-scanner.ts +0 -604
- package/src/security/types.ts +0 -55
- package/src/services/directory.ts +0 -434
- package/src/simulator/agent-simulator.ts +0 -941
- package/src/types/generator.ts +0 -140
- package/src/types/ucp-profile.ts +0 -140
- package/src/types/validation.ts +0 -89
- package/src/validator/index.ts +0 -194
- package/src/validator/network-validator.ts +0 -417
- package/src/validator/rules-validator.ts +0 -297
- package/src/validator/structural-validator.ts +0 -476
- package/tests/fixtures/non-compliant-profile.json +0 -25
- package/tests/fixtures/official-sample-profile.json +0 -75
- package/tests/integration/benchmark.test.ts +0 -207
- package/tests/integration/database.test.ts +0 -163
- package/tests/integration/directory-api.test.ts +0 -268
- package/tests/integration/simulate-api.test.ts +0 -230
- package/tests/integration/validate-api.test.ts +0 -269
- package/tests/setup.ts +0 -15
- package/tests/unit/agent-simulator.test.ts +0 -575
- package/tests/unit/compliance-generator.test.ts +0 -374
- package/tests/unit/directory-service.test.ts +0 -272
- package/tests/unit/feed-analyzer.test.ts +0 -517
- package/tests/unit/lint-suggestions.test.ts +0 -423
- package/tests/unit/official-samples.test.ts +0 -211
- package/tests/unit/pdf-report.test.ts +0 -390
- package/tests/unit/sdk-validator.test.ts +0 -531
- package/tests/unit/security-scanner.test.ts +0 -410
- package/tests/unit/validation.test.ts +0 -390
- package/tsconfig.json +0 -20
- package/vercel.json +0 -34
- package/vitest.config.ts +0 -22
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Security Posture Scanner for UCP Endpoints
|
|
4
|
+
* Scans UCP endpoints for common security misconfigurations
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.scanEndpointSecurity = scanEndpointSecurity;
|
|
8
|
+
const types_js_1 = require("./types.js");
|
|
9
|
+
const DEFAULT_TIMEOUT_MS = 15000;
|
|
10
|
+
/**
|
|
11
|
+
* Run a full security scan on a UCP endpoint
|
|
12
|
+
*/
|
|
13
|
+
async function scanEndpointSecurity(domain, options = {}) {
|
|
14
|
+
const timeoutMs = options.timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
15
|
+
const checks = [];
|
|
16
|
+
// Normalize domain
|
|
17
|
+
const normalizedDomain = domain.replace(/^https?:\/\//, '').replace(/\/.*$/, '');
|
|
18
|
+
const endpoint = `https://${normalizedDomain}/.well-known/ucp`;
|
|
19
|
+
// Run all security checks
|
|
20
|
+
checks.push(await checkHttpsRequired(normalizedDomain));
|
|
21
|
+
checks.push(await checkPrivateIp(normalizedDomain));
|
|
22
|
+
// Fetch the endpoint to analyze response
|
|
23
|
+
const response = await fetchEndpointSafely(endpoint, timeoutMs);
|
|
24
|
+
if (response.success && response.response && response.headers && response.body !== undefined) {
|
|
25
|
+
checks.push(await checkTlsVersion(endpoint, response.response));
|
|
26
|
+
checks.push(await checkCorsConfiguration(response.response, response.headers));
|
|
27
|
+
checks.push(await checkSecurityHeaders(response.headers));
|
|
28
|
+
checks.push(await checkContentType(response.headers));
|
|
29
|
+
checks.push(await checkRateLimiting(response.headers));
|
|
30
|
+
checks.push(await checkCacheHeaders(response.headers));
|
|
31
|
+
checks.push(await checkErrorDisclosure(response.body));
|
|
32
|
+
checks.push(checkResponseTime(response.responseTimeMs));
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// Endpoint not reachable - add skip results
|
|
36
|
+
checks.push(createSkippedCheck(types_js_1.SecurityCheckIds.TLS_VERSION, 'TLS Version', 'Endpoint not reachable'));
|
|
37
|
+
checks.push(createSkippedCheck(types_js_1.SecurityCheckIds.CORS_CONFIG, 'CORS Configuration', 'Endpoint not reachable'));
|
|
38
|
+
checks.push(createSkippedCheck(types_js_1.SecurityCheckIds.SECURITY_HEADERS, 'Security Headers', 'Endpoint not reachable'));
|
|
39
|
+
checks.push(createSkippedCheck(types_js_1.SecurityCheckIds.CONTENT_TYPE, 'Content-Type', 'Endpoint not reachable'));
|
|
40
|
+
checks.push(createSkippedCheck(types_js_1.SecurityCheckIds.RATE_LIMITING, 'Rate Limiting', 'Endpoint not reachable'));
|
|
41
|
+
checks.push(createSkippedCheck(types_js_1.SecurityCheckIds.CACHE_HEADERS, 'Cache Headers', 'Endpoint not reachable'));
|
|
42
|
+
checks.push(createSkippedCheck(types_js_1.SecurityCheckIds.ERROR_DISCLOSURE, 'Error Disclosure', 'Endpoint not reachable'));
|
|
43
|
+
checks.push(createSkippedCheck(types_js_1.SecurityCheckIds.RESPONSE_TIME, 'Response Time', 'Endpoint not reachable'));
|
|
44
|
+
}
|
|
45
|
+
// Calculate score breakdown (additive model)
|
|
46
|
+
const score_breakdown = calculateScoreBreakdown(checks);
|
|
47
|
+
const summary = calculateSummary(checks);
|
|
48
|
+
const score = score_breakdown.total; // Use additive score as primary
|
|
49
|
+
const grade = calculateGrade(score);
|
|
50
|
+
return {
|
|
51
|
+
domain: normalizedDomain,
|
|
52
|
+
endpoint,
|
|
53
|
+
scanned_at: new Date().toISOString(),
|
|
54
|
+
score,
|
|
55
|
+
grade,
|
|
56
|
+
score_breakdown,
|
|
57
|
+
checks,
|
|
58
|
+
summary,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Fetch endpoint safely with timeout
|
|
63
|
+
*/
|
|
64
|
+
async function fetchEndpointSafely(url, timeoutMs) {
|
|
65
|
+
const controller = new AbortController();
|
|
66
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
67
|
+
const startTime = Date.now();
|
|
68
|
+
try {
|
|
69
|
+
const response = await fetch(url, {
|
|
70
|
+
signal: controller.signal,
|
|
71
|
+
headers: {
|
|
72
|
+
'Accept': 'application/json',
|
|
73
|
+
'User-Agent': 'UCP-Security-Scanner/1.0',
|
|
74
|
+
'Origin': 'https://ucptools.dev',
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
clearTimeout(timeoutId);
|
|
78
|
+
const responseTimeMs = Date.now() - startTime;
|
|
79
|
+
const body = await response.text();
|
|
80
|
+
return {
|
|
81
|
+
success: true,
|
|
82
|
+
response,
|
|
83
|
+
headers: response.headers,
|
|
84
|
+
body,
|
|
85
|
+
responseTimeMs,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
clearTimeout(timeoutId);
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check 1: HTTPS Required
|
|
98
|
+
*/
|
|
99
|
+
async function checkHttpsRequired(domain) {
|
|
100
|
+
// Try HTTP to see if it redirects or is accessible
|
|
101
|
+
try {
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
104
|
+
const response = await fetch(`http://${domain}/.well-known/ucp`, {
|
|
105
|
+
signal: controller.signal,
|
|
106
|
+
redirect: 'manual',
|
|
107
|
+
headers: { 'User-Agent': 'UCP-Security-Scanner/1.0' },
|
|
108
|
+
});
|
|
109
|
+
clearTimeout(timeoutId);
|
|
110
|
+
// Check if HTTP redirects to HTTPS
|
|
111
|
+
if (response.status >= 300 && response.status < 400) {
|
|
112
|
+
const location = response.headers.get('location');
|
|
113
|
+
if (location?.startsWith('https://')) {
|
|
114
|
+
return {
|
|
115
|
+
id: types_js_1.SecurityCheckIds.HTTPS_REQUIRED,
|
|
116
|
+
name: 'HTTPS Enforcement',
|
|
117
|
+
description: 'HTTP requests should redirect to HTTPS',
|
|
118
|
+
status: 'pass',
|
|
119
|
+
severity: 'critical',
|
|
120
|
+
details: 'HTTP correctly redirects to HTTPS',
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// HTTP is accessible without redirect
|
|
125
|
+
if (response.ok) {
|
|
126
|
+
return {
|
|
127
|
+
id: types_js_1.SecurityCheckIds.HTTPS_REQUIRED,
|
|
128
|
+
name: 'HTTPS Enforcement',
|
|
129
|
+
description: 'HTTP requests should redirect to HTTPS',
|
|
130
|
+
status: 'fail',
|
|
131
|
+
severity: 'critical',
|
|
132
|
+
details: 'HTTP endpoint is accessible without redirect to HTTPS',
|
|
133
|
+
recommendation: 'Configure your server to redirect all HTTP traffic to HTTPS',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// HTTP returns error (good - means HTTPS only)
|
|
137
|
+
return {
|
|
138
|
+
id: types_js_1.SecurityCheckIds.HTTPS_REQUIRED,
|
|
139
|
+
name: 'HTTPS Enforcement',
|
|
140
|
+
description: 'HTTP requests should redirect to HTTPS',
|
|
141
|
+
status: 'pass',
|
|
142
|
+
severity: 'critical',
|
|
143
|
+
details: 'HTTP endpoint not accessible (HTTPS only)',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// HTTP connection failed - HTTPS only
|
|
148
|
+
return {
|
|
149
|
+
id: types_js_1.SecurityCheckIds.HTTPS_REQUIRED,
|
|
150
|
+
name: 'HTTPS Enforcement',
|
|
151
|
+
description: 'HTTP requests should redirect to HTTPS',
|
|
152
|
+
status: 'pass',
|
|
153
|
+
severity: 'critical',
|
|
154
|
+
details: 'HTTP endpoint not accessible (HTTPS only)',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Check 2: Private IP Detection
|
|
160
|
+
*/
|
|
161
|
+
async function checkPrivateIp(domain) {
|
|
162
|
+
// Check if domain looks like a private IP or localhost
|
|
163
|
+
const privatePatterns = [
|
|
164
|
+
/^localhost$/i,
|
|
165
|
+
/^127\.\d+\.\d+\.\d+$/,
|
|
166
|
+
/^10\.\d+\.\d+\.\d+$/,
|
|
167
|
+
/^172\.(1[6-9]|2\d|3[01])\.\d+\.\d+$/,
|
|
168
|
+
/^192\.168\.\d+\.\d+$/,
|
|
169
|
+
/^::1$/,
|
|
170
|
+
/^fc00:/i,
|
|
171
|
+
/^fd00:/i,
|
|
172
|
+
];
|
|
173
|
+
const isPrivate = privatePatterns.some(pattern => pattern.test(domain));
|
|
174
|
+
if (isPrivate) {
|
|
175
|
+
return {
|
|
176
|
+
id: types_js_1.SecurityCheckIds.PRIVATE_IP,
|
|
177
|
+
name: 'Private IP Detection',
|
|
178
|
+
description: 'Endpoint should not use private/internal IP addresses',
|
|
179
|
+
status: 'fail',
|
|
180
|
+
severity: 'high',
|
|
181
|
+
details: `Domain "${domain}" appears to be a private or internal address`,
|
|
182
|
+
recommendation: 'Use a public domain name for production UCP endpoints',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
id: types_js_1.SecurityCheckIds.PRIVATE_IP,
|
|
187
|
+
name: 'Private IP Detection',
|
|
188
|
+
description: 'Endpoint should not use private/internal IP addresses',
|
|
189
|
+
status: 'pass',
|
|
190
|
+
severity: 'high',
|
|
191
|
+
details: 'Domain is publicly routable',
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Check 3: TLS Version
|
|
196
|
+
*/
|
|
197
|
+
async function checkTlsVersion(_url, _response) {
|
|
198
|
+
// Note: Browser fetch doesn't expose TLS version directly
|
|
199
|
+
// We can only verify HTTPS is working
|
|
200
|
+
return {
|
|
201
|
+
id: types_js_1.SecurityCheckIds.TLS_VERSION,
|
|
202
|
+
name: 'TLS Configuration',
|
|
203
|
+
description: 'Endpoint should use TLS 1.2 or higher',
|
|
204
|
+
status: 'pass',
|
|
205
|
+
severity: 'high',
|
|
206
|
+
details: 'HTTPS connection successful (TLS version not directly verifiable from browser)',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Check 4: CORS Configuration
|
|
211
|
+
*/
|
|
212
|
+
async function checkCorsConfiguration(_response, headers) {
|
|
213
|
+
const acao = headers.get('access-control-allow-origin');
|
|
214
|
+
const acam = headers.get('access-control-allow-methods');
|
|
215
|
+
const acah = headers.get('access-control-allow-headers');
|
|
216
|
+
if (!acao) {
|
|
217
|
+
return {
|
|
218
|
+
id: types_js_1.SecurityCheckIds.CORS_CONFIG,
|
|
219
|
+
name: 'CORS Configuration',
|
|
220
|
+
description: 'CORS should be properly configured for AI agent access',
|
|
221
|
+
status: 'warn',
|
|
222
|
+
severity: 'medium',
|
|
223
|
+
details: 'No Access-Control-Allow-Origin header found',
|
|
224
|
+
recommendation: 'Configure CORS headers to allow AI agents to access your UCP endpoint',
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
// Check if CORS is too permissive
|
|
228
|
+
if (acao === '*') {
|
|
229
|
+
return {
|
|
230
|
+
id: types_js_1.SecurityCheckIds.CORS_CONFIG,
|
|
231
|
+
name: 'CORS Configuration',
|
|
232
|
+
description: 'CORS should be properly configured for AI agent access',
|
|
233
|
+
status: 'warn',
|
|
234
|
+
severity: 'low',
|
|
235
|
+
details: 'CORS allows all origins (*). Consider restricting to known AI agent domains.',
|
|
236
|
+
recommendation: 'For production, consider restricting CORS to specific trusted origins',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const hasGoodConfig = acao && (acam || acah);
|
|
240
|
+
return {
|
|
241
|
+
id: types_js_1.SecurityCheckIds.CORS_CONFIG,
|
|
242
|
+
name: 'CORS Configuration',
|
|
243
|
+
description: 'CORS should be properly configured for AI agent access',
|
|
244
|
+
status: hasGoodConfig ? 'pass' : 'warn',
|
|
245
|
+
severity: 'medium',
|
|
246
|
+
details: `CORS configured: Origin=${acao}${acam ? `, Methods=${acam}` : ''}`,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Check 5: Security Headers
|
|
251
|
+
*/
|
|
252
|
+
async function checkSecurityHeaders(headers) {
|
|
253
|
+
const securityHeaders = {
|
|
254
|
+
'x-content-type-options': headers.get('x-content-type-options'),
|
|
255
|
+
'x-frame-options': headers.get('x-frame-options'),
|
|
256
|
+
'x-xss-protection': headers.get('x-xss-protection'),
|
|
257
|
+
'strict-transport-security': headers.get('strict-transport-security'),
|
|
258
|
+
'content-security-policy': headers.get('content-security-policy'),
|
|
259
|
+
};
|
|
260
|
+
const presentHeaders = [];
|
|
261
|
+
const missingHeaders = [];
|
|
262
|
+
// Check important headers
|
|
263
|
+
if (securityHeaders['strict-transport-security']) {
|
|
264
|
+
presentHeaders.push('HSTS');
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
missingHeaders.push('HSTS');
|
|
268
|
+
}
|
|
269
|
+
if (securityHeaders['x-content-type-options']) {
|
|
270
|
+
presentHeaders.push('X-Content-Type-Options');
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
missingHeaders.push('X-Content-Type-Options');
|
|
274
|
+
}
|
|
275
|
+
if (securityHeaders['x-frame-options']) {
|
|
276
|
+
presentHeaders.push('X-Frame-Options');
|
|
277
|
+
}
|
|
278
|
+
// Calculate status based on critical headers
|
|
279
|
+
const hasHsts = !!securityHeaders['strict-transport-security'];
|
|
280
|
+
const hasXcto = !!securityHeaders['x-content-type-options'];
|
|
281
|
+
let status = 'pass';
|
|
282
|
+
let severity = 'medium';
|
|
283
|
+
if (!hasHsts && !hasXcto) {
|
|
284
|
+
status = 'fail';
|
|
285
|
+
severity = 'medium';
|
|
286
|
+
}
|
|
287
|
+
else if (!hasHsts || !hasXcto) {
|
|
288
|
+
status = 'warn';
|
|
289
|
+
severity = 'low';
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
id: types_js_1.SecurityCheckIds.SECURITY_HEADERS,
|
|
293
|
+
name: 'Security Headers',
|
|
294
|
+
description: 'Response should include security headers (HSTS, X-Content-Type-Options)',
|
|
295
|
+
status,
|
|
296
|
+
severity,
|
|
297
|
+
details: presentHeaders.length > 0
|
|
298
|
+
? `Present: ${presentHeaders.join(', ')}${missingHeaders.length > 0 ? `. Missing: ${missingHeaders.join(', ')}` : ''}`
|
|
299
|
+
: 'No security headers found',
|
|
300
|
+
recommendation: missingHeaders.length > 0
|
|
301
|
+
? `Add missing security headers: ${missingHeaders.join(', ')}`
|
|
302
|
+
: undefined,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Check 6: Content-Type
|
|
307
|
+
*/
|
|
308
|
+
async function checkContentType(headers) {
|
|
309
|
+
const contentType = headers.get('content-type');
|
|
310
|
+
if (!contentType) {
|
|
311
|
+
return {
|
|
312
|
+
id: types_js_1.SecurityCheckIds.CONTENT_TYPE,
|
|
313
|
+
name: 'Content-Type Header',
|
|
314
|
+
description: 'Response should have correct Content-Type for JSON',
|
|
315
|
+
status: 'warn',
|
|
316
|
+
severity: 'low',
|
|
317
|
+
details: 'No Content-Type header found',
|
|
318
|
+
recommendation: 'Set Content-Type: application/json for UCP endpoints',
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const isJson = contentType.includes('application/json');
|
|
322
|
+
return {
|
|
323
|
+
id: types_js_1.SecurityCheckIds.CONTENT_TYPE,
|
|
324
|
+
name: 'Content-Type Header',
|
|
325
|
+
description: 'Response should have correct Content-Type for JSON',
|
|
326
|
+
status: isJson ? 'pass' : 'warn',
|
|
327
|
+
severity: 'low',
|
|
328
|
+
details: `Content-Type: ${contentType}`,
|
|
329
|
+
recommendation: isJson ? undefined : 'Set Content-Type: application/json for UCP endpoints',
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Check 7: Rate Limiting
|
|
334
|
+
*/
|
|
335
|
+
async function checkRateLimiting(headers) {
|
|
336
|
+
// Look for common rate limiting headers
|
|
337
|
+
const rateLimitHeaders = {
|
|
338
|
+
'x-ratelimit-limit': headers.get('x-ratelimit-limit'),
|
|
339
|
+
'x-ratelimit-remaining': headers.get('x-ratelimit-remaining'),
|
|
340
|
+
'x-rate-limit-limit': headers.get('x-rate-limit-limit'),
|
|
341
|
+
'ratelimit-limit': headers.get('ratelimit-limit'),
|
|
342
|
+
'retry-after': headers.get('retry-after'),
|
|
343
|
+
};
|
|
344
|
+
const hasRateLimiting = Object.values(rateLimitHeaders).some(v => v !== null);
|
|
345
|
+
if (hasRateLimiting) {
|
|
346
|
+
const limitValue = rateLimitHeaders['x-ratelimit-limit'] ||
|
|
347
|
+
rateLimitHeaders['x-rate-limit-limit'] ||
|
|
348
|
+
rateLimitHeaders['ratelimit-limit'];
|
|
349
|
+
return {
|
|
350
|
+
id: types_js_1.SecurityCheckIds.RATE_LIMITING,
|
|
351
|
+
name: 'Rate Limiting',
|
|
352
|
+
description: 'Endpoint should have rate limiting to prevent abuse',
|
|
353
|
+
status: 'pass',
|
|
354
|
+
severity: 'high',
|
|
355
|
+
details: `Rate limiting detected${limitValue ? `: ${limitValue} requests` : ''}`,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
id: types_js_1.SecurityCheckIds.RATE_LIMITING,
|
|
360
|
+
name: 'Rate Limiting',
|
|
361
|
+
description: 'Endpoint should have rate limiting to prevent abuse',
|
|
362
|
+
status: 'warn',
|
|
363
|
+
severity: 'high',
|
|
364
|
+
details: 'No rate limiting headers detected (may still be present server-side)',
|
|
365
|
+
recommendation: 'Implement rate limiting to protect against abuse and DoS attacks',
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Check 8: Cache Headers
|
|
370
|
+
*/
|
|
371
|
+
async function checkCacheHeaders(headers) {
|
|
372
|
+
const cacheControl = headers.get('cache-control');
|
|
373
|
+
const etag = headers.get('etag');
|
|
374
|
+
const lastModified = headers.get('last-modified');
|
|
375
|
+
const hasCaching = cacheControl || etag || lastModified;
|
|
376
|
+
if (!hasCaching) {
|
|
377
|
+
return {
|
|
378
|
+
id: types_js_1.SecurityCheckIds.CACHE_HEADERS,
|
|
379
|
+
name: 'Cache Headers',
|
|
380
|
+
description: 'Response should have appropriate caching headers',
|
|
381
|
+
status: 'pass',
|
|
382
|
+
severity: 'info',
|
|
383
|
+
details: 'No caching headers found (optional)',
|
|
384
|
+
recommendation: 'Consider adding Cache-Control headers for better performance',
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const details = [];
|
|
388
|
+
if (cacheControl)
|
|
389
|
+
details.push(`Cache-Control: ${cacheControl}`);
|
|
390
|
+
if (etag)
|
|
391
|
+
details.push('ETag present');
|
|
392
|
+
if (lastModified)
|
|
393
|
+
details.push('Last-Modified present');
|
|
394
|
+
return {
|
|
395
|
+
id: types_js_1.SecurityCheckIds.CACHE_HEADERS,
|
|
396
|
+
name: 'Cache Headers',
|
|
397
|
+
description: 'Response should have appropriate caching headers',
|
|
398
|
+
status: 'pass',
|
|
399
|
+
severity: 'info',
|
|
400
|
+
details: details.join(', '),
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Check 9: Error Disclosure
|
|
405
|
+
*/
|
|
406
|
+
async function checkErrorDisclosure(body) {
|
|
407
|
+
// Look for signs of stack traces or internal error details
|
|
408
|
+
const sensitivePatterns = [
|
|
409
|
+
/stack\s*trace/i,
|
|
410
|
+
/at\s+\w+\s+\([^)]+:\d+:\d+\)/, // Stack trace lines
|
|
411
|
+
/exception|error.*at\s+line/i,
|
|
412
|
+
/mysql|postgresql|mongodb|redis/i, // Database names in errors
|
|
413
|
+
/\/home\/|\/var\/|\/usr\/|C:\\|D:\\/i, // File paths
|
|
414
|
+
/password|secret|api[_-]?key/i,
|
|
415
|
+
];
|
|
416
|
+
const foundPatterns = [];
|
|
417
|
+
for (const pattern of sensitivePatterns) {
|
|
418
|
+
if (pattern.test(body)) {
|
|
419
|
+
const match = body.match(pattern);
|
|
420
|
+
if (match) {
|
|
421
|
+
foundPatterns.push(match[0].substring(0, 30));
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (foundPatterns.length > 0) {
|
|
426
|
+
return {
|
|
427
|
+
id: types_js_1.SecurityCheckIds.ERROR_DISCLOSURE,
|
|
428
|
+
name: 'Error Information Disclosure',
|
|
429
|
+
description: 'Response should not expose internal error details or stack traces',
|
|
430
|
+
status: 'warn',
|
|
431
|
+
severity: 'medium',
|
|
432
|
+
details: `Potentially sensitive information found: ${foundPatterns.join(', ')}`,
|
|
433
|
+
recommendation: 'Ensure production responses do not expose stack traces or internal paths',
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
id: types_js_1.SecurityCheckIds.ERROR_DISCLOSURE,
|
|
438
|
+
name: 'Error Information Disclosure',
|
|
439
|
+
description: 'Response should not expose internal error details or stack traces',
|
|
440
|
+
status: 'pass',
|
|
441
|
+
severity: 'medium',
|
|
442
|
+
details: 'No sensitive error information detected',
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Check 10: Response Time
|
|
447
|
+
*/
|
|
448
|
+
function checkResponseTime(responseTimeMs) {
|
|
449
|
+
if (!responseTimeMs) {
|
|
450
|
+
return createSkippedCheck(types_js_1.SecurityCheckIds.RESPONSE_TIME, 'Response Time', 'Could not measure response time');
|
|
451
|
+
}
|
|
452
|
+
let status = 'pass';
|
|
453
|
+
let severity = 'low';
|
|
454
|
+
let recommendation;
|
|
455
|
+
if (responseTimeMs > 5000) {
|
|
456
|
+
status = 'fail';
|
|
457
|
+
severity = 'medium';
|
|
458
|
+
recommendation = 'Response time is very slow. Consider optimizing your endpoint or using a CDN.';
|
|
459
|
+
}
|
|
460
|
+
else if (responseTimeMs > 2000) {
|
|
461
|
+
status = 'warn';
|
|
462
|
+
severity = 'low';
|
|
463
|
+
recommendation = 'Response time is slow. Consider optimizing or using caching.';
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
id: types_js_1.SecurityCheckIds.RESPONSE_TIME,
|
|
467
|
+
name: 'Response Time',
|
|
468
|
+
description: 'Endpoint should respond quickly to prevent timeouts',
|
|
469
|
+
status,
|
|
470
|
+
severity,
|
|
471
|
+
details: `Response time: ${responseTimeMs}ms`,
|
|
472
|
+
recommendation,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Create a skipped check result
|
|
477
|
+
*/
|
|
478
|
+
function createSkippedCheck(id, name, reason) {
|
|
479
|
+
return {
|
|
480
|
+
id,
|
|
481
|
+
name,
|
|
482
|
+
description: reason,
|
|
483
|
+
status: 'skip',
|
|
484
|
+
severity: 'info',
|
|
485
|
+
details: `Skipped: ${reason}`,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Calculate summary from checks
|
|
490
|
+
*/
|
|
491
|
+
function calculateSummary(checks) {
|
|
492
|
+
return {
|
|
493
|
+
passed: checks.filter(c => c.status === 'pass').length,
|
|
494
|
+
failed: checks.filter(c => c.status === 'fail').length,
|
|
495
|
+
warnings: checks.filter(c => c.status === 'warn').length,
|
|
496
|
+
skipped: checks.filter(c => c.status === 'skip').length,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Calculate security score (0-100)
|
|
501
|
+
*/
|
|
502
|
+
function calculateScore(checks) {
|
|
503
|
+
const weights = {
|
|
504
|
+
critical: 25,
|
|
505
|
+
high: 20,
|
|
506
|
+
medium: 15,
|
|
507
|
+
low: 10,
|
|
508
|
+
info: 5,
|
|
509
|
+
};
|
|
510
|
+
let totalWeight = 0;
|
|
511
|
+
let earnedPoints = 0;
|
|
512
|
+
for (const check of checks) {
|
|
513
|
+
if (check.status === 'skip')
|
|
514
|
+
continue;
|
|
515
|
+
const weight = weights[check.severity];
|
|
516
|
+
totalWeight += weight;
|
|
517
|
+
if (check.status === 'pass') {
|
|
518
|
+
earnedPoints += weight;
|
|
519
|
+
}
|
|
520
|
+
else if (check.status === 'warn') {
|
|
521
|
+
earnedPoints += weight * 0.5;
|
|
522
|
+
}
|
|
523
|
+
// 'fail' gets 0 points
|
|
524
|
+
}
|
|
525
|
+
if (totalWeight === 0)
|
|
526
|
+
return 0;
|
|
527
|
+
return Math.round((earnedPoints / totalWeight) * 100);
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Calculate grade from score
|
|
531
|
+
*/
|
|
532
|
+
function calculateGrade(score) {
|
|
533
|
+
if (score >= 90)
|
|
534
|
+
return 'A';
|
|
535
|
+
if (score >= 80)
|
|
536
|
+
return 'B';
|
|
537
|
+
if (score >= 70)
|
|
538
|
+
return 'C';
|
|
539
|
+
if (score >= 60)
|
|
540
|
+
return 'D';
|
|
541
|
+
return 'F';
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Category configuration for additive scoring
|
|
545
|
+
*/
|
|
546
|
+
const CATEGORY_CONFIG = {
|
|
547
|
+
// Transport Security - 30 pts
|
|
548
|
+
[types_js_1.SecurityCheckIds.HTTPS_REQUIRED]: { category: 'transport', maxScore: 15 },
|
|
549
|
+
[types_js_1.SecurityCheckIds.TLS_VERSION]: { category: 'transport', maxScore: 10 },
|
|
550
|
+
[types_js_1.SecurityCheckIds.PRIVATE_IP]: { category: 'transport', maxScore: 5 },
|
|
551
|
+
// Headers - 25 pts
|
|
552
|
+
[types_js_1.SecurityCheckIds.SECURITY_HEADERS]: { category: 'headers', maxScore: 20 },
|
|
553
|
+
[types_js_1.SecurityCheckIds.CACHE_HEADERS]: { category: 'headers', maxScore: 5 },
|
|
554
|
+
// API Protection - 25 pts
|
|
555
|
+
[types_js_1.SecurityCheckIds.RATE_LIMITING]: { category: 'api_protection', maxScore: 15 },
|
|
556
|
+
[types_js_1.SecurityCheckIds.CORS_CONFIG]: { category: 'api_protection', maxScore: 10 },
|
|
557
|
+
// Data Safety - 10 pts
|
|
558
|
+
[types_js_1.SecurityCheckIds.CONTENT_TYPE]: { category: 'data_safety', maxScore: 5 },
|
|
559
|
+
[types_js_1.SecurityCheckIds.ERROR_DISCLOSURE]: { category: 'data_safety', maxScore: 5 },
|
|
560
|
+
// Performance - 10 pts
|
|
561
|
+
[types_js_1.SecurityCheckIds.RESPONSE_TIME]: { category: 'performance', maxScore: 10 },
|
|
562
|
+
};
|
|
563
|
+
/**
|
|
564
|
+
* Generate status message for a category
|
|
565
|
+
*/
|
|
566
|
+
function generateCategoryStatus(categoryName, score, maxScore) {
|
|
567
|
+
const percent = maxScore > 0 ? Math.round((score / maxScore) * 100) : 0;
|
|
568
|
+
const statusMessages = {
|
|
569
|
+
transport: {
|
|
570
|
+
excellent: 'Strong transport security with HTTPS enforcement',
|
|
571
|
+
good: 'Good transport security, minor improvements possible',
|
|
572
|
+
partial: 'Basic transport security, improvements recommended',
|
|
573
|
+
missing: 'Transport security needs attention',
|
|
574
|
+
},
|
|
575
|
+
headers: {
|
|
576
|
+
excellent: 'Comprehensive security headers configured',
|
|
577
|
+
good: 'Good header configuration with room for improvement',
|
|
578
|
+
partial: 'Some security headers present',
|
|
579
|
+
missing: 'Security headers need configuration',
|
|
580
|
+
},
|
|
581
|
+
api_protection: {
|
|
582
|
+
excellent: 'API well-protected against abuse',
|
|
583
|
+
good: 'Good API protection, consider additional measures',
|
|
584
|
+
partial: 'Basic API protection in place',
|
|
585
|
+
missing: 'API protection measures needed',
|
|
586
|
+
},
|
|
587
|
+
data_safety: {
|
|
588
|
+
excellent: 'Data handling follows best practices',
|
|
589
|
+
good: 'Good data safety practices',
|
|
590
|
+
partial: 'Some data safety measures in place',
|
|
591
|
+
missing: 'Data safety needs improvement',
|
|
592
|
+
},
|
|
593
|
+
performance: {
|
|
594
|
+
excellent: 'Excellent response performance',
|
|
595
|
+
good: 'Good response times',
|
|
596
|
+
partial: 'Acceptable performance, optimization possible',
|
|
597
|
+
missing: 'Performance optimization recommended',
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
const messages = statusMessages[categoryName] || {
|
|
601
|
+
excellent: 'Excellent',
|
|
602
|
+
good: 'Good',
|
|
603
|
+
partial: 'Partial',
|
|
604
|
+
missing: 'Needs attention',
|
|
605
|
+
};
|
|
606
|
+
if (percent >= 90)
|
|
607
|
+
return messages.excellent;
|
|
608
|
+
if (percent >= 70)
|
|
609
|
+
return messages.good;
|
|
610
|
+
if (percent >= 40)
|
|
611
|
+
return messages.partial;
|
|
612
|
+
return messages.missing;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Calculate security score breakdown (additive model)
|
|
616
|
+
*/
|
|
617
|
+
function calculateScoreBreakdown(checks) {
|
|
618
|
+
// Initialize categories
|
|
619
|
+
const categories = {
|
|
620
|
+
transport: { score: 0, maxScore: 30, status: '', checks: [] },
|
|
621
|
+
headers: { score: 0, maxScore: 25, status: '', checks: [] },
|
|
622
|
+
api_protection: { score: 0, maxScore: 25, status: '', checks: [] },
|
|
623
|
+
data_safety: { score: 0, maxScore: 10, status: '', checks: [] },
|
|
624
|
+
performance: { score: 0, maxScore: 10, status: '', checks: [] },
|
|
625
|
+
};
|
|
626
|
+
// Process each check
|
|
627
|
+
for (const check of checks) {
|
|
628
|
+
const config = CATEGORY_CONFIG[check.id];
|
|
629
|
+
if (!config)
|
|
630
|
+
continue;
|
|
631
|
+
const category = categories[config.category];
|
|
632
|
+
if (!category)
|
|
633
|
+
continue;
|
|
634
|
+
let earnedScore = 0;
|
|
635
|
+
const passed = check.status === 'pass';
|
|
636
|
+
if (check.status === 'pass') {
|
|
637
|
+
earnedScore = config.maxScore;
|
|
638
|
+
}
|
|
639
|
+
else if (check.status === 'warn') {
|
|
640
|
+
earnedScore = Math.round(config.maxScore * 0.5);
|
|
641
|
+
}
|
|
642
|
+
// 'fail' and 'skip' get 0 points
|
|
643
|
+
category.score += earnedScore;
|
|
644
|
+
category.checks.push({
|
|
645
|
+
id: check.id,
|
|
646
|
+
name: check.name,
|
|
647
|
+
score: earnedScore,
|
|
648
|
+
maxScore: config.maxScore,
|
|
649
|
+
passed,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
// Generate status messages for each category
|
|
653
|
+
for (const [name, category] of Object.entries(categories)) {
|
|
654
|
+
category.status = generateCategoryStatus(name, category.score, category.maxScore);
|
|
655
|
+
}
|
|
656
|
+
// Calculate totals
|
|
657
|
+
const total = Object.values(categories).reduce((sum, cat) => sum + cat.score, 0);
|
|
658
|
+
const maxTotal = Object.values(categories).reduce((sum, cat) => sum + cat.maxScore, 0);
|
|
659
|
+
return {
|
|
660
|
+
transport: categories.transport,
|
|
661
|
+
headers: categories.headers,
|
|
662
|
+
api_protection: categories.api_protection,
|
|
663
|
+
data_safety: categories.data_safety,
|
|
664
|
+
performance: categories.performance,
|
|
665
|
+
total,
|
|
666
|
+
maxTotal,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
//# sourceMappingURL=security-scanner.js.map
|