@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/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';
@@ -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);