Skip to content

Conversation

@IntegerAlex
Copy link
Owner

@IntegerAlex IntegerAlex commented Aug 16, 2025

PR Type

Enhancement, Tests


Description

Major hash generation stabilization: Implemented comprehensive security validation, threat detection, and entropy analysis to prevent manipulation attempts and ensure consistent fingerprint generation
Enhanced debugging and troubleshooting: Added detailed debug logging system with normalization step tracking, hash comparison utilities, and diagnostic reporting for fingerprint variations
Robust error handling and fallback management: Created comprehensive error categorization, retry logic with exponential backoff, and consistent fallback value management for improved reliability
Improved data processing: Enhanced normalization utilities for predictable fingerprint generation across different environments and browser states
Comprehensive validation engine: Added input sanitization, security checks, and configurable validation rules with strict/lenient modes
Enhanced serialization: Implemented deterministic object serialization with performance optimization and statistics collection
Extensive test coverage: Added comprehensive test suites for hash comparison utilities and debug logging system with 482 lines of test code
Configuration improvements: Enhanced main entry point with new capabilities and updated TypeScript configuration for better development workflow


Diagram Walkthrough

flowchart LR
  A["SystemInfo Input"] --> B["Validation Engine"]
  B --> C["Security Checks"]
  C --> D["Normalization"]
  D --> E["Enhanced Serialization"]
  E --> F["Hash Generation"]
  F --> G["Debug Logging"]
  H["Fallback System"] --> D
  I["Error Handler"] --> H
  J["Comparison Tools"] --> F
Loading

File Walkthrough

Relevant files
Enhancement
10 files
security.ts
Comprehensive Security Validation System for Hash Generation

src/security.ts

• Added comprehensive security validation system for fingerprint hash
generation
• Implemented threat detection for manipulation attempts,
spoofing, and collision risks
• Created entropy analysis and
preservation utilities for hash stability
• Added extensive input
validation patterns for script injection, SQL injection, and other
attacks

+1573/-0
comparison.ts
Hash Comparison and Troubleshooting Utilities                       

src/comparison.ts

• Added detailed hash comparison utilities for troubleshooting
fingerprint variations
• Implemented difference detection and impact
analysis between SystemInfo inputs
• Created hash variation analysis
and stability metrics calculation
• Added troubleshooting tools with
diagnosis reports and test suite generation

+1360/-0
fallback.ts
Fallback Value Management and Retry Logic System                 

src/fallback.ts

• Added consistent fallback value management for SystemInfo properties

• Implemented retry logic with exponential backoff for temporary
failures
• Created error categorization system for appropriate failure
handling
• Added fallback history tracking and consistency validation

+548/-0 
json.ts
Enhanced JSON Generation with Hash Configuration Support 

src/json.ts

• Added optional hashConfig parameter to generateJSON function

Updated hash generation call to accept configuration options

Enhanced function signature to support configurable hash generation

+4/-3     
validation.ts
Comprehensive Input Validation and Security Engine             

src/validation.ts

• Added comprehensive validation engine with input sanitization and
security checks
• Implemented validation error types and detailed
error reporting system
• Created security validation integration with
threat detection capabilities
• Added configurable validation rules
with strict/lenient modes

+996/-0 
hash.ts
Enhanced Hash Generation with Debug and Validation             

src/hash.ts

• Enhanced hash generation with predictable fingerprint capabilities
and improved normalization
• Added debug mode support with detailed
logging and processing information
• Implemented input comparison
functionality for analyzing differences between SystemInfo objects

Integrated validation engine, fallback management, and enhanced
serialization for better stability

+554/-69
debug.ts
Debug Logging System for Hash Generation                                 

src/debug.ts

• Created comprehensive debug logging system for fingerprint hash
generation
• Implemented normalization step tracking with before/after
value logging
• Added debug session management with statistics and
summary reporting
• Provided configurable logging levels and export
capabilities for debugging

+542/-0 
serialization.ts
Enhanced Deterministic Object Serialization                           

src/serialization.ts

• Implemented enhanced serialization utilities for deterministic
object serialization
• Added normalization integration during
serialization process
• Created performance comparison between
enhanced and legacy serialization methods
• Provided configurable
serialization behavior with statistics collection

+445/-0 
normalization.ts
Enhanced Data Normalization Utilities                                       

src/normalization.ts

• Created enhanced normalization utilities for predictable fingerprint
generation
• Implemented consistent data formatting across different
environments and browser states
• Added debug logging integration for
normalization steps tracking
• Provided optimized normalization paths
for primitive and complex data types

+395/-0 
index.ts
Enhanced Main Entry Point with New Features                           

src/index.ts

• Enhanced main entry point with new hash generation and debug
capabilities
• Added error handling exports and configuration options

• Integrated new validation and debugging features into the public API

• Provided both default and named exports for better module
compatibility

+104/-34
Configuration changes
1 files
tsconfig.json
TypeScript Configuration Update for Test File Exclusion   

tsconfig.json

• Added exclusion of test files from TypeScript compilation
• Updated
exclude pattern to ignore src/**/*.test.ts files

+1/-1     
Error handling
1 files
errorHandler.ts
Comprehensive Error Handling and Recovery System                 

src/errorHandler.ts

• Implemented comprehensive error handling system with categorization
and retry logic
• Added error tracking and statistics collection for
debugging and analysis
• Created fallback integration for handling
permanent and temporary failures
• Provided configurable retry
mechanisms with exponential backoff

+426/-0 
Tests
2 files
comparison.test.ts
Comprehensive test suite for hash comparison functionality

src/comparison.test.ts

• Added comprehensive test suite for hash comparison utilities with
290 lines of test code
• Implemented tests for HashComparator class
covering identical inputs, value differences, type changes, precision
differences, and whitespace differences
• Created tests for hash
variation analysis, troubleshooting functionality, and convenience
functions
• Added mock SystemInfo object factory for consistent test
data generation

+290/-0 
debug.test.ts
Complete test coverage for debug logging system                   

src/debug.test.ts

• Added complete test suite for debug logging system with 192 lines of
test code
• Implemented tests for DebugLogger class covering session
management, normalization step logging, fallback logging, and
validation logging
• Created tests for log level filtering, summary
report generation, and JSON export functionality
• Added integration
tests for debug system with normalization functions

+192/-0 

@netlify
Copy link

netlify bot commented Aug 16, 2025

Deploy Preview for fingerprint-oss failed. Why did it fail? →

Name Link
🔨 Latest commit 9a55999
🔍 Latest deploy log https://app.netlify.com/projects/fingerprint-oss/deploys/68a076d7f7350e000894f125

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 16, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Introduces configurable, debug-enabled hash generation with deterministic normalization and serialization.
    • Adds full comparison, variation analysis, and troubleshooting tools for fingerprint stability.
    • Implements robust validation, security checks, error handling, and consistent fallbacks.
    • Expands public API and updates user info/JSON output (supports hash config, combined confidence, VPN status).
  • Tests
    • Adds comprehensive end-to-end tests for comparison workflows and debug logging lifecycle.
  • Chores
    • Ignores local Netlify folder in version control.
    • Updates TypeScript config to exclude test files from compilation.

Walkthrough

Introduces a comprehensive hashing, normalization, serialization, validation, debugging, comparison, fallback, error handling, and security framework. Expands public APIs, adds extensive tests, updates JSON and index exports, excludes tests from TS build, and ignores the local Netlify folder.

Changes

Cohort / File(s) Summary
Hashing pipeline & core utilities
src/hash.ts, src/normalization.ts, src/serialization.ts, src/validation.ts, src/security.ts
Replaces hash generation with a configurable pipeline. Adds deterministic normalization, enhanced serializer, validation engine with security analysis, and related types/APIs.
Debugging system
src/debug.ts, src/debug.test.ts
Adds a structured debug logger with session lifecycle, levels, normalization-step logging, exports, and tests covering lifecycle, filtering, summaries, and integration.
Comparison & troubleshooting
src/comparison.ts, src/comparison.test.ts
Adds detailed hash comparison, variation analysis, troubleshooting toolkit, reporting, singleton accessors, and a comprehensive test suite.
Fallbacks & error handling
src/fallback.ts, src/errorHandler.ts
Introduces fallback manager with retry/backoff, consistent defaults, history/stats; adds error handling with categorization, retry policy, and analytics.
Public API & JSON generation
src/index.ts, src/json.ts
Extends public API (hash types/functions, error handler exports); updates userInfo and generateJSON to accept and pass hashConfig; enriches confidence/geolocation handling.
Build/config
.gitignore, tsconfig.json
Ignores .netlify directory; excludes src/**/*.test.ts from TS compilation.

Sequence Diagram(s)

sequenceDiagram
  participant App as Caller
  participant IDX as index.ts
  participant JSON as generateJSON
  participant HASH as generateId / generateIdWithDebug
  participant VAL as ValidationEngine
  participant FB as FallbackManager
  participant NORM as Normalization
  participant SER as EnhancedSerializer
  participant DBG as DebugLogger
  participant SEC as SecurityValidator

  App->>IDX: userInfo({ hashConfig? })
  IDX->>JSON: generateJSON(geo, systemInfo, combinedScore?, hashConfig?)
  JSON->>HASH: generateId(systemInfo, hashConfig)
  alt debugMode
    HASH->>DBG: startSession()
  end
  HASH->>VAL: validateSystemInfo(systemInfo)
  VAL-->>HASH: ValidationResult (errors, sanitizedData, security?)
  HASH->>FB: populate fallbacks (if needed)
  FB-->>HASH: Fallbacks applied
  HASH->>NORM: normalizeValue(systemInfo, per-property)
  NORM-->>HASH: normalized object
  HASH->>SER: serialize(normalized, config)
  SER-->>HASH: SerializationResult (string)
  HASH-->>JSON: hash string (+debugInfo?)
  JSON-->>IDX: JSON payload (hash, confidence, geo)
  IDX-->>App: Result
  opt debugMode
    HASH->>DBG: endSession() -> DebugSession
  end
Loading
sequenceDiagram
  participant Dev as Caller
  participant CMP as HashComparator
  participant HASH as generateId
  participant REP as Reporter

  Dev->>CMP: compareSystemInfo(info1, info2, config?)
  CMP->>HASH: generateId(info1, config)
  CMP->>HASH: generateId(info2, config)
  HASH-->>CMP: hash1
  HASH-->>CMP: hash2
  CMP->>CMP: diff + classify differences
  CMP-->>Dev: DetailedComparisonResult
  Dev->>REP: createDifferenceReport(result)
  REP-->>Dev: Text report
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

Review effort 4/5

Poem

A nibble of hashes, a hop through the logs,
I thump out comparisons, chase variance fogs.
With fallbacks and retries, I tidy each trail—
Normalize, serialize, never to fail.
Debug carrots glowing, reports in the light—
One fluffy fingerprint, perfectly right. 🥕✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch blame-kiro

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@codiumai-pr-agent-free
Copy link
Contributor

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: Run end-to-end tests

Failed stage: Run Unit Tests [❌]

Failed test name: helper.test.ts > Helper Functions > getOSInfo > should detect iOS (currently detects as macOS due to order issue)

Failure summary:

The action failed because 19 unit tests failed across multiple test files. The main issues are:

1. Canvas API implementation issues: Many errors are related to
"HTMLCanvasElement.prototype.getContext" not being implemented in the JSDOM environment. This
affects tests in index.test.ts that rely on canvas functionality.

2. Browser detection issues: Tests in incognito.test.ts fail because the test environment is using
'Chromium' but tests expect 'Firefox' or 'Safari'.

3. iOS detection issue: In helper.test.ts, the getOSInfo function fails to detect iOS correctly,
returning 'unknown' instead of '14.6'.

4. Toast component error handling: Multiple tests in compliance.test.ts expect functions to throw
errors when document or requestAnimationFrame is missing, but they don't.

5. VPN detection issues: In json.test.ts, the vpnStatus property is undefined when it should contain
VPN information.

6. Bot detection issue: In systemInfo.test.ts, the Facebook crawler user agent isn't being properly
detected as a bot.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

151:  �[36;1mcd test/unit�[0m
152:  �[36;1mnpm install�[0m
153:  �[36;1mnpm test�[0m
154:  shell: /usr/bin/bash -e {0}
155:  ##[endgroup]
156:  added 116 packages, and audited 117 packages in 2s
157:  28 packages are looking for funding
158:  run `npm fund` for details
159:  1 critical severity vulnerability
160:  To address all issues, run:
161:  npm audit fix
162:  Run `npm audit` for details.
163:  > fingerprint-oss-unit-tests@0.0.1 test
164:  > npx vitest run
165:  �[1m�[46m RUN �[49m�[22m �[36mv3.2.4 �[39m�[90m/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit�[39m
166:  �[31m❯�[39m helper.test.ts �[2m(�[22m�[2m32 tests�[22m�[2m | �[22m�[31m1 failed�[39m�[2m)�[22m�[32m 53�[2mms�[22m�[39m
167:  �[32m✓�[39m Helper Functions�[2m > �[22mgetColorGamut�[2m > �[22mshould return "unknown" when matchMedia is not available�[32m 5�[2mms�[22m�[39m
168:  �[32m✓�[39m Helper Functions�[2m > �[22mgetColorGamut�[2m > �[22mshould return "rec2020" when rec2020 matches�[32m 1�[2mms�[22m�[39m
169:  �[32m✓�[39m Helper Functions�[2m > �[22mgetColorGamut�[2m > �[22mshould return "p3" when p3 matches but rec2020 does not�[32m 1�[2mms�[22m�[39m
170:  �[32m✓�[39m Helper Functions�[2m > �[22mgetColorGamut�[2m > �[22mshould return "srgb" when only srgb matches�[32m 1�[2mms�[22m�[39m
171:  �[32m✓�[39m Helper Functions�[2m > �[22mgetColorGamut�[2m > �[22mshould return "unknown" when no color gamut matches�[32m 1�[2mms�[22m�[39m
172:  �[32m✓�[39m Helper Functions�[2m > �[22mgetVendorFlavors�[2m > �[22mshould detect Chrome�[32m 2�[2mms�[22m�[39m
173:  �[32m✓�[39m Helper Functions�[2m > �[22mgetVendorFlavors�[2m > �[22mshould detect Firefox�[32m 2�[2mms�[22m�[39m
174:  �[32m✓�[39m Helper Functions�[2m > �[22mgetVendorFlavors�[2m > �[22mshould detect Safari (without Chrome)�[32m 1�[2mms�[22m�[39m
175:  �[32m✓�[39m Helper Functions�[2m > �[22mgetVendorFlavors�[2m > �[22mshould return empty array for unknown user agent�[32m 1�[2mms�[22m�[39m
176:  �[32m✓�[39m Helper Functions�[2m > �[22mgetVendorFlavors�[2m > �[22mshould detect multiple flavors when present�[32m 1�[2mms�[22m�[39m
177:  �[32m✓�[39m Helper Functions�[2m > �[22misLocalStorageEnabled�[2m > �[22mshould return true when localStorage is available�[32m 2�[2mms�[22m�[39m
178:  �[32m✓�[39m Helper Functions�[2m > �[22misLocalStorageEnabled�[2m > �[22mshould return false when localStorage throws an error�[32m 1�[2mms�[22m�[39m
179:  �[32m✓�[39m Helper Functions�[2m > �[22misLocalStorageEnabled�[2m > �[22mshould return false when localStorage is undefined�[32m 0�[2mms�[22m�[39m
180:  �[32m✓�[39m Helper Functions�[2m > �[22misSessionStorageEnabled�[2m > �[22mshould return true when sessionStorage is available�[32m 1�[2mms�[22m�[39m
181:  �[32m✓�[39m Helper Functions�[2m > �[22misSessionStorageEnabled�[2m > �[22mshould return false when sessionStorage throws an error�[32m 1�[2mms�[22m�[39m
182:  �[32m✓�[39m Helper Functions�[2m > �[22misIndexedDBEnabled�[2m > �[22mshould return true when indexedDB is available�[32m 0�[2mms�[22m�[39m
...

184:  �[32m✓�[39m Helper Functions�[2m > �[22misIndexedDBEnabled�[2m > �[22mshould return false when indexedDB is null�[32m 1�[2mms�[22m�[39m
185:  �[32m✓�[39m Helper Functions�[2m > �[22mgetTouchSupportInfo�[2m > �[22mshould return correct touch support info�[32m 1�[2mms�[22m�[39m
186:  �[32m✓�[39m Helper Functions�[2m > �[22mgetTouchSupportInfo�[2m > �[22mshould handle missing touch support�[32m 2�[2mms�[22m�[39m
187:  �[32m✓�[39m Helper Functions�[2m > �[22mgetOSInfo�[2m > �[22mshould detect Windows 10�[32m 1�[2mms�[22m�[39m
188:  �[32m✓�[39m Helper Functions�[2m > �[22mgetOSInfo�[2m > �[22mshould detect macOS�[32m 1�[2mms�[22m�[39m
189:  �[32m✓�[39m Helper Functions�[2m > �[22mgetOSInfo�[2m > �[22mshould detect Android�[32m 1�[2mms�[22m�[39m
190:  �[31m   �[31m�[31m Helper Functions�[2m > �[22mgetOSInfo�[2m > �[22mshould detect iOS (currently detects as macOS due to order issue)�[39m�[32m 11�[2mms�[22m�[39m
191:  �[31m     → expected 'unknown' to be '14.6' // Object.is equality�[39m
192:  �[32m✓�[39m Helper Functions�[2m > �[22mgetOSInfo�[2m > �[22mshould detect Linux�[32m 1�[2mms�[22m�[39m
193:  �[32m✓�[39m Helper Functions�[2m > �[22mgetOSInfo�[2m > �[22mshould handle missing navigator�[32m 3�[2mms�[22m�[39m
194:  �[32m✓�[39m Helper Functions�[2m > �[22mgetMathFingerprint�[2m > �[22mshould return consistent math constants�[32m 2�[2mms�[22m�[39m
195:  �[32m✓�[39m Helper Functions�[2m > �[22mgetMathFingerprint�[2m > �[22mshould return the same values on multiple calls�[32m 4�[2mms�[22m�[39m
196:  �[32m✓�[39m Helper Functions�[2m > �[22mgetPluginsInfo�[2m > �[22mshould return empty array when no plugins�[32m 1�[2mms�[22m�[39m
197:  �[32m✓�[39m Helper Functions�[2m > �[22mgetPluginsInfo�[2m > �[22mshould handle navigator.plugins being undefined�[32m 1�[2mms�[22m�[39m
198:  �[32m✓�[39m Helper Functions�[2m > �[22mgetCanvasFingerprint�[2m > �[22mshould return canvas fingerprint with expected properties�[32m 1�[2mms�[22m�[39m
199:  �[32m✓�[39m Helper Functions�[2m > �[22mgetCanvasFingerprint�[2m > �[22mshould handle canvas creation failure gracefully�[32m 1�[2mms�[22m�[39m
200:  �[32m✓�[39m hash.test.ts �[2m(�[22m�[2m21 tests�[22m�[2m)�[22m�[32m 94�[2mms�[22m�[39m
201:  �[31m❯�[39m index.test.ts �[2m(�[22m�[2m35 tests�[22m�[2m | �[22m�[31m2 failed�[39m�[2m)�[22m�[32m 247�[2mms�[22m�[39m
202:  �[32m✓�[39m Index Module�[2m > �[22mModule Exports�[2m > �[22mshould export all required core functions�[32m 7�[2mms�[22m�[39m
203:  �[32m✓�[39m Index Module�[2m > �[22mModule Exports�[2m > �[22mshould export helper functions�[32m 2�[2mms�[22m�[39m
204:  �[32m✓�[39m Index Module�[2m > �[22mModule Exports�[2m > �[22mshould export confidence functions�[32m 1�[2mms�[22m�[39m
205:  �[32m✓�[39m Index Module�[2m > �[22mModule Exports�[2m > �[22mshould export utility functions and classes�[32m 1�[2mms�[22m�[39m
206:  �[32m✓�[39m Index Module�[2m > �[22mModule Exports�[2m > �[22mshould export default function�[32m 1�[2mms�[22m�[39m
207:  �[32m✓�[39m Index Module�[2m > �[22mCore Functionality�[2m > �[22mshould be able to call main functions without errors�[32m 77�[2mms�[22m�[39m
208:  �[32m✓�[39m Index Module�[2m > �[22mCore Functionality�[2m > �[22mshould handle browser environment properly�[32m 6�[2mms�[22m�[39m
209:  �[32m✓�[39m Index Module�[2m > �[22mCore Functionality�[2m > �[22mshould generate consistent fingerprint data�[32m 26�[2mms�[22m�[39m
210:  �[31m   �[31m�[31m Index Module�[2m > �[22mCore Functionality�[2m > �[22mshould handle geolocation integration properly�[39m�[32m 11�[2mms�[22m�[39m
211:  �[31m     → expected 'object' to be 'number' // Object.is equality�[39m
212:  �[32m✓�[39m Index Module�[2m > �[22mCore Functionality�[2m > �[22mshould have proper TypeScript compilation�[32m 1�[2mms�[22m�[39m
213:  �[32m✓�[39m Index Module�[2m > �[22mError Handling and Edge Cases�[2m > �[22mshould handle missing browser APIs gracefully�[32m 3�[2mms�[22m�[39m
214:  �[32m✓�[39m Index Module�[2m > �[22mError Handling and Edge Cases�[2m > �[22mshould handle non-browser environment gracefully�[32m 1�[2mms�[22m�[39m
215:  �[32m✓�[39m Index Module�[2m > �[22mError Handling and Edge Cases�[2m > �[22mshould handle fetch errors gracefully�[32m 1�[2mms�[22m�[39m
216:  �[32m✓�[39m Index Module�[2m > �[22mError Handling and Edge Cases�[2m > �[22mshould handle malformed geolocation responses�[32m 2�[2mms�[22m�[39m
217:  �[32m✓�[39m Index Module�[2m > �[22mError Handling and Edge Cases�[2m > �[22mshould handle bot detection properly�[32m 8�[2mms�[22m�[39m
218:  �[32m✓�[39m Index Module�[2m > �[22mPrivacy Detection�[2m > �[22mshould detect incognito mode�[32m 2�[2mms�[22m�[39m
219:  �[32m✓�[39m Index Module�[2m > �[22mPrivacy Detection�[2m > �[22mshould detect ad blockers�[32m 1�[2mms�[22m�[39m
220:  �[32m✓�[39m Index Module�[2m > �[22mPrivacy Detection�[2m > �[22mshould get VPN status with timezone data�[32m 1�[2mms�[22m�[39m
221:  �[32m✓�[39m Index Module�[2m > �[22mConfidence Assessment�[2m > �[22mshould calculate language consistency�[32m 1�[2mms�[22m�[39m
222:  �[32m✓�[39m Index Module�[2m > �[22mConfidence Assessment�[2m > �[22mshould identify risky ASNs�[32m 2�[2mms�[22m�[39m
223:  �[32m✓�[39m Index Module�[2m > �[22mConfidence Assessment�[2m > �[22mshould detect UA platform mismatches�[32m 7�[2mms�[22m�[39m
224:  �[32m✓�[39m Index Module�[2m > �[22mConfidence Assessment�[2m > �[22mshould check browser consistency�[32m 2�[2mms�[22m�[39m
225:  �[32m✓�[39m Index Module�[2m > �[22mDefault Function Integration�[2m > �[22mshould work with default transparency settings�[32m 17�[2mms�[22m�[39m
226:  �[32m✓�[39m Index Module�[2m > �[22mDefault Function Integration�[2m > �[22mshould work with custom message�[32m 9�[2mms�[22m�[39m
227:  �[32m✓�[39m Index Module�[2m > �[22mDefault Function Integration�[2m > �[22mshould work without any configuration�[32m 4�[2mms�[22m�[39m
228:  �[31m   �[31m�[31m Index Module�[2m > �[22mDefault Function Integration�[2m > �[22mshould handle errors and return fallback data�[39m�[32m 11�[2mms�[22m�[39m
229:  �[31m     → expected "error" to be called with arguments: [ Array(2) ]�[90m
230:  Received: 
231:  �[1m  1st error call:
232:  �[22m�[2m  [�[22m
233:  �[32m-   "Data collection error:",�[90m
234:  �[32m-   Any<Error>,�[90m
235:  �[31m+   "Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)�[90m
236:  �[31m+     at module.exports (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)�[90m
237:  �[31m+     at HTMLCanvasElementImpl.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)�[90m
238:  �[31m+     at HTMLCanvasElement.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:131:58)�[90m
239:  �[31m+     at getWebGLInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/helper.ts:154:27)�[90m
240:  �[31m+     at getSystemInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/systemInfo.ts:181:22)�[90m
241:  �[31m+     at async Promise.all (index 0)�[90m
242:  �[31m+     at Module.userInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/index.ts:117:39)�[90m
243:  �[31m+     at /home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/index.test.ts:395:22�[90m
244:  �[31m+     at file:///home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20",�[90m
245:  �[31m+   undefined,�[90m
246:  �[2m  ]�[22m
247:  �[1m  2nd error call:
248:  �[22m�[2m  [�[22m
249:  �[32m-   "Data collection error:",�[90m
250:  �[32m-   Any<Error>,�[90m
251:  �[31m+   "Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)�[90m
252:  �[31m+     at module.exports (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)�[90m
253:  �[31m+     at HTMLCanvasElementImpl.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)�[90m
254:  �[31m+     at HTMLCanvasElement.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:131:58)�[90m
255:  �[31m+     at getWebGLInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/helper.ts:154:57)�[90m
256:  �[31m+     at getSystemInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/systemInfo.ts:181:22)�[90m
257:  �[31m+     at async Promise.all (index 0)�[90m
258:  �[31m+     at Module.userInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/index.ts:117:39)�[90m
259:  �[31m+     at /home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/index.test.ts:395:22�[90m
260:  �[31m+     at file:///home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20",�[90m
261:  �[31m+   undefined,�[90m
262:  �[2m  ]�[22m
263:  �[1m  3rd error call:
264:  �[22m�[2m  [�[22m
265:  �[32m-   "Data collection error:",�[90m
266:  �[32m-   Any<Error>,�[90m
267:  �[31m+   "Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)�[90m
268:  �[31m+     at module.exports (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)�[90m
269:  �[31m+     at HTMLCanvasElementImpl.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)�[90m
270:  �[31m+     at HTMLCanvasElement.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:131:58)�[90m
271:  �[31m+     at getCanvasFingerprint (/home/runner/work/fingerprint-oss/fingerprint-oss/src/helper.ts:285:28)�[90m
272:  �[31m+     at getSystemInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/systemInfo.ts:182:17)�[90m
273:  �[31m+     at async Promise.all (index 0)�[90m
274:  �[31m+     at Module.userInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/index.ts:117:39)�[90m
275:  �[31m+     at /home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/index.test.ts:395:22�[90m
276:  �[31m+     at file:///home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20",�[90m
277:  �[31m+   undefined,�[90m
278:  �[2m  ]�[22m
279:  �[1m  4th error call:
280:  �[22m�[2m  [�[22m
281:  �[32m-   "Data collection error:",�[90m
282:  �[32m-   Any<Error>,�[90m
283:  �[31m+   "Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)�[90m
284:  �[31m+     at module.exports (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)�[90m
...

294:  �[2m  ]�[22m
295:  �[31m�[90m
296:  Number of calls: �[1m4�[22m
297:  �[31m�[39m
298:  �[32m✓�[39m Index Module�[2m > �[22mPerformance and Stability�[2m > �[22mshould handle concurrent calls properly�[32m 12�[2mms�[22m�[39m
299:  �[32m✓�[39m Index Module�[2m > �[22mPerformance and Stability�[2m > �[22mshould have reasonable execution time for system info�[32m 2�[2mms�[22m�[39m
300:  �[32m✓�[39m Index Module�[2m > �[22mPerformance and Stability�[2m > �[22mshould handle memory constraints gracefully�[32m 2�[2mms�[22m�[39m
301:  �[32m✓�[39m Index Module�[2m > �[22mInput Validation and Security�[2m > �[22mshould handle malicious input gracefully�[32m 2�[2mms�[22m�[39m
302:  �[32m✓�[39m Index Module�[2m > �[22mInput Validation and Security�[2m > �[22mshould validate confidence score bounds�[32m 3�[2mms�[22m�[39m
303:  �[32m✓�[39m Index Module�[2m > �[22mInput Validation and Security�[2m > �[22mshould handle extremely large data gracefully�[32m 3�[2mms�[22m�[39m
304:  �[32m✓�[39m Index Module�[2m > �[22mBrowser Compatibility�[2m > �[22mshould work in Chrome-like environment�[32m 4�[2mms�[22m�[39m
305:  �[32m✓�[39m Index Module�[2m > �[22mBrowser Compatibility�[2m > �[22mshould work in Firefox-like environment�[32m 10�[2mms�[22m�[39m
306:  �[32m✓�[39m Index Module�[2m > �[22mBrowser Compatibility�[2m > �[22mshould work in Safari-like environment�[32m 3�[2mms�[22m�[39m
307:  �[32m✓�[39m confidence.test.ts �[2m(�[22m�[2m33 tests�[22m�[2m)�[22m�[32m 19�[2mms�[22m�[39m
308:  �[32m✓�[39m adblocker.test.ts �[2m(�[22m�[2m19 tests�[22m�[2m)�[22m�[32m 22�[2mms�[22m�[39m
309:  �[31m❯�[39m json.test.ts �[2m(�[22m�[2m13 tests�[22m�[2m | �[22m�[31m2 failed�[39m�[2m)�[22m�[32m 33�[2mms�[22m�[39m
310:  �[32m✓�[39m JSON Module�[2m > �[22mgenerateJSON�[2m > �[22mshould generate JSON with system confidence assessment�[32m 6�[2mms�[22m�[39m
311:  �[32m✓�[39m JSON Module�[2m > �[22mgenerateJSON�[2m > �[22mshould include combined confidence when provided�[32m 1�[2mms�[22m�[39m
312:  �[31m   �[31m�[31m JSON Module�[2m > �[22mgenerateJSON�[2m > �[22mshould include geolocation information when provided�[39m�[32m 15�[2mms�[22m�[39m
313:  �[31m     → expected { vpnStatus: undefined, …(7) } to match object { vpnStatus: { vpn: { …(2) } }, …(7) }�[39m
314:  �[32m✓�[39m JSON Module�[2m > �[22mgenerateJSON�[2m > �[22mshould handle null geolocation gracefully�[32m 1�[2mms�[22m�[39m
315:  �[32m✓�[39m JSON Module�[2m > �[22mgenerateJSON�[2m > �[22mshould include system information�[32m 1�[2mms�[22m�[39m
316:  �[31m   �[31m�[31m JSON Module�[2m > �[22mgenerateJSON�[2m > �[22mshould include generated hash�[39m�[32m 2�[2mms�[22m�[39m
317:  �[31m     → expected undefined to be 'mock-hash-12345' // Object.is equality�[39m
318:  �[32m✓�[39m JSON Module�[2m > �[22mgenerateJSON�[2m > �[22mshould interpret different confidence levels correctly�[32m 1�[2mms�[22m�[39m
319:  �[32m✓�[39m JSON Module�[2m > �[22mgenerateJSON�[2m > �[22mshould handle bot detection signals�[32m 1�[2mms�[22m�[39m
320:  �[32m✓�[39m JSON Module�[2m > �[22mgenerateJSON�[2m > �[22mshould detect proxy and VPN factors in combined assessment�[32m 1�[2mms�[22m�[39m
321:  �[32m✓�[39m JSON Module�[2m > �[22mgenerateJSON�[2m > �[22mshould handle missing geolocation traits gracefully�[32m 1�[2mms�[22m�[39m
322:  �[32m✓�[39m JSON Module�[2m > �[22mgenerateJSON�[2m > �[22mshould use correct timezone for VPN detection�[32m 1�[2mms�[22m�[39m
323:  �[32m✓�[39m JSON Module�[2m > �[22mgenerateJSON�[2m > �[22mshould handle edge cases in confidence assessment�[32m 1�[2mms�[22m�[39m
324:  �[32m✓�[39m JSON Module�[2m > �[22mgenerateJSON�[2m > �[22mshould maintain data integrity across calls�[32m 1�[2mms�[22m�[39m
325:  �[31m❯�[39m compliance.test.ts �[2m(�[22m�[2m26 tests�[22m�[2m | �[22m�[31m7 failed�[39m�[2m)�[22m�[32m 62�[2mms�[22m�[39m
326:  �[32m✓�[39m Compliance Module - Toast�[2m > �[22mToast.show�[2m > �[22mshould create and display a toast notification�[32m 10�[2mms�[22m�[39m
...

331:  �[32m✓�[39m Compliance Module - Toast�[2m > �[22mToast.show�[2m > �[22mshould not inject styles on subsequent calls�[32m 1�[2mms�[22m�[39m
332:  �[32m✓�[39m Compliance Module - Toast�[2m > �[22mToast.show�[2m > �[22mshould handle hide animation after timeout�[32m 1�[2mms�[22m�[39m
333:  �[32m✓�[39m Compliance Module - Toast�[2m > �[22mToast.show�[2m > �[22mshould support multiple simultaneous toasts�[32m 1�[2mms�[22m�[39m
334:  �[32m✓�[39m Compliance Module - Toast�[2m > �[22mToast.show�[2m > �[22mshould handle empty message�[32m 1�[2mms�[22m�[39m
335:  �[32m✓�[39m Compliance Module - Toast�[2m > �[22mToast.show�[2m > �[22mshould handle very long messages�[32m 3�[2mms�[22m�[39m
336:  �[32m✓�[39m Compliance Module - Toast�[2m > �[22mToast.show�[2m > �[22mshould handle special characters in message�[32m 1�[2mms�[22m�[39m
337:  �[32m✓�[39m Compliance Module - Toast�[2m > �[22mToast.show�[2m > �[22mshould handle zero duration�[32m 1�[2mms�[22m�[39m
338:  �[32m✓�[39m Compliance Module - Toast�[2m > �[22mToast.show�[2m > �[22mshould handle negative duration�[32m 1�[2mms�[22m�[39m
339:  �[32m✓�[39m Compliance Module - Toast�[2m > �[22mStyle injection�[2m > �[22mshould inject correct CSS styles�[32m 1�[2mms�[22m�[39m
340:  �[32m✓�[39m Compliance Module - Toast�[2m > �[22mStyle injection�[2m > �[22mshould include glassmorphism styles�[32m 1�[2mms�[22m�[39m
341:  �[32m✓�[39m Compliance Module - Toast�[2m > �[22mStyle injection�[2m > �[22mshould include responsive design�[32m 1�[2mms�[22m�[39m
342:  �[32m✓�[39m Compliance Module - Toast�[2m > �[22mStyle injection�[2m > �[22mshould include animation properties�[32m 1�[2mms�[22m�[39m
343:  �[32m✓�[39m Compliance Module - Toast�[2m > �[22mStyle injection�[2m > �[22mshould set high z-index�[32m 6�[2mms�[22m�[39m
344:  �[32m✓�[39m Compliance Module - Toast�[2m > �[22mDOM interaction�[2m > �[22mshould properly clean up event listeners�[32m 1�[2mms�[22m�[39m
345:  �[31m   �[31m�[31m Compliance Module - Toast�[2m > �[22mDOM interaction�[2m > �[22mshould handle missing document gracefully�[39m�[32m 11�[2mms�[22m�[39m
346:  �[31m     → expected [Function] to throw an error�[39m
347:  �[31m   �[31m�[31m Compliance Module - Toast�[2m > �[22mDOM interaction�[2m > �[22mshould handle missing document.body gracefully�[39m�[32m 2�[2mms�[22m�[39m
348:  �[31m     → expected [Function] to throw an error�[39m
349:  �[31m   �[31m�[31m Compliance Module - Toast�[2m > �[22mDOM interaction�[2m > �[22mshould handle missing requestAnimationFrame gracefully�[39m�[32m 1�[2mms�[22m�[39m
350:  �[31m     → expected [Function] to throw an error�[39m
351:  �[31m   �[31m�[31m Compliance Module - Toast�[2m > �[22mEdge cases and error handling�[2m > �[22mshould handle very short duration�[39m�[32m 1�[2mms�[22m�[39m
352:  �[31m     → expected "spy" to be called with arguments: [ Any<Function>, 1 ]�[90m
353:  Number of calls: �[1m0�[22m
354:  �[31m�[39m
355:  �[31m   �[31m�[31m Compliance Module - Toast�[2m > �[22mEdge cases and error handling�[2m > �[22mshould handle very long duration�[39m�[32m 1�[2mms�[22m�[39m
356:  �[31m     → expected "spy" to be called with arguments: [ Any<Function>, 999999999 ]�[90m
357:  Number of calls: �[1m0�[22m
358:  �[31m�[39m
359:  �[31m   �[31m�[31m Compliance Module - Toast�[2m > �[22mEdge cases and error handling�[2m > �[22mshould handle concurrent shows correctly�[39m�[32m 1�[2mms�[22m�[39m
360:  �[31m     → expected "spy" to be called 10 times, but got 0 times�[39m
361:  �[31m   �[31m�[31m Compliance Module - Toast�[2m > �[22mEdge cases and error handling�[2m > �[22mshould maintain static state across instances�[39m�[32m 4�[2mms�[22m�[39m
362:  �[31m     → expected +0 to be 1 // Object.is equality�[39m
363:  �[32m✓�[39m geo-ip.test.ts �[2m(�[22m�[2m11 tests�[22m�[2m)�[22m�[32m 13�[2mms�[22m�[39m
364:  �[31m❯�[39m systemInfo.test.ts �[2m(�[22m�[2m12 tests�[22m�[2m | �[22m�[31m1 failed�[39m�[2m)�[22m�[32m 35�[2mms�[22m�[39m
365:  �[32m✓�[39m SystemInfo Module�[2m > �[22mdetectBot�[2m > �[22mshould return bot detected for non-browser environment�[32m 6�[2mms�[22m�[39m
366:  �[31m   �[31m�[31m SystemInfo Module�[2m > �[22mdetectBot�[2m > �[22mshould detect bots based on user agent patterns�[39m�[32m 14�[2mms�[22m�[39m
367:  �[31m     → Failed for userAgent: facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php) (index: 2): expected false to be true // Object.is equality�[39m
368:  �[32m✓�[39m SystemInfo Module�[2m > �[22mdetectBot�[2m > �[22mshould detect webdriver flag�[32m 1�[2mms�[22m�[39m
369:  �[32m✓�[39m SystemInfo Module�[2m > �[22mdetectBot�[2m > �[22mshould detect missing storage as medium signal�[32m 1�[2mms�[22m�[39m
370:  �[32m✓�[39m SystemInfo Module�[2m > �[22mdetectBot�[2m > �[22mshould detect few plugins as medium signal�[32m 1�[2mms�[22m�[39m
371:  �[32m✓�[39m SystemInfo Module�[2m > �[22mdetectBot�[2m > �[22mshould detect no plugins as medium signal�[32m 1�[2mms�[22m�[39m
372:  �[32m✓�[39m SystemInfo Module�[2m > �[22mdetectBot�[2m > �[22mshould detect small screen as weak signal�[32m 1�[2mms�[22m�[39m
373:  �[32m✓�[39m SystemInfo Module�[2m > �[22mdetectBot�[2m > �[22mshould detect unusual hardware concurrency as weak signal�[32m 1�[2mms�[22m�[39m
374:  �[32m✓�[39m SystemInfo Module�[2m > �[22mdetectBot�[2m > �[22mshould return false for normal browsers�[32m 1�[2mms�[22m�[39m
375:  �[32m✓�[39m SystemInfo Module�[2m > �[22mdetectBot�[2m > �[22mshould calculate confidence score correctly with multiple signals�[32m 1�[2mms�[22m�[39m
376:  �[32m✓�[39m SystemInfo Module�[2m > �[22mdetectBot�[2m > �[22mshould cap confidence score at 0.9�[32m 4�[2mms�[22m�[39m
377:  �[32m✓�[39m SystemInfo Module�[2m > �[22mdetectBot�[2m > �[22mshould handle edge cases gracefully�[32m 1�[2mms�[22m�[39m
378:  �[31m❯�[39m incognito.test.ts �[2m(�[22m�[2m13 tests�[22m�[2m | �[22m�[31m6 failed�[39m�[2m)�[22m�[32m 28�[2mms�[22m�[39m
379:  �[32m✓�[39m Incognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould detect normal browsing mode in Chrome�[32m 4�[2mms�[22m�[39m
380:  �[31m   �[31m�[31m Incognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould detect incognito mode in Chrome using quota detection�[39m�[32m 8�[2mms�[22m�[39m
381:  �[31m     → expected false to be true // Object.is equality�[39m
382:  �[31m   �[31m�[31m Incognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould detect normal browsing mode in Firefox�[39m�[32m 3�[2mms�[22m�[39m
383:  �[31m     → expected 'Chromium' to contain 'Firefox'�[39m
384:  �[31m   �[31m�[31m Incognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould detect private mode in Firefox using indexedDB detection�[39m�[32m 1�[2mms�[22m�[39m
385:  �[31m     → expected false to be true // Object.is equality�[39m
386:  �[31m   �[31m�[31m Incognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould detect normal browsing mode in Safari�[39m�[32m 1�[2mms�[22m�[39m
387:  �[31m     → expected 'Chromium' to contain 'Safari'�[39m
388:  �[31m   �[31m�[31m Incognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould detect private mode in Safari using storage detection�[39m�[32m 1�[2mms�[22m�[39m
389:  �[31m     → expected false to be true // Object.is equality�[39m
390:  �[32m✓�[39m Incognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould handle unknown browsers gracefully�[32m 1�[2mms�[22m�[39m
391:  �[32m✓�[39m Incognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould handle storage errors gracefully�[32m 1�[2mms�[22m�[39m
392:  �[32m✓�[39m Incognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould handle quota API errors gracefully�[32m 1�[2mms�[22m�[39m
393:  �[32m✓�[39m Incognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould provide consistent results on multiple calls�[32m 1�[2mms�[22m�[39m
394:  �[32m✓�[39m Incognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould handle non-browser environments�[32m 1�[2mms�[22m�[39m
395:  �[32m✓�[39m Incognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould detect Edge browser correctly�[32m 1�[2mms�[22m�[39m
396:  �[31m   �[31m�[31m Incognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould handle mobile Safari on iOS�[39m�[32m 1�[2mms�[22m�[39m
397:  �[31m     → expected 'Chromium' to contain 'Safari'�[39m
398:  �[32m✓�[39m vpn.test.ts �[2m(�[22m�[2m18 tests�[22m�[2m)�[22m�[32m 12�[2mms�[22m�[39m
399:  �[31m⎯⎯⎯⎯⎯⎯�[39m�[1m�[41m Failed Tests 19 �[49m�[22m�[31m⎯⎯⎯⎯⎯⎯⎯�[39m
400:  �[41m�[1m FAIL �[22m�[49m compliance.test.ts�[2m > �[22mCompliance Module - Toast�[2m > �[22mDOM interaction�[2m > �[22mshould handle missing document gracefully
401:  �[31m�[1mAssertionError�[22m: expected [Function] to throw an error�[39m
402:  �[36m �[2m❯�[22m compliance.test.ts:�[2m268:10�[22m�[39m
403:  �[90m266| �[39m      �[34mexpect�[39m(() �[33m=>�[39m {
404:  �[90m267| �[39m        �[33mToast�[39m�[33m.�[39m�[34mshow�[39m(�[32m'Test message'�[39m)�[33m;�[39m
405:  �[90m268| �[39m      })�[33m.�[39m�[34mtoThrow�[39m()�[33m;�[39m �[90m// Should throw due to missing document�[39m
406:  �[90m   | �[39m         �[31m^�[39m
407:  �[90m269| �[39m
408:  �[90m270| �[39m      global�[33m.�[39mdocument �[33m=�[39m originalDocument�[33m;�[39m
409:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/19]⎯�[22m�[39m
410:  �[41m�[1m FAIL �[22m�[49m compliance.test.ts�[2m > �[22mCompliance Module - Toast�[2m > �[22mDOM interaction�[2m > �[22mshould handle missing document.body gracefully
411:  �[31m�[1mAssertionError�[22m: expected [Function] to throw an error�[39m
412:  �[36m �[2m❯�[22m compliance.test.ts:�[2m279:10�[22m�[39m
413:  �[90m277| �[39m      �[34mexpect�[39m(() �[33m=>�[39m {
414:  �[90m278| �[39m        �[33mToast�[39m�[33m.�[39m�[34mshow�[39m(�[32m'Test message'�[39m)�[33m;�[39m
415:  �[90m279| �[39m      })�[33m.�[39m�[34mtoThrow�[39m()�[33m;�[39m �[90m// Should throw due to missing body�[39m
416:  �[90m   | �[39m         �[31m^�[39m
417:  �[90m280| �[39m
418:  �[90m281| �[39m      global�[33m.�[39mdocument�[33m.�[39mbody �[33m=�[39m originalBody�[33m;�[39m
419:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/19]⎯�[22m�[39m
420:  �[41m�[1m FAIL �[22m�[49m compliance.test.ts�[2m > �[22mCompliance Module - Toast�[2m > �[22mDOM interaction�[2m > �[22mshould handle missing requestAnimationFrame gracefully
421:  �[31m�[1mAssertionError�[22m: expected [Function] to throw an error�[39m
422:  �[36m �[2m❯�[22m compliance.test.ts:�[2m291:10�[22m�[39m
423:  �[90m289| �[39m      �[34mexpect�[39m(() �[33m=>�[39m {
424:  �[90m290| �[39m        �[33mToast�[39m�[33m.�[39m�[34mshow�[39m(�[32m'Test message'�[39m)�[33m;�[39m
425:  �[90m291| �[39m      })�[33m.�[39m�[34mtoThrow�[39m()�[33m;�[39m �[90m// Will throw due to missing RAF�[39m
426:  �[90m   | �[39m         �[31m^�[39m
427:  �[90m292| �[39m
428:  �[90m293| �[39m      global�[33m.�[39mrequestAnimationFrame �[33m=�[39m originalRAF�[33m;�[39m
429:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/19]⎯�[22m�[39m
430:  �[41m�[1m FAIL �[22m�[49m compliance.test.ts�[2m > �[22mCompliance Module - Toast�[2m > �[22mEdge cases and error handling�[2m > �[22mshould handle very short duration
431:  �[31m�[1mAssertionError�[22m: expected "spy" to be called with arguments: [ Any<Function>, 1 ]�[90m
432:  Number of calls: �[1m0�[22m
433:  �[31m�[39m
434:  �[36m �[2m❯�[22m compliance.test.ts:�[2m301:26�[22m�[39m
435:  �[90m299| �[39m      �[33mToast�[39m�[33m.�[39m�[34mshow�[39m(�[32m'Test message'�[39m�[33m,�[39m �[34m1�[39m)�[33m;�[39m
436:  �[90m300| �[39m
437:  �[90m301| �[39m      �[34mexpect�[39m(setTimeout)�[33m.�[39m�[34mtoHaveBeenCalledWith�[39m(expect�[33m.�[39m�[34many�[39m(�[33mFunction�[39m)�[33m,�[39m �[34m1�[39m)�[33m;�[39m
438:  �[90m   | �[39m                         �[31m^�[39m
439:  �[90m302| �[39m    })�[33m;�[39m
440:  �[90m303| �[39m
441:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/19]⎯�[22m�[39m
442:  �[41m�[1m FAIL �[22m�[49m compliance.test.ts�[2m > �[22mCompliance Module - Toast�[2m > �[22mEdge cases and error handling�[2m > �[22mshould handle very long duration
443:  �[31m�[1mAssertionError�[22m: expected "spy" to be called with arguments: [ Any<Function>, 999999999 ]�[90m
444:  Number of calls: �[1m0�[22m
445:  �[31m�[39m
446:  �[36m �[2m❯�[22m compliance.test.ts:�[2m307:26�[22m�[39m
447:  �[90m305| �[39m      �[33mToast�[39m�[33m.�[39m�[34mshow�[39m(�[32m'Test message'�[39m�[33m,�[39m �[34m999999999�[39m)�[33m;�[39m
448:  �[90m306| �[39m
449:  �[90m307| �[39m      expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 99…
450:  �[90m   | �[39m                         �[31m^�[39m
451:  �[90m308| �[39m    })�[33m;�[39m
452:  �[90m309| �[39m
453:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/19]⎯�[22m�[39m
454:  �[41m�[1m FAIL �[22m�[49m compliance.test.ts�[2m > �[22mCompliance Module - Toast�[2m > �[22mEdge cases and error handling�[2m > �[22mshould handle concurrent shows correctly
455:  �[31m�[1mAssertionError�[22m: expected "spy" to be called 10 times, but got 0 times�[39m
456:  �[36m �[2m❯�[22m compliance.test.ts:�[2m316:31�[22m�[39m
457:  �[90m314| �[39m      }
458:  �[90m315| �[39m
459:  �[90m316| �[39m      �[34mexpect�[39m(mockAppendChild)�[33m.�[39m�[34mtoHaveBeenCalledTimes�[39m(�[34m10�[39m)�[33m;�[39m
460:  �[90m   | �[39m                              �[31m^�[39m
461:  �[90m317| �[39m    })�[33m;�[39m
462:  �[90m318| �[39m
463:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/19]⎯�[22m�[39m
464:  �[41m�[1m FAIL �[22m�[49m compliance.test.ts�[2m > �[22mCompliance Module - Toast�[2m > �[22mEdge cases and error handling�[2m > �[22mshould maintain static state across instances
465:  �[31m�[1mAssertionError�[22m: expected +0 to be 1 // Object.is equality�[39m
466:  �[32m- Expected�[39m
467:  �[31m+ Received�[39m
468:  �[32m- 1�[39m
469:  �[31m+ 0�[39m
470:  �[36m �[2m❯�[22m compliance.test.ts:�[2m328:50�[22m�[39m
471:  �[90m326| �[39m      const callsAfterSecond = (document.createElement as any).mock.ca…
472:  �[90m327| �[39m
473:  �[90m328| �[39m      expect(callsAfterSecond - callsAfterFirst).toBe(1); // Only the …
474:  �[90m   | �[39m                                                 �[31m^�[39m
475:  �[90m329| �[39m    })�[33m;�[39m
476:  �[90m330| �[39m  })�[33m;�[39m
477:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[7/19]⎯�[22m�[39m
478:  �[41m�[1m FAIL �[22m�[49m helper.test.ts�[2m > �[22mHelper Functions�[2m > �[22mgetOSInfo�[2m > �[22mshould detect iOS (currently detects as macOS due to order issue)
479:  �[31m�[1mAssertionError�[22m: expected 'unknown' to be '14.6' // Object.is equality�[39m
480:  Expected: �[32m"14.6"�[39m
481:  Received: �[31m"unknown"�[39m
482:  �[36m �[2m❯�[22m helper.test.ts:�[2m347:30�[22m�[39m
483:  �[90m345| �[39m      // Note: This should be 'iOS' but due to order in detection logi…
484:  �[90m346| �[39m      �[34mexpect�[39m(osInfo�[33m.�[39mos)�[33m.�[39m�[34mtoBe�[39m(�[32m'macOS'�[39m)�[33m;�[39m 
485:  �[90m347| �[39m      �[34mexpect�[39m(osInfo�[33m.�[39mversion)�[33m.�[39m�[34mtoBe�[39m(�[32m'14.6'�[39m)�[33m;�[39m
486:  �[90m   | �[39m                             �[31m^�[39m
487:  �[90m348| �[39m    })�[33m;�[39m
488:  �[90m349| �[39m
489:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[8/19]⎯�[22m�[39m
490:  �[41m�[1m FAIL �[22m�[49m incognito.test.ts�[2m > �[22mIncognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould detect incognito mode in Chrome using quota detection
491:  �[31m�[1mAssertionError�[22m: expected false to be true // Object.is equality�[39m
492:  �[32m- Expected�[39m
493:  �[31m+ Received�[39m
494:  �[32m- true�[39m
495:  �[31m+ false�[39m
496:  �[36m �[2m❯�[22m incognito.test.ts:�[2m31:32�[22m�[39m
497:  �[90m 29| �[39m      �[35mconst�[39m result �[33m=�[39m �[35mawait�[39m �[34mdetectIncognito�[39m()�[33m;�[39m
498:  �[90m 30| �[39m      
499:  �[90m 31| �[39m      �[34mexpect�[39m(result�[33m.�[39misPrivate)�[33m.�[39m�[34mtoBe�[39m(�[35mtrue�[39m)�[33m;�[39m
500:  �[90m   | �[39m                               �[31m^�[39m
501:  �[90m 32| �[39m      �[34mexpect�[39m(result�[33m.�[39mbrowserName)�[33m.�[39m�[34mtoContain�[39m(�[32m'Chrome'�[39m)�[33m;�[39m
502:  �[90m 33| �[39m    })�[33m;�[39m
503:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[9/19]⎯�[22m�[39m
504:  �[41m�[1m FAIL �[22m�[49m incognito.test.ts�[2m > �[22mIncognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould detect normal browsing mode in Firefox
505:  �[31m�[1mAssertionError�[22m: expected 'Chromium' to contain 'Firefox'�[39m
506:  Expected: �[32m"Firefox"�[39m
507:  Received: �[31m"Chromium"�[39m
508:  �[36m �[2m❯�[22m incognito.test.ts:�[2m42:34�[22m�[39m
509:  �[90m 40| �[39m      �[34mexpect�[39m(result)�[33m.�[39m�[34mtoHaveProperty�[39m(�[32m'isPrivate'�[39m)�[33m;�[39m
510:  �[90m 41| �[39m      �[34mexpect�[39m(result)�[33m.�[39m�[34mtoHaveProperty�[39m(�[32m'browserName'�[39m)�[33m;�[39m
511:  �[90m 42| �[39m      �[34mexpect�[39m(result�[33m.�[39mbrowserName)�[33m.�[39m�[34mtoContain�[39m(�[32m'Firefox'�[39m)�[33m;�[39m
512:  �[90m   | �[39m                                 �[31m^�[39m
513:  �[90m 43| �[39m    })�[33m;�[39m
514:  �[90m 44| �[39m
515:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[10/19]⎯�[22m�[39m
516:  �[41m�[1m FAIL �[22m�[49m incognito.test.ts�[2m > �[22mIncognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould detect private mode in Firefox using indexedDB detection
517:  �[31m�[1mAssertionError�[22m: expected false to be true // Object.is equality�[39m
518:  �[32m- Expected�[39m
519:  �[31m+ Received�[39m
520:  �[32m- true�[39m
521:  �[31m+ false�[39m
522:  �[36m �[2m❯�[22m incognito.test.ts:�[2m51:32�[22m�[39m
523:  �[90m 49| �[39m      �[35mconst�[39m result �[33m=�[39m �[35mawait�[39m �[34mdetectIncognito�[39m()�[33m;�[39m
524:  �[90m 50| �[39m      
525:  �[90m 51| �[39m      �[34mexpect�[39m(result�[33m.�[39misPrivate)�[33m.�[39m�[34mtoBe�[39m(�[35mtrue�[39m)�[33m;�[39m
526:  �[90m   | �[39m                               �[31m^�[39m
527:  �[90m 52| �[39m      �[34mexpect�[39m(result�[33m.�[39mbrowserName)�[33m.�[39m�[34mtoContain�[39m(�[32m'Firefox'�[39m)�[33m;�[39m
528:  �[90m 53| �[39m    })�[33m;�[39m
529:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[11/19]⎯�[22m�[39m
530:  �[41m�[1m FAIL �[22m�[49m incognito.test.ts�[2m > �[22mIncognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould detect normal browsing mode in Safari
531:  �[31m�[1mAssertionError�[22m: expected 'Chromium' to contain 'Safari'�[39m
532:  Expected: �[32m"Safari"�[39m
533:  Received: �[31m"Chromium"�[39m
534:  �[36m �[2m❯�[22m incognito.test.ts:�[2m62:34�[22m�[39m
535:  �[90m 60| �[39m      �[34mexpect�[39m(result)�[33m.�[39m�[34mtoHaveProperty�[39m(�[32m'isPrivate'�[39m)�[33m;�[39m
536:  �[90m 61| �[39m      �[34mexpect�[39m(result)�[33m.�[39m�[34mtoHaveProperty�[39m(�[32m'browserName'�[39m)�[33m;�[39m
537:  �[90m 62| �[39m      �[34mexpect�[39m(result�[33m.�[39mbrowserName)�[33m.�[39m�[34mtoContain�[39m(�[32m'Safari'�[39m)�[33m;�[39m
538:  �[90m   | �[39m                                 �[31m^�[39m
539:  �[90m 63| �[39m    })�[33m;�[39m
540:  �[90m 64| �[39m
541:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[12/19]⎯�[22m�[39m
542:  �[41m�[1m FAIL �[22m�[49m incognito.test.ts�[2m > �[22mIncognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould detect private mode in Safari using storage detection
543:  �[31m�[1mAssertionError�[22m: expected false to be true // Object.is equality�[39m
544:  �[32m- Expected�[39m
545:  �[31m+ Received�[39m
546:  �[32m- true�[39m
547:  �[31m+ false�[39m
548:  �[36m �[2m❯�[22m incognito.test.ts:�[2m71:32�[22m�[39m
549:  �[90m 69| �[39m      �[35mconst�[39m result �[33m=�[39m �[35mawait�[39m �[34mdetectIncognito�[39m()�[33m;�[39m
550:  �[90m 70| �[39m      
551:  �[90m 71| �[39m      �[34mexpect�[39m(result�[33m.�[39misPrivate)�[33m.�[39m�[34mtoBe�[39m(�[35mtrue�[39m)�[33m;�[39m
552:  �[90m   | �[39m                               �[31m^�[39m
553:  �[90m 72| �[39m      �[34mexpect�[39m(result�[33m.�[39mbrowserName)�[33m.�[39m�[34mtoContain�[39m(�[32m'Safari'�[39m)�[33m;�[39m
554:  �[90m 73| �[39m    })�[33m;�[39m
555:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[13/19]⎯�[22m�[39m
556:  �[41m�[1m FAIL �[22m�[49m incognito.test.ts�[2m > �[22mIncognito Detection Module�[2m > �[22mdetectIncognito�[2m > �[22mshould handle mobile Safari on iOS
557:  �[31m�[1mAssertionError�[22m: expected 'Chromium' to contain 'Safari'�[39m
558:  Expected: �[32m"Safari"�[39m
559:  Received: �[31m"Chromium"�[39m
560:  �[36m �[2m❯�[22m incognito.test.ts:�[2m155:34�[22m�[39m
561:  �[90m153| �[39m      �[34mexpect�[39m(result)�[33m.�[39m�[34mtoHaveProperty�[39m(�[32m'isPrivate'�[39m)�[33m;�[39m
562:  �[90m154| �[39m      �[34mexpect�[39m(result)�[33m.�[39m�[34mtoHaveProperty�[39m(�[32m'browserName'�[39m)�[33m;�[39m
563:  �[90m155| �[39m      �[34mexpect�[39m(result�[33m.�[39mbrowserName)�[33m.�[39m�[34mtoContain�[39m(�[32m'Safari'�[39m)�[33m;�[39m
564:  �[90m   | �[39m                                 �[31m^�[39m
565:  �[90m156| �[39m    })�[33m;�[39m
566:  �[90m157| �[39m  })�[33m;�[39m
567:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[14/19]⎯�[22m�[39m
568:  �[41m�[1m FAIL �[22m�[49m index.test.ts�[2m > �[22mIndex Module�[2m > �[22mCore Functionality�[2m > �[22mshould handle geolocation integration properly
569:  �[31m�[1mAssertionError�[22m: expected 'object' to be 'number' // Object.is equality�[39m
570:  Expected: �[32m"number"�[39m
571:  Received: �[31m"object"�[39m
572:  �[36m �[2m❯�[22m index.test.ts:�[2m228:52�[22m�[39m
573:  �[90m226| �[39m      �[34mexpect�[39m(jsonData)�[33m.�[39m�[34mtoHaveProperty�[39m(�[32m'systemInfo'�[39m)�[33m;�[39m
574:  �[90m227| �[39m      �[34mexpect�[39m(jsonData)�[33m.�[39m�[34mtoHaveProperty�[39m(�[32m'hash'�[39m)�[33m;�[39m
575:  �[90m228| �[39m      �[34mexpect�[39m(�[35mtypeof�[39m jsonData�[33m.�[39mconfidenceAssessment)�[33m.�[39m�[34mtoBe�[39m(�[32m'number'�[39m)�[33m;�[39m
576:  �[90m   | �[39m                                                   �[31m^�[39m
577:  �[90m229| �[39m    })�[33m;�[39m
578:  �[90m230| �[39m
579:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[15/19]⎯�[22m�[39m
580:  �[41m�[1m FAIL �[22m�[49m index.test.ts�[2m > �[22mIndex Module�[2m > �[22mDefault Function Integration�[2m > �[22mshould handle errors and return fallback data
581:  �[31m�[1mAssertionError�[22m�[39m: expected "error" to be called with arguments: [ Array(2) ]�[90m
582:  Received: 
583:  �[1m  1st error call:
584:  �[22m�[2m  [�[22m
585:  �[32m-   "Data collection error:",�[90m
586:  �[32m-   Any<Error>,�[90m
587:  �[31m+   "Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)�[90m
588:  �[31m+     at module.exports (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)�[90m
589:  �[31m+     at HTMLCanvasElementImpl.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)�[90m
590:  �[31m+     at HTMLCanvasElement.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:131:58)�[90m
591:  �[31m+     at getWebGLInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/helper.ts:154:27)�[90m
592:  �[31m+     at getSystemInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/systemInfo.ts:181:22)�[90m
593:  �[31m+     at async Promise.all (index 0)�[90m
594:  �[31m+     at Module.userInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/index.ts:117:39)�[90m
595:  �[31m+     at /home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/index.test.ts:395:22�[90m
596:  �[31m+     at file:///home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20",�[90m
597:  �[31m+   undefined,�[90m
598:  �[2m  ]�[22m
599:  �[1m  2nd error call:
600:  �[22m�[2m  [�[22m
601:  �[32m-   "Data collection error:",�[90m
602:  �[32m-   Any<Error>,�[90m
603:  �[31m+   "Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)�[90m
604:  �[31m+     at module.exports (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)�[90m
605:  �[31m+     at HTMLCanvasElementImpl.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)�[90m
606:  �[31m+     at HTMLCanvasElement.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:131:58)�[90m
607:  �[31m+     at getWebGLInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/helper.ts:154:57)�[90m
608:  �[31m+     at getSystemInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/systemInfo.ts:181:22)�[90m
609:  �[31m+     at async Promise.all (index 0)�[90m
610:  �[31m+     at Module.userInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/index.ts:117:39)�[90m
611:  �[31m+     at /home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/index.test.ts:395:22�[90m
612:  �[31m+     at file:///home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20",�[90m
613:  �[31m+   undefined,�[90m
614:  �[2m  ]�[22m
615:  �[1m  3rd error call:
616:  �[22m�[2m  [�[22m
617:  �[32m-   "Data collection error:",�[90m
618:  �[32m-   Any<Error>,�[90m
619:  �[31m+   "Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)�[90m
620:  �[31m+     at module.exports (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)�[90m
621:  �[31m+     at HTMLCanvasElementImpl.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)�[90m
622:  �[31m+     at HTMLCanvasElement.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:131:58)�[90m
623:  �[31m+     at getCanvasFingerprint (/home/runner/work/fingerprint-oss/fingerprint-oss/src/helper.ts:285:28)�[90m
624:  �[31m+     at getSystemInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/systemInfo.ts:182:17)�[90m
625:  �[31m+     at async Promise.all (index 0)�[90m
626:  �[31m+     at Module.userInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/index.ts:117:39)�[90m
627:  �[31m+     at /home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/index.test.ts:395:22�[90m
628:  �[31m+     at file:///home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20",�[90m
629:  �[31m+   undefined,�[90m
630:  �[2m  ]�[22m
631:  �[1m  4th error call:
632:  �[22m�[2m  [�[22m
633:  �[32m-   "Data collection error:",�[90m
634:  �[32m-   Any<Error>,�[90m
635:  �[31m+   "Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)�[90m
636:  �[31m+     at module.exports (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)�[90m
...

638:  �[31m+     at HTMLCanvasElement.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:131:58)�[90m
639:  �[31m+     at getFontPreferences (/home/runner/work/fingerprint-oss/fingerprint-oss/src/helper.ts:416:32)�[90m
640:  �[31m+     at getSystemInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/systemInfo.ts:196:26)�[90m
641:  �[31m+     at async Promise.all (index 0)�[90m
642:  �[31m+     at Module.userInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/index.ts:117:39)�[90m
643:  �[31m+     at /home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/index.test.ts:395:22�[90m
644:  �[31m+     at file:///home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20",�[90m
645:  �[31m+   undefined,�[90m
646:  �[2m  ]�[22m
647:  �[39m�[90m
648:  Number of calls: �[1m4�[22m
649:  �[39m
650:  �[36m �[2m❯�[22m index.test.ts:�[2m399:26�[22m�[39m
651:  �[90m397| �[39m      �[34mexpect�[39m(result)�[33m.�[39m�[34mtoBeDefined�[39m()�[33m;�[39m
652:  �[90m398| �[39m      �[34mexpect�[39m(result)�[33m.�[39m�[34mtoHaveProperty�[39m(�[32m'hash'�[39m)�[33m;�[39m
653:  �[90m399| �[39m      expect(consoleSpy).toHaveBeenCalledWith('Data collection error:'…
654:  �[90m   | �[39m                         �[31m^�[39m
655:  �[90m400| �[39m      
656:  �[90m401| �[39m      consoleSpy�[33m.�[39m�[34mmockRestore�[39m()�[33m;�[39m
657:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[16/19]⎯�[22m�[39m
658:  �[41m�[1m FAIL �[22m�[49m json.test.ts�[2m > �[22mJSON Module�[2m > �[22mgenerateJSON�[2m > �[22mshould include geolocation information when provided
659:  �[31m�[1mAssertionError�[22m: expected { vpnStatus: undefined, …(7) } to match object { vpnStatus: { vpn: { …(2) } }, …(7) }�[39m
660:  �[32m- Expected�[39m
...

669:  �[32m-     "vpn": {�[39m
670:  �[32m-       "probability": 0.5,�[39m
671:  �[32m-       "status": false,�[39m
672:  �[32m-     },�[39m
673:  �[32m-   },�[39m
674:  �[31m+   "vpnStatus": undefined,�[39m
675:  �[2m  }�[22m
676:  �[36m �[2m❯�[22m json.test.ts:�[2m121:34�[22m�[39m
677:  �[90m119| �[39m      
678:  �[90m120| �[39m      �[34mexpect�[39m(result)�[33m.�[39m�[34mtoHaveProperty�[39m(�[32m'geolocation'�[39m)�[33m;�[39m
679:  �[90m121| �[39m      �[34mexpect�[39m(result�[33m.�[39mgeolocation)�[33m.�[39m�[34mtoMatchObject�[39m({
680:  �[90m   | �[39m                                 �[31m^�[39m
681:  �[90m122| �[39m        vpnStatus�[33m:�[39m { vpn�[33m:�[39m { status�[33m:�[39m �[35mfalse�[39m�[33m,�[39m probability�[33m:�[39m �[34m0.5�[39m } }�[33m,�[39m
682:  �[90m123| �[39m        ip�[33m:�[39m �[32m'192.168.1.1'�[39m�[33m,�[39m
683:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[17/19]⎯�[22m�[39m
684:  �[41m�[1m FAIL �[22m�[49m json.test.ts�[2m > �[22mJSON Module�[2m > �[22mgenerateJSON�[2m > �[22mshould include generated hash
685:  �[31m�[1mAssertionError�[22m: expected undefined to be 'mock-hash-12345' // Object.is equality�[39m
686:  �[32m- Expected:�[39m 
687:  "mock-hash-12345"
688:  �[31m+ Received:�[39m 
689:  undefined
690:  �[36m �[2m❯�[22m json.test.ts:�[2m161:27�[22m�[39m
691:  �[90m159| �[39m      
692:  �[90m160| �[39m      �[34mexpect�[39m(result)�[33m.�[39m�[34mtoHaveProperty�[39m(�[32m'hash'�[39m)�[33m;�[39m
693:  �[90m161| �[39m      �[34mexpect�[39m(result�[33m.�[39mhash)�[33m.�[39m�[34mtoBe�[39m(�[32m'mock-hash-12345'�[39m)�[33m;�[39m
694:  �[90m   | �[39m                          �[31m^�[39m
695:  �[90m162| �[39m    })�[33m;�[39m
696:  �[90m163| �[39m
697:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[18/19]⎯�[22m�[39m
698:  �[41m�[1m FAIL �[22m�[49m systemInfo.test.ts�[2m > �[22mSystemInfo Module�[2m > �[22mdetectBot�[2m > �[22mshould detect bots based on user agent patterns
699:  �[31m�[1mAssertionError�[22m: Failed for userAgent: facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php) (index: 2): expected false to be true // Object.is equality�[39m
700:  �[32m- Expected�[39m
701:  �[31m+ Received�[39m
702:  �[32m- true�[39m
703:  �[31m+ false�[39m
704:  �[36m �[2m❯�[22m systemInfo.test.ts:�[2m115:86�[22m�[39m
705:  �[90m113| �[39m        �[35mconst�[39m result �[33m=�[39m �[34mdetectBot�[39m()�[33m;�[39m
706:  �[90m114| �[39m        
707:  �[90m115| �[39m        expect(result.isBot, `Failed for userAgent: ${userAgent} (inde…
708:  �[90m   | �[39m                                                                                     �[31m^�[39m
709:  �[90m116| �[39m        expect(result.signals.some(s => s.startsWith('strong:ua-')), `…
710:  �[90m117| �[39m        expect(result.confidence, `Low confidence for: ${userAgent}`).…
711:  �[90m �[2m❯�[22m systemInfo.test.ts:�[2m103:21�[22m�[39m
712:  �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[19/19]⎯�[22m�[39m
713:  �[2m Test Files �[22m �[1m�[31m6 failed�[39m�[22m�[2m | �[22m�[1m�[32m5 passed�[39m�[22m�[90m (11)�[39m
714:  �[2m      Tests �[22m �[1m�[31m19 failed�[39m�[22m�[2m | �[22m�[1m�[32m214 passed�[39m�[22m�[90m (233)�[39m
715:  �[2m   Start at �[22m 12:17:48
716:  �[2m   Duration �[22m 4.03s�[2m (transform 586ms, setup 226ms, collect 1.07s, tests 620ms, environment 6.95s, prepare 1.03s)�[22m
717:  ##[error]AssertionError: expected [Function] to throw an error
718:   ❯ compliance.test.ts:268:10
719:  
720:  
721:  ##[error]AssertionError: expected [Function] to throw an error
722:   ❯ compliance.test.ts:279:10
723:  
724:  
725:  ##[error]AssertionError: expected [Function] to throw an error
726:   ❯ compliance.test.ts:291:10
727:  
728:  
729:  ##[error]AssertionError: expected "spy" to be called with arguments: [ Any<Function>, 1 ]
730:  
731:  Number of calls: 0
732:  
733:   ❯ compliance.test.ts:301:26
734:  
735:  
736:  ##[error]AssertionError: expected "spy" to be called with arguments: [ Any<Function>, 999999999 ]
737:  
738:  Number of calls: 0
739:  
740:   ❯ compliance.test.ts:307:26
741:  
742:  
743:  ##[error]AssertionError: expected "spy" to be called 10 times, but got 0 times
744:   ❯ compliance.test.ts:316:31
745:  
746:  
747:  ##[error]AssertionError: expected +0 to be 1 // Object.is equality
748:  
749:  - Expected
750:  + Received
751:  
752:  - 1
753:  + 0
754:  
755:   ❯ compliance.test.ts:328:50
756:  
757:  
758:  ##[error]AssertionError: expected 'unknown' to be '14.6' // Object.is equality
759:  
760:  Expected: "14.6"
761:  Received: "unknown"
762:  
763:   ❯ helper.test.ts:347:30
764:  
765:  
766:  ##[error]AssertionError: expected false to be true // Object.is equality
767:  
768:  - Expected
769:  + Received
770:  
771:  - true
772:  + false
773:  
774:   ❯ incognito.test.ts:31:32
775:  
776:  
777:  ##[error]AssertionError: expected 'Chromium' to contain 'Firefox'
778:  
779:  Expected: "Firefox"
780:  Received: "Chromium"
781:  
782:   ❯ incognito.test.ts:42:34
783:  
784:  
785:  ##[error]AssertionError: expected false to be true // Object.is equality
786:  
787:  - Expected
788:  + Received
789:  
790:  - true
791:  + false
792:  
793:   ❯ incognito.test.ts:51:32
794:  
795:  
796:  ##[error]AssertionError: expected 'Chromium' to contain 'Safari'
797:  
798:  Expected: "Safari"
799:  Received: "Chromium"
800:  
801:   ❯ incognito.test.ts:62:34
802:  
803:  
804:  ##[error]AssertionError: expected false to be true // Object.is equality
805:  
806:  - Expected
807:  + Received
808:  
809:  - true
810:  + false
811:  
812:   ❯ incognito.test.ts:71:32
813:  
814:  
815:  ##[error]AssertionError: expected 'Chromium' to contain 'Safari'
816:  
817:  Expected: "Safari"
818:  Received: "Chromium"
819:  
820:   ❯ incognito.test.ts:155:34
821:  
822:  
823:  ##[error]AssertionError: expected 'object' to be 'number' // Object.is equality
824:  
825:  Expected: "number"
826:  Received: "object"
827:  
828:   ❯ index.test.ts:228:52
829:  
830:  
831:  ##[error]AssertionError: expected "error" to be called with arguments: [ Array(2) ]
832:  
833:  Received: 
834:  
835:    1st error call:
836:  
837:    [
838:  -   "Data collection error:",
839:  -   Any<Error>,
840:  +   "Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
841:  +     at module.exports (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
842:  +     at HTMLCanvasElementImpl.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)
843:  +     at HTMLCanvasElement.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:131:58)
844:  +     at getWebGLInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/helper.ts:154:27)
845:  +     at getSystemInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/systemInfo.ts:181:22)
846:  +     at async Promise.all (index 0)
847:  +     at Module.userInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/index.ts:117:39)
848:  +     at /home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/index.test.ts:395:22
849:  +     at file:///home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20",
850:  +   undefined,
851:    ]
852:  
853:    2nd error call:
854:  
855:    [
856:  -   "Data collection error:",
857:  -   Any<Error>,
858:  +   "Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
859:  +     at module.exports (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
860:  +     at HTMLCanvasElementImpl.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)
861:  +     at HTMLCanvasElement.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:131:58)
862:  +     at getWebGLInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/helper.ts:154:57)
863:  +     at getSystemInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/systemInfo.ts:181:22)
864:  +     at async Promise.all (index 0)
865:  +     at Module.userInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/index.ts:117:39)
866:  +     at /home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/index.test.ts:395:22
867:  +     at file:///home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20",
868:  +   undefined,
869:    ]
870:  
871:    3rd error call:
872:  
873:    [
874:  -   "Data collection error:",
875:  -   Any<Error>,
876:  +   "Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
877:  +     at module.exports (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
878:  +     at HTMLCanvasElementImpl.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)
879:  +     at HTMLCanvasElement.getContext (/home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:131:58)
880:  +     at getCanvasFingerprint (/home/runner/work/fingerprint-oss/fingerprint-oss/src/helper.ts:285:28)
881:  +     at getSystemInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/systemInfo.ts:182:17)
882:  +     at async Promise.all (index 0)
883:  +     at Module.userInfo (/home/runner/work/fingerprint-oss/fingerprint-oss/src/index.ts:117:39)
884:  +     at /home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/index.test.ts:395:22
885:  +     at file:///home/runner/work/fingerprint-oss/fingerprint-oss/test/unit/node_modules/@vitest/runner/dist/chunk-hooks.js:752:20",
886:  +   undefined,
887:    ]
888:  
889:    4th error call:
890:  
891:    [
892:  -   "Data collection error:",
893:  -   Any<Error>,
894:  +   "Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
895:  +     at module.exports (/home/runner/work/fingerprint-oss/fingerp
896:  ##[error]AssertionError: expected { vpnStatus: undefined, …(7) } to match object { vpnStatus: { vpn: { …(2) } }, …(7) }
897:  
...

904:        "isAnonymousVpn": false,
905:        "network": "192.168.1.0/24",
906:      },
907:  -   "vpnStatus": {
908:  -     "vpn": {
909:  -       "probability": 0.5,
910:  -       "status": false,
911:  -     },
912:  -   },
913:  +   "vpnStatus": undefined,
914:    }
915:  
916:   ❯ json.test.ts:121:34
917:  
918:  
919:  ##[error]AssertionError: expected undefined to be 'mock-hash-12345' // Object.is equality
920:  
921:  - Expected: 
922:  "mock-hash-12345"
923:  
924:  + Received: 
925:  undefined
926:  
927:   ❯ json.test.ts:161:27
928:  
929:  
930:  ##[error]AssertionError: Failed for userAgent: facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php) (index: 2): expected false to be true // Object.is equality
931:  
932:  - Expected
933:  + Received
934:  
935:  - true
936:  + false
937:  
938:   ❯ systemInfo.test.ts:115:86
939:   ❯ systemInfo.test.ts:103:21
940:  
941:  
942:  ##[error]Process completed with exit code 1.
943:  ##[group]Run actions/upload-artifact@v4

@codiumai-pr-agent-free
Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Security Validation Logic

The security validation logic in the SecurityValidator class contains complex threat detection algorithms that should be carefully reviewed for false positives and edge cases, particularly in the detectManipulationAttempts and detectSpoofingAttempts methods.

validateSecurity(originalInfo: SystemInfo, normalizedInfo: any): SecurityValidationResult {
  const threats: SecurityThreat[] = [];
  const recommendations: string[] = [];

  // Perform entropy analysis
  const entropyAnalysis = this.analyzeEntropy(originalInfo, normalizedInfo);

  // Check for entropy loss
  if (entropyAnalysis.entropyPreservationRatio < this.config.minEntropyThreshold) {
    threats.push({
      type: SecurityThreatType.ENTROPY_LOSS,
      severity: 'high',
      property: 'global',
      description: `Normalization reduced entropy by ${((1 - entropyAnalysis.entropyPreservationRatio) * 100).toFixed(1)}%`,
      originalValue: entropyAnalysis.originalEntropy,
      normalizedValue: entropyAnalysis.normalizedEntropy,
      riskScore: 1 - entropyAnalysis.entropyPreservationRatio
    });
    recommendations.push('Consider adjusting normalization rules to preserve more entropy');
  }

  // Check for manipulation attempts
  if (this.config.enableManipulationDetection) {
    const manipulationThreats = this.detectManipulationAttempts(originalInfo);
    threats.push(...manipulationThreats);
  }

  // Check for spoofing attempts
  if (this.config.enableSpoofingDetection) {
    const spoofingThreats = this.detectSpoofingAttempts(originalInfo);
    threats.push(...spoofingThreats);
  }
Hash Comparison Performance

The compareSystemInfo method performs deep object comparison which could be computationally expensive for large SystemInfo objects. Consider reviewing the implementation for potential performance optimizations.

async compareSystemInfo(
  info1: SystemInfo,
  info2: SystemInfo,
  hashConfig?: HashGeneratorConfig
): Promise<DetailedComparisonResult> {
  const startTime = performance.now();
  const comparisonId = this.generateComparisonId();

  // Generate hashes with debug information
  const debugConfig = { 
    debugMode: this.config.includeDebugInfo,
    ...hashConfig 
  };

  const result1 = await generateIdWithDebug(info1, debugConfig);
  const result2 = await generateIdWithDebug(info2, debugConfig);

  // Find differences in original inputs
  const originalDifferences = this.findObjectDifferences('', info1, info2);

  // Find differences in normalized inputs
  let normalizedDifferences: PropertyDifference[] = [];
  if (result1.debugInfo && result2.debugInfo) {
    normalizedDifferences = this.findObjectDifferences(
      '',
      result1.debugInfo.normalizedInput,
      result2.debugInfo.normalizedInput
    );
  }

  // Analyze impact of differences
  const impactAnalysis = this.analyzeImpact(
    originalDifferences,
    normalizedDifferences,
    result1.hash === result2.hash
  );

  const endTime = performance.now();

  return {
    identical: result1.hash === result2.hash && originalDifferences.length === 0,
    hashesMatch: result1.hash === result2.hash,
    hash1: result1.hash,
    hash2: result2.hash,
    differences: originalDifferences,
    normalizedDifferences,
    impactAnalysis,
    debugInfo1: result1.debugInfo,
    debugInfo2: result2.debugInfo,
    comparisonMetadata: {
      comparisonId,
      timestamp: Date.now(),
      processingTime: endTime - startTime,
      config: debugConfig,
      deepAnalysisEnabled: this.config.enableDeepAnalysis || false
    }
  };
Entropy Calculation

The entropy calculation methods (calculateEntropy, analyzeEntropy) use complex mathematical operations that should be verified for numerical stability and accuracy, especially with edge cases like empty inputs or very large inputs.

private calculateEntropy(data: any): number {
  const serialized = JSON.stringify(data);
  const charFrequency = new Map<string, number>();

  // Count character frequencies
  for (const char of serialized) {
    charFrequency.set(char, (charFrequency.get(char) || 0) + 1);
  }

  // Calculate Shannon entropy
  let entropy = 0;
  const totalChars = serialized.length;

  for (const frequency of charFrequency.values()) {
    const probability = frequency / totalChars;
    entropy -= probability * Math.log2(probability);
  }

  // Normalize entropy (max possible entropy for this length)
  const maxEntropy = Math.log2(Math.min(totalChars, 256)); // Assuming 256 possible characters
  return maxEntropy > 0 ? entropy / maxEntropy : 0;
}

@IntegerAlex IntegerAlex requested a review from Copilot August 16, 2025 12:18
@codiumai-pr-agent-free
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Excessive complexity for hash generation

The PR introduces significant complexity with multiple new modules and thousands
of lines of code for what should be a relatively simple hash generation task.
Consider a more focused approach that addresses the core stability issues
without the extensive abstraction layers, which could make maintenance and
debugging more difficult.

Examples:

src/security.ts [326-335]
private detectScriptInjection(info: SystemInfo): SecurityThreat[] {
  const threats: SecurityThreat[] = [];
  const scriptPatterns = [
    /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
    /javascript:/gi,
    /on\w+\s*=/gi,
    /eval\s*\(/gi,
    /Function\s*\(/gi,
    /<iframe|<object|<embed|<link|<meta/gi
  ];
src/comparison.ts [815-953]
export class HashTroubleshooter {
  private comparator: HashComparator;

  constructor(config?: Partial<ComparisonConfig>) {
    this.comparator = new HashComparator(config);
  }

  /**
   * Diagnoses why two SystemInfo inputs produce different hashes
   */

 ... (clipped 30 lines)

Solution Walkthrough:

Before:

// PR's approach: A complex pipeline with many large, separate modules.
Input -> ValidationEngine(security.ts, validation.ts)
      -> FallbackManager(fallback.ts, errorHandler.ts)
      -> Normalizer(normalization.ts)
      -> Serializer(serialization.ts)
      -> Hasher(hash.ts)
// Also includes separate large modules for Comparison and Debugging.

After:

// Suggested simpler approach:
Input -> validateAndSanitize(input)
      -> applyFallbacks(sanitizedInput)
      -> normalize(fallbackInput)
      -> serialize(normalizedInput)
      -> hash(serializedInput)

// Logic is more contained within fewer modules, avoiding extensive
// abstraction layers and features not core to stable hash generation.
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies that the PR introduces massive architectural complexity with thousands of lines of code across many new modules, which is a critical high-level concern for maintainability.

High
General
Improve browser compatibility

The Math.log2() function is not supported in older browsers. To ensure
compatibility across all environments, implement a fallback for Math.log2()
using the natural logarithm.

src/security.ts [1066-1083]

 private calculateStringEntropy(str: string): number {
   if (!str || str.length === 0) return 0;
   
   const charFreq = new Map<string, number>();
   for (const char of str) {
     charFreq.set(char, (charFreq.get(char) || 0) + 1);
   }
   
   let entropy = 0;
   for (const freq of charFreq.values()) {
     const probability = freq / str.length;
-    entropy -= probability * Math.log2(probability);
+    // Use Math.log with base conversion for broader compatibility
+    entropy -= probability * (Math.log(probability) / Math.log(2));
   }
   
   // Normalize by maximum possible entropy for this string length
-  const maxEntropy = Math.log2(Math.min(str.length, 256));
+  const maxEntropy = Math.log(Math.min(str.length, 256)) / Math.log(2);
   return maxEntropy > 0 ? entropy / maxEntropy : 0;
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that Math.log2() is not supported in older browsers like IE and proposes a mathematically sound alternative using Math.log() for better compatibility, which is a significant improvement for a library intended for wide use.

Medium
Cache regex patterns

The regex patterns are recreated on every function call, which is inefficient.
Move these pattern definitions outside the function as constants to avoid
recreating them on each invocation, improving performance for this
security-critical function.

src/validation.ts [237-266]

+// Check for script injection patterns
+const SCRIPT_PATTERNS = [
+  /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+  /javascript:/gi,
+  /on\w+\s*=/gi,
+  /eval\s*\(/gi,
+  /Function\s*\(/gi
+];
+
+// Check for SQL injection patterns
+const SQL_PATTERNS = [
+  /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION)\b)/gi,
+  /(';|--;|\|\||\/\*|\*\/|'.*OR.*'|'.*AND.*')/gi
+];
+
+// Check for path traversal patterns
+const PATH_TRAVERSAL_PATTERNS = [
+  /\.\.\//gi,
+  /\.\.\\/gi,
+  /%2e%2e%2f/gi,
+  /%2e%2e%5c/gi
+];
+
+const ALL_MALICIOUS_PATTERNS = [...SCRIPT_PATTERNS, ...SQL_PATTERNS, ...PATH_TRAVERSAL_PATTERNS];
+
 /**
  * Detects potentially malicious input patterns
  * @param info - The SystemInfo object to analyze
  * @returns True if malicious patterns are detected
  */
 detectMaliciousInput(info: SystemInfo): boolean {
-  // Check for script injection patterns
-  const scriptPatterns = [
-    /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
-    /javascript:/gi,
-    /on\w+\s*=/gi,
-    /eval\s*\(/gi,
-    /Function\s*\(/gi
-  ];
+  const allPatterns = ALL_MALICIOUS_PATTERNS;
 
-  // Check for SQL injection patterns
-  const sqlPatterns = [
-    /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION)\b)/gi,
-    /(';|--;|\|\||\/\*|\*\/|'.*OR.*'|'.*AND.*')/gi
-  ];
-
-  // Check for path traversal patterns
-  const pathTraversalPatterns = [
-    /\.\.\//gi,
-    /\.\.\\/gi,
-    /%2e%2e%2f/gi,
-    /%2e%2e%5c/gi
-  ];
-
-  const allPatterns = [...scriptPatterns, ...sqlPatterns, ...pathTraversalPatterns];
-
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that regex patterns are recompiled on every call to detectMaliciousInput, and moving them to constants improves performance by avoiding this redundant work.

Medium
Remove unnecessary async

The function is using await keyword but doesn't contain any asynchronous
operations. Remove the async keyword from the function declaration since it's
unnecessarily creating a Promise wrapper around the return value, which adds
overhead.

src/hash.ts [155-167]

 /**
  * Optimized version of buildStableInfo without fallback tracking for better performance
  */
-async function buildStableInfoOptimized(
+function buildStableInfoOptimized(
   systemInfo: SystemInfo, 
   fallbackManager: FallbackManager
-): Promise<any> {
+): any {
   const getFallbackValue = (property: keyof SystemInfo, value: any, reason: FallbackReason) => {
     if (value === null || value === undefined || 
         (typeof value === 'string' && value.trim() === '') ||
         (Array.isArray(value) && value.length === 0)) {
       return fallbackManager.getFallbackValue(property, reason).value;
     }
     return value;
   };
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that buildStableInfoOptimized is unnecessarily async as it contains no await calls, and removing it provides a minor performance improvement.

Low
Possible issue
Fix retry logic inconsistency

The comment and implementation are inconsistent. If maxRetries is 3, the comment
says we can make attempts 1-4, but the code allows attempts 1-3 only. The
condition should be attemptCount > this.config.maxRetries + 1 to match the
comment or the comment should be updated.

src/errorHandler.ts [181-187]

 shouldRetry(error: Error, attemptCount: number): boolean {
   // Don't retry if we've exceeded the max retry limit
   // maxRetries represents the maximum number of retry attempts allowed
-  // So if maxRetries is 3, we can make attempts 1, 2, 3, and 4 (original + 3 retries)
+  // So if maxRetries is 3, we can make attempts 1, 2, 3 (original + 2 retries)
   if (attemptCount > this.config.maxRetries) {
     return false;
   }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies an inconsistency between the comment and the retry logic, and the proposed fix of updating the comment makes the code clearer and less misleading.

Low
Prevent division by zero

The function has a potential division by zero issue when value.length is 0. This
would cause a runtime error when calculating the diversityRatio. Add a check to
handle empty strings.

src/security.ts [1525-1545]

 private hasManipulationPatterns(value: string): boolean {
   // Check for obvious fake/test patterns
   const fakePatterns = [
     /fake|test|mock|dummy|placeholder/i,
     /^(a+|1+|0+|x+)$/i, // Repeated single characters
     /^(abc|123|xyz|test)$/i, // Common test values
     /blocked|unavailable|error|null/i
   ];
   
   for (const pattern of fakePatterns) {
     if (pattern.test(value)) {
       return true;
     }
   }
   
   // Check for very low character diversity
+  if (value.length === 0) return false;
   const uniqueChars = new Set(value.toLowerCase()).size;
   const diversityRatio = uniqueChars / value.length;
   
   return diversityRatio < 0.3 && value.length > 5;
 }
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly identifies a potential 0/0 division if value.length is 0, which results in NaN. While this does not cause a runtime error in JavaScript, the proposed change improves code robustness by explicitly handling this edge case.

Low
  • More

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces comprehensive stabilization in hash generation for fingerprint consistency, focusing on security validation, threat detection, entropy analysis, and debugging capabilities for reliable fingerprint generation across different environments.

Key changes include:

  • Enhanced security and validation infrastructure: New security validation system with threat detection, entropy analysis, and manipulation resistance to prevent fingerprint spoofing
  • Robust error handling and debugging: Comprehensive error categorization, retry logic with exponential backoff, and detailed debug logging system for troubleshooting fingerprint variations
  • Improved data processing: Enhanced normalization utilities, deterministic serialization, and fallback management for consistent fingerprint generation

Reviewed Changes

Copilot reviewed 13 out of 15 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/security.ts Comprehensive security validation system with threat detection and entropy analysis
src/validation.ts Input validation engine with sanitization and security checks
src/comparison.ts Hash comparison utilities for troubleshooting fingerprint variations
src/fallback.ts Fallback value management with retry logic and error categorization
src/serialization.ts Enhanced deterministic object serialization with normalization
src/normalization.ts Enhanced data normalization utilities for predictable fingerprint generation
src/debug.ts Debug logging system with normalization step tracking
src/errorHandler.ts Error handling system with categorization and retry logic
src/hash.ts Enhanced hash generation with validation, fallback management, and debug capabilities
src/json.ts Updated JSON generation to support hash configuration
src/index.ts Enhanced main entry point with new features and exports
tsconfig.json Updated TypeScript configuration to exclude test files

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.

// Fallback to basic normalization if module not available
return this.basicNormalize(data);
}
}
Copy link

Copilot AI Aug 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using require() in TypeScript can lead to issues with module resolution and type safety. Consider using dynamic import() instead for consistency with ES modules: const normalizationModule = await import('./normalization');

Suggested change
}
/**
* Normalizes data for security validation (simplified normalization)
* @param data - The data to normalize
* @returns Promise resolving to normalized data for security analysis
*/
private async normalizeForSecurityCheck(data: SystemInfo): Promise<any> {
// Use dynamic import to avoid circular dependencies
try {
const normalizationModule = await import('./normalization');
return normalizationModule.normalizeValue(data);
} catch (error) {
// Fallback to basic normalization if module not available
return this.basicNormalize(data);
}
}

Copilot uses AI. Check for mistakes.
return normalizationModule.normalizeValue(data);
} catch (error) {
// Fallback to basic normalization if module not available
return this.basicNormalize(data);
Copy link

Copilot AI Aug 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling in the try-catch block silently falls back to basicNormalize() without logging the error reason. Consider adding a debug log or warning to help with troubleshooting: console.warn('Failed to load normalization module:', error);

Suggested change
return this.basicNormalize(data);
} catch (error) {
// Fallback to basic normalization if module not available
console.warn('Failed to load normalization module:', error);
return this.basicNormalize(data);

Copilot uses AI. Check for mistakes.

// Check for suspicious numeric values (more lenient ranges)
if (typeof info.colorDepth === 'number' && (info.colorDepth < 0 || info.colorDepth > 128)) return true;
if (typeof info.hardwareConcurrency === 'number' && (info.hardwareConcurrency < 0 || info.hardwareConcurrency > 512)) return true;
Copy link

Copilot AI Aug 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Magic numbers like 128 and 512 are used multiple times without explanation. Consider defining these as named constants at the top of the file: const MAX_COLOR_DEPTH = 128; const MAX_HARDWARE_CONCURRENCY = 512;

Suggested change
if (typeof info.hardwareConcurrency === 'number' && (info.hardwareConcurrency < 0 || info.hardwareConcurrency > 512)) return true;
if (typeof info.colorDepth === 'number' && (info.colorDepth < 0 || info.colorDepth > MAX_COLOR_DEPTH)) return true;
if (typeof info.hardwareConcurrency === 'number' && (info.hardwareConcurrency < 0 || info.hardwareConcurrency > MAX_HARDWARE_CONCURRENCY)) return true;

Copilot uses AI. Check for mistakes.
private hasInconsistentHardware(info: SystemInfo): boolean {
// Check for impossible hardware combinations
if (typeof info.hardwareConcurrency === 'number' && info.hardwareConcurrency > 128) return true; // Unrealistic CPU count
if (info.deviceMemory && info.deviceMemory > 32) return true; // Unrealistic memory
Copy link

Copilot AI Aug 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The comment says 'Unrealistic CPU count' but the threshold is 128, which may become outdated as hardware evolves. Consider making this configurable or using a more future-proof threshold with better documentation.

Suggested change
if (info.deviceMemory && info.deviceMemory > 32) return true; // Unrealistic memory
// Check for impossible hardware combinations
// Unrealistic CPU count: configurable threshold. Default is 128, which covers virtually all consumer and most server hardware as of 2024.
// Adjust MAX_REALISTIC_CPU_COUNT if hardware trends change or for specific deployment environments.
if (typeof info.hardwareConcurrency === 'number' && info.hardwareConcurrency > MAX_REALISTIC_CPU_COUNT) return true;
if (info.deviceMemory && info.deviceMemory > 32) return true; // Unrealistic memory

Copilot uses AI. Check for mistakes.
const multiplier = Math.pow(10, clampedPrecision);
// For negative numbers, we need to handle the epsilon differently
const epsilon = value >= 0 ? Number.EPSILON : -Number.EPSILON;
const roundedValue = Math.round((value + epsilon) * multiplier) / multiplier;
Copy link

Copilot AI Aug 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The epsilon addition logic for negative numbers is incorrect. Adding a negative epsilon to a negative number makes the rounding less accurate, not more. The epsilon should be added consistently regardless of sign, or the logic should be: const adjustedValue = value >= 0 ? value + epsilon : value - epsilon;

Suggested change
const roundedValue = Math.round((value + epsilon) * multiplier) / multiplier;
const adjustedValue = value >= 0 ? value + Number.EPSILON : value - Number.EPSILON;
const roundedValue = Math.round(adjustedValue * multiplier) / multiplier;

Copilot uses AI. Check for mistakes.
// Arrays and objects are handled by the normalization utilities before this point
return value;
}

Copy link

Copilot AI Aug 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The enhancedReplacer function is defined but never used in the codebase. Consider removing it or integrating it into the serialization process to avoid dead code.

Suggested change

Copilot uses AI. Check for mistakes.
}

// Handle special cases before normalization
if (obj instanceof ArrayBuffer) {
Copy link

Copilot AI Aug 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ArrayBuffer handling is duplicated in multiple places (lines 266, 289, 331). Consider extracting this logic into a shared utility function to reduce code duplication.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

🧹 Nitpick comments (19)
tsconfig.json (1)

17-17: Tests excluded from TS build: sensible default

Excluding "src/**/*.test.ts" from the TypeScript build keeps production artifacts clean while letting the test runner handle compilation. If you also use ".spec.ts" naming anywhere, consider adding that pattern too.

-  "exclude": ["node_modules", "dist", "src/**/*.test.ts"]
+  "exclude": ["node_modules", "dist", "src/**/*.test.ts", "src/**/*.spec.ts"]
src/comparison.test.ts (3)

128-137: Assert normalization effect for precision-only differences

You're asserting detection of a precision difference and LOW severity. To further validate stabilization, consider asserting that such a difference is counted as normalized-away and doesn’t affect the hash impact (if your comparator exposes that info).

   const audioDiff = result.differences.find(d => d.property === 'audio');
   expect(audioDiff?.type).toBe(DifferenceType.PRECISION_DIFFERENCE);
   expect(audioDiff?.severity).toBe(DifferenceSeverity.LOW);
+  // Optional: verify impact analysis recognizes normalization
+  expect(result.impactAnalysis.normalizedAwayDifferences).toBeGreaterThan(0);
+  // Optional: if available on differences: affectsHash should be false
+  // expect(audioDiff?.affectsHash).toBe(false);

184-200: Variation analysis assertions are well chosen

The expectations on hash counts, variation rate, and presence of stability metrics/recommendations cover the critical outputs. Consider adding a negative test for the error path (inputs.length < 2) to lock in guardrails.

it('should require at least two inputs for variation analysis', async () => {
  await expect(analyzeHashVariations([createMockSystemInfo()])).rejects.toThrow();
});

243-277: Troubleshooter test suite generation: solid structure

Validating variations and metadata totals is on point. Optionally, also assert that each variation has expectedStable reflected in downstream expectations if exposed in the generated suite.

src/json.ts (2)

73-78: Signature extension: consider typing the return shape

The function returns a rich, structured object but doesn’t expose an explicit return type. Consider exporting a response interface to lock in the API contract for consumers.

export interface GeneratedFingerprintJSON {
  confidenceAssessment: {
    system: { score: number; rating: string; description: string; reliability: string; level: 'low' | 'medium-low' | 'medium' | 'medium-high' | 'high'; factors: string; };
    combined?: { score: number; rating: string; description: string; reliability: string; level: 'low' | 'medium-low' | 'medium' | 'medium-high' | 'high'; factors: string; };
  };
  geolocation: {
    vpnStatus?: string;
    ip: string;
    city: string;
    region: { isoCode: string; name: string };
    country: { isoCode: string; name: string };
    continent: { code: string; name: string };
    location: { accuracyRadius: number; latitude: number; longitude: number; timeZone: string };
    traits: { isAnonymous: boolean; isAnonymousProxy: boolean; isAnonymousVpn: boolean; network: string };
  } | null;
  systemInfo: SystemInfo;
  hash: string;
}

And then:

-export async function generateJSON(...): Promise<any> {
+export async function generateJSON(...): Promise<GeneratedFingerprintJSON> {

90-93: Optional: be defensive around getVpnStatus failures

If getVpnStatus can throw (network, parsing), consider wrapping it to avoid failing the whole response and defaulting vpnStatus to undefined.

-  const vpnStatus = geolocationInfo ? await getVpnStatus({
-    geoip: geolocationInfo.location?.timeZone || 'UTC',
-    localtime: systemInfo?.timezone || 'UTC'
-  }) : undefined;
+  let vpnStatus: string | undefined = undefined;
+  if (geolocationInfo) {
+    try {
+      vpnStatus = await getVpnStatus({
+        geoip: geolocationInfo.location?.timeZone || 'UTC',
+        localtime: systemInfo?.timezone || 'UTC'
+      });
+    } catch {
+      vpnStatus = undefined;
+    }
+  }
src/debug.test.ts (1)

141-152: Avoid brittle assumptions on log entry counts

Asserting an exact logEntries length can be fragile if the logger emits a “session started/ended” entry. Prefer asserting minimum count and presence of the test message.

-  expect(parsed.logEntries).toHaveLength(2); // start + test message
+  expect(parsed.logEntries.length).toBeGreaterThanOrEqual(1);
+  expect(parsed.logEntries.some((e: any) => e.message?.includes('test message'))).toBe(true);
src/security.ts (1)

1469-1478: Null-safe lowercasing for WebGL spoofing checks

webGL.vendor/renderer are checked for truthiness above, but adding defensive normalization avoids surprises if types drift.

-    return suspiciousVendors.includes(webGL.vendor.toLowerCase()) ||
-           suspiciousRenderers.includes(webGL.renderer.toLowerCase());
+    const vendor = String(webGL.vendor || '').toLowerCase();
+    const renderer = String(webGL.renderer || '').toLowerCase();
+    return suspiciousVendors.includes(vendor) || suspiciousRenderers.includes(renderer);
src/index.ts (1)

110-152: LGTM overall; minor note on Toast duplication

Flow and new hash-related exports look good. The transparency branch shows two toasts when message is provided (brand + message). If that’s intentional for emphasis, keep it; otherwise, collapse to one toast.

src/normalization.ts (1)

181-207: Consider stable, locale-independent comparator for primitive array sort

Current sort coerces to string and compares via </>. For more consistent cross-env ordering (especially with unicode), use localeCompare with fixed options.

-      .sort((a, b) => {
-        const strA = String(a);
-        const strB = String(b);
-        return strA < strB ? -1 : strA > strB ? 1 : 0;
-      });
+      .sort((a, b) => String(a).localeCompare(String(b), 'en', { sensitivity: 'base' }));
src/validation.ts (1)

933-941: Dynamic import vs require: check bundler/TS module config

Using require('./normalization') inside TS may break in ESM builds. If you target ESM, prefer dynamic import() and make the callsite async, or gate based on environment.

Would you like me to provide an async-safe refactor that preserves the current sync API using a lazy singleton?

src/errorHandler.ts (2)

337-349: Avoid mutating error message to store timestamp

Wrapping the original error message in a new Error loses the original stack and conflates message with metadata. Store a separate timestamp field or extend the stored object instead.

-    this.errorHistory.get(propertyKey)!.push({
-      ...errorInfo,
-      // Add timestamp for tracking
-      error: new Error(`${errorInfo.error.message} [${new Date().toISOString()}]`)
-    });
+    this.errorHistory.get(propertyKey)!.push({
+      ...errorInfo,
+      // Preserve original error; track timestamp separately if needed
+      // @ts-expect-error allow augmentation for internal history
+      timestamp: Date.now()
+    });

If you prefer strict typing, extend ErrorInfo with an optional timestamp?: number and update references accordingly.


379-420: Retry success rate metric may be misleading

Counting “successful retries” as “entries where shouldRetry was true” does not reflect actual success. Consider tracking resolutions that no longer produce errors for the same property/attempt.

Would you like a follow-up patch to capture “resolved after retry” by correlating consecutive attempts per property?

src/hash.ts (3)

140-146: Avoid double-normalization to reduce CPU/time.

You normalize once via normalizeSystemInfo(...) and again inside EnhancedSerializer (enableNormalization: true). This duplicates work. Either rely on the serializer for normalization or turn it off in serialization when you already normalized.

Minimal change to disable serializer normalization (keep current behavior otherwise):

-  const serializationConfig: Partial<SerializationConfig> = {
-    enableNormalization: true,
+  const serializationConfig: Partial<SerializationConfig> = {
+    enableNormalization: false,
     sortKeys: true,
     sortArrays: true,
     ...config?.serializationConfig
   };

Also applies to: 305-310


205-214: Hard-coded Brave plugin filter may create bias and instability.

Filtering plugins by name ('Brave') can skew fingerprints and lead to non-portable behavior. Consider documenting this choice or making it configurable (e.g., via HashGeneratorConfig.serializationConfig or a dedicated filter option).


399-432: Remove unused helpers to shrink surface area and bundle size.

findObjectDifferences, getNestedProperty, enhancedReplacer, replacer, deepSortObject, and legacyDeepSortObject are not used in this module. Removing them improves maintainability and reduces code size. If they are intended for reuse, export them from a dedicated utility module.

Also applies to: 434-439, 534-551, 557-563, 575-602, 607-629

src/serialization.ts (1)

242-253: Normalizing object keys may cause collisions or unexpected merges.

normalizeStringValue(key) alters property names (Unicode normalization, whitespace collapse). If two distinct keys normalize to the same string, values will collide during serialization. Confirm this is desired; otherwise, consider restricting key normalization or using a reversible mapping.

src/fallback.ts (1)

375-416: Optional: route retry logs through DebugLogger for consistency.

console.debug/warn here bypass the debug system. Consider emitting via the global debug logger (when enabled) to centralize observability.

src/comparison.ts (1)

395-399: Allow ignoring nested properties by full path.

Currently, the ignore-check only considers the immediate key. Use the full currentPath so callers can ignore specific nested paths.

Minimal change:

-      if (this.config.ignoredProperties?.includes(key)) {
+      if (this.config.ignoredProperties?.includes(currentPath) || this.config.ignoredProperties?.includes(key)) {
         continue;
       }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c32668e and 9a55999.

📒 Files selected for processing (15)
  • .gitignore (1 hunks)
  • src/comparison.test.ts (1 hunks)
  • src/comparison.ts (1 hunks)
  • src/debug.test.ts (1 hunks)
  • src/debug.ts (1 hunks)
  • src/errorHandler.ts (1 hunks)
  • src/fallback.ts (1 hunks)
  • src/hash.ts (2 hunks)
  • src/index.ts (7 hunks)
  • src/json.ts (3 hunks)
  • src/normalization.ts (1 hunks)
  • src/security.ts (1 hunks)
  • src/serialization.ts (1 hunks)
  • src/validation.ts (1 hunks)
  • tsconfig.json (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (12)
src/comparison.test.ts (2)
src/types.ts (1)
  • SystemInfo (58-94)
src/comparison.ts (6)
  • HashComparator (150-772)
  • analyzeHashVariations (223-282)
  • analyzeHashVariations (804-810)
  • getHashTroubleshooter (1343-1348)
  • compareSystemInfo (161-218)
  • compareSystemInfo (792-799)
src/debug.test.ts (2)
src/debug.ts (4)
  • DebugLogger (127-505)
  • getDebugLogger (515-523)
  • startDebugSession (528-531)
  • endDebugSession (536-542)
src/normalization.ts (2)
  • reliableRound (27-74)
  • normalizeStringValue (84-132)
src/normalization.ts (1)
src/debug.ts (1)
  • getDebugLogger (515-523)
src/fallback.ts (1)
src/types.ts (6)
  • WebGLInfo (96-100)
  • CanvasInfo (102-106)
  • PluginInfo (137-141)
  • MathInfo (108-117)
  • FontPreferencesInfo (120-122)
  • SystemInfo (58-94)
src/validation.ts (2)
src/types.ts (1)
  • SystemInfo (58-94)
src/security.ts (2)
  • SecurityValidationResult (33-39)
  • SecurityValidator (101-1568)
src/hash.ts (8)
src/index.ts (8)
  • HashGeneratorConfig (208-208)
  • HashDebugInfo (212-212)
  • HashGenerationResult (209-209)
  • InputComparisonResult (210-210)
  • InputDifference (211-211)
  • generateId (221-221)
  • generateIdWithDebug (222-222)
  • compareInputs (223-223)
src/serialization.ts (5)
  • SerializationConfig (21-36)
  • SerializationResult (54-61)
  • serializeWithNormalization (424-426)
  • EnhancedSerializer (84-394)
  • enhancedReplacer (287-322)
src/debug.ts (2)
  • DebugConfig (56-66)
  • getDebugLogger (515-523)
src/types.ts (1)
  • SystemInfo (58-94)
src/fallback.ts (2)
  • FallbackManager (180-543)
  • getFallbackValue (200-221)
src/validation.ts (1)
  • ValidationEngine (75-996)
src/normalization.ts (4)
  • normalizeArrayValue (142-252)
  • normalizeStringValue (84-132)
  • reliableRound (27-74)
  • normalizeValue (383-395)
src/comparison.ts (2)
  • compareSystemInfo (161-218)
  • compareSystemInfo (792-799)
src/errorHandler.ts (3)
src/index.ts (3)
  • ErrorCategory (248-248)
  • DEFAULT_ERROR_CONFIG (249-249)
  • ErrorHandler (247-247)
src/types.ts (1)
  • SystemInfo (58-94)
src/fallback.ts (3)
  • FallbackResult (172-175)
  • FallbackManager (180-543)
  • shouldRetry (339-356)
src/serialization.ts (1)
src/normalization.ts (3)
  • normalizeValue (383-395)
  • normalizeStringValue (84-132)
  • reliableRound (27-74)
src/security.ts (2)
src/types.ts (1)
  • SystemInfo (58-94)
src/normalization.ts (1)
  • normalizeValue (383-395)
src/json.ts (1)
src/hash.ts (2)
  • HashGeneratorConfig (28-35)
  • generateId (91-99)
src/comparison.ts (2)
src/hash.ts (3)
  • HashDebugInfo (40-49)
  • HashGeneratorConfig (28-35)
  • generateIdWithDebug (226-359)
src/types.ts (1)
  • SystemInfo (58-94)
src/index.ts (5)
src/hash.ts (1)
  • HashGeneratorConfig (28-35)
src/systemInfo.ts (1)
  • getSystemInfo (132-210)
src/geo-ip.ts (1)
  • fetchGeolocationInfo (106-161)
src/compliance.ts (1)
  • Toast (13-99)
src/json.ts (1)
  • generateJSON (73-140)
🪛 Biome (2.1.2)
src/validation.ts

[error] 889-889: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)


[error] 889-889: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)

🔇 Additional comments (17)
.gitignore (1)

8-9: LGTM: Ignore Netlify local artifacts

Adding the local Netlify folder to .gitignore is a sensible hygiene improvement and avoids committing CLI artifacts. No further changes needed.

src/comparison.test.ts (5)

26-81: Comprehensive SystemInfo mock: great coverage

The mock covers a wide range of fields and realistic values, which strengthens the end-to-end tests. This aligns well with the SystemInfo interface shown in src/types.ts.


139-148: Whitespace difference checks are precise and useful

Good job verifying both the difference type and negligible severity for whitespace-only changes. This ensures normalization respects benign differences.


170-181: Readable report assertions ensure observability

Asserting key sections in the generated report helps keep regressions in check for developer-facing diagnostics. Looks good.


202-209: Perfect stability case covered

Great to see a 0 variation-rate and perfect consistency/predictability scenario validated. This anchors the upper bound.


280-289: Convenience wrapper coverage

Ensures top-level helpers aren’t overlooked. Looks good.

src/json.ts (2)

12-13: Propagating hashConfig into generateId: good extensibility

Importing HashGeneratorConfig and threading the optional config through to generateId enables controlled, configurable hashing without breaking the signature for existing callers.


120-134: Geolocation shape hardening is thoughtful

Providing safe defaults for nested geolocation fields and including vpnStatus improves shape stability. The selective traits passthrough is reasonable.

Also applies to: 136-139

src/debug.test.ts (4)

35-45: Session lifecycle assertions look good

The sessionId format and non-zero processing time checks are robust without being brittle. Nice coverage of the basics.


103-123: Log level filtering behavior validated

Ensuring DEBUG logs are filtered under a WARN threshold while WARN logs persist is the right test for level gating. Looks good.


125-139: Summary report expectations are pragmatic

Checking total steps and per-type counts provides a solid invariant for summary generation. Good coverage.


172-192: Integration with normalization module is solid

Verifying that named properties and types appear in normalizationSteps confirms cross-module logging coherence. Good test.

src/security.ts (1)

1154-1166: Prevent potential undefined serialization in uniform pattern check

Same JSON.stringify guard to avoid iterating over undefined.

-    const serialized = JSON.stringify(normalizedInfo);
+    const serialized = JSON.stringify(normalizedInfo) ?? '';
src/index.ts (1)

206-250: Public surface expansion looks consistent

Type re-exports and named exports align with the new hashing and error-handling modules. No issues spotted.

src/serialization.ts (1)

180-188: LGTM: Depth guard and stats tracking are solid.

The maxDepth guard and maxDepthReached updates are clear and prevent runaway recursion.

src/fallback.ts (1)

186-191: LGTM: FallbackManager state and config initialization look robust.

Constructor wiring for custom fallbacks, retry config, and histories is clean and ready for extension.

src/comparison.ts (1)

200-217: LGTM: Clear separation of raw vs normalized differences and metadata.

The result shape (hashesMatch, normalizedDifferences, impactAnalysis, metadata) is well structured for diagnostics.

Comment on lines +354 to +361
const entry: DebugLogEntry = {
id: `log_${++this.logCounter}`,
timestamp: performance.now(),
level,
category,
message,
data: data ? this.cloneValue(data) : undefined
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Incorrect timestamp source for console output (performance.now vs Date.now).

entry.timestamp is set via performance.now() but later formatted with new Date(entry.timestamp), yielding an epoch-relative timestamp. Use Date.now() for wall-clock timestamps, while keeping performance.now() for durations.

Apply this diff:

-    const entry: DebugLogEntry = {
-      id: `log_${++this.logCounter}`,
-      timestamp: performance.now(),
+    const entry: DebugLogEntry = {
+      id: `log_${++this.logCounter}`,
+      timestamp: Date.now(),
       level,
       category,
       message,
       data: data ? this.cloneValue(data) : undefined
     };

Optionally keep both fields if you need high-resolution relative timings.

Also applies to: 483-489

🤖 Prompt for AI Agents
In src/debug.ts around lines 354-361 (and also update the similar block at
483-489), the entry.timestamp is incorrectly set using performance.now() but
later formatted with new Date(...), producing an epoch-relative timestamp;
change entry.timestamp to Date.now() to record a wall-clock timestamp for
console output, and if you still need high-resolution relative timings add a
separate field (e.g., perfTimestamp or elapsed) set to performance.now() so both
wall-clock and high-resolution times are preserved.

Comment on lines +121 to +127
if (!shouldRetry || attemptCount >= this.config.maxRetries) {
const fallbackResult = this.fallbackManager.getFallbackValue(
property,
fallbackReason,
originalValue
);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Retry limit off-by-one: make handleSystemInfoError consistent with shouldRetry

shouldRetry allows attempts up to and including maxRetries (original + retries), but handleSystemInfoError falls back when attemptCount >= maxRetries. Use > to align both.

-    if (!shouldRetry || attemptCount >= this.config.maxRetries) {
+    if (!shouldRetry || attemptCount > this.config.maxRetries) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!shouldRetry || attemptCount >= this.config.maxRetries) {
const fallbackResult = this.fallbackManager.getFallbackValue(
property,
fallbackReason,
originalValue
);
if (!shouldRetry || attemptCount > this.config.maxRetries) {
const fallbackResult = this.fallbackManager.getFallbackValue(
property,
fallbackReason,
originalValue
);
🤖 Prompt for AI Agents
In src/errorHandler.ts around lines 121 to 127, the fallback is triggered when
attemptCount >= this.config.maxRetries which is off-by-one compared to
shouldRetry (which allows attempts up to and including maxRetries); change the
comparison to attemptCount > this.config.maxRetries so the handler only falls
back after exceeding maxRetries, and run tests to verify behavior remains
consistent with shouldRetry logic.

Comment on lines +183 to 184
webGLImageHash: getFallbackValue('webGL', systemInfo.webGL?.imageHash, FallbackReason.MISSING_PROPERTY),

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: Fallback returns wrong type for webGLImageHash (object vs string).

When systemInfo.webGL?.imageHash is missing, you request a fallback for the webGL property and assign it directly to webGLImageHash. The fallback value for webGL is a WebGLInfo object, not a string. This yields an object where a string is expected, causing non-deterministic hash input and potential runtime surprises.

Apply this diff to extract the nested imageHash from the WebGL fallback:

-    webGLImageHash: getFallbackValue('webGL', systemInfo.webGL?.imageHash, FallbackReason.MISSING_PROPERTY),
+    webGLImageHash: (() => {
+      const hash = systemInfo.webGL?.imageHash;
+      if (hash === null || hash === undefined || (typeof hash === 'string' && hash.trim() === '')) {
+        return fallbackManager.getFallbackValue('webGL', FallbackReason.MISSING_PROPERTY, hash).value.imageHash;
+      }
+      return hash;
+    })(),

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/hash.ts around lines 183-184, the current call uses
getFallbackValue('webGL', systemInfo.webGL?.imageHash, ...) which returns a
WebGLInfo object when falling back, producing an object where a string is
expected; change it to request the WebGL object fallback
(getFallbackValue('webGL', systemInfo.webGL, FallbackReason.MISSING_PROPERTY))
and then extract its imageHash property, ensuring the final webGLImageHash is a
string (e.g., access .imageHash and coerce or default to an empty string if
undefined).

Comment on lines +186 to 189
detectedFontsString: systemInfo.fontPreferences?.detectedFonts && systemInfo.fontPreferences.detectedFonts.length > 0
? normalizeArrayValue(systemInfo.fontPreferences.detectedFonts).join(',')
: getFallbackValue('fontPreferences', null, FallbackReason.MISSING_PROPERTY),

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: Fallback for detectedFontsString uses object fallback instead of string list.

When fonts are missing, you return getFallbackValue('fontPreferences', ...), which is a FontPreferencesInfo object, not a comma-separated string. This breaks the shape of your stable input.

Apply this diff:

-    detectedFontsString: systemInfo.fontPreferences?.detectedFonts && systemInfo.fontPreferences.detectedFonts.length > 0
-      ? normalizeArrayValue(systemInfo.fontPreferences.detectedFonts).join(',')
-      : getFallbackValue('fontPreferences', null, FallbackReason.MISSING_PROPERTY),
+    detectedFontsString: systemInfo.fontPreferences?.detectedFonts && systemInfo.fontPreferences.detectedFonts.length > 0
+      ? normalizeArrayValue(systemInfo.fontPreferences.detectedFonts).join(',')
+      : normalizeArrayValue(
+          fallbackManager.getFallbackValue('fontPreferences', FallbackReason.MISSING_PROPERTY).value.detectedFonts
+        ).join(','),

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/hash.ts around lines 186 to 189, the fallback returns the whole
FontPreferencesInfo object instead of a comma-separated detectedFonts string;
replace the fallback to target the detectedFonts property (e.g. call
getFallbackValue('fontPreferences.detectedFonts', null,
FallbackReason.MISSING_PROPERTY)) and ensure the returned fallback is a string
(if it can be an array, normalize and join it into a comma-separated string) so
detectedFontsString always yields a string list.

Comment on lines +191 to +192
canvasFingerprint: getFallbackValue('canvas', systemInfo.canvas?.geometry, FallbackReason.MISSING_PROPERTY),
audioFingerprint: getFallbackValue('audio', systemInfo.audio, FallbackReason.MISSING_PROPERTY),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: Fallback returns wrong type for canvasFingerprint (object vs string).

Similar to webGL, the canvas fallback returns a CanvasInfo object. Assigning it directly to canvasFingerprint (expects a string/geometry) introduces a type mismatch.

Apply this diff:

-    canvasFingerprint: getFallbackValue('canvas', systemInfo.canvas?.geometry, FallbackReason.MISSING_PROPERTY),
+    canvasFingerprint: (() => {
+      const geo = systemInfo.canvas?.geometry;
+      if (geo === null || geo === undefined || (typeof geo === 'string' && geo.trim() === '')) {
+        return fallbackManager.getFallbackValue('canvas', FallbackReason.MISSING_PROPERTY, geo).value.geometry;
+      }
+      return geo;
+    })(),

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/hash.ts around lines 191-192, the canvasFingerprint is being set from
getFallbackValue('canvas', systemInfo.canvas?.geometry, ...) which can return a
CanvasInfo object (not the geometry string) and causes a type mismatch; change
the assignment so that if the primary value is missing you extract the geometry
string from the fallback (i.e., if getFallbackValue returns an object, use its
geometry property) or call the fallback for a specific canvas-geometry key so
canvasFingerprint is always a string/geometry value rather than a CanvasInfo
object.

Comment on lines +818 to +820
const key = JSON.stringify(value);
valueFrequency.set(key, (valueFrequency.get(key) || 0) + 1);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure distribution keys are strings (avoid undefined keys and TS type mismatch)

JSON.stringify(value) can be undefined. Coerce to a string to satisfy Map<string, number> and avoid runtime surprises.

-      const key = JSON.stringify(value);
+      const key = JSON.stringify(value) ?? '__undefined__'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const key = JSON.stringify(value);
valueFrequency.set(key, (valueFrequency.get(key) || 0) + 1);
}
const key = JSON.stringify(value) ?? '__undefined__';
valueFrequency.set(key, (valueFrequency.get(key) || 0) + 1);
}
🤖 Prompt for AI Agents
In src/security.ts around lines 818 to 820, JSON.stringify(value) can return
undefined which breaks the Map<string, number> invariant and can cause
runtime/key-type issues; coerce the result to a string before using it as a key
(e.g. replace the current expression with a deterministic string coercion such
as const key = String(JSON.stringify(value)) or JSON.stringify(value) ??
'undefined'), then use that key when updating valueFrequency so all keys are
guaranteed strings.

Comment on lines +939 to +962
const highEntropyFields = [
'webGLImageHash',
'canvasFingerprint',
'audioFingerprint',
'canvasGeometry',
'canvasText',
'userAgent',
'detectedFontsString'
];

for (const field of highEntropyFields) {
const value = this.getNestedValue(normalizedInfo, field);
if (value && typeof value === 'string') {
const entropy = this.calculateStringEntropy(value);
// More aggressive entropy detection - lowered threshold
if (entropy < 0.4 && value.length > 3) {
riskScore += 0.1;
}

// Check for obvious manipulation patterns
if (this.hasManipulationPatterns(value)) {
riskScore += 0.05;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Align entropy field paths with actual data shape and handle arrays

The current highEntropyFields uses non-nested keys (e.g., webGLImageHash, canvasFingerprint, detectedFontsString) that likely don’t exist on normalized data. Use nested paths and handle arrays for font lists.

-    const highEntropyFields = [
-      'webGLImageHash', 
-      'canvasFingerprint', 
-      'audioFingerprint',
-      'canvasGeometry',
-      'canvasText',
-      'userAgent',
-      'detectedFontsString'
-    ];
+    const highEntropyFields = [
+      'webGL.imageHash',
+      'canvas.geometry',
+      'canvas.text',
+      'userAgent',
+      'fontPreferences.detectedFonts'
+    ];
@@
-    for (const field of highEntropyFields) {
-      const value = this.getNestedValue(normalizedInfo, field);
-      if (value && typeof value === 'string') {
+    for (const field of highEntropyFields) {
+      const raw = this.getNestedValue(normalizedInfo, field);
+      const value = Array.isArray(raw) ? raw.join(',') : raw;
+      if (value && typeof value === 'string') {
         const entropy = this.calculateStringEntropy(value);
         // More aggressive entropy detection - lowered threshold
         if (entropy < 0.4 && value.length > 3) { 
           riskScore += 0.1;
         }
         
         // Check for obvious manipulation patterns
         if (this.hasManipulationPatterns(value)) {
           riskScore += 0.05;
         }
       }
     }

Would you like me to update any downstream analyzers that may rely on the previous flat keys?


🏁 Script executed:

#!/bin/bash
set -e

echo "Searching for getNestedValue references and its definition..."
rg -n --color=never "getNestedValue" .

echo "Searching for normalizedInfo usage and its type/interface..."
rg -n --color=never "normalizedInfo\b" .
rg -n --color=never "interface NormalizedInfo" .

Length of output: 5961


🏁 Script executed:

#!/bin/bash
set -e

echo "Searching for normalizeSystemInfo definition in src/hash.ts..."
rg -n -C3 "normalizeSystemInfo" src/hash.ts

Length of output: 1092


🏁 Script executed:

#!/bin/bash
set -e

echo "### getNestedValue implementation ###"
rg -n -C5 "private getNestedValue" src/security.ts

echo "### webGL references in normalization ###"
rg -n -C3 "webGL" src/hash.ts

echo "### canvas references in normalization ###"
rg -n -C3 "canvas" src/hash.ts

echo "### font references in normalization ###"
rg -n -C3 "font" src/hash.ts

Length of output: 4159


🏁 Script executed:

#!/bin/bash
set -e

echo "### Checking if 'userAgent' is normalized ###"
rg -n -C3 "userAgent:" src/hash.ts || echo "No direct 'userAgent:' key in normalizedInfo"

echo "### Checking for any 'canvasText' references ###"
rg -n canvasText src/hash.ts || echo "No 'canvasText' in normalization"

Length of output: 1108


Align entropy fields with the flat normalizedInfo shape

The highEntropyFields array currently includes keys that don’t exist on the flattened normalizedInfo (e.g. canvasGeometry, canvasText). Remove those two entries so you only iterate over actual normalized properties:

• File: src/security.ts at the calculateEntropy method
• Lines: where highEntropyFields is defined

- const highEntropyFields = [
-   'webGLImageHash',
-   'canvasFingerprint',
-   'audioFingerprint',
-   'canvasGeometry',
-   'canvasText',
-   'userAgent',
-   'detectedFontsString'
- ];
+ const highEntropyFields = [
+   'webGLImageHash',
+   'canvasFingerprint',
+   'audioFingerprint',
+   'userAgent',
+   'detectedFontsString'
+ ];

No nested paths are needed—normalizeSystemInfo returns a flat object—and all of these fields are already serialized as strings (arrays like fonts are joined during normalization).

Let me know if we should update any downstream tests or analyzers that rely on the removed keys.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const highEntropyFields = [
'webGLImageHash',
'canvasFingerprint',
'audioFingerprint',
'canvasGeometry',
'canvasText',
'userAgent',
'detectedFontsString'
];
for (const field of highEntropyFields) {
const value = this.getNestedValue(normalizedInfo, field);
if (value && typeof value === 'string') {
const entropy = this.calculateStringEntropy(value);
// More aggressive entropy detection - lowered threshold
if (entropy < 0.4 && value.length > 3) {
riskScore += 0.1;
}
// Check for obvious manipulation patterns
if (this.hasManipulationPatterns(value)) {
riskScore += 0.05;
}
}
const highEntropyFields = [
'webGLImageHash',
'canvasFingerprint',
'audioFingerprint',
'userAgent',
'detectedFontsString'
];
for (const field of highEntropyFields) {
const value = this.getNestedValue(normalizedInfo, field);
if (value && typeof value === 'string') {
const entropy = this.calculateStringEntropy(value);
// More aggressive entropy detection - lowered threshold
if (entropy < 0.4 && value.length > 3) {
riskScore += 0.1;
}
// Check for obvious manipulation patterns
if (this.hasManipulationPatterns(value)) {
riskScore += 0.05;
}
}
}
🤖 Prompt for AI Agents
In src/security.ts around lines 939 to 962, the highEntropyFields array includes
keys (canvasGeometry, canvasText) that don't exist on the flattened
normalizedInfo object; remove those two entries from the array so the loop only
iterates actual normalized properties (webGLImageHash, canvasFingerprint,
audioFingerprint, userAgent, detectedFontsString), leaving the rest of the
entropy checks intact and no nested path handling is required.

Comment on lines +245 to +256
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
/javascript:/gi,
/on\w+\s*=/gi,
/eval\s*\(/gi,
/Function\s*\(/gi
];

// Check for SQL injection patterns
const sqlPatterns = [
/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION)\b)/gi,
/(';|--;|\|\||\/\*|\*\/|'.*OR.*'|'.*AND.*')/gi
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix stateful RegExp usage: remove g with .test()

The /gi flags with .test() can skip matches due to lastIndex. Drop g for reliability.

-    const scriptPatterns = [
-      /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
-      /javascript:/gi,
-      /on\w+\s*=/gi,
-      /eval\s*\(/gi,
-      /Function\s*\(/gi
-    ];
+    const scriptPatterns = [
+      /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/i,
+      /javascript:/i,
+      /on\w+\s*=/i,
+      /eval\s*\(/i,
+      /Function\s*\(/i
+    ];
@@
-    const sqlPatterns = [
-      /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION)\b)/gi,
-      /(';|--;|\|\||\/\*|\*\/|'.*OR.*'|'.*AND.*')/gi
-    ];
+    const sqlPatterns = [
+      /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION)\b)/i,
+      /(';|--;|\|\||\/\*|\*\/|'.*OR.*'|'.*AND.*')/i
+    ];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
/javascript:/gi,
/on\w+\s*=/gi,
/eval\s*\(/gi,
/Function\s*\(/gi
];
// Check for SQL injection patterns
const sqlPatterns = [
/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION)\b)/gi,
/(';|--;|\|\||\/\*|\*\/|'.*OR.*'|'.*AND.*')/gi
];
const scriptPatterns = [
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/i,
/javascript:/i,
/on\w+\s*=/i,
/eval\s*\(/i,
/Function\s*\(/i
];
// Check for SQL injection patterns
const sqlPatterns = [
/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION)\b)/i,
/(';|--;|\|\||\/\*|\*\/|'.*OR.*'|'.*AND.*')/i
];
🤖 Prompt for AI Agents
In src/validation.ts around lines 245 to 256, the review points out that the
regular expressions in the XSS and SQL pattern arrays use the global flag (`g`)
while being used with `.test()`, which can cause skipped matches due to the
RegExp `lastIndex` state; remove the `g` flag from these regex literals (e.g.,
change `/.../gi` to `/.../i` or `/.../` as appropriate) so `.test()` works
reliably, or alternatively if global matching is required elsewhere, reset
`regex.lastIndex = 0` before each `.test()` call — apply the simpler fix by
dropping `g` for all patterns in those arrays.

Comment on lines +260 to +264
/\.\.\//gi,
/\.\.\\/gi,
/%2e%2e%2f/gi,
/%2e%2e%5c/gi
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix stateful RegExp usage for path traversal detection

Remove g when using .test().

-    const pathTraversalPatterns = [
-      /\.\.\//gi,
-      /\.\.\\/gi,
-      /%2e%2e%2f/gi,
-      /%2e%2e%5c/gi
-    ];
+    const pathTraversalPatterns = [
+      /\.\.\//i,
+      /\.\.\\/i,
+      /%2e%2e%2f/i,
+      /%2e%2e%5c/i
+    ];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/\.\.\//gi,
/\.\.\\/gi,
/%2e%2e%2f/gi,
/%2e%2e%5c/gi
];
const pathTraversalPatterns = [
/\.\.\//i,
/\.\.\\/i,
/%2e%2e%2f/i,
/%2e%2e%5c/i
];
🤖 Prompt for AI Agents
In src/validation.ts around lines 260 to 264 the regex array uses the global
flag (`g`) but the code calls `.test()` which is stateful with `g`; remove the
`g` flag from each pattern (e.g. change `/\.\.\//gi` to `/\.\.\//i`,
`/\.\.\\/gi` to `/\.\.\\/i`, etc.) so `.test()` is deterministic, or
alternatively instantiate new RegExp objects each test; update the four patterns
accordingly.

Comment on lines +886 to +893
// Helper methods for sanitization
private sanitizeString(value: string): string {
return value
.replace(/[\x00-\x1F\x7F-\x9F]/g, '') // Remove control characters
.replace(/\s+/g, ' ') // Normalize whitespace
.trim()
.substring(0, this.config.maxStringLength); // Truncate if too long
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Address linter error: control characters in regex

Biome flags control chars in /[\x00-\x1F\x7F-\x9F]/g. Use Unicode escapes to satisfy the rule without changing behavior.

-    return value
-      .replace(/[\x00-\x1F\x7F-\x9F]/g, '') // Remove control characters
+    return value
+      .replace(/[\u0000-\u001F\u007F-\u009F]/g, '') // Remove control characters
       .replace(/\s+/g, ' ') // Normalize whitespace
       .trim()
       .substring(0, this.config.maxStringLength); // Truncate if too long

Alternatively, if your runtime supports Unicode property escapes broadly, /\p{Cc}/gu is even clearer.

🧰 Tools
🪛 Biome (2.1.2)

[error] 889-889: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)


[error] 889-889: Unexpected control character in a regular expression.

Control characters are unusual and potentially incorrect inputs, so they are disallowed.

(lint/suspicious/noControlCharactersInRegex)

🤖 Prompt for AI Agents
In src/validation.ts around lines 886 to 893, the regex /[\x00-\x1F\x7F-\x9F]/g
triggers a linter rule about control-character escapes; replace those hex
escapes with Unicode escapes (e.g. /[\u0000-\u001F\u007F-\u009F]/g) or, if your
runtime supports Unicode property escapes, use /\p{Cc}/gu instead, keep the
global (and unicode) flags as appropriate and leave the rest of the
sanitizeString logic (whitespace normalize, trim, substring) unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants