@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.
Files changed (330) hide show
  1. package/dist/auth/config.d.ts +20 -0
  2. package/dist/auth/config.d.ts.map +1 -0
  3. package/dist/auth/config.js +114 -0
  4. package/dist/auth/config.js.map +1 -0
  5. package/dist/auth/index.d.ts +5 -0
  6. package/dist/auth/index.d.ts.map +1 -0
  7. package/dist/auth/index.js +17 -0
  8. package/dist/auth/index.js.map +1 -0
  9. package/dist/auth/middleware.d.ts +45 -0
  10. package/dist/auth/middleware.d.ts.map +1 -0
  11. package/dist/auth/middleware.js +170 -0
  12. package/dist/auth/middleware.js.map +1 -0
  13. package/dist/auth/service.d.ts +80 -0
  14. package/dist/auth/service.d.ts.map +1 -0
  15. package/dist/auth/service.js +298 -0
  16. package/dist/auth/service.js.map +1 -0
  17. package/dist/cli/index.d.ts +6 -0
  18. package/dist/cli/index.d.ts.map +1 -0
  19. package/dist/cli/index.js +375 -0
  20. package/dist/cli/index.js.map +1 -0
  21. package/dist/cli/mock-server.d.ts +20 -0
  22. package/dist/cli/mock-server.d.ts.map +1 -0
  23. package/dist/cli/mock-server.js +261 -0
  24. package/dist/cli/mock-server.js.map +1 -0
  25. package/dist/compliance/compliance-generator.d.ts +34 -0
  26. package/dist/compliance/compliance-generator.d.ts.map +1 -0
  27. package/dist/compliance/compliance-generator.js +320 -0
  28. package/dist/compliance/compliance-generator.js.map +1 -0
  29. package/dist/compliance/index.d.ts +8 -0
  30. package/dist/compliance/index.d.ts.map +1 -0
  31. package/dist/compliance/index.js +17 -0
  32. package/dist/compliance/index.js.map +1 -0
  33. package/dist/compliance/templates.d.ts +34 -0
  34. package/dist/compliance/templates.d.ts.map +1 -0
  35. package/{src/compliance/templates.ts → dist/compliance/templates.js} +117 -155
  36. package/dist/compliance/templates.js.map +1 -0
  37. package/dist/compliance/types.d.ts +64 -0
  38. package/dist/compliance/types.d.ts.map +1 -0
  39. package/dist/compliance/types.js +64 -0
  40. package/dist/compliance/types.js.map +1 -0
  41. package/dist/db/index.d.ts +17 -0
  42. package/dist/db/index.d.ts.map +1 -0
  43. package/dist/db/index.js +80 -0
  44. package/dist/db/index.js.map +1 -0
  45. package/dist/db/schema.d.ts +3886 -0
  46. package/dist/db/schema.d.ts.map +1 -0
  47. package/dist/db/schema.js +425 -0
  48. package/dist/db/schema.js.map +1 -0
  49. package/dist/db/utils.d.ts +252 -0
  50. package/dist/db/utils.d.ts.map +1 -0
  51. package/dist/db/utils.js +295 -0
  52. package/dist/db/utils.js.map +1 -0
  53. package/dist/feed-analyzer/feed-analyzer.d.ts +26 -0
  54. package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -0
  55. package/{src/feed-analyzer/feed-analyzer.ts → dist/feed-analyzer/feed-analyzer.js} +856 -726
  56. package/dist/feed-analyzer/feed-analyzer.js.map +1 -0
  57. package/dist/feed-analyzer/index.d.ts +8 -0
  58. package/dist/feed-analyzer/index.d.ts.map +1 -0
  59. package/dist/feed-analyzer/index.js +19 -0
  60. package/dist/feed-analyzer/index.js.map +1 -0
  61. package/dist/feed-analyzer/types.d.ts +285 -0
  62. package/dist/feed-analyzer/types.d.ts.map +1 -0
  63. package/dist/feed-analyzer/types.js +175 -0
  64. package/dist/feed-analyzer/types.js.map +1 -0
  65. package/{src/generator/index.ts → dist/generator/index.d.ts} +1 -1
  66. package/dist/generator/index.d.ts.map +1 -0
  67. package/dist/generator/index.js +13 -0
  68. package/dist/generator/index.js.map +1 -0
  69. package/dist/generator/key-generator.d.ts +24 -0
  70. package/dist/generator/key-generator.d.ts.map +1 -0
  71. package/dist/generator/key-generator.js +144 -0
  72. package/dist/generator/key-generator.js.map +1 -0
  73. package/dist/generator/profile-builder.d.ts +15 -0
  74. package/dist/generator/profile-builder.d.ts.map +1 -0
  75. package/dist/generator/profile-builder.js +338 -0
  76. package/dist/generator/profile-builder.js.map +1 -0
  77. package/dist/hosting/artifacts-generator.d.ts +10 -0
  78. package/dist/hosting/artifacts-generator.d.ts.map +1 -0
  79. package/{src/hosting/artifacts-generator.ts → dist/hosting/artifacts-generator.js} +191 -241
  80. package/dist/hosting/artifacts-generator.js.map +1 -0
  81. package/{src/hosting/index.ts → dist/hosting/index.d.ts} +1 -1
  82. package/dist/hosting/index.d.ts.map +1 -0
  83. package/dist/hosting/index.js +10 -0
  84. package/dist/hosting/index.js.map +1 -0
  85. package/dist/index.d.ts +18 -0
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.js +78 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/lib/analytics.d.ts +337 -0
  90. package/dist/lib/analytics.d.ts.map +1 -0
  91. package/dist/lib/analytics.js +188 -0
  92. package/dist/lib/analytics.js.map +1 -0
  93. package/{src/security/index.ts → dist/security/index.d.ts} +8 -15
  94. package/dist/security/index.d.ts.map +1 -0
  95. package/dist/security/index.js +12 -0
  96. package/dist/security/index.js.map +1 -0
  97. package/dist/security/security-scanner.d.ts +10 -0
  98. package/dist/security/security-scanner.d.ts.map +1 -0
  99. package/dist/security/security-scanner.js +669 -0
  100. package/dist/security/security-scanner.js.map +1 -0
  101. package/dist/security/types.d.ts +80 -0
  102. package/dist/security/types.d.ts.map +1 -0
  103. package/dist/security/types.js +21 -0
  104. package/dist/security/types.js.map +1 -0
  105. package/dist/services/analytics.d.ts +114 -0
  106. package/dist/services/analytics.d.ts.map +1 -0
  107. package/dist/services/analytics.js +862 -0
  108. package/dist/services/analytics.js.map +1 -0
  109. package/dist/services/badge.d.ts +31 -0
  110. package/dist/services/badge.d.ts.map +1 -0
  111. package/dist/services/badge.js +152 -0
  112. package/dist/services/badge.js.map +1 -0
  113. package/dist/services/cron.d.ts +125 -0
  114. package/dist/services/cron.d.ts.map +1 -0
  115. package/dist/services/cron.js +613 -0
  116. package/dist/services/cron.js.map +1 -0
  117. package/dist/services/directory.d.ts +106 -0
  118. package/dist/services/directory.d.ts.map +1 -0
  119. package/dist/services/directory.js +351 -0
  120. package/dist/services/directory.js.map +1 -0
  121. package/dist/services/email.d.ts +112 -0
  122. package/dist/services/email.d.ts.map +1 -0
  123. package/dist/services/email.js +772 -0
  124. package/dist/services/email.js.map +1 -0
  125. package/dist/services/hosted-profiles.d.ts +77 -0
  126. package/dist/services/hosted-profiles.d.ts.map +1 -0
  127. package/dist/services/hosted-profiles.js +433 -0
  128. package/dist/services/hosted-profiles.js.map +1 -0
  129. package/dist/services/latency.d.ts +67 -0
  130. package/dist/services/latency.d.ts.map +1 -0
  131. package/dist/services/latency.js +274 -0
  132. package/dist/services/latency.js.map +1 -0
  133. package/dist/services/manifest-compliance.d.ts +64 -0
  134. package/dist/services/manifest-compliance.d.ts.map +1 -0
  135. package/dist/services/manifest-compliance.js +271 -0
  136. package/dist/services/manifest-compliance.js.map +1 -0
  137. package/dist/services/monitoring-diff.d.ts +31 -0
  138. package/dist/services/monitoring-diff.d.ts.map +1 -0
  139. package/dist/services/monitoring-diff.js +189 -0
  140. package/dist/services/monitoring-diff.js.map +1 -0
  141. package/dist/services/notifications.d.ts +46 -0
  142. package/dist/services/notifications.d.ts.map +1 -0
  143. package/dist/services/notifications.js +88 -0
  144. package/dist/services/notifications.js.map +1 -0
  145. package/dist/services/stripe.d.ts +93 -0
  146. package/dist/services/stripe.d.ts.map +1 -0
  147. package/dist/services/stripe.js +490 -0
  148. package/dist/services/stripe.js.map +1 -0
  149. package/dist/services/validation-history.d.ts +99 -0
  150. package/dist/services/validation-history.d.ts.map +1 -0
  151. package/dist/services/validation-history.js +344 -0
  152. package/dist/services/validation-history.js.map +1 -0
  153. package/dist/services/validation-logging.d.ts +103 -0
  154. package/dist/services/validation-logging.d.ts.map +1 -0
  155. package/dist/services/validation-logging.js +210 -0
  156. package/dist/services/validation-logging.js.map +1 -0
  157. package/dist/services/validation.d.ts +119 -0
  158. package/dist/services/validation.d.ts.map +1 -0
  159. package/dist/services/validation.js +1185 -0
  160. package/dist/services/validation.js.map +1 -0
  161. package/dist/simulator/agent-simulator.d.ts +69 -0
  162. package/dist/simulator/agent-simulator.d.ts.map +1 -0
  163. package/dist/simulator/agent-simulator.js +870 -0
  164. package/dist/simulator/agent-simulator.js.map +1 -0
  165. package/{src/simulator/index.ts → dist/simulator/index.d.ts} +7 -7
  166. package/dist/simulator/index.d.ts.map +1 -0
  167. package/dist/simulator/index.js +23 -0
  168. package/dist/simulator/index.js.map +1 -0
  169. package/{src/simulator/types.ts → dist/simulator/types.d.ts} +171 -170
  170. package/dist/simulator/types.d.ts.map +1 -0
  171. package/dist/simulator/types.js +18 -0
  172. package/dist/simulator/types.js.map +1 -0
  173. package/dist/types/acp-validation.d.ts +87 -0
  174. package/dist/types/acp-validation.d.ts.map +1 -0
  175. package/dist/types/acp-validation.js +40 -0
  176. package/dist/types/acp-validation.js.map +1 -0
  177. package/dist/types/analytics.d.ts +182 -0
  178. package/dist/types/analytics.d.ts.map +1 -0
  179. package/dist/types/analytics.js +7 -0
  180. package/dist/types/analytics.js.map +1 -0
  181. package/dist/types/generator.d.ts +106 -0
  182. package/dist/types/generator.d.ts.map +1 -0
  183. package/dist/types/generator.js +6 -0
  184. package/dist/types/generator.js.map +1 -0
  185. package/{src/types/index.ts → dist/types/index.d.ts} +1 -1
  186. package/dist/types/index.d.ts.map +1 -0
  187. package/dist/types/index.js +23 -0
  188. package/dist/types/index.js.map +1 -0
  189. package/dist/types/ucp-profile.d.ts +111 -0
  190. package/dist/types/ucp-profile.d.ts.map +1 -0
  191. package/dist/types/ucp-profile.js +45 -0
  192. package/dist/types/ucp-profile.js.map +1 -0
  193. package/dist/types/validation.d.ts +76 -0
  194. package/dist/types/validation.d.ts.map +1 -0
  195. package/dist/types/validation.js +42 -0
  196. package/dist/types/validation.js.map +1 -0
  197. package/dist/validator/acp/index.d.ts +31 -0
  198. package/dist/validator/acp/index.d.ts.map +1 -0
  199. package/dist/validator/acp/index.js +574 -0
  200. package/dist/validator/acp/index.js.map +1 -0
  201. package/dist/validator/index.d.ts +26 -0
  202. package/dist/validator/index.d.ts.map +1 -0
  203. package/dist/validator/index.js +161 -0
  204. package/dist/validator/index.js.map +1 -0
  205. package/dist/validator/network-validator.d.ts +28 -0
  206. package/dist/validator/network-validator.d.ts.map +1 -0
  207. package/dist/validator/network-validator.js +319 -0
  208. package/dist/validator/network-validator.js.map +1 -0
  209. package/dist/validator/rules-validator.d.ts +19 -0
  210. package/dist/validator/rules-validator.d.ts.map +1 -0
  211. package/dist/validator/rules-validator.js +306 -0
  212. package/dist/validator/rules-validator.js.map +1 -0
  213. package/dist/validator/sdk-validator.d.ts +58 -0
  214. package/dist/validator/sdk-validator.d.ts.map +1 -0
  215. package/{src/validator/sdk-validator.ts → dist/validator/sdk-validator.js} +273 -330
  216. package/dist/validator/sdk-validator.js.map +1 -0
  217. package/dist/validator/structural-validator.d.ts +11 -0
  218. package/dist/validator/structural-validator.d.ts.map +1 -0
  219. package/dist/validator/structural-validator.js +549 -0
  220. package/dist/validator/structural-validator.js.map +1 -0
  221. package/dist/validator/utils.d.ts +51 -0
  222. package/dist/validator/utils.d.ts.map +1 -0
  223. package/dist/validator/utils.js +132 -0
  224. package/dist/validator/utils.js.map +1 -0
  225. package/package.json +44 -12
  226. package/CLAUDE.md +0 -109
  227. package/api/analyze-feed.js +0 -140
  228. package/api/badge.js +0 -185
  229. package/api/benchmark.js +0 -177
  230. package/api/directory-stats.ts +0 -29
  231. package/api/directory.ts +0 -73
  232. package/api/generate-compliance.js +0 -143
  233. package/api/generate-schema.js +0 -457
  234. package/api/generate.js +0 -132
  235. package/api/security-scan.js +0 -133
  236. package/api/simulate.js +0 -187
  237. package/api/tsconfig.json +0 -10
  238. package/api/validate.js +0 -1351
  239. package/apify-actor/.actor/actor.json +0 -68
  240. package/apify-actor/.actor/input_schema.json +0 -32
  241. package/apify-actor/APIFY-STORE-LISTING.md +0 -412
  242. package/apify-actor/Dockerfile +0 -8
  243. package/apify-actor/README.md +0 -166
  244. package/apify-actor/main.ts +0 -111
  245. package/apify-actor/package.json +0 -17
  246. package/apify-actor/src/main.js +0 -199
  247. package/docs/BRAND-IDENTITY.md +0 -238
  248. package/docs/BRAND-STYLE-GUIDE.md +0 -356
  249. package/drizzle/0000_black_king_cobra.sql +0 -39
  250. package/drizzle/meta/0000_snapshot.json +0 -309
  251. package/drizzle/meta/_journal.json +0 -13
  252. package/drizzle.config.ts +0 -10
  253. package/public/.well-known/ucp +0 -25
  254. package/public/android-chrome-192x192.png +0 -0
  255. package/public/android-chrome-512x512.png +0 -0
  256. package/public/apple-touch-icon.png +0 -0
  257. package/public/brand.css +0 -321
  258. package/public/directory.html +0 -701
  259. package/public/favicon-16x16.png +0 -0
  260. package/public/favicon-32x32.png +0 -0
  261. package/public/favicon.ico +0 -0
  262. package/public/guides/bigcommerce.html +0 -743
  263. package/public/guides/fastucp.html +0 -838
  264. package/public/guides/magento.html +0 -779
  265. package/public/guides/shopify.html +0 -726
  266. package/public/guides/squarespace.html +0 -749
  267. package/public/guides/wix.html +0 -747
  268. package/public/guides/woocommerce.html +0 -733
  269. package/public/index.html +0 -3835
  270. package/public/learn.html +0 -396
  271. package/public/logo.jpeg +0 -0
  272. package/public/og-image-icon.png +0 -0
  273. package/public/og-image.png +0 -0
  274. package/public/robots.txt +0 -6
  275. package/public/site.webmanifest +0 -31
  276. package/public/sitemap.xml +0 -69
  277. package/public/social/linkedin-banner-1128x191.png +0 -0
  278. package/public/social/temp.PNG +0 -0
  279. package/public/social/x-header-1500x500.png +0 -0
  280. package/public/verify.html +0 -410
  281. package/scripts/generate-favicons.js +0 -44
  282. package/scripts/generate-ico.js +0 -23
  283. package/scripts/generate-og-image.js +0 -45
  284. package/scripts/reset-db.ts +0 -77
  285. package/scripts/seed-db.ts +0 -71
  286. package/scripts/setup-benchmark-db.js +0 -70
  287. package/src/api/server.ts +0 -266
  288. package/src/cli/index.ts +0 -302
  289. package/src/compliance/compliance-generator.ts +0 -452
  290. package/src/compliance/index.ts +0 -28
  291. package/src/compliance/types.ts +0 -170
  292. package/src/db/index.ts +0 -28
  293. package/src/db/schema.ts +0 -84
  294. package/src/feed-analyzer/index.ts +0 -34
  295. package/src/feed-analyzer/types.ts +0 -354
  296. package/src/generator/key-generator.ts +0 -124
  297. package/src/generator/profile-builder.ts +0 -402
  298. package/src/index.ts +0 -105
  299. package/src/security/security-scanner.ts +0 -604
  300. package/src/security/types.ts +0 -55
  301. package/src/services/directory.ts +0 -434
  302. package/src/simulator/agent-simulator.ts +0 -941
  303. package/src/types/generator.ts +0 -140
  304. package/src/types/ucp-profile.ts +0 -140
  305. package/src/types/validation.ts +0 -89
  306. package/src/validator/index.ts +0 -194
  307. package/src/validator/network-validator.ts +0 -417
  308. package/src/validator/rules-validator.ts +0 -297
  309. package/src/validator/structural-validator.ts +0 -476
  310. package/tests/fixtures/non-compliant-profile.json +0 -25
  311. package/tests/fixtures/official-sample-profile.json +0 -75
  312. package/tests/integration/benchmark.test.ts +0 -207
  313. package/tests/integration/database.test.ts +0 -163
  314. package/tests/integration/directory-api.test.ts +0 -268
  315. package/tests/integration/simulate-api.test.ts +0 -230
  316. package/tests/integration/validate-api.test.ts +0 -269
  317. package/tests/setup.ts +0 -15
  318. package/tests/unit/agent-simulator.test.ts +0 -575
  319. package/tests/unit/compliance-generator.test.ts +0 -374
  320. package/tests/unit/directory-service.test.ts +0 -272
  321. package/tests/unit/feed-analyzer.test.ts +0 -517
  322. package/tests/unit/lint-suggestions.test.ts +0 -423
  323. package/tests/unit/official-samples.test.ts +0 -211
  324. package/tests/unit/pdf-report.test.ts +0 -390
  325. package/tests/unit/sdk-validator.test.ts +0 -531
  326. package/tests/unit/security-scanner.test.ts +0 -410
  327. package/tests/unit/validation.test.ts +0 -390
  328. package/tsconfig.json +0 -20
  329. package/vercel.json +0 -34
  330. 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