@ucptools/validator 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/config.d.ts +20 -0
- package/dist/auth/config.d.ts.map +1 -0
- package/dist/auth/config.js +114 -0
- package/dist/auth/config.js.map +1 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +17 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware.d.ts +45 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +170 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/service.d.ts +80 -0
- package/dist/auth/service.d.ts.map +1 -0
- package/dist/auth/service.js +298 -0
- package/dist/auth/service.js.map +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +375 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/mock-server.d.ts +20 -0
- package/dist/cli/mock-server.d.ts.map +1 -0
- package/dist/cli/mock-server.js +261 -0
- package/dist/cli/mock-server.js.map +1 -0
- package/dist/compliance/compliance-generator.d.ts +34 -0
- package/dist/compliance/compliance-generator.d.ts.map +1 -0
- package/dist/compliance/compliance-generator.js +320 -0
- package/dist/compliance/compliance-generator.js.map +1 -0
- package/dist/compliance/index.d.ts +8 -0
- package/dist/compliance/index.d.ts.map +1 -0
- package/dist/compliance/index.js +17 -0
- package/dist/compliance/index.js.map +1 -0
- package/dist/compliance/templates.d.ts +34 -0
- package/dist/compliance/templates.d.ts.map +1 -0
- package/{src/compliance/templates.ts → dist/compliance/templates.js} +117 -155
- package/dist/compliance/templates.js.map +1 -0
- package/dist/compliance/types.d.ts +64 -0
- package/dist/compliance/types.d.ts.map +1 -0
- package/dist/compliance/types.js +64 -0
- package/dist/compliance/types.js.map +1 -0
- package/dist/db/index.d.ts +17 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +80 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +3886 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +425 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/utils.d.ts +252 -0
- package/dist/db/utils.d.ts.map +1 -0
- package/dist/db/utils.js +295 -0
- package/dist/db/utils.js.map +1 -0
- package/dist/feed-analyzer/feed-analyzer.d.ts +26 -0
- package/dist/feed-analyzer/feed-analyzer.d.ts.map +1 -0
- package/{src/feed-analyzer/feed-analyzer.ts → dist/feed-analyzer/feed-analyzer.js} +856 -726
- package/dist/feed-analyzer/feed-analyzer.js.map +1 -0
- package/dist/feed-analyzer/index.d.ts +8 -0
- package/dist/feed-analyzer/index.d.ts.map +1 -0
- package/dist/feed-analyzer/index.js +19 -0
- package/dist/feed-analyzer/index.js.map +1 -0
- package/dist/feed-analyzer/types.d.ts +285 -0
- package/dist/feed-analyzer/types.d.ts.map +1 -0
- package/dist/feed-analyzer/types.js +175 -0
- package/dist/feed-analyzer/types.js.map +1 -0
- package/{src/generator/index.ts → dist/generator/index.d.ts} +1 -1
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +13 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/key-generator.d.ts +24 -0
- package/dist/generator/key-generator.d.ts.map +1 -0
- package/dist/generator/key-generator.js +144 -0
- package/dist/generator/key-generator.js.map +1 -0
- package/dist/generator/profile-builder.d.ts +15 -0
- package/dist/generator/profile-builder.d.ts.map +1 -0
- package/dist/generator/profile-builder.js +338 -0
- package/dist/generator/profile-builder.js.map +1 -0
- package/dist/hosting/artifacts-generator.d.ts +10 -0
- package/dist/hosting/artifacts-generator.d.ts.map +1 -0
- package/{src/hosting/artifacts-generator.ts → dist/hosting/artifacts-generator.js} +191 -241
- package/dist/hosting/artifacts-generator.js.map +1 -0
- package/{src/hosting/index.ts → dist/hosting/index.d.ts} +1 -1
- package/dist/hosting/index.d.ts.map +1 -0
- package/dist/hosting/index.js +10 -0
- package/dist/hosting/index.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/analytics.d.ts +337 -0
- package/dist/lib/analytics.d.ts.map +1 -0
- package/dist/lib/analytics.js +188 -0
- package/dist/lib/analytics.js.map +1 -0
- package/{src/security/index.ts → dist/security/index.d.ts} +8 -15
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +12 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/security-scanner.d.ts +10 -0
- package/dist/security/security-scanner.d.ts.map +1 -0
- package/dist/security/security-scanner.js +669 -0
- package/dist/security/security-scanner.js.map +1 -0
- package/dist/security/types.d.ts +80 -0
- package/dist/security/types.d.ts.map +1 -0
- package/dist/security/types.js +21 -0
- package/dist/security/types.js.map +1 -0
- package/dist/services/analytics.d.ts +114 -0
- package/dist/services/analytics.d.ts.map +1 -0
- package/dist/services/analytics.js +862 -0
- package/dist/services/analytics.js.map +1 -0
- package/dist/services/badge.d.ts +31 -0
- package/dist/services/badge.d.ts.map +1 -0
- package/dist/services/badge.js +152 -0
- package/dist/services/badge.js.map +1 -0
- package/dist/services/cron.d.ts +125 -0
- package/dist/services/cron.d.ts.map +1 -0
- package/dist/services/cron.js +613 -0
- package/dist/services/cron.js.map +1 -0
- package/dist/services/directory.d.ts +106 -0
- package/dist/services/directory.d.ts.map +1 -0
- package/dist/services/directory.js +351 -0
- package/dist/services/directory.js.map +1 -0
- package/dist/services/email.d.ts +112 -0
- package/dist/services/email.d.ts.map +1 -0
- package/dist/services/email.js +772 -0
- package/dist/services/email.js.map +1 -0
- package/dist/services/hosted-profiles.d.ts +77 -0
- package/dist/services/hosted-profiles.d.ts.map +1 -0
- package/dist/services/hosted-profiles.js +433 -0
- package/dist/services/hosted-profiles.js.map +1 -0
- package/dist/services/latency.d.ts +67 -0
- package/dist/services/latency.d.ts.map +1 -0
- package/dist/services/latency.js +274 -0
- package/dist/services/latency.js.map +1 -0
- package/dist/services/manifest-compliance.d.ts +64 -0
- package/dist/services/manifest-compliance.d.ts.map +1 -0
- package/dist/services/manifest-compliance.js +271 -0
- package/dist/services/manifest-compliance.js.map +1 -0
- package/dist/services/monitoring-diff.d.ts +31 -0
- package/dist/services/monitoring-diff.d.ts.map +1 -0
- package/dist/services/monitoring-diff.js +189 -0
- package/dist/services/monitoring-diff.js.map +1 -0
- package/dist/services/notifications.d.ts +46 -0
- package/dist/services/notifications.d.ts.map +1 -0
- package/dist/services/notifications.js +88 -0
- package/dist/services/notifications.js.map +1 -0
- package/dist/services/stripe.d.ts +93 -0
- package/dist/services/stripe.d.ts.map +1 -0
- package/dist/services/stripe.js +490 -0
- package/dist/services/stripe.js.map +1 -0
- package/dist/services/validation-history.d.ts +99 -0
- package/dist/services/validation-history.d.ts.map +1 -0
- package/dist/services/validation-history.js +344 -0
- package/dist/services/validation-history.js.map +1 -0
- package/dist/services/validation-logging.d.ts +103 -0
- package/dist/services/validation-logging.d.ts.map +1 -0
- package/dist/services/validation-logging.js +210 -0
- package/dist/services/validation-logging.js.map +1 -0
- package/dist/services/validation.d.ts +119 -0
- package/dist/services/validation.d.ts.map +1 -0
- package/dist/services/validation.js +1185 -0
- package/dist/services/validation.js.map +1 -0
- package/dist/simulator/agent-simulator.d.ts +69 -0
- package/dist/simulator/agent-simulator.d.ts.map +1 -0
- package/dist/simulator/agent-simulator.js +870 -0
- package/dist/simulator/agent-simulator.js.map +1 -0
- package/{src/simulator/index.ts → dist/simulator/index.d.ts} +7 -7
- package/dist/simulator/index.d.ts.map +1 -0
- package/dist/simulator/index.js +23 -0
- package/dist/simulator/index.js.map +1 -0
- package/{src/simulator/types.ts → dist/simulator/types.d.ts} +171 -170
- package/dist/simulator/types.d.ts.map +1 -0
- package/dist/simulator/types.js +18 -0
- package/dist/simulator/types.js.map +1 -0
- package/dist/types/acp-validation.d.ts +87 -0
- package/dist/types/acp-validation.d.ts.map +1 -0
- package/dist/types/acp-validation.js +40 -0
- package/dist/types/acp-validation.js.map +1 -0
- package/dist/types/analytics.d.ts +182 -0
- package/dist/types/analytics.d.ts.map +1 -0
- package/dist/types/analytics.js +7 -0
- package/dist/types/analytics.js.map +1 -0
- package/dist/types/generator.d.ts +106 -0
- package/dist/types/generator.d.ts.map +1 -0
- package/dist/types/generator.js +6 -0
- package/dist/types/generator.js.map +1 -0
- package/{src/types/index.ts → dist/types/index.d.ts} +1 -1
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/ucp-profile.d.ts +111 -0
- package/dist/types/ucp-profile.d.ts.map +1 -0
- package/dist/types/ucp-profile.js +45 -0
- package/dist/types/ucp-profile.js.map +1 -0
- package/dist/types/validation.d.ts +76 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +42 -0
- package/dist/types/validation.js.map +1 -0
- package/dist/validator/acp/index.d.ts +31 -0
- package/dist/validator/acp/index.d.ts.map +1 -0
- package/dist/validator/acp/index.js +574 -0
- package/dist/validator/acp/index.js.map +1 -0
- package/dist/validator/index.d.ts +26 -0
- package/dist/validator/index.d.ts.map +1 -0
- package/dist/validator/index.js +161 -0
- package/dist/validator/index.js.map +1 -0
- package/dist/validator/network-validator.d.ts +28 -0
- package/dist/validator/network-validator.d.ts.map +1 -0
- package/dist/validator/network-validator.js +319 -0
- package/dist/validator/network-validator.js.map +1 -0
- package/dist/validator/rules-validator.d.ts +19 -0
- package/dist/validator/rules-validator.d.ts.map +1 -0
- package/dist/validator/rules-validator.js +306 -0
- package/dist/validator/rules-validator.js.map +1 -0
- package/dist/validator/sdk-validator.d.ts +58 -0
- package/dist/validator/sdk-validator.d.ts.map +1 -0
- package/{src/validator/sdk-validator.ts → dist/validator/sdk-validator.js} +273 -330
- package/dist/validator/sdk-validator.js.map +1 -0
- package/dist/validator/structural-validator.d.ts +11 -0
- package/dist/validator/structural-validator.d.ts.map +1 -0
- package/dist/validator/structural-validator.js +549 -0
- package/dist/validator/structural-validator.js.map +1 -0
- package/dist/validator/utils.d.ts +51 -0
- package/dist/validator/utils.d.ts.map +1 -0
- package/dist/validator/utils.js +132 -0
- package/dist/validator/utils.js.map +1 -0
- package/package.json +44 -12
- package/CLAUDE.md +0 -109
- package/api/analyze-feed.js +0 -140
- package/api/badge.js +0 -185
- package/api/benchmark.js +0 -177
- package/api/directory-stats.ts +0 -29
- package/api/directory.ts +0 -73
- package/api/generate-compliance.js +0 -143
- package/api/generate-schema.js +0 -457
- package/api/generate.js +0 -132
- package/api/security-scan.js +0 -133
- package/api/simulate.js +0 -187
- package/api/tsconfig.json +0 -10
- package/api/validate.js +0 -1351
- package/apify-actor/.actor/actor.json +0 -68
- package/apify-actor/.actor/input_schema.json +0 -32
- package/apify-actor/APIFY-STORE-LISTING.md +0 -412
- package/apify-actor/Dockerfile +0 -8
- package/apify-actor/README.md +0 -166
- package/apify-actor/main.ts +0 -111
- package/apify-actor/package.json +0 -17
- package/apify-actor/src/main.js +0 -199
- package/docs/BRAND-IDENTITY.md +0 -238
- package/docs/BRAND-STYLE-GUIDE.md +0 -356
- package/drizzle/0000_black_king_cobra.sql +0 -39
- package/drizzle/meta/0000_snapshot.json +0 -309
- package/drizzle/meta/_journal.json +0 -13
- package/drizzle.config.ts +0 -10
- package/public/.well-known/ucp +0 -25
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/brand.css +0 -321
- package/public/directory.html +0 -701
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/guides/bigcommerce.html +0 -743
- package/public/guides/fastucp.html +0 -838
- package/public/guides/magento.html +0 -779
- package/public/guides/shopify.html +0 -726
- package/public/guides/squarespace.html +0 -749
- package/public/guides/wix.html +0 -747
- package/public/guides/woocommerce.html +0 -733
- package/public/index.html +0 -3835
- package/public/learn.html +0 -396
- package/public/logo.jpeg +0 -0
- package/public/og-image-icon.png +0 -0
- package/public/og-image.png +0 -0
- package/public/robots.txt +0 -6
- package/public/site.webmanifest +0 -31
- package/public/sitemap.xml +0 -69
- package/public/social/linkedin-banner-1128x191.png +0 -0
- package/public/social/temp.PNG +0 -0
- package/public/social/x-header-1500x500.png +0 -0
- package/public/verify.html +0 -410
- package/scripts/generate-favicons.js +0 -44
- package/scripts/generate-ico.js +0 -23
- package/scripts/generate-og-image.js +0 -45
- package/scripts/reset-db.ts +0 -77
- package/scripts/seed-db.ts +0 -71
- package/scripts/setup-benchmark-db.js +0 -70
- package/src/api/server.ts +0 -266
- package/src/cli/index.ts +0 -302
- package/src/compliance/compliance-generator.ts +0 -452
- package/src/compliance/index.ts +0 -28
- package/src/compliance/types.ts +0 -170
- package/src/db/index.ts +0 -28
- package/src/db/schema.ts +0 -84
- package/src/feed-analyzer/index.ts +0 -34
- package/src/feed-analyzer/types.ts +0 -354
- package/src/generator/key-generator.ts +0 -124
- package/src/generator/profile-builder.ts +0 -402
- package/src/index.ts +0 -105
- package/src/security/security-scanner.ts +0 -604
- package/src/security/types.ts +0 -55
- package/src/services/directory.ts +0 -434
- package/src/simulator/agent-simulator.ts +0 -941
- package/src/types/generator.ts +0 -140
- package/src/types/ucp-profile.ts +0 -140
- package/src/types/validation.ts +0 -89
- package/src/validator/index.ts +0 -194
- package/src/validator/network-validator.ts +0 -417
- package/src/validator/rules-validator.ts +0 -297
- package/src/validator/structural-validator.ts +0 -476
- package/tests/fixtures/non-compliant-profile.json +0 -25
- package/tests/fixtures/official-sample-profile.json +0 -75
- package/tests/integration/benchmark.test.ts +0 -207
- package/tests/integration/database.test.ts +0 -163
- package/tests/integration/directory-api.test.ts +0 -268
- package/tests/integration/simulate-api.test.ts +0 -230
- package/tests/integration/validate-api.test.ts +0 -269
- package/tests/setup.ts +0 -15
- package/tests/unit/agent-simulator.test.ts +0 -575
- package/tests/unit/compliance-generator.test.ts +0 -374
- package/tests/unit/directory-service.test.ts +0 -272
- package/tests/unit/feed-analyzer.test.ts +0 -517
- package/tests/unit/lint-suggestions.test.ts +0 -423
- package/tests/unit/official-samples.test.ts +0 -211
- package/tests/unit/pdf-report.test.ts +0 -390
- package/tests/unit/sdk-validator.test.ts +0 -531
- package/tests/unit/security-scanner.test.ts +0 -410
- package/tests/unit/validation.test.ts +0 -390
- package/tsconfig.json +0 -20
- package/vercel.json +0 -34
- package/vitest.config.ts +0 -22
|
@@ -1,410 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for Security Posture Scanner
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
6
|
-
import { scanEndpointSecurity } from '../../src/security/security-scanner.js';
|
|
7
|
-
import { SecurityCheckIds } from '../../src/security/types.js';
|
|
8
|
-
|
|
9
|
-
// Mock fetch globally
|
|
10
|
-
const mockFetch = vi.fn();
|
|
11
|
-
global.fetch = mockFetch;
|
|
12
|
-
|
|
13
|
-
describe('Security Scanner', () => {
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
mockFetch.mockReset();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
vi.restoreAllMocks();
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe('scanEndpointSecurity', () => {
|
|
23
|
-
it('should return a complete security scan result', async () => {
|
|
24
|
-
// Mock HTTP check (redirect to HTTPS)
|
|
25
|
-
mockFetch.mockResolvedValueOnce({
|
|
26
|
-
ok: false,
|
|
27
|
-
status: 301,
|
|
28
|
-
headers: new Headers({ 'location': 'https://example.com/.well-known/ucp' }),
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Mock HTTPS endpoint check
|
|
32
|
-
mockFetch.mockResolvedValueOnce({
|
|
33
|
-
ok: true,
|
|
34
|
-
status: 200,
|
|
35
|
-
headers: new Headers({
|
|
36
|
-
'content-type': 'application/json',
|
|
37
|
-
'strict-transport-security': 'max-age=31536000',
|
|
38
|
-
'x-content-type-options': 'nosniff',
|
|
39
|
-
'access-control-allow-origin': '*',
|
|
40
|
-
}),
|
|
41
|
-
text: async () => JSON.stringify({ ucp: { version: '2026-01-11' } }),
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const result = await scanEndpointSecurity('example.com');
|
|
45
|
-
|
|
46
|
-
expect(result.domain).toBe('example.com');
|
|
47
|
-
expect(result.endpoint).toBe('https://example.com/.well-known/ucp');
|
|
48
|
-
expect(result.scanned_at).toBeTruthy();
|
|
49
|
-
expect(result.score).toBeGreaterThanOrEqual(0);
|
|
50
|
-
expect(result.score).toBeLessThanOrEqual(100);
|
|
51
|
-
expect(result.grade).toMatch(/^[A-F]$/);
|
|
52
|
-
expect(result.checks).toBeInstanceOf(Array);
|
|
53
|
-
expect(result.checks.length).toBeGreaterThan(0);
|
|
54
|
-
expect(result.summary).toHaveProperty('passed');
|
|
55
|
-
expect(result.summary).toHaveProperty('failed');
|
|
56
|
-
expect(result.summary).toHaveProperty('warnings');
|
|
57
|
-
expect(result.summary).toHaveProperty('skipped');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('should detect HTTPS enforcement via redirect', async () => {
|
|
61
|
-
// HTTP redirects to HTTPS
|
|
62
|
-
mockFetch.mockResolvedValueOnce({
|
|
63
|
-
ok: false,
|
|
64
|
-
status: 301,
|
|
65
|
-
headers: new Headers({ 'location': 'https://example.com/.well-known/ucp' }),
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// HTTPS endpoint
|
|
69
|
-
mockFetch.mockResolvedValueOnce({
|
|
70
|
-
ok: true,
|
|
71
|
-
status: 200,
|
|
72
|
-
headers: new Headers({ 'content-type': 'application/json' }),
|
|
73
|
-
text: async () => '{}',
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const result = await scanEndpointSecurity('example.com');
|
|
77
|
-
|
|
78
|
-
const httpsCheck = result.checks.find(c => c.id === SecurityCheckIds.HTTPS_REQUIRED);
|
|
79
|
-
expect(httpsCheck).toBeDefined();
|
|
80
|
-
expect(httpsCheck?.status).toBe('pass');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should fail HTTPS check if HTTP serves content', async () => {
|
|
84
|
-
// HTTP serves content without redirect
|
|
85
|
-
mockFetch.mockResolvedValueOnce({
|
|
86
|
-
ok: true,
|
|
87
|
-
status: 200,
|
|
88
|
-
headers: new Headers({ 'content-type': 'application/json' }),
|
|
89
|
-
text: async () => '{}',
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// HTTPS endpoint
|
|
93
|
-
mockFetch.mockResolvedValueOnce({
|
|
94
|
-
ok: true,
|
|
95
|
-
status: 200,
|
|
96
|
-
headers: new Headers({ 'content-type': 'application/json' }),
|
|
97
|
-
text: async () => '{}',
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const result = await scanEndpointSecurity('example.com');
|
|
101
|
-
|
|
102
|
-
const httpsCheck = result.checks.find(c => c.id === SecurityCheckIds.HTTPS_REQUIRED);
|
|
103
|
-
expect(httpsCheck).toBeDefined();
|
|
104
|
-
expect(httpsCheck?.status).toBe('fail');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('should detect private IP addresses', async () => {
|
|
108
|
-
// HTTP check fails
|
|
109
|
-
mockFetch.mockRejectedValueOnce(new Error('connection refused'));
|
|
110
|
-
|
|
111
|
-
const result = await scanEndpointSecurity('192.168.1.1');
|
|
112
|
-
|
|
113
|
-
const privateIpCheck = result.checks.find(c => c.id === SecurityCheckIds.PRIVATE_IP);
|
|
114
|
-
expect(privateIpCheck).toBeDefined();
|
|
115
|
-
expect(privateIpCheck?.status).toBe('fail');
|
|
116
|
-
expect(privateIpCheck?.severity).toBe('high');
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it('should pass private IP check for public domains', async () => {
|
|
120
|
-
mockFetch.mockRejectedValueOnce(new Error('connection refused'));
|
|
121
|
-
|
|
122
|
-
const result = await scanEndpointSecurity('example.com');
|
|
123
|
-
|
|
124
|
-
const privateIpCheck = result.checks.find(c => c.id === SecurityCheckIds.PRIVATE_IP);
|
|
125
|
-
expect(privateIpCheck).toBeDefined();
|
|
126
|
-
expect(privateIpCheck?.status).toBe('pass');
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('should check security headers', async () => {
|
|
130
|
-
// HTTP fails
|
|
131
|
-
mockFetch.mockRejectedValueOnce(new Error('connection refused'));
|
|
132
|
-
|
|
133
|
-
// HTTPS with good security headers
|
|
134
|
-
mockFetch.mockResolvedValueOnce({
|
|
135
|
-
ok: true,
|
|
136
|
-
status: 200,
|
|
137
|
-
headers: new Headers({
|
|
138
|
-
'content-type': 'application/json',
|
|
139
|
-
'strict-transport-security': 'max-age=31536000; includeSubDomains',
|
|
140
|
-
'x-content-type-options': 'nosniff',
|
|
141
|
-
'x-frame-options': 'DENY',
|
|
142
|
-
}),
|
|
143
|
-
text: async () => '{}',
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
const result = await scanEndpointSecurity('example.com');
|
|
147
|
-
|
|
148
|
-
const headersCheck = result.checks.find(c => c.id === SecurityCheckIds.SECURITY_HEADERS);
|
|
149
|
-
expect(headersCheck).toBeDefined();
|
|
150
|
-
expect(headersCheck?.status).toBe('pass');
|
|
151
|
-
expect(headersCheck?.details).toContain('HSTS');
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('should warn on missing security headers', async () => {
|
|
155
|
-
mockFetch.mockRejectedValueOnce(new Error('connection refused'));
|
|
156
|
-
|
|
157
|
-
// HTTPS with no security headers
|
|
158
|
-
mockFetch.mockResolvedValueOnce({
|
|
159
|
-
ok: true,
|
|
160
|
-
status: 200,
|
|
161
|
-
headers: new Headers({
|
|
162
|
-
'content-type': 'application/json',
|
|
163
|
-
}),
|
|
164
|
-
text: async () => '{}',
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
const result = await scanEndpointSecurity('example.com');
|
|
168
|
-
|
|
169
|
-
const headersCheck = result.checks.find(c => c.id === SecurityCheckIds.SECURITY_HEADERS);
|
|
170
|
-
expect(headersCheck).toBeDefined();
|
|
171
|
-
expect(headersCheck?.status).toBe('fail');
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('should detect CORS configuration', async () => {
|
|
175
|
-
mockFetch.mockRejectedValueOnce(new Error('connection refused'));
|
|
176
|
-
|
|
177
|
-
mockFetch.mockResolvedValueOnce({
|
|
178
|
-
ok: true,
|
|
179
|
-
status: 200,
|
|
180
|
-
headers: new Headers({
|
|
181
|
-
'content-type': 'application/json',
|
|
182
|
-
'access-control-allow-origin': 'https://trusted.com',
|
|
183
|
-
'access-control-allow-methods': 'GET, POST',
|
|
184
|
-
}),
|
|
185
|
-
text: async () => '{}',
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const result = await scanEndpointSecurity('example.com');
|
|
189
|
-
|
|
190
|
-
const corsCheck = result.checks.find(c => c.id === SecurityCheckIds.CORS_CONFIG);
|
|
191
|
-
expect(corsCheck).toBeDefined();
|
|
192
|
-
expect(corsCheck?.status).toBe('pass');
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it('should warn on wildcard CORS', async () => {
|
|
196
|
-
mockFetch.mockRejectedValueOnce(new Error('connection refused'));
|
|
197
|
-
|
|
198
|
-
mockFetch.mockResolvedValueOnce({
|
|
199
|
-
ok: true,
|
|
200
|
-
status: 200,
|
|
201
|
-
headers: new Headers({
|
|
202
|
-
'content-type': 'application/json',
|
|
203
|
-
'access-control-allow-origin': '*',
|
|
204
|
-
}),
|
|
205
|
-
text: async () => '{}',
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
const result = await scanEndpointSecurity('example.com');
|
|
209
|
-
|
|
210
|
-
const corsCheck = result.checks.find(c => c.id === SecurityCheckIds.CORS_CONFIG);
|
|
211
|
-
expect(corsCheck).toBeDefined();
|
|
212
|
-
expect(corsCheck?.status).toBe('warn');
|
|
213
|
-
expect(corsCheck?.details).toContain('all origins');
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it('should detect rate limiting headers', async () => {
|
|
217
|
-
mockFetch.mockRejectedValueOnce(new Error('connection refused'));
|
|
218
|
-
|
|
219
|
-
mockFetch.mockResolvedValueOnce({
|
|
220
|
-
ok: true,
|
|
221
|
-
status: 200,
|
|
222
|
-
headers: new Headers({
|
|
223
|
-
'content-type': 'application/json',
|
|
224
|
-
'x-ratelimit-limit': '100',
|
|
225
|
-
'x-ratelimit-remaining': '99',
|
|
226
|
-
}),
|
|
227
|
-
text: async () => '{}',
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
const result = await scanEndpointSecurity('example.com');
|
|
231
|
-
|
|
232
|
-
const rateLimitCheck = result.checks.find(c => c.id === SecurityCheckIds.RATE_LIMITING);
|
|
233
|
-
expect(rateLimitCheck).toBeDefined();
|
|
234
|
-
expect(rateLimitCheck?.status).toBe('pass');
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it('should warn when no rate limiting detected', async () => {
|
|
238
|
-
mockFetch.mockRejectedValueOnce(new Error('connection refused'));
|
|
239
|
-
|
|
240
|
-
mockFetch.mockResolvedValueOnce({
|
|
241
|
-
ok: true,
|
|
242
|
-
status: 200,
|
|
243
|
-
headers: new Headers({
|
|
244
|
-
'content-type': 'application/json',
|
|
245
|
-
}),
|
|
246
|
-
text: async () => '{}',
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
const result = await scanEndpointSecurity('example.com');
|
|
250
|
-
|
|
251
|
-
const rateLimitCheck = result.checks.find(c => c.id === SecurityCheckIds.RATE_LIMITING);
|
|
252
|
-
expect(rateLimitCheck).toBeDefined();
|
|
253
|
-
expect(rateLimitCheck?.status).toBe('warn');
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it('should check content-type header', async () => {
|
|
257
|
-
mockFetch.mockRejectedValueOnce(new Error('connection refused'));
|
|
258
|
-
|
|
259
|
-
mockFetch.mockResolvedValueOnce({
|
|
260
|
-
ok: true,
|
|
261
|
-
status: 200,
|
|
262
|
-
headers: new Headers({
|
|
263
|
-
'content-type': 'application/json; charset=utf-8',
|
|
264
|
-
}),
|
|
265
|
-
text: async () => '{}',
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
const result = await scanEndpointSecurity('example.com');
|
|
269
|
-
|
|
270
|
-
const contentTypeCheck = result.checks.find(c => c.id === SecurityCheckIds.CONTENT_TYPE);
|
|
271
|
-
expect(contentTypeCheck).toBeDefined();
|
|
272
|
-
expect(contentTypeCheck?.status).toBe('pass');
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('should detect potential error disclosure', async () => {
|
|
276
|
-
mockFetch.mockRejectedValueOnce(new Error('connection refused'));
|
|
277
|
-
|
|
278
|
-
mockFetch.mockResolvedValueOnce({
|
|
279
|
-
ok: true,
|
|
280
|
-
status: 200,
|
|
281
|
-
headers: new Headers({
|
|
282
|
-
'content-type': 'application/json',
|
|
283
|
-
}),
|
|
284
|
-
text: async () => JSON.stringify({
|
|
285
|
-
error: 'Error at /home/user/app/server.js:42:15',
|
|
286
|
-
stack: 'at Object.<anonymous> (/home/user/app/server.js:42:15)',
|
|
287
|
-
}),
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
const result = await scanEndpointSecurity('example.com');
|
|
291
|
-
|
|
292
|
-
const errorCheck = result.checks.find(c => c.id === SecurityCheckIds.ERROR_DISCLOSURE);
|
|
293
|
-
expect(errorCheck).toBeDefined();
|
|
294
|
-
expect(errorCheck?.status).toBe('warn');
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
it('should handle unreachable endpoints gracefully', async () => {
|
|
298
|
-
// Both HTTP and HTTPS fail
|
|
299
|
-
mockFetch.mockRejectedValue(new Error('Network error'));
|
|
300
|
-
|
|
301
|
-
const result = await scanEndpointSecurity('unreachable.example.com');
|
|
302
|
-
|
|
303
|
-
expect(result.domain).toBe('unreachable.example.com');
|
|
304
|
-
expect(result.checks.length).toBeGreaterThan(0);
|
|
305
|
-
// Should have skipped checks for unreachable endpoint
|
|
306
|
-
expect(result.summary.skipped).toBeGreaterThan(0);
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
it('should calculate appropriate grades', async () => {
|
|
310
|
-
mockFetch.mockRejectedValueOnce(new Error('connection refused'));
|
|
311
|
-
|
|
312
|
-
// Good security setup
|
|
313
|
-
mockFetch.mockResolvedValueOnce({
|
|
314
|
-
ok: true,
|
|
315
|
-
status: 200,
|
|
316
|
-
headers: new Headers({
|
|
317
|
-
'content-type': 'application/json',
|
|
318
|
-
'strict-transport-security': 'max-age=31536000',
|
|
319
|
-
'x-content-type-options': 'nosniff',
|
|
320
|
-
'access-control-allow-origin': 'https://trusted.com',
|
|
321
|
-
'x-ratelimit-limit': '100',
|
|
322
|
-
}),
|
|
323
|
-
text: async () => '{}',
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
const result = await scanEndpointSecurity('secure.example.com');
|
|
327
|
-
|
|
328
|
-
expect(result.score).toBeGreaterThanOrEqual(60);
|
|
329
|
-
expect(['A', 'B', 'C']).toContain(result.grade);
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
it('should normalize domain input', async () => {
|
|
333
|
-
mockFetch.mockRejectedValueOnce(new Error('connection refused'));
|
|
334
|
-
mockFetch.mockResolvedValueOnce({
|
|
335
|
-
ok: true,
|
|
336
|
-
status: 200,
|
|
337
|
-
headers: new Headers({ 'content-type': 'application/json' }),
|
|
338
|
-
text: async () => '{}',
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
const result = await scanEndpointSecurity('https://example.com/path/to/something');
|
|
342
|
-
|
|
343
|
-
expect(result.domain).toBe('example.com');
|
|
344
|
-
expect(result.endpoint).toBe('https://example.com/.well-known/ucp');
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
it('should detect localhost as private IP', async () => {
|
|
348
|
-
mockFetch.mockRejectedValueOnce(new Error('connection refused'));
|
|
349
|
-
|
|
350
|
-
const result = await scanEndpointSecurity('localhost');
|
|
351
|
-
|
|
352
|
-
const privateIpCheck = result.checks.find(c => c.id === SecurityCheckIds.PRIVATE_IP);
|
|
353
|
-
expect(privateIpCheck?.status).toBe('fail');
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
it('should detect various private IP ranges', async () => {
|
|
357
|
-
const privateIps = ['10.0.0.1', '172.16.0.1', '192.168.0.1', '127.0.0.1'];
|
|
358
|
-
|
|
359
|
-
for (const ip of privateIps) {
|
|
360
|
-
mockFetch.mockReset();
|
|
361
|
-
mockFetch.mockRejectedValue(new Error('connection refused'));
|
|
362
|
-
|
|
363
|
-
const result = await scanEndpointSecurity(ip);
|
|
364
|
-
|
|
365
|
-
const privateIpCheck = result.checks.find(c => c.id === SecurityCheckIds.PRIVATE_IP);
|
|
366
|
-
expect(privateIpCheck?.status).toBe('fail');
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
describe('Score Calculation', () => {
|
|
372
|
-
it('should give higher scores for passing critical checks', async () => {
|
|
373
|
-
mockFetch.mockRejectedValueOnce(new Error('connection refused'));
|
|
374
|
-
|
|
375
|
-
// All good headers
|
|
376
|
-
mockFetch.mockResolvedValueOnce({
|
|
377
|
-
ok: true,
|
|
378
|
-
status: 200,
|
|
379
|
-
headers: new Headers({
|
|
380
|
-
'content-type': 'application/json',
|
|
381
|
-
'strict-transport-security': 'max-age=31536000',
|
|
382
|
-
'x-content-type-options': 'nosniff',
|
|
383
|
-
'x-frame-options': 'DENY',
|
|
384
|
-
'access-control-allow-origin': 'https://trusted.com',
|
|
385
|
-
'access-control-allow-methods': 'GET, POST',
|
|
386
|
-
'x-ratelimit-limit': '100',
|
|
387
|
-
'cache-control': 'no-store',
|
|
388
|
-
}),
|
|
389
|
-
text: async () => '{}',
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
const goodResult = await scanEndpointSecurity('secure.example.com');
|
|
393
|
-
|
|
394
|
-
mockFetch.mockReset();
|
|
395
|
-
mockFetch.mockRejectedValueOnce(new Error('connection refused'));
|
|
396
|
-
|
|
397
|
-
// Minimal headers
|
|
398
|
-
mockFetch.mockResolvedValueOnce({
|
|
399
|
-
ok: true,
|
|
400
|
-
status: 200,
|
|
401
|
-
headers: new Headers({}),
|
|
402
|
-
text: async () => '{}',
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
const badResult = await scanEndpointSecurity('insecure.example.com');
|
|
406
|
-
|
|
407
|
-
expect(goodResult.score).toBeGreaterThan(badResult.score);
|
|
408
|
-
});
|
|
409
|
-
});
|
|
410
|
-
});
|