@surbee/cipher 0.1.0 → 0.2.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/README.md +95 -0
- package/dist/checks/index.d.mts +215 -0
- package/dist/checks/index.d.ts +215 -0
- package/dist/checks/index.js +1157 -0
- package/dist/checks/index.mjs +60 -0
- package/dist/chunk-P2MIOVFQ.mjs +1104 -0
- package/dist/index.d.mts +38 -244
- package/dist/index.d.ts +38 -244
- package/dist/index.js +1716 -35
- package/dist/index.mjs +649 -35
- package/dist/types-C8t_T3bP.d.mts +251 -0
- package/dist/types-C8t_T3bP.d.ts +251 -0
- package/package.json +16 -4
- package/src/checks/behavioral.ts +0 -527
- package/src/checks/content.ts +0 -372
- package/src/checks/device.ts +0 -384
- package/src/checks/index.ts +0 -59
- package/src/checks/timing.ts +0 -256
- package/src/cipher.ts +0 -225
- package/src/index.ts +0 -75
- package/src/tiers.ts +0 -507
- package/src/types.ts +0 -366
- package/test/cipher.test.ts +0 -245
- package/test/fixtures.ts +0 -627
- package/tsconfig.json +0 -20
package/src/types.ts
DELETED
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cipher SDK Types
|
|
3
|
-
*
|
|
4
|
-
* Core type definitions for the Cipher response validation system.
|
|
5
|
-
* Implementation details are hidden - the SDK communicates with Surbee's
|
|
6
|
-
* secure validation engine.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
// ============================================
|
|
10
|
-
// TIER SYSTEM
|
|
11
|
-
// ============================================
|
|
12
|
-
|
|
13
|
-
export type CipherTier = 1 | 2 | 3 | 4 | 5;
|
|
14
|
-
|
|
15
|
-
// ============================================
|
|
16
|
-
// CHECK DEFINITIONS (43 checks across 6 categories)
|
|
17
|
-
// ============================================
|
|
18
|
-
|
|
19
|
-
export type CheckId =
|
|
20
|
-
// Tier 1 - Basic behavioral (6 checks)
|
|
21
|
-
| 'rapid_completion'
|
|
22
|
-
| 'uniform_timing'
|
|
23
|
-
| 'low_interaction'
|
|
24
|
-
| 'straight_line_answers'
|
|
25
|
-
| 'impossibly_fast'
|
|
26
|
-
| 'minimal_effort'
|
|
27
|
-
// Tier 2 - Device/automation (9 checks)
|
|
28
|
-
| 'excessive_paste'
|
|
29
|
-
| 'pointer_spikes'
|
|
30
|
-
| 'webdriver_detected'
|
|
31
|
-
| 'automation_detected'
|
|
32
|
-
| 'no_plugins'
|
|
33
|
-
| 'suspicious_user_agent'
|
|
34
|
-
| 'device_fingerprint_mismatch'
|
|
35
|
-
| 'screen_anomaly'
|
|
36
|
-
| 'suspicious_pauses'
|
|
37
|
-
// Tier 3 - Enhanced behavioral (7 checks)
|
|
38
|
-
| 'robotic_typing'
|
|
39
|
-
| 'mouse_teleporting'
|
|
40
|
-
| 'no_corrections'
|
|
41
|
-
| 'excessive_tab_switching'
|
|
42
|
-
| 'window_focus_loss'
|
|
43
|
-
| 'ai_content_basic'
|
|
44
|
-
| 'contradiction_basic'
|
|
45
|
-
// Tier 4 - Advanced (8 checks)
|
|
46
|
-
| 'hover_behavior'
|
|
47
|
-
| 'scroll_patterns'
|
|
48
|
-
| 'mouse_acceleration'
|
|
49
|
-
| 'vpn_detection'
|
|
50
|
-
| 'datacenter_ip'
|
|
51
|
-
| 'plagiarism_basic'
|
|
52
|
-
| 'quality_assessment'
|
|
53
|
-
| 'semantic_analysis'
|
|
54
|
-
// Tier 5 - Maximum (13 checks)
|
|
55
|
-
| 'ai_content_full'
|
|
56
|
-
| 'contradiction_full'
|
|
57
|
-
| 'plagiarism_full'
|
|
58
|
-
| 'fraud_ring_detection'
|
|
59
|
-
| 'answer_sharing'
|
|
60
|
-
| 'coordinated_timing'
|
|
61
|
-
| 'device_sharing'
|
|
62
|
-
| 'tor_detection'
|
|
63
|
-
| 'proxy_detection'
|
|
64
|
-
| 'timezone_validation'
|
|
65
|
-
| 'baseline_deviation'
|
|
66
|
-
| 'perplexity_analysis'
|
|
67
|
-
| 'burstiness_analysis';
|
|
68
|
-
|
|
69
|
-
export interface CheckResult {
|
|
70
|
-
/** The check that was run */
|
|
71
|
-
checkId: CheckId;
|
|
72
|
-
/** Whether the check passed */
|
|
73
|
-
passed: boolean;
|
|
74
|
-
/** Suspicion score (0-1, higher = more suspicious) */
|
|
75
|
-
score: number;
|
|
76
|
-
/** Human-readable details */
|
|
77
|
-
details?: string;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ============================================
|
|
81
|
-
// INPUT TYPES
|
|
82
|
-
// ============================================
|
|
83
|
-
|
|
84
|
-
export interface ResponseInput {
|
|
85
|
-
/** The question text */
|
|
86
|
-
question: string;
|
|
87
|
-
/** The user's answer */
|
|
88
|
-
answer: string;
|
|
89
|
-
/** Question type for context */
|
|
90
|
-
questionType?: 'text' | 'multiple_choice' | 'rating' | 'scale' | 'boolean';
|
|
91
|
-
/** Time spent on this question in milliseconds */
|
|
92
|
-
responseTimeMs?: number;
|
|
93
|
-
/** Question index in the survey */
|
|
94
|
-
questionIndex?: number;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export interface ValidationInput {
|
|
98
|
-
/** Array of question/answer pairs */
|
|
99
|
-
responses: ResponseInput[];
|
|
100
|
-
/** Behavioral metrics from client-side tracking */
|
|
101
|
-
behavioralMetrics?: BehavioralMetrics;
|
|
102
|
-
/** Device/browser information */
|
|
103
|
-
deviceInfo?: DeviceInfo;
|
|
104
|
-
/** Survey context */
|
|
105
|
-
context?: SurveyContext;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export interface SurveyContext {
|
|
109
|
-
/** Survey ID for cross-respondent analysis */
|
|
110
|
-
surveyId?: string;
|
|
111
|
-
/** Expected completion time in seconds */
|
|
112
|
-
expectedDurationSeconds?: number;
|
|
113
|
-
/** Actual completion time in seconds */
|
|
114
|
-
actualDurationSeconds?: number;
|
|
115
|
-
/** Survey type for context-aware analysis */
|
|
116
|
-
surveyType?: 'nps' | 'csat' | 'research' | 'feedback' | 'quiz';
|
|
117
|
-
/** Total number of questions */
|
|
118
|
-
totalQuestions?: number;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ============================================
|
|
122
|
-
// BEHAVIORAL METRICS (from client tracker)
|
|
123
|
-
// ============================================
|
|
124
|
-
|
|
125
|
-
export interface BehavioralMetrics {
|
|
126
|
-
sessionId: string;
|
|
127
|
-
startedAt: number;
|
|
128
|
-
duration: number;
|
|
129
|
-
lastActiveAt: number;
|
|
130
|
-
|
|
131
|
-
// Mouse metrics
|
|
132
|
-
mouseMovements: MouseMovement[];
|
|
133
|
-
mouseClicks: MouseClick[];
|
|
134
|
-
mouseMovementCount: number;
|
|
135
|
-
avgMouseVelocity: number;
|
|
136
|
-
|
|
137
|
-
// Keyboard metrics
|
|
138
|
-
keystrokeDynamics: KeystrokeEvent[];
|
|
139
|
-
keypressCount: number;
|
|
140
|
-
backspaceCount: number;
|
|
141
|
-
avgKeystrokeDwell: number;
|
|
142
|
-
keystrokeVariance: number;
|
|
143
|
-
|
|
144
|
-
// Scroll metrics
|
|
145
|
-
scrollEvents: ScrollEvent[];
|
|
146
|
-
scrollEventCount: number;
|
|
147
|
-
|
|
148
|
-
// Focus metrics
|
|
149
|
-
focusEvents: FocusEvent[];
|
|
150
|
-
tabSwitchCount: number;
|
|
151
|
-
totalBlurDuration: number;
|
|
152
|
-
|
|
153
|
-
// Interaction metrics
|
|
154
|
-
pasteEvents: number;
|
|
155
|
-
copyEvents: number;
|
|
156
|
-
|
|
157
|
-
// Hover metrics (tier 3+)
|
|
158
|
-
hoverEvents: HoverEvent[];
|
|
159
|
-
|
|
160
|
-
// Question timing
|
|
161
|
-
responseTime: number[];
|
|
162
|
-
questionStartTimes: Record<string, number>;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export interface MouseMovement {
|
|
166
|
-
x: number;
|
|
167
|
-
y: number;
|
|
168
|
-
t: number;
|
|
169
|
-
velocity: number;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export interface MouseClick {
|
|
173
|
-
x: number;
|
|
174
|
-
y: number;
|
|
175
|
-
t: number;
|
|
176
|
-
hadHover: boolean;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export interface KeystrokeEvent {
|
|
180
|
-
key: string;
|
|
181
|
-
downAt: number;
|
|
182
|
-
upAt: number;
|
|
183
|
-
dwell: number;
|
|
184
|
-
flightTime: number;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export interface ScrollEvent {
|
|
188
|
-
y: number;
|
|
189
|
-
t: number;
|
|
190
|
-
velocity: number;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export interface FocusEvent {
|
|
194
|
-
type: 'focus' | 'blur' | 'hidden' | 'visible';
|
|
195
|
-
t: number;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export interface HoverEvent {
|
|
199
|
-
element: string;
|
|
200
|
-
duration: number;
|
|
201
|
-
t: number;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// ============================================
|
|
205
|
-
// DEVICE INFO
|
|
206
|
-
// ============================================
|
|
207
|
-
|
|
208
|
-
export interface DeviceInfo {
|
|
209
|
-
userAgent: string;
|
|
210
|
-
platform: string;
|
|
211
|
-
language: string;
|
|
212
|
-
languages: string[];
|
|
213
|
-
timezone: string;
|
|
214
|
-
timezoneOffset: number;
|
|
215
|
-
screenWidth: number;
|
|
216
|
-
screenHeight: number;
|
|
217
|
-
screenAvailWidth: number;
|
|
218
|
-
screenAvailHeight: number;
|
|
219
|
-
colorDepth: number;
|
|
220
|
-
pixelRatio: number;
|
|
221
|
-
touchSupport: boolean;
|
|
222
|
-
maxTouchPoints: number;
|
|
223
|
-
hardwareConcurrency: number;
|
|
224
|
-
deviceMemory: number;
|
|
225
|
-
cookiesEnabled: boolean;
|
|
226
|
-
webDriver: boolean;
|
|
227
|
-
automationDetected: boolean;
|
|
228
|
-
canvasFingerprint: string | null;
|
|
229
|
-
webglVendor: string | null;
|
|
230
|
-
webglRenderer: string | null;
|
|
231
|
-
pluginCount: number;
|
|
232
|
-
collectedAt: number;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// ============================================
|
|
236
|
-
// VALIDATION RESULT
|
|
237
|
-
// ============================================
|
|
238
|
-
|
|
239
|
-
export interface ValidationResult {
|
|
240
|
-
/** Overall quality score (0-1, higher = better quality) */
|
|
241
|
-
score: number;
|
|
242
|
-
/** Whether the response passed validation */
|
|
243
|
-
passed: boolean;
|
|
244
|
-
/** Recommendation for the response */
|
|
245
|
-
recommendation: 'keep' | 'review' | 'discard';
|
|
246
|
-
/** Confidence in the assessment (0-1) */
|
|
247
|
-
confidence: number;
|
|
248
|
-
/** Flags that were triggered */
|
|
249
|
-
flags: string[];
|
|
250
|
-
/** Human-readable summary analysis */
|
|
251
|
-
summary: ValidationSummary;
|
|
252
|
-
/** Detailed check results */
|
|
253
|
-
checks: CheckResult[];
|
|
254
|
-
/** Processing metadata */
|
|
255
|
-
meta: ValidationMeta;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
export interface ValidationSummary {
|
|
259
|
-
/** Short verdict (e.g., "Likely legitimate", "Suspected bot") */
|
|
260
|
-
verdict: string;
|
|
261
|
-
/** List of issues found */
|
|
262
|
-
issues: string[];
|
|
263
|
-
/** List of positive signals */
|
|
264
|
-
positives: string[];
|
|
265
|
-
/** Actionable suggestion for the user */
|
|
266
|
-
suggestion: string;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
export interface ValidationMeta {
|
|
270
|
-
/** Tier used for validation */
|
|
271
|
-
tier: CipherTier;
|
|
272
|
-
/** Processing time in milliseconds */
|
|
273
|
-
processingTimeMs: number;
|
|
274
|
-
/** Number of checks run */
|
|
275
|
-
checksRun: number;
|
|
276
|
-
/** Number of checks passed */
|
|
277
|
-
checksPassed: number;
|
|
278
|
-
/** Request ID for support */
|
|
279
|
-
requestId: string;
|
|
280
|
-
/** Timestamp */
|
|
281
|
-
timestamp: number;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// ============================================
|
|
285
|
-
// CONFIGURATION
|
|
286
|
-
// ============================================
|
|
287
|
-
|
|
288
|
-
export interface CipherConfig {
|
|
289
|
-
/**
|
|
290
|
-
* API key from Surbee dashboard (Settings > API Keys)
|
|
291
|
-
* Format: cipher_sk_...
|
|
292
|
-
*/
|
|
293
|
-
apiKey: string;
|
|
294
|
-
/**
|
|
295
|
-
* Validation tier (1-5)
|
|
296
|
-
* - Tier 1-2: Basic checks (~free)
|
|
297
|
-
* - Tier 3: Enhanced analysis
|
|
298
|
-
* - Tier 4: Advanced validation
|
|
299
|
-
* - Tier 5: Maximum accuracy
|
|
300
|
-
*/
|
|
301
|
-
tier?: CipherTier;
|
|
302
|
-
/** Custom thresholds */
|
|
303
|
-
thresholds?: {
|
|
304
|
-
/** Score below this = fail (default: 0.4) */
|
|
305
|
-
fail?: number;
|
|
306
|
-
/** Score below this = review (default: 0.7) */
|
|
307
|
-
review?: number;
|
|
308
|
-
};
|
|
309
|
-
/** Enable debug logging */
|
|
310
|
-
debug?: boolean;
|
|
311
|
-
/** Custom API endpoint (for enterprise/self-hosted) */
|
|
312
|
-
endpoint?: string;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// ============================================
|
|
316
|
-
// BATCH OPERATIONS
|
|
317
|
-
// ============================================
|
|
318
|
-
|
|
319
|
-
export interface BatchValidationInput {
|
|
320
|
-
submissions: ValidationInput[];
|
|
321
|
-
/** Enable cross-submission fraud detection (tier 5) */
|
|
322
|
-
crossAnalysis?: boolean;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export interface BatchValidationResult {
|
|
326
|
-
results: ValidationResult[];
|
|
327
|
-
summary: {
|
|
328
|
-
total: number;
|
|
329
|
-
passed: number;
|
|
330
|
-
review: number;
|
|
331
|
-
failed: number;
|
|
332
|
-
avgScore: number;
|
|
333
|
-
};
|
|
334
|
-
/** Cross-submission fraud indicators (tier 5 only) */
|
|
335
|
-
fraudIndicators?: FraudIndicators;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
export interface FraudIndicators {
|
|
339
|
-
/** Response IDs with duplicate answers */
|
|
340
|
-
duplicateAnswers: string[];
|
|
341
|
-
/** Whether coordinated timing was detected */
|
|
342
|
-
coordinatedTiming: boolean;
|
|
343
|
-
/** Whether device sharing was detected */
|
|
344
|
-
deviceSharing: boolean;
|
|
345
|
-
/** Fraud ring score (0-1) */
|
|
346
|
-
fraudRingScore: number;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// ============================================
|
|
350
|
-
// ERROR TYPES
|
|
351
|
-
// ============================================
|
|
352
|
-
|
|
353
|
-
export interface CipherError {
|
|
354
|
-
code: CipherErrorCode;
|
|
355
|
-
message: string;
|
|
356
|
-
details?: Record<string, unknown>;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
export type CipherErrorCode =
|
|
360
|
-
| 'INVALID_API_KEY'
|
|
361
|
-
| 'RATE_LIMITED'
|
|
362
|
-
| 'INSUFFICIENT_CREDITS'
|
|
363
|
-
| 'INVALID_INPUT'
|
|
364
|
-
| 'TIER_NOT_AVAILABLE'
|
|
365
|
-
| 'SERVER_ERROR'
|
|
366
|
-
| 'NETWORK_ERROR';
|
package/test/cipher.test.ts
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cipher SDK Test Suite
|
|
3
|
-
*
|
|
4
|
-
* Run with: npx tsx test/cipher.test.ts
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { Cipher } from '../src/cipher';
|
|
8
|
-
import {
|
|
9
|
-
legitimateSubmission,
|
|
10
|
-
spamSubmission,
|
|
11
|
-
botSubmission,
|
|
12
|
-
straightLiningSubmission,
|
|
13
|
-
aiAssistedSubmission,
|
|
14
|
-
} from './fixtures';
|
|
15
|
-
|
|
16
|
-
// Colors for terminal output
|
|
17
|
-
const colors = {
|
|
18
|
-
reset: '\x1b[0m',
|
|
19
|
-
green: '\x1b[32m',
|
|
20
|
-
red: '\x1b[31m',
|
|
21
|
-
yellow: '\x1b[33m',
|
|
22
|
-
blue: '\x1b[34m',
|
|
23
|
-
cyan: '\x1b[36m',
|
|
24
|
-
dim: '\x1b[2m',
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
function log(message: string, color?: keyof typeof colors) {
|
|
28
|
-
if (color) {
|
|
29
|
-
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
30
|
-
} else {
|
|
31
|
-
console.log(message);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function printResult(name: string, result: any) {
|
|
36
|
-
const passIcon = result.passed ? '✓' : '✗';
|
|
37
|
-
const passColor = result.passed ? 'green' : 'red';
|
|
38
|
-
|
|
39
|
-
log(`\n${passIcon} ${name}`, passColor);
|
|
40
|
-
log(` Score: ${(result.score * 100).toFixed(1)}%`, result.score >= 0.7 ? 'green' : result.score >= 0.4 ? 'yellow' : 'red');
|
|
41
|
-
log(` Recommendation: ${result.recommendation}`, result.recommendation === 'keep' ? 'green' : result.recommendation === 'review' ? 'yellow' : 'red');
|
|
42
|
-
log(` Confidence: ${(result.confidence * 100).toFixed(0)}%`, 'dim');
|
|
43
|
-
|
|
44
|
-
if (result.flags.length > 0) {
|
|
45
|
-
log(` Flags: ${result.flags.join(', ')}`, 'yellow');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (result.reasoning) {
|
|
49
|
-
log(` Reasoning: ${result.reasoning}`, 'dim');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
log(` Processing: ${result.meta.processingTimeMs}ms | Checks: ${result.meta.checksPassed}/${result.meta.checksRun} passed`, 'dim');
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function printCheckDetails(checks: any[]) {
|
|
56
|
-
const failed = checks.filter(c => !c.passed);
|
|
57
|
-
const suspicious = checks.filter(c => c.passed && c.score > 0.3);
|
|
58
|
-
|
|
59
|
-
if (failed.length > 0) {
|
|
60
|
-
log('\n Failed checks:', 'red');
|
|
61
|
-
for (const check of failed) {
|
|
62
|
-
log(` • ${check.checkId}: ${check.details || 'Failed'} (score: ${check.score.toFixed(2)})`, 'red');
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (suspicious.length > 0) {
|
|
67
|
-
log('\n Suspicious (passed but flagged):', 'yellow');
|
|
68
|
-
for (const check of suspicious) {
|
|
69
|
-
log(` • ${check.checkId}: ${check.details || 'Suspicious'} (score: ${check.score.toFixed(2)})`, 'yellow');
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function runTests() {
|
|
75
|
-
log('\n╔════════════════════════════════════════════════════════════╗', 'cyan');
|
|
76
|
-
log('║ CIPHER SDK TEST SUITE ║', 'cyan');
|
|
77
|
-
log('╚════════════════════════════════════════════════════════════╝', 'cyan');
|
|
78
|
-
|
|
79
|
-
// ==========================================================================
|
|
80
|
-
// TIER 1 TESTS (Basic offline)
|
|
81
|
-
// ==========================================================================
|
|
82
|
-
log('\n\n━━━ TIER 1 (Basic Offline) ━━━', 'blue');
|
|
83
|
-
|
|
84
|
-
const tier1 = new Cipher({ tier: 1, offline: true, debug: false });
|
|
85
|
-
|
|
86
|
-
log('\nTier 1 Info:', 'dim');
|
|
87
|
-
const tier1Info = tier1.getTierInfo();
|
|
88
|
-
log(` Name: ${tier1Info.name}`, 'dim');
|
|
89
|
-
log(` Checks: ${tier1Info.checks.length}`, 'dim');
|
|
90
|
-
log(` Cost: $${tier1.estimateCost()} per response`, 'dim');
|
|
91
|
-
|
|
92
|
-
// Test 1.1: Legitimate submission
|
|
93
|
-
const t1_legitimate = tier1.validateSync(legitimateSubmission);
|
|
94
|
-
printResult('Legitimate Submission', t1_legitimate);
|
|
95
|
-
|
|
96
|
-
// Test 1.2: Spam submission
|
|
97
|
-
const t1_spam = tier1.validateSync(spamSubmission);
|
|
98
|
-
printResult('Spam Submission', t1_spam);
|
|
99
|
-
printCheckDetails(t1_spam.checks);
|
|
100
|
-
|
|
101
|
-
// Test 1.3: Bot submission
|
|
102
|
-
const t1_bot = tier1.validateSync(botSubmission);
|
|
103
|
-
printResult('Bot Submission', t1_bot);
|
|
104
|
-
printCheckDetails(t1_bot.checks);
|
|
105
|
-
|
|
106
|
-
// ==========================================================================
|
|
107
|
-
// TIER 2 TESTS (Advanced offline)
|
|
108
|
-
// ==========================================================================
|
|
109
|
-
log('\n\n━━━ TIER 2 (Advanced Offline) ━━━', 'blue');
|
|
110
|
-
|
|
111
|
-
const tier2 = new Cipher({ tier: 2, offline: true, debug: false });
|
|
112
|
-
|
|
113
|
-
log('\nTier 2 Info:', 'dim');
|
|
114
|
-
const tier2Info = tier2.getTierInfo();
|
|
115
|
-
log(` Name: ${tier2Info.name}`, 'dim');
|
|
116
|
-
log(` Checks: ${tier2Info.checks.length}`, 'dim');
|
|
117
|
-
log(` Cost: $${tier2.estimateCost()} per response`, 'dim');
|
|
118
|
-
|
|
119
|
-
// Test 2.1: Legitimate submission
|
|
120
|
-
const t2_legitimate = tier2.validateSync(legitimateSubmission);
|
|
121
|
-
printResult('Legitimate Submission', t2_legitimate);
|
|
122
|
-
|
|
123
|
-
// Test 2.2: Straight-lining
|
|
124
|
-
const t2_straightline = tier2.validateSync(straightLiningSubmission);
|
|
125
|
-
printResult('Straight-lining Submission', t2_straightline);
|
|
126
|
-
printCheckDetails(t2_straightline.checks);
|
|
127
|
-
|
|
128
|
-
// Test 2.3: AI-assisted (paste heavy)
|
|
129
|
-
const t2_aiAssisted = tier2.validateSync(aiAssistedSubmission);
|
|
130
|
-
printResult('AI-Assisted Submission (Paste Heavy)', t2_aiAssisted);
|
|
131
|
-
printCheckDetails(t2_aiAssisted.checks);
|
|
132
|
-
|
|
133
|
-
// Test 2.4: Bot with headless device
|
|
134
|
-
const t2_bot = tier2.validateSync(botSubmission);
|
|
135
|
-
printResult('Bot with Headless Browser', t2_bot);
|
|
136
|
-
printCheckDetails(t2_bot.checks);
|
|
137
|
-
|
|
138
|
-
// ==========================================================================
|
|
139
|
-
// TIER 3-5 TESTS (AI-powered - requires API key)
|
|
140
|
-
// ==========================================================================
|
|
141
|
-
log('\n\n━━━ TIER 3-5 (AI-Powered) ━━━', 'blue');
|
|
142
|
-
|
|
143
|
-
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
144
|
-
|
|
145
|
-
if (!apiKey) {
|
|
146
|
-
log('\n⚠ ANTHROPIC_API_KEY not set - skipping AI-powered tests', 'yellow');
|
|
147
|
-
log(' Set ANTHROPIC_API_KEY environment variable to test tiers 3-5', 'dim');
|
|
148
|
-
} else {
|
|
149
|
-
// Tier 3 test
|
|
150
|
-
log('\n--- Tier 3 (Claude Sonnet 4.5) ---', 'blue');
|
|
151
|
-
const tier3 = new Cipher({ tier: 3, apiKey, debug: false });
|
|
152
|
-
|
|
153
|
-
const t3_legitimate = await tier3.validate(legitimateSubmission);
|
|
154
|
-
printResult('Legitimate Submission (Tier 3)', t3_legitimate);
|
|
155
|
-
|
|
156
|
-
const t3_spam = await tier3.validate(spamSubmission);
|
|
157
|
-
printResult('Spam Submission (Tier 3)', t3_spam);
|
|
158
|
-
printCheckDetails(t3_spam.checks);
|
|
159
|
-
|
|
160
|
-
// Tier 5 test (most thorough)
|
|
161
|
-
log('\n--- Tier 5 (Claude Opus 4.5) ---', 'blue');
|
|
162
|
-
const tier5 = new Cipher({ tier: 5, apiKey, debug: false });
|
|
163
|
-
|
|
164
|
-
log('\nTier 5 Info:', 'dim');
|
|
165
|
-
const tier5Info = tier5.getTierInfo();
|
|
166
|
-
log(` Name: ${tier5Info.name}`, 'dim');
|
|
167
|
-
log(` Checks: ${tier5Info.checks.length}`, 'dim');
|
|
168
|
-
log(` Est. Cost: $${tier5.estimateCost()} per response`, 'dim');
|
|
169
|
-
|
|
170
|
-
const t5_legitimate = await tier5.validate(legitimateSubmission);
|
|
171
|
-
printResult('Legitimate Submission (Tier 5)', t5_legitimate);
|
|
172
|
-
|
|
173
|
-
const t5_bot = await tier5.validate(botSubmission);
|
|
174
|
-
printResult('Bot Submission (Tier 5)', t5_bot);
|
|
175
|
-
printCheckDetails(t5_bot.checks);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ==========================================================================
|
|
179
|
-
// BATCH VALIDATION TEST
|
|
180
|
-
// ==========================================================================
|
|
181
|
-
log('\n\n━━━ BATCH VALIDATION ━━━', 'blue');
|
|
182
|
-
|
|
183
|
-
const batchCipher = new Cipher({ tier: 2, offline: true });
|
|
184
|
-
|
|
185
|
-
const batchResult = await batchCipher.validateBatch({
|
|
186
|
-
submissions: [
|
|
187
|
-
legitimateSubmission,
|
|
188
|
-
spamSubmission,
|
|
189
|
-
botSubmission,
|
|
190
|
-
straightLiningSubmission,
|
|
191
|
-
],
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
log('\nBatch Results:', 'cyan');
|
|
195
|
-
log(` Total: ${batchResult.summary.total}`, 'dim');
|
|
196
|
-
log(` Passed: ${batchResult.summary.passed}`, 'green');
|
|
197
|
-
log(` Review: ${batchResult.summary.review}`, 'yellow');
|
|
198
|
-
log(` Failed: ${batchResult.summary.failed}`, 'red');
|
|
199
|
-
log(` Avg Score: ${(batchResult.summary.avgScore * 100).toFixed(1)}%`, 'dim');
|
|
200
|
-
|
|
201
|
-
// ==========================================================================
|
|
202
|
-
// INDIVIDUAL CHECK IMPORTS TEST
|
|
203
|
-
// ==========================================================================
|
|
204
|
-
log('\n\n━━━ INDIVIDUAL CHECKS ━━━', 'blue');
|
|
205
|
-
|
|
206
|
-
// Import individual checks
|
|
207
|
-
const {
|
|
208
|
-
checkMinimalEffort,
|
|
209
|
-
checkStraightLining,
|
|
210
|
-
checkLowInteraction,
|
|
211
|
-
checkWebDriverDetected,
|
|
212
|
-
} = await import('../src/checks');
|
|
213
|
-
|
|
214
|
-
log('\nTesting individual check imports:', 'dim');
|
|
215
|
-
|
|
216
|
-
const minEffortResult = checkMinimalEffort(spamSubmission.responses);
|
|
217
|
-
log(` checkMinimalEffort: ${minEffortResult.passed ? '✓' : '✗'} (score: ${minEffortResult.score.toFixed(2)})`, minEffortResult.passed ? 'green' : 'red');
|
|
218
|
-
|
|
219
|
-
const straightLineResult = checkStraightLining(straightLiningSubmission.responses);
|
|
220
|
-
log(` checkStraightLining: ${straightLineResult.passed ? '✓' : '✗'} (score: ${straightLineResult.score.toFixed(2)})`, straightLineResult.passed ? 'green' : 'red');
|
|
221
|
-
|
|
222
|
-
const lowInteractionResult = checkLowInteraction(botSubmission.behavioralMetrics);
|
|
223
|
-
log(` checkLowInteraction: ${lowInteractionResult.passed ? '✓' : '✗'} (score: ${lowInteractionResult.score.toFixed(2)})`, lowInteractionResult.passed ? 'green' : 'red');
|
|
224
|
-
|
|
225
|
-
const webDriverResult = checkWebDriverDetected(botSubmission.deviceInfo);
|
|
226
|
-
log(` checkWebDriverDetected: ${webDriverResult.passed ? '✓' : '✗'} (score: ${webDriverResult.score.toFixed(2)})`, webDriverResult.passed ? 'green' : 'red');
|
|
227
|
-
|
|
228
|
-
// ==========================================================================
|
|
229
|
-
// SUMMARY
|
|
230
|
-
// ==========================================================================
|
|
231
|
-
log('\n\n╔════════════════════════════════════════════════════════════╗', 'cyan');
|
|
232
|
-
log('║ TEST COMPLETE ║', 'cyan');
|
|
233
|
-
log('╚════════════════════════════════════════════════════════════╝', 'cyan');
|
|
234
|
-
|
|
235
|
-
log('\nExpected results:', 'dim');
|
|
236
|
-
log(' • Legitimate submissions should PASS with high scores', 'dim');
|
|
237
|
-
log(' • Spam/bot submissions should FAIL or be flagged for review', 'dim');
|
|
238
|
-
log(' • Straight-lining should be detected', 'dim');
|
|
239
|
-
log(' • Paste-heavy behavior should be flagged', 'dim');
|
|
240
|
-
log(' • Headless browsers should be detected', 'dim');
|
|
241
|
-
log('\n');
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Run tests
|
|
245
|
-
runTests().catch(console.error);
|