@ucptools/validator 1.0.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/CLAUDE.md +109 -0
- package/CONTRIBUTING.md +113 -0
- package/LICENSE +21 -0
- package/README.md +203 -0
- package/api/analyze-feed.js +140 -0
- package/api/badge.js +185 -0
- package/api/benchmark.js +177 -0
- package/api/directory-stats.ts +29 -0
- package/api/directory.ts +73 -0
- package/api/generate-compliance.js +143 -0
- package/api/generate-schema.js +457 -0
- package/api/generate.js +132 -0
- package/api/security-scan.js +133 -0
- package/api/simulate.js +187 -0
- package/api/tsconfig.json +10 -0
- package/api/validate.js +1351 -0
- package/apify-actor/.actor/actor.json +68 -0
- package/apify-actor/.actor/input_schema.json +32 -0
- package/apify-actor/APIFY-STORE-LISTING.md +412 -0
- package/apify-actor/Dockerfile +8 -0
- package/apify-actor/README.md +166 -0
- package/apify-actor/main.ts +111 -0
- package/apify-actor/package.json +17 -0
- package/apify-actor/src/main.js +199 -0
- package/docs/BRAND-IDENTITY.md +238 -0
- package/docs/BRAND-STYLE-GUIDE.md +356 -0
- package/drizzle/0000_black_king_cobra.sql +39 -0
- package/drizzle/meta/0000_snapshot.json +309 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +10 -0
- package/examples/full-profile.json +70 -0
- package/examples/minimal-profile.json +23 -0
- package/package.json +69 -0
- package/public/.well-known/ucp +25 -0
- 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 +321 -0
- package/public/directory.html +701 -0
- 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 +743 -0
- package/public/guides/fastucp.html +838 -0
- package/public/guides/magento.html +779 -0
- package/public/guides/shopify.html +726 -0
- package/public/guides/squarespace.html +749 -0
- package/public/guides/wix.html +747 -0
- package/public/guides/woocommerce.html +733 -0
- package/public/index.html +3835 -0
- package/public/learn.html +396 -0
- 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 +6 -0
- package/public/site.webmanifest +31 -0
- package/public/sitemap.xml +69 -0
- 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 +410 -0
- package/scripts/generate-favicons.js +44 -0
- package/scripts/generate-ico.js +23 -0
- package/scripts/generate-og-image.js +45 -0
- package/scripts/reset-db.ts +77 -0
- package/scripts/seed-db.ts +71 -0
- package/scripts/setup-benchmark-db.js +70 -0
- package/src/api/server.ts +266 -0
- package/src/cli/index.ts +302 -0
- package/src/compliance/compliance-generator.ts +452 -0
- package/src/compliance/index.ts +28 -0
- package/src/compliance/templates.ts +338 -0
- package/src/compliance/types.ts +170 -0
- package/src/db/index.ts +28 -0
- package/src/db/schema.ts +84 -0
- package/src/feed-analyzer/feed-analyzer.ts +726 -0
- package/src/feed-analyzer/index.ts +34 -0
- package/src/feed-analyzer/types.ts +354 -0
- package/src/generator/index.ts +7 -0
- package/src/generator/key-generator.ts +124 -0
- package/src/generator/profile-builder.ts +402 -0
- package/src/hosting/artifacts-generator.ts +679 -0
- package/src/hosting/index.ts +6 -0
- package/src/index.ts +105 -0
- package/src/security/index.ts +15 -0
- package/src/security/security-scanner.ts +604 -0
- package/src/security/types.ts +55 -0
- package/src/services/directory.ts +434 -0
- package/src/simulator/agent-simulator.ts +941 -0
- package/src/simulator/index.ts +7 -0
- package/src/simulator/types.ts +170 -0
- package/src/types/generator.ts +140 -0
- package/src/types/index.ts +7 -0
- package/src/types/ucp-profile.ts +140 -0
- package/src/types/validation.ts +89 -0
- package/src/validator/index.ts +194 -0
- package/src/validator/network-validator.ts +417 -0
- package/src/validator/rules-validator.ts +297 -0
- package/src/validator/sdk-validator.ts +330 -0
- package/src/validator/structural-validator.ts +476 -0
- package/tests/fixtures/non-compliant-profile.json +25 -0
- package/tests/fixtures/official-sample-profile.json +75 -0
- package/tests/integration/benchmark.test.ts +207 -0
- package/tests/integration/database.test.ts +163 -0
- package/tests/integration/directory-api.test.ts +268 -0
- package/tests/integration/simulate-api.test.ts +230 -0
- package/tests/integration/validate-api.test.ts +269 -0
- package/tests/setup.ts +15 -0
- package/tests/unit/agent-simulator.test.ts +575 -0
- package/tests/unit/compliance-generator.test.ts +374 -0
- package/tests/unit/directory-service.test.ts +272 -0
- package/tests/unit/feed-analyzer.test.ts +517 -0
- package/tests/unit/lint-suggestions.test.ts +423 -0
- package/tests/unit/official-samples.test.ts +211 -0
- package/tests/unit/pdf-report.test.ts +390 -0
- package/tests/unit/sdk-validator.test.ts +531 -0
- package/tests/unit/security-scanner.test.ts +410 -0
- package/tests/unit/validation.test.ts +390 -0
- package/tsconfig.json +20 -0
- package/vercel.json +34 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel Serverless Function: Security Posture Scanner
|
|
3
|
+
* POST /api/security-scan
|
|
4
|
+
*
|
|
5
|
+
* Scans UCP endpoints for common security misconfigurations.
|
|
6
|
+
* Helps merchants understand their security posture before exposing
|
|
7
|
+
* endpoints to AI agents.
|
|
8
|
+
*
|
|
9
|
+
* Request body:
|
|
10
|
+
* { "domain": "example.com", "options"?: { timeoutMs?: number } }
|
|
11
|
+
*
|
|
12
|
+
* Response:
|
|
13
|
+
* Security scan result including checks, score, grade, and recommendations.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export default async function handler(req, res) {
|
|
17
|
+
// Handle CORS
|
|
18
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
19
|
+
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
20
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
21
|
+
|
|
22
|
+
if (req.method === 'OPTIONS') {
|
|
23
|
+
return res.status(200).end();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (req.method !== 'POST') {
|
|
27
|
+
return res.status(405).json({
|
|
28
|
+
error: 'Method not allowed',
|
|
29
|
+
message: 'Use POST to run a security scan',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const { domain, options = {} } = req.body || {};
|
|
35
|
+
|
|
36
|
+
if (!domain) {
|
|
37
|
+
return res.status(400).json({
|
|
38
|
+
error: 'Missing domain',
|
|
39
|
+
message: 'Please provide a domain to scan',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Clean domain
|
|
44
|
+
const cleanDomain = domain
|
|
45
|
+
.replace(/^https?:\/\//, '')
|
|
46
|
+
.replace(/\/.*$/, '')
|
|
47
|
+
.toLowerCase()
|
|
48
|
+
.trim();
|
|
49
|
+
|
|
50
|
+
if (!cleanDomain) {
|
|
51
|
+
return res.status(400).json({
|
|
52
|
+
error: 'Invalid domain',
|
|
53
|
+
message: 'Please provide a valid domain',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Dynamic import of the security scanner
|
|
58
|
+
const { scanEndpointSecurity } = await import('../src/security/index.js');
|
|
59
|
+
|
|
60
|
+
// Run security scan
|
|
61
|
+
const scanOptions = {
|
|
62
|
+
timeoutMs: options.timeoutMs || 15000,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const result = await scanEndpointSecurity(cleanDomain, scanOptions);
|
|
66
|
+
|
|
67
|
+
// Build response
|
|
68
|
+
const response = {
|
|
69
|
+
ok: result.score >= 60,
|
|
70
|
+
domain: result.domain,
|
|
71
|
+
endpoint: result.endpoint,
|
|
72
|
+
scanned_at: result.scanned_at,
|
|
73
|
+
|
|
74
|
+
// Score and grade
|
|
75
|
+
score: result.score,
|
|
76
|
+
grade: result.grade,
|
|
77
|
+
|
|
78
|
+
// Summary
|
|
79
|
+
summary: result.summary,
|
|
80
|
+
|
|
81
|
+
// All checks with details
|
|
82
|
+
checks: result.checks.map(check => ({
|
|
83
|
+
id: check.id,
|
|
84
|
+
name: check.name,
|
|
85
|
+
description: check.description,
|
|
86
|
+
status: check.status,
|
|
87
|
+
severity: check.severity,
|
|
88
|
+
details: check.details,
|
|
89
|
+
recommendation: check.recommendation,
|
|
90
|
+
})),
|
|
91
|
+
|
|
92
|
+
// Group by status for easier UI rendering
|
|
93
|
+
by_status: {
|
|
94
|
+
passed: result.checks.filter(c => c.status === 'pass'),
|
|
95
|
+
failed: result.checks.filter(c => c.status === 'fail'),
|
|
96
|
+
warnings: result.checks.filter(c => c.status === 'warn'),
|
|
97
|
+
skipped: result.checks.filter(c => c.status === 'skip'),
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// Critical issues that need immediate attention
|
|
101
|
+
critical_issues: result.checks.filter(
|
|
102
|
+
c => c.status === 'fail' && (c.severity === 'critical' || c.severity === 'high')
|
|
103
|
+
),
|
|
104
|
+
|
|
105
|
+
// Badge
|
|
106
|
+
badge: {
|
|
107
|
+
text: `Security: ${result.grade} (${result.score}/100)`,
|
|
108
|
+
color: getBadgeColor(result.score),
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return res.status(200).json(response);
|
|
113
|
+
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error('Security scan error:', error);
|
|
116
|
+
return res.status(500).json({
|
|
117
|
+
error: 'Security scan failed',
|
|
118
|
+
message: error.message || 'An unexpected error occurred',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get badge color from score
|
|
125
|
+
*/
|
|
126
|
+
function getBadgeColor(score) {
|
|
127
|
+
if (score >= 90) return 'brightgreen';
|
|
128
|
+
if (score >= 80) return 'green';
|
|
129
|
+
if (score >= 70) return 'yellowgreen';
|
|
130
|
+
if (score >= 60) return 'yellow';
|
|
131
|
+
if (score >= 40) return 'orange';
|
|
132
|
+
return 'red';
|
|
133
|
+
}
|
package/api/simulate.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vercel Serverless Function: AI Agent Simulation
|
|
3
|
+
* POST /api/simulate
|
|
4
|
+
*
|
|
5
|
+
* Simulates how an AI agent would interact with a UCP-enabled merchant.
|
|
6
|
+
* Tests real-world functionality, not just spec compliance.
|
|
7
|
+
*
|
|
8
|
+
* Request body:
|
|
9
|
+
* { "domain": "example.com", "options"?: { ... } }
|
|
10
|
+
*
|
|
11
|
+
* Response:
|
|
12
|
+
* Complete simulation result including discovery, capabilities,
|
|
13
|
+
* services, checkout flow, and payment readiness.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export default async function handler(req, res) {
|
|
17
|
+
// Handle CORS
|
|
18
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
19
|
+
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
20
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
21
|
+
|
|
22
|
+
if (req.method === 'OPTIONS') {
|
|
23
|
+
return res.status(200).end();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (req.method !== 'POST') {
|
|
27
|
+
return res.status(405).json({
|
|
28
|
+
error: 'Method not allowed',
|
|
29
|
+
message: 'Use POST to simulate AI agent interaction',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const { domain, options = {} } = req.body || {};
|
|
35
|
+
|
|
36
|
+
if (!domain) {
|
|
37
|
+
return res.status(400).json({
|
|
38
|
+
error: 'Missing domain',
|
|
39
|
+
message: 'Please provide a domain to simulate',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Clean domain
|
|
44
|
+
const cleanDomain = domain
|
|
45
|
+
.replace(/^https?:\/\//, '')
|
|
46
|
+
.replace(/\/.*$/, '')
|
|
47
|
+
.toLowerCase()
|
|
48
|
+
.trim();
|
|
49
|
+
|
|
50
|
+
if (!cleanDomain) {
|
|
51
|
+
return res.status(400).json({
|
|
52
|
+
error: 'Invalid domain',
|
|
53
|
+
message: 'Please provide a valid domain',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Dynamic import of the simulator
|
|
58
|
+
const { simulateAgentInteraction } = await import('../src/simulator/index.js');
|
|
59
|
+
|
|
60
|
+
// Run simulation with timeout
|
|
61
|
+
const simulationOptions = {
|
|
62
|
+
timeoutMs: options.timeoutMs || 30000,
|
|
63
|
+
skipRestApiTest: options.skipRestApiTest || false,
|
|
64
|
+
skipSchemaValidation: options.skipSchemaValidation || false,
|
|
65
|
+
testCheckoutFlow: options.testCheckoutFlow !== false,
|
|
66
|
+
verbose: options.verbose || false,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const result = await simulateAgentInteraction(cleanDomain, simulationOptions);
|
|
70
|
+
|
|
71
|
+
// Build response
|
|
72
|
+
const response = {
|
|
73
|
+
ok: result.ok,
|
|
74
|
+
domain: result.domain,
|
|
75
|
+
simulated_at: result.simulatedAt,
|
|
76
|
+
duration_ms: result.durationMs,
|
|
77
|
+
|
|
78
|
+
// Score and grade
|
|
79
|
+
score: result.overallScore,
|
|
80
|
+
grade: getGrade(result.overallScore),
|
|
81
|
+
|
|
82
|
+
// Summary
|
|
83
|
+
summary: {
|
|
84
|
+
total_steps: result.summary.totalSteps,
|
|
85
|
+
passed: result.summary.passedSteps,
|
|
86
|
+
failed: result.summary.failedSteps,
|
|
87
|
+
warnings: result.summary.warningSteps,
|
|
88
|
+
skipped: result.summary.skippedSteps,
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// Discovery
|
|
92
|
+
discovery: {
|
|
93
|
+
success: result.discovery.success,
|
|
94
|
+
profile_url: result.discovery.profileUrl,
|
|
95
|
+
services: result.discovery.services,
|
|
96
|
+
capabilities: result.discovery.capabilities,
|
|
97
|
+
transports: result.discovery.transports,
|
|
98
|
+
steps: result.discovery.steps,
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
// Capabilities
|
|
102
|
+
capabilities: result.capabilities.map(cap => ({
|
|
103
|
+
name: cap.name,
|
|
104
|
+
version: cap.version,
|
|
105
|
+
schema_accessible: cap.schemaAccessible,
|
|
106
|
+
spec_accessible: cap.specAccessible,
|
|
107
|
+
is_extension: cap.isExtension,
|
|
108
|
+
parent: cap.parentCapability,
|
|
109
|
+
})),
|
|
110
|
+
|
|
111
|
+
// Services
|
|
112
|
+
services: result.services.map(svc => ({
|
|
113
|
+
name: svc.name,
|
|
114
|
+
version: svc.version,
|
|
115
|
+
transports: svc.transports,
|
|
116
|
+
})),
|
|
117
|
+
|
|
118
|
+
// REST API (if tested)
|
|
119
|
+
rest_api: result.restApi ? {
|
|
120
|
+
success: result.restApi.success,
|
|
121
|
+
schema_loaded: result.restApi.schemaLoaded,
|
|
122
|
+
endpoint_accessible: result.restApi.endpointAccessible,
|
|
123
|
+
steps: result.restApi.steps,
|
|
124
|
+
} : null,
|
|
125
|
+
|
|
126
|
+
// Checkout flow (if tested)
|
|
127
|
+
checkout: result.checkout ? {
|
|
128
|
+
success: result.checkout.success,
|
|
129
|
+
can_create_checkout: result.checkout.canCreateCheckout,
|
|
130
|
+
checkout_schema_valid: result.checkout.checkoutSchemaValid,
|
|
131
|
+
order_flow_supported: result.checkout.orderFlowSupported,
|
|
132
|
+
fulfillment_supported: result.checkout.fulfillmentSupported,
|
|
133
|
+
steps: result.checkout.steps,
|
|
134
|
+
} : null,
|
|
135
|
+
|
|
136
|
+
// Payment readiness
|
|
137
|
+
payment: result.payment ? {
|
|
138
|
+
success: result.payment.success,
|
|
139
|
+
handlers_found: result.payment.handlersFound,
|
|
140
|
+
webhook_verifiable: result.payment.webhookVerifiable,
|
|
141
|
+
signing_key_valid: result.payment.signingKeyValid,
|
|
142
|
+
steps: result.payment.steps,
|
|
143
|
+
} : null,
|
|
144
|
+
|
|
145
|
+
// Recommendations
|
|
146
|
+
recommendations: result.recommendations,
|
|
147
|
+
|
|
148
|
+
// Badge
|
|
149
|
+
badge: {
|
|
150
|
+
text: `AI Agent Ready: ${getGrade(result.overallScore)} (${result.overallScore}/100)`,
|
|
151
|
+
color: getBadgeColor(result.overallScore),
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return res.status(200).json(response);
|
|
156
|
+
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error('Simulation error:', error);
|
|
159
|
+
return res.status(500).json({
|
|
160
|
+
error: 'Simulation failed',
|
|
161
|
+
message: error.message || 'An unexpected error occurred',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get letter grade from score
|
|
168
|
+
*/
|
|
169
|
+
function getGrade(score) {
|
|
170
|
+
if (score >= 90) return 'A';
|
|
171
|
+
if (score >= 80) return 'B';
|
|
172
|
+
if (score >= 70) return 'C';
|
|
173
|
+
if (score >= 60) return 'D';
|
|
174
|
+
return 'F';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get badge color from score
|
|
179
|
+
*/
|
|
180
|
+
function getBadgeColor(score) {
|
|
181
|
+
if (score >= 90) return 'brightgreen';
|
|
182
|
+
if (score >= 80) return 'green';
|
|
183
|
+
if (score >= 70) return 'yellowgreen';
|
|
184
|
+
if (score >= 60) return 'yellow';
|
|
185
|
+
if (score >= 40) return 'orange';
|
|
186
|
+
return 'red';
|
|
187
|
+
}
|