guardrail-ship 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/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/mock-implementation.d.ts +1 -0
- package/dist/mock-implementation.d.ts.map +1 -0
- package/dist/mock-implementation.js +2 -0
- package/dist/mock-implementation.js.map +1 -0
- package/dist/mockproof/__tests__/import-graph-scanner.test.d.ts +5 -0
- package/dist/mockproof/__tests__/import-graph-scanner.test.d.ts.map +1 -0
- package/dist/mockproof/__tests__/import-graph-scanner.test.js +92 -0
- package/dist/mockproof/__tests__/import-graph-scanner.test.js.map +1 -0
- package/dist/mockproof/import-graph-scanner.d.ts +93 -0
- package/dist/mockproof/import-graph-scanner.d.ts.map +1 -0
- package/dist/mockproof/import-graph-scanner.js +411 -0
- package/dist/mockproof/import-graph-scanner.js.map +1 -0
- package/dist/mockproof/index.d.ts +10 -0
- package/dist/mockproof/index.d.ts.map +1 -0
- package/dist/mockproof/index.js +10 -0
- package/dist/mockproof/index.js.map +1 -0
- package/dist/reality-mode/auth-enforcer.d.ts +13 -0
- package/dist/reality-mode/auth-enforcer.d.ts.map +1 -0
- package/dist/reality-mode/auth-enforcer.js +90 -0
- package/dist/reality-mode/auth-enforcer.js.map +1 -0
- package/dist/reality-mode/explorer/critical-flows.d.ts +71 -0
- package/dist/reality-mode/explorer/critical-flows.d.ts.map +1 -0
- package/dist/reality-mode/explorer/critical-flows.js +463 -0
- package/dist/reality-mode/explorer/critical-flows.js.map +1 -0
- package/dist/reality-mode/explorer/flow-parser.d.ts +52 -0
- package/dist/reality-mode/explorer/flow-parser.d.ts.map +1 -0
- package/dist/reality-mode/explorer/flow-parser.js +250 -0
- package/dist/reality-mode/explorer/flow-parser.js.map +1 -0
- package/dist/reality-mode/explorer/index.d.ts +11 -0
- package/dist/reality-mode/explorer/index.d.ts.map +1 -0
- package/dist/reality-mode/explorer/index.js +11 -0
- package/dist/reality-mode/explorer/index.js.map +1 -0
- package/dist/reality-mode/explorer/runtime-explorer.d.ts +35 -0
- package/dist/reality-mode/explorer/runtime-explorer.d.ts.map +1 -0
- package/dist/reality-mode/explorer/runtime-explorer.js +688 -0
- package/dist/reality-mode/explorer/runtime-explorer.js.map +1 -0
- package/dist/reality-mode/explorer/surface-discovery.d.ts +60 -0
- package/dist/reality-mode/explorer/surface-discovery.d.ts.map +1 -0
- package/dist/reality-mode/explorer/surface-discovery.js +357 -0
- package/dist/reality-mode/explorer/surface-discovery.js.map +1 -0
- package/dist/reality-mode/explorer/types.d.ts +275 -0
- package/dist/reality-mode/explorer/types.d.ts.map +1 -0
- package/dist/reality-mode/explorer/types.js +8 -0
- package/dist/reality-mode/explorer/types.js.map +1 -0
- package/dist/reality-mode/fake-success-detector.d.ts +10 -0
- package/dist/reality-mode/fake-success-detector.d.ts.map +1 -0
- package/dist/reality-mode/fake-success-detector.js +76 -0
- package/dist/reality-mode/fake-success-detector.js.map +1 -0
- package/dist/reality-mode/index.d.ts +14 -0
- package/dist/reality-mode/index.d.ts.map +1 -0
- package/dist/reality-mode/index.js +14 -0
- package/dist/reality-mode/index.js.map +1 -0
- package/dist/reality-mode/reality-scanner.d.ts +48 -0
- package/dist/reality-mode/reality-scanner.d.ts.map +1 -0
- package/dist/reality-mode/reality-scanner.js +516 -0
- package/dist/reality-mode/reality-scanner.js.map +1 -0
- package/dist/reality-mode/report-generator.d.ts +11 -0
- package/dist/reality-mode/report-generator.d.ts.map +1 -0
- package/dist/reality-mode/report-generator.js +233 -0
- package/dist/reality-mode/report-generator.js.map +1 -0
- package/dist/reality-mode/traffic-classifier.d.ts +14 -0
- package/dist/reality-mode/traffic-classifier.d.ts.map +1 -0
- package/dist/reality-mode/traffic-classifier.js +131 -0
- package/dist/reality-mode/traffic-classifier.js.map +1 -0
- package/dist/reality-mode/types.d.ts +90 -0
- package/dist/reality-mode/types.d.ts.map +1 -0
- package/dist/reality-mode/types.js +2 -0
- package/dist/reality-mode/types.js.map +1 -0
- package/dist/ship-badge/__tests__/ship-badge-generator.test.d.ts +5 -0
- package/dist/ship-badge/__tests__/ship-badge-generator.test.d.ts.map +1 -0
- package/dist/ship-badge/__tests__/ship-badge-generator.test.js +146 -0
- package/dist/ship-badge/__tests__/ship-badge-generator.test.js.map +1 -0
- package/dist/ship-badge/index.d.ts +9 -0
- package/dist/ship-badge/index.d.ts.map +1 -0
- package/dist/ship-badge/index.js +9 -0
- package/dist/ship-badge/index.js.map +1 -0
- package/dist/ship-badge/ship-badge-generator.d.ts +136 -0
- package/dist/ship-badge/ship-badge-generator.d.ts.map +1 -0
- package/dist/ship-badge/ship-badge-generator.js +681 -0
- package/dist/ship-badge/ship-badge-generator.js.map +1 -0
- package/package.json +20 -0
- package/src/index.ts +7 -0
- package/src/mock-implementation.ts +0 -0
- package/src/mockproof/__tests__/import-graph-scanner.test.ts +115 -0
- package/src/mockproof/import-graph-scanner.d.ts +93 -0
- package/src/mockproof/import-graph-scanner.d.ts.map +1 -0
- package/src/mockproof/import-graph-scanner.js +482 -0
- package/src/mockproof/import-graph-scanner.ts +540 -0
- package/src/mockproof/index.ts +18 -0
- package/src/reality-mode/auth-enforcer.ts +97 -0
- package/src/reality-mode/explorer/critical-flows.ts +504 -0
- package/src/reality-mode/explorer/flow-parser.ts +293 -0
- package/src/reality-mode/explorer/index.ts +22 -0
- package/src/reality-mode/explorer/runtime-explorer.ts +715 -0
- package/src/reality-mode/explorer/surface-discovery.ts +498 -0
- package/src/reality-mode/explorer/templates/example-flows/auth-flow.yaml +41 -0
- package/src/reality-mode/explorer/templates/example-flows/checkout-flow.yaml +66 -0
- package/src/reality-mode/explorer/templates/example-flows/contact-form.yaml +43 -0
- package/src/reality-mode/explorer/templates/github-action.yml +132 -0
- package/src/reality-mode/explorer/types.ts +356 -0
- package/src/reality-mode/fake-success-detector.ts +89 -0
- package/src/reality-mode/index.ts +19 -0
- package/src/reality-mode/reality-scanner.d.ts +123 -0
- package/src/reality-mode/reality-scanner.d.ts.map +1 -0
- package/src/reality-mode/reality-scanner.js +526 -0
- package/src/reality-mode/reality-scanner.ts +576 -0
- package/src/reality-mode/report-generator.ts +253 -0
- package/src/reality-mode/traffic-classifier.ts +169 -0
- package/src/reality-mode/types.ts +95 -0
- package/src/ship-badge/__tests__/ship-badge-generator.test.ts +162 -0
- package/src/ship-badge/index.ts +16 -0
- package/src/ship-badge/ship-badge-generator.d.ts +136 -0
- package/src/ship-badge/ship-badge-generator.d.ts.map +1 -0
- package/src/ship-badge/ship-badge-generator.js +779 -0
- package/src/ship-badge/ship-badge-generator.ts +873 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reality-scanner.d.ts","sourceRoot":"","sources":["reality-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;IAC1C,MAAM,EAAE,CAAC,OAAO,EAAE,kBAAkB,GAAG,mBAAmB,KAAK,OAAO,CAAC;CACxE;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,SAAS,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,UAAU,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,YAAY,GAAG,QAAQ,CAAC;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,YAAY,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,OAAO,EAAE;QACP,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC;IACvB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,QAAQ,EAAE,OAAO,CAAC;CACnB;AA4CD,eAAO,MAAM,qBAAqB,EAAE,WAAW,EAwE9C,CAAC;AAEF,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,SAAS,CAA6B;IAC9C,OAAO,CAAC,OAAO,CAAoB;gBAEvB,MAAM,GAAE,OAAO,CAAC,iBAAiB,CAAM;IAUnD;;OAEG;IACH,sBAAsB,CAAC,MAAM,EAAE;QAC7B,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC;QACvB,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,MAAM;IAqNV;;OAEG;IACH,cAAc,CACZ,QAAQ,EAAE,kBAAkB,EAAE,EAC9B,SAAS,EAAE,mBAAmB,EAAE,GAC/B,aAAa,EAAE;IAiClB;;OAEG;IACH,OAAO,CAAC,WAAW;IAgBnB;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM;IAuDjD;;OAEG;IACH,yBAAyB,IAAI,MAAM,EAAE,EAAE;CAcxC;AAED,eAAO,MAAM,cAAc,gBAAuB,CAAC"}
|
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Reality Mode - Runtime Fake Detection
|
|
4
|
+
*
|
|
5
|
+
* "Stop shipping pretend features. Guardrail runs your app and catches the lies."
|
|
6
|
+
*
|
|
7
|
+
* This module spins up the app, intercepts network calls, clicks through the UI,
|
|
8
|
+
* and detects:
|
|
9
|
+
* - Calls to localhost, jsonplaceholder, staging domains, ngrok
|
|
10
|
+
* - Routes returning demo/placeholder responses
|
|
11
|
+
* - Silent fallback success patterns
|
|
12
|
+
* - Mock billing and fake invoice IDs
|
|
13
|
+
*
|
|
14
|
+
* The killer feature: a "flight recorder" replay showing exactly what happened.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.realityScanner =
|
|
18
|
+
exports.RealityScanner =
|
|
19
|
+
exports.DEFAULT_FAKE_PATTERNS =
|
|
20
|
+
void 0;
|
|
21
|
+
const FAKE_DOMAIN_PATTERNS = [
|
|
22
|
+
/localhost:\d+/i,
|
|
23
|
+
/127\.0\.0\.1:\d+/i,
|
|
24
|
+
/jsonplaceholder\.typicode\.com/i,
|
|
25
|
+
/reqres\.in/i,
|
|
26
|
+
/mockapi\.io/i,
|
|
27
|
+
/mocky\.io/i,
|
|
28
|
+
/httpbin\.org/i,
|
|
29
|
+
/\.ngrok\.io/i,
|
|
30
|
+
/\.ngrok-free\.app/i,
|
|
31
|
+
/staging\./i,
|
|
32
|
+
/\.local\//i,
|
|
33
|
+
/\.test\//i,
|
|
34
|
+
/api\.example\.com/i,
|
|
35
|
+
/fake\.api/i,
|
|
36
|
+
/demo\.api/i,
|
|
37
|
+
];
|
|
38
|
+
const FAKE_RESPONSE_PATTERNS = [
|
|
39
|
+
{ pattern: /inv_demo_/i, name: "Demo invoice ID" },
|
|
40
|
+
{ pattern: /user_demo_/i, name: "Demo user ID" },
|
|
41
|
+
{ pattern: /cus_demo_/i, name: "Demo customer ID" },
|
|
42
|
+
{ pattern: /sub_demo_/i, name: "Demo subscription ID" },
|
|
43
|
+
{ pattern: /sk_test_/i, name: "Test Stripe key" },
|
|
44
|
+
{ pattern: /pk_test_/i, name: "Test Stripe public key" },
|
|
45
|
+
{ pattern: /"success":\s*true.*"demo"/i, name: "Demo success response" },
|
|
46
|
+
{ pattern: /lorem\s+ipsum/i, name: "Lorem ipsum placeholder" },
|
|
47
|
+
{ pattern: /john\.doe|jane\.doe/i, name: "Placeholder name" },
|
|
48
|
+
{ pattern: /user@example\.com/i, name: "Placeholder email" },
|
|
49
|
+
{ pattern: /placeholder\.(com|jpg|png)/i, name: "Placeholder domain/image" },
|
|
50
|
+
{
|
|
51
|
+
pattern: /"id":\s*("demo"|"test"|"fake"|1234567890)/i,
|
|
52
|
+
name: "Fake ID pattern",
|
|
53
|
+
},
|
|
54
|
+
{ pattern: /"status":\s*"simulated"/i, name: "Simulated status" },
|
|
55
|
+
{ pattern: /"mock":\s*true/i, name: "Mock flag enabled" },
|
|
56
|
+
{ pattern: /"isDemo":\s*true/i, name: "Demo mode flag" },
|
|
57
|
+
];
|
|
58
|
+
const SILENT_FALLBACK_PATTERNS = [
|
|
59
|
+
{
|
|
60
|
+
pattern: /"error":\s*null.*"data":\s*\[\]/i,
|
|
61
|
+
name: "Empty success on error",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
pattern: /catch.*return\s*\{\s*success:\s*true/i,
|
|
65
|
+
name: "Success on catch",
|
|
66
|
+
},
|
|
67
|
+
{ pattern: /"fallback":\s*true/i, name: "Fallback flag" },
|
|
68
|
+
];
|
|
69
|
+
exports.DEFAULT_FAKE_PATTERNS = [
|
|
70
|
+
// Fake domain patterns
|
|
71
|
+
{
|
|
72
|
+
id: "fake-api-domain",
|
|
73
|
+
name: "Fake API Domain",
|
|
74
|
+
description: "Request to a mock/staging/localhost API domain",
|
|
75
|
+
severity: "critical",
|
|
76
|
+
detect: (item) => {
|
|
77
|
+
if (item.type !== "request") return false;
|
|
78
|
+
return FAKE_DOMAIN_PATTERNS.some((p) => p.test(item.url));
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
// Demo response patterns
|
|
82
|
+
{
|
|
83
|
+
id: "demo-response-data",
|
|
84
|
+
name: "Demo Response Data",
|
|
85
|
+
description: "Response contains demo/placeholder data",
|
|
86
|
+
severity: "critical",
|
|
87
|
+
detect: (item) => {
|
|
88
|
+
if (item.type !== "response" || !item.body) return false;
|
|
89
|
+
return FAKE_RESPONSE_PATTERNS.some(({ pattern }) =>
|
|
90
|
+
pattern.test(item.body),
|
|
91
|
+
);
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
// Silent fallback
|
|
95
|
+
{
|
|
96
|
+
id: "silent-fallback-success",
|
|
97
|
+
name: "Silent Fallback Success",
|
|
98
|
+
description:
|
|
99
|
+
"Code silently returns success on error (catch returns default)",
|
|
100
|
+
severity: "warning",
|
|
101
|
+
detect: (item) => {
|
|
102
|
+
if (item.type !== "response" || !item.body) return false;
|
|
103
|
+
return SILENT_FALLBACK_PATTERNS.some(({ pattern }) =>
|
|
104
|
+
pattern.test(item.body),
|
|
105
|
+
);
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
// HTTP status checks
|
|
109
|
+
{
|
|
110
|
+
id: "mock-status-code",
|
|
111
|
+
name: "Mock Status Code",
|
|
112
|
+
description: "Response with unusual status indicating mock (418, 999)",
|
|
113
|
+
severity: "warning",
|
|
114
|
+
detect: (item) => {
|
|
115
|
+
if (item.type !== "response") return false;
|
|
116
|
+
return [418, 999, 0].includes(item.status);
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
// Test keys in production
|
|
120
|
+
{
|
|
121
|
+
id: "test-api-keys",
|
|
122
|
+
name: "Test API Keys",
|
|
123
|
+
description: "Test/demo API keys detected in request or response",
|
|
124
|
+
severity: "critical",
|
|
125
|
+
detect: (item) => {
|
|
126
|
+
const content =
|
|
127
|
+
item.type === "request"
|
|
128
|
+
? JSON.stringify(item.headers) + (item.body || "")
|
|
129
|
+
: item.body || "";
|
|
130
|
+
return /sk_test_|pk_test_|api_key_test|demo_api_key/i.test(content);
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
// Billing simulation
|
|
134
|
+
{
|
|
135
|
+
id: "simulated-billing",
|
|
136
|
+
name: "Simulated Billing",
|
|
137
|
+
description: "Billing/payment response appears to be simulated",
|
|
138
|
+
severity: "critical",
|
|
139
|
+
detect: (item) => {
|
|
140
|
+
if (item.type !== "response" || !item.body) return false;
|
|
141
|
+
const billingUrls = /stripe|billing|payment|checkout|subscription/i;
|
|
142
|
+
const isBillingEndpoint = billingUrls.test(item.url);
|
|
143
|
+
const hasDemoData = /demo|test|simulate|fake|mock/i.test(item.body);
|
|
144
|
+
return isBillingEndpoint && hasDemoData;
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
class RealityScanner {
|
|
149
|
+
config;
|
|
150
|
+
replay = [];
|
|
151
|
+
detections = [];
|
|
152
|
+
requests = [];
|
|
153
|
+
responses = [];
|
|
154
|
+
actions = [];
|
|
155
|
+
constructor(config = {}) {
|
|
156
|
+
this.config = {
|
|
157
|
+
timeout: 30000,
|
|
158
|
+
patterns: exports.DEFAULT_FAKE_PATTERNS,
|
|
159
|
+
screenshotOnDetection: true,
|
|
160
|
+
headless: true,
|
|
161
|
+
...config,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Generate Playwright test code for Reality Mode scanning
|
|
166
|
+
*/
|
|
167
|
+
generatePlaywrightTest(config) {
|
|
168
|
+
const { baseUrl, clickPaths, outputDir } = config;
|
|
169
|
+
return `/**
|
|
170
|
+
* Reality Mode - Auto-generated Playwright Test
|
|
171
|
+
*
|
|
172
|
+
* This test runs your app and intercepts all network calls to detect fake data.
|
|
173
|
+
* Generated by Guardrail Reality Mode.
|
|
174
|
+
*/
|
|
175
|
+
|
|
176
|
+
import { test, expect, Page, Request, Response } from '@playwright/test';
|
|
177
|
+
import * as fs from 'fs';
|
|
178
|
+
import * as path from 'path';
|
|
179
|
+
|
|
180
|
+
const FAKE_DOMAIN_PATTERNS = ${JSON.stringify(
|
|
181
|
+
FAKE_DOMAIN_PATTERNS.map((r) => r.source),
|
|
182
|
+
null,
|
|
183
|
+
2,
|
|
184
|
+
)};
|
|
185
|
+
|
|
186
|
+
const FAKE_RESPONSE_PATTERNS = ${JSON.stringify(FAKE_RESPONSE_PATTERNS, null, 2)};
|
|
187
|
+
|
|
188
|
+
interface Detection {
|
|
189
|
+
type: 'fake-domain' | 'demo-data' | 'simulated-billing' | 'test-keys';
|
|
190
|
+
url: string;
|
|
191
|
+
evidence: string;
|
|
192
|
+
timestamp: number;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
interface ReplayStep {
|
|
196
|
+
timestamp: number;
|
|
197
|
+
type: 'request' | 'response' | 'action';
|
|
198
|
+
data: any;
|
|
199
|
+
detections: Detection[];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
test.describe('Reality Mode Scan', () => {
|
|
203
|
+
let detections: Detection[] = [];
|
|
204
|
+
let replay: ReplayStep[] = [];
|
|
205
|
+
|
|
206
|
+
test.beforeEach(async ({ page }) => {
|
|
207
|
+
detections = [];
|
|
208
|
+
replay = [];
|
|
209
|
+
|
|
210
|
+
// Intercept all network requests
|
|
211
|
+
page.on('request', (request: Request) => {
|
|
212
|
+
const url = request.url();
|
|
213
|
+
const step: ReplayStep = {
|
|
214
|
+
timestamp: Date.now(),
|
|
215
|
+
type: 'request',
|
|
216
|
+
data: { url, method: request.method() },
|
|
217
|
+
detections: []
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Check for fake domains
|
|
221
|
+
for (const pattern of FAKE_DOMAIN_PATTERNS) {
|
|
222
|
+
if (new RegExp(pattern, 'i').test(url)) {
|
|
223
|
+
const detection: Detection = {
|
|
224
|
+
type: 'fake-domain',
|
|
225
|
+
url,
|
|
226
|
+
evidence: \`URL matches fake domain pattern: \${pattern}\`,
|
|
227
|
+
timestamp: Date.now()
|
|
228
|
+
};
|
|
229
|
+
step.detections.push(detection);
|
|
230
|
+
detections.push(detection);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
replay.push(step);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
page.on('response', async (response: Response) => {
|
|
238
|
+
const url = response.url();
|
|
239
|
+
let body = '';
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
body = await response.text();
|
|
243
|
+
} catch (e) {
|
|
244
|
+
// Some responses can't be read
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const step: ReplayStep = {
|
|
248
|
+
timestamp: Date.now(),
|
|
249
|
+
type: 'response',
|
|
250
|
+
data: { url, status: response.status(), bodyPreview: body.slice(0, 500) },
|
|
251
|
+
detections: []
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// Check for demo data patterns
|
|
255
|
+
for (const { pattern, name } of FAKE_RESPONSE_PATTERNS) {
|
|
256
|
+
if (new RegExp(pattern.source || pattern, 'i').test(body)) {
|
|
257
|
+
const detection: Detection = {
|
|
258
|
+
type: 'demo-data',
|
|
259
|
+
url,
|
|
260
|
+
evidence: \`Response contains \${name}\`,
|
|
261
|
+
timestamp: Date.now()
|
|
262
|
+
};
|
|
263
|
+
step.detections.push(detection);
|
|
264
|
+
detections.push(detection);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
replay.push(step);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('should detect fake data in production flow', async ({ page }) => {
|
|
273
|
+
// Navigate to the app
|
|
274
|
+
await page.goto('${baseUrl}');
|
|
275
|
+
|
|
276
|
+
replay.push({
|
|
277
|
+
timestamp: Date.now(),
|
|
278
|
+
type: 'action',
|
|
279
|
+
data: { type: 'navigation', url: '${baseUrl}' },
|
|
280
|
+
detections: []
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Execute click paths
|
|
284
|
+
const clickPaths = ${JSON.stringify(clickPaths, null, 4)};
|
|
285
|
+
|
|
286
|
+
for (const path of clickPaths) {
|
|
287
|
+
for (const selector of path) {
|
|
288
|
+
try {
|
|
289
|
+
await page.waitForSelector(selector, { timeout: 5000 });
|
|
290
|
+
await page.click(selector);
|
|
291
|
+
|
|
292
|
+
replay.push({
|
|
293
|
+
timestamp: Date.now(),
|
|
294
|
+
type: 'action',
|
|
295
|
+
data: { type: 'click', selector },
|
|
296
|
+
detections: []
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Wait for network activity to settle
|
|
300
|
+
await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => {});
|
|
301
|
+
} catch (e) {
|
|
302
|
+
// Selector not found, skip
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Save results
|
|
308
|
+
const result = {
|
|
309
|
+
verdict: detections.length > 0 ? 'fake' : 'real',
|
|
310
|
+
score: Math.max(0, 100 - detections.length * 10),
|
|
311
|
+
detections,
|
|
312
|
+
replay,
|
|
313
|
+
summary: {
|
|
314
|
+
totalRequests: replay.filter(r => r.type === 'request').length,
|
|
315
|
+
fakeRequests: detections.filter(d => d.type === 'fake-domain').length,
|
|
316
|
+
totalActions: replay.filter(r => r.type === 'action').length,
|
|
317
|
+
criticalIssues: detections.length
|
|
318
|
+
},
|
|
319
|
+
timestamp: new Date().toISOString()
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Write results
|
|
323
|
+
const outputDir = '${outputDir.replace(/\\/g, "\\\\")}';
|
|
324
|
+
await fs.promises.mkdir(outputDir, { recursive: true });
|
|
325
|
+
await fs.promises.writeFile(
|
|
326
|
+
path.join(outputDir, 'reality-mode-result.json'),
|
|
327
|
+
JSON.stringify(result, null, 2)
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// Generate human-readable report
|
|
331
|
+
const report = generateReport(result);
|
|
332
|
+
await fs.promises.writeFile(
|
|
333
|
+
path.join(outputDir, 'reality-mode-report.txt'),
|
|
334
|
+
report
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
// Fail if fake data detected
|
|
338
|
+
expect(detections.length, \`Found \${detections.length} fake data issues\`).toBe(0);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
function generateReport(result: any): string {
|
|
343
|
+
const lines: string[] = [];
|
|
344
|
+
|
|
345
|
+
lines.push('╔══════════════════════════════════════════════════════════════╗');
|
|
346
|
+
lines.push('║ 🔍 Reality Mode Scan Results 🔍 ║');
|
|
347
|
+
lines.push('╚══════════════════════════════════════════════════════════════╝');
|
|
348
|
+
lines.push('');
|
|
349
|
+
|
|
350
|
+
if (result.verdict === 'real') {
|
|
351
|
+
lines.push('✅ VERDICT: REAL - No fake data detected!');
|
|
352
|
+
lines.push('');
|
|
353
|
+
lines.push(' Your app is shipping real features.');
|
|
354
|
+
} else {
|
|
355
|
+
lines.push('❌ VERDICT: FAKE - Fake data detected!');
|
|
356
|
+
lines.push('');
|
|
357
|
+
lines.push(\` Found \${result.detections.length} issues that indicate fake/mock data.\`);
|
|
358
|
+
lines.push('');
|
|
359
|
+
lines.push('─'.repeat(64));
|
|
360
|
+
lines.push('');
|
|
361
|
+
lines.push('DETECTIONS:');
|
|
362
|
+
lines.push('');
|
|
363
|
+
|
|
364
|
+
for (const detection of result.detections) {
|
|
365
|
+
lines.push(\` ❌ \${detection.type.toUpperCase()}\`);
|
|
366
|
+
lines.push(\` URL: \${detection.url}\`);
|
|
367
|
+
lines.push(\` Evidence: \${detection.evidence}\`);
|
|
368
|
+
lines.push('');
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
lines.push('─'.repeat(64));
|
|
373
|
+
lines.push(\`Score: \${result.score}/100\`);
|
|
374
|
+
lines.push(\`Total requests intercepted: \${result.summary.totalRequests}\`);
|
|
375
|
+
lines.push(\`Actions performed: \${result.summary.totalActions}\`);
|
|
376
|
+
lines.push(\`Generated: \${result.timestamp}\`);
|
|
377
|
+
|
|
378
|
+
return lines.join('\\n');
|
|
379
|
+
}
|
|
380
|
+
`;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Analyze intercepted network traffic for fake patterns
|
|
384
|
+
*/
|
|
385
|
+
analyzeTraffic(requests, responses) {
|
|
386
|
+
const detections = [];
|
|
387
|
+
const patterns = this.config.patterns || exports.DEFAULT_FAKE_PATTERNS;
|
|
388
|
+
for (const request of requests) {
|
|
389
|
+
for (const pattern of patterns) {
|
|
390
|
+
if (pattern.detect(request)) {
|
|
391
|
+
detections.push({
|
|
392
|
+
pattern,
|
|
393
|
+
request,
|
|
394
|
+
timestamp: request.timestamp,
|
|
395
|
+
evidence: this.getEvidence(request, pattern),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
for (const response of responses) {
|
|
401
|
+
for (const pattern of patterns) {
|
|
402
|
+
if (pattern.detect(response)) {
|
|
403
|
+
detections.push({
|
|
404
|
+
pattern,
|
|
405
|
+
response,
|
|
406
|
+
timestamp: response.timestamp,
|
|
407
|
+
evidence: this.getEvidence(response, pattern),
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return detections;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Generate evidence description for a detection
|
|
416
|
+
*/
|
|
417
|
+
getEvidence(item, pattern) {
|
|
418
|
+
if (item.type === "request") {
|
|
419
|
+
if (pattern.id === "fake-api-domain") {
|
|
420
|
+
const matched = FAKE_DOMAIN_PATTERNS.find((p) => p.test(item.url));
|
|
421
|
+
return `Request URL "${item.url}" matches fake domain pattern`;
|
|
422
|
+
}
|
|
423
|
+
return `Request to ${item.url} flagged by ${pattern.name}`;
|
|
424
|
+
} else {
|
|
425
|
+
if (pattern.id === "demo-response-data" && item.body) {
|
|
426
|
+
const matched = FAKE_RESPONSE_PATTERNS.find(({ pattern }) =>
|
|
427
|
+
pattern.test(item.body),
|
|
428
|
+
);
|
|
429
|
+
return `Response contains ${matched?.name || "demo data"}`;
|
|
430
|
+
}
|
|
431
|
+
return `Response from ${item.url} flagged by ${pattern.name}`;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Generate a human-readable Reality Mode report
|
|
436
|
+
*/
|
|
437
|
+
generateReport(result) {
|
|
438
|
+
const lines = [];
|
|
439
|
+
lines.push(
|
|
440
|
+
"╔══════════════════════════════════════════════════════════════╗",
|
|
441
|
+
);
|
|
442
|
+
lines.push(
|
|
443
|
+
"║ 🔍 Reality Mode Scan Results 🔍 ║",
|
|
444
|
+
);
|
|
445
|
+
lines.push(
|
|
446
|
+
"╚══════════════════════════════════════════════════════════════╝",
|
|
447
|
+
);
|
|
448
|
+
lines.push("");
|
|
449
|
+
const verdictEmoji =
|
|
450
|
+
result.verdict === "real"
|
|
451
|
+
? "✅"
|
|
452
|
+
: result.verdict === "fake"
|
|
453
|
+
? "❌"
|
|
454
|
+
: "⚠️";
|
|
455
|
+
const verdictText = result.verdict.toUpperCase();
|
|
456
|
+
lines.push(`${verdictEmoji} VERDICT: ${verdictText}`);
|
|
457
|
+
lines.push(` Reality Score: ${result.score}/100`);
|
|
458
|
+
lines.push("");
|
|
459
|
+
if (result.verdict === "real") {
|
|
460
|
+
lines.push(" 🎉 Congratulations! Your app is shipping real features.");
|
|
461
|
+
lines.push(
|
|
462
|
+
" No fake data, mock APIs, or placeholder content detected.",
|
|
463
|
+
);
|
|
464
|
+
} else {
|
|
465
|
+
lines.push(` Found ${result.summary.criticalIssues} critical issues`);
|
|
466
|
+
lines.push(` Found ${result.summary.warnings} warnings`);
|
|
467
|
+
lines.push("");
|
|
468
|
+
lines.push("─".repeat(64));
|
|
469
|
+
lines.push("");
|
|
470
|
+
lines.push("DETECTIONS:");
|
|
471
|
+
lines.push("");
|
|
472
|
+
for (const detection of result.detections) {
|
|
473
|
+
const icon = detection.pattern.severity === "critical" ? "🚨" : "⚠️";
|
|
474
|
+
lines.push(`${icon} ${detection.pattern.name}`);
|
|
475
|
+
lines.push(` ${detection.pattern.description}`);
|
|
476
|
+
lines.push(` Evidence: ${detection.evidence}`);
|
|
477
|
+
if (detection.request) {
|
|
478
|
+
lines.push(` URL: ${detection.request.url}`);
|
|
479
|
+
}
|
|
480
|
+
if (detection.response) {
|
|
481
|
+
lines.push(` URL: ${detection.response.url}`);
|
|
482
|
+
}
|
|
483
|
+
lines.push("");
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
lines.push("─".repeat(64));
|
|
487
|
+
lines.push("");
|
|
488
|
+
lines.push("SUMMARY:");
|
|
489
|
+
lines.push(
|
|
490
|
+
` Total requests intercepted: ${result.summary.totalRequests}`,
|
|
491
|
+
);
|
|
492
|
+
lines.push(` Fake/mock requests: ${result.summary.fakeRequests}`);
|
|
493
|
+
lines.push(` User actions performed: ${result.summary.totalActions}`);
|
|
494
|
+
lines.push(` Scan duration: ${result.duration}ms`);
|
|
495
|
+
lines.push("");
|
|
496
|
+
lines.push(`Generated: ${result.timestamp}`);
|
|
497
|
+
return lines.join("\n");
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Generate common click paths for testing
|
|
501
|
+
*/
|
|
502
|
+
generateDefaultClickPaths() {
|
|
503
|
+
return [
|
|
504
|
+
// Auth flow
|
|
505
|
+
['[data-testid="login-button"]', '[data-testid="signup-button"]'],
|
|
506
|
+
// Navigation
|
|
507
|
+
["nav a", "header a", '[role="navigation"] a'],
|
|
508
|
+
// Main actions
|
|
509
|
+
[
|
|
510
|
+
'button[type="submit"]',
|
|
511
|
+
".cta-button",
|
|
512
|
+
'[data-testid="primary-action"]',
|
|
513
|
+
],
|
|
514
|
+
// Dashboard/billing
|
|
515
|
+
['[href*="dashboard"]', '[href*="billing"]', '[href*="settings"]'],
|
|
516
|
+
// Checkout flow
|
|
517
|
+
[
|
|
518
|
+
'[data-testid="add-to-cart"]',
|
|
519
|
+
'[data-testid="checkout"]',
|
|
520
|
+
'[data-testid="pay"]',
|
|
521
|
+
],
|
|
522
|
+
];
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
exports.RealityScanner = RealityScanner;
|
|
526
|
+
exports.realityScanner = new RealityScanner();
|