@webdecoy/fcaptcha 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +34 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webdecoy/fcaptcha",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "Open source CAPTCHA with PoW, bot detection, and Vision AI protection",
5
5
  "main": "index.js",
6
6
  "exports": {
package/server.js CHANGED
@@ -495,6 +495,39 @@ function detectHeadless(signals, userAgent) {
495
495
  return detections;
496
496
  }
497
497
 
498
+ // Flags anti-detection patch traces collected by the client. FALSE-POSITIVE-
499
+ // SAFE: a genuine browser never produces these (internal contradictions /
500
+ // native-function tampering, not environment-shape heuristics that would
501
+ // misfire on real Linux/VPN users). Targets stealth agents (e.g. Manus AI)
502
+ // driving real-but-patched Chromium. Only the two FP-safe signals are scored;
503
+ // the client also collects privacy-extension-ambiguous artifacts (patched_*)
504
+ // for observability that are intentionally NOT scored. Keep in sync with Go/Py.
505
+ function detectStealthArtifacts(signals) {
506
+ const detections = [];
507
+ const env = signals.environmental || {};
508
+
509
+ // Function.prototype.toString proxied — the signature move of stealth
510
+ // frameworks (used to make their other native overrides look untouched).
511
+ const artifactSignals = (env.stealthArtifacts && env.stealthArtifacts.signals) || [];
512
+ if (artifactSignals.includes('tostring_proxied')) {
513
+ detections.push({
514
+ category: 'headless', score: 0.9, confidence: 0.85,
515
+ reason: 'Function.prototype.toString is proxied (stealth automation patch)'
516
+ });
517
+ }
518
+
519
+ // Notification.permission === 'denied' while the Permissions API reports
520
+ // 'prompt': a state a real browser cannot reach (classic headless tell).
521
+ if (env.permissionProbe && env.permissionProbe.contradiction === true) {
522
+ detections.push({
523
+ category: 'headless', score: 0.85, confidence: 0.85,
524
+ reason: 'Notification permission contradicts Permissions API (headless/stealth tell)'
525
+ });
526
+ }
527
+
528
+ return detections;
529
+ }
530
+
498
531
  function detectAutomation(signals) {
499
532
  const detections = [];
500
533
  const env = signals.environmental || {};
@@ -1098,6 +1131,7 @@ function runVerification(signals, ip, siteKey, userAgent, headers = {}, ja3Hash
1098
1131
  detections.push(
1099
1132
  ...detectVisionAI(signals),
1100
1133
  ...detectHeadless(signals, userAgent),
1134
+ ...detectStealthArtifacts(signals),
1101
1135
  ...detectAutomation(signals),
1102
1136
  ...detectCDP(signals),
1103
1137
  ...detectBehavioral(signals),