@ucptools/validator 1.0.0 → 1.0.1

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