@webdecoy/fcaptcha 1.26.0 → 1.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/server.js +50 -1
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -1221,6 +1221,52 @@ function applyDispositiveFloor(score, detections) {
|
|
|
1221
1221
|
return selfDeclared ? Math.max(score, DISPOSITIVE_FLOOR) : score;
|
|
1222
1222
|
}
|
|
1223
1223
|
|
|
1224
|
+
/**
|
|
1225
|
+
* Behavioural corroboration floor.
|
|
1226
|
+
*
|
|
1227
|
+
* A source-patched browser scrubs every JS-observable automation flag at the binary
|
|
1228
|
+
* level, so it trips no headless, cdp, fingerprint, datacenter or bot category.
|
|
1229
|
+
* Those weights total 0.59, and the weighted sum silently keeps all of it — leaving
|
|
1230
|
+
* such a browser a ceiling of 0.41 against a 0.5 threshold. It cannot be blocked
|
|
1231
|
+
* however obviously robotic its movement is.
|
|
1232
|
+
*
|
|
1233
|
+
* Measured, not argued: the corpus sample of that adversary trips seven correct
|
|
1234
|
+
* behavioural detections and scores 0.234. Every one fires; the aggregation
|
|
1235
|
+
* discards the verdict. Same structural flaw that let a bare curl through before
|
|
1236
|
+
* v1.23.0, landing this time on the behavioural detection that is the point of the
|
|
1237
|
+
* product. It treats absence of environmental evidence as evidence of absence — but
|
|
1238
|
+
* on this adversary a clean environment is the attack working, not innocence.
|
|
1239
|
+
*
|
|
1240
|
+
* The rule: when two or more behavioural categories independently reach 0.5, floor
|
|
1241
|
+
* the score at 0.6.
|
|
1242
|
+
*
|
|
1243
|
+
* Constants swept over a 40-point grid against the labelled corpus
|
|
1244
|
+
* (bench/tools/sweep-corroboration.js), not reasoned about. No human in the
|
|
1245
|
+
* 126-sample panel reaches two agreeing behavioural categories at any threshold
|
|
1246
|
+
* tested, while 66 of 75 agents do at 0.5. Requiring three fails outright — all
|
|
1247
|
+
* sixteen such combinations leave the adversary allowed. The floor value does not
|
|
1248
|
+
* affect separation, so 0.6 is a policy choice: the block boundary, kept distinct
|
|
1249
|
+
* from the 0.9 reserved for a browser that declares its own automation.
|
|
1250
|
+
*
|
|
1251
|
+
* Caveat: the adversary is one synthetic, hand-authored sample. This shows the
|
|
1252
|
+
* arithmetic works on the shape the corpus describes, not that it works on a real
|
|
1253
|
+
* source-patched browser.
|
|
1254
|
+
*/
|
|
1255
|
+
const CORROBORATION_AGREE_AT = 0.5;
|
|
1256
|
+
const CORROBORATION_MIN_AGREE = 2;
|
|
1257
|
+
const CORROBORATION_FLOOR = 0.6;
|
|
1258
|
+
|
|
1259
|
+
// Categories a browser trips by how it moves rather than by what it is. A
|
|
1260
|
+
// patched binary can hide what it is; it cannot hide that nothing is moving the
|
|
1261
|
+
// pointer like a hand.
|
|
1262
|
+
const BEHAVIOURAL_CATEGORIES = ['vision_ai', 'behavioral', 'automation', 'cdp'];
|
|
1263
|
+
|
|
1264
|
+
function applyCorroborationFloor(score, categoryScores) {
|
|
1265
|
+
const agreeing = BEHAVIOURAL_CATEGORIES
|
|
1266
|
+
.filter((c) => (categoryScores[c] || 0) >= CORROBORATION_AGREE_AT).length;
|
|
1267
|
+
return agreeing >= CORROBORATION_MIN_AGREE ? Math.max(score, CORROBORATION_FLOOR) : score;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1224
1270
|
// Bind the token to the page that minted it and to the action it was minted
|
|
1225
1271
|
// for. All three ride inside the signed payload, so a token issued for
|
|
1226
1272
|
// `action=login` on example.com cannot be replayed against a password-reset
|
|
@@ -1508,7 +1554,10 @@ function runVerification(signals, ip, siteKey, userAgent, headers = {}, ja3Hash
|
|
|
1508
1554
|
detections.push(...advancedDetections);
|
|
1509
1555
|
|
|
1510
1556
|
const categoryScores = calculateCategoryScores(detections);
|
|
1511
|
-
const finalScore =
|
|
1557
|
+
const finalScore = applyCorroborationFloor(
|
|
1558
|
+
applyDispositiveFloor(calculateFinalScore(categoryScores), detections),
|
|
1559
|
+
categoryScores
|
|
1560
|
+
);
|
|
1512
1561
|
|
|
1513
1562
|
let recommendation;
|
|
1514
1563
|
if (finalScore < 0.3) recommendation = 'allow';
|