capskip 1.2.0 → 1.4.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.
@@ -9,7 +9,7 @@ POST http://<host>:<port>/in.php → submit captcha
9
9
  GET http://<host>:<port>/res.php → poll result
10
10
  ```
11
11
 
12
- The SDK only supports the four captcha types documented by CapSkip. Every solve
12
+ The SDK only supports the captcha types documented by CapSkip. Every solve
13
13
  method returns a `Promise`.
14
14
 
15
15
  ---
@@ -24,9 +24,14 @@ method returns a `Promise`.
24
24
  | Cloudflare Turnstile | `turnstile()` | `turnstile` |
25
25
  | GeeTest v3 (slide) | `geetest()` | `geetest` |
26
26
  | ALTCHA (proof-of-work) | `altcha()` | `altcha` |
27
+ | Capy Puzzle (slide) | `capy()` | `capy` |
28
+ | CaptchaFox (widget) | `captchafox()` | `captchafox` |
29
+ | Friendly Captcha (proof-of-work) | `friendlyCaptcha()` | `friendly_captcha` |
30
+ | AWS WAF (challenge page or widget) | `amazonWaf()` | `amazon_waf` |
27
31
 
28
- **Proxy** is supported for reCAPTCHA, Turnstile, GeeTest, and ALTCHA — not for image
29
- captcha. For ALTCHA the proxy is used only for the `challengeUrl` fetch.
32
+ **Proxy** is supported for every method except image captcha. For ALTCHA the
33
+ proxy is used only for the `challengeUrl` fetch; for Capy, only for the
34
+ puzzle-image fetch, which is the only request a Capy solve makes.
30
35
 
31
36
  ---
32
37
 
@@ -377,6 +382,337 @@ purely visual and never reaches CapSkip.
377
382
 
378
383
  ---
379
384
 
385
+ ## 7. Capy Puzzle — `capy(sitekey, url, { ... })`
386
+
387
+ Capy is a slide puzzle: a piece has to be dragged into the hole cut out of a
388
+ photograph. CapSkip locates the hole and produces the drag path — one HTTP fetch
389
+ plus pixel math, no browser.
390
+
391
+ ### POST `/in.php`
392
+
393
+ | Parameter | Type | Required | Description |
394
+ |---|---|---|---|
395
+ | `key` | string | Yes | CapSkip API key |
396
+ | `method` | string | Yes | `capy` |
397
+ | `captchakey` | string | Yes | The site's public Capy key, conventionally prefixed `PUZZLE_`. The SDK sends its `sitekey` argument here. |
398
+ | `pageurl` | string | Yes | Full URL of the page the captcha is on |
399
+ | `api_server` | string | No | Root of the Capy API the key lives behind. Default `https://jp.api.capy.me` |
400
+ | `version` | string | No | `puzzle` (the default). Only the puzzle family is solved |
401
+ | `useragent` | string | No | User-Agent to send with the puzzle request |
402
+ | `json` | int | No | `0` plain text, `1` JSON |
403
+ | `proxy` | string | No | Proxy address |
404
+ | `proxytype` | string | No | Proxy type |
405
+
406
+ `sitekey` and `websiteKey` are accepted by the server as aliases for
407
+ `captchakey`; the SDK sends the documented name.
408
+
409
+ `version: 'avatar'` is refused by the SDK before the request is made. Avatar is a
410
+ different challenge behind a different endpoint, and answering it with a puzzle
411
+ answer would bill for a solve the target site rejects, indistinguishably from a
412
+ broken solver.
413
+
414
+ > `api.capy.me` no longer resolves, although several solver services still
415
+ > document it. The live host is `jp.api.capy.me`; if a site's widget points
416
+ > somewhere else, pass that as `api_server`.
417
+
418
+ ### Getting the captcha key
419
+
420
+ It is in the page source as `capy_captchakey`, or in the widget script URL:
421
+ `<script src="https://jp.api.capy.me/puzzle/get_js/?k=PUZZLE_XXXX">`.
422
+
423
+ ### GET `/res.php`
424
+
425
+ The answer is **not a token**. It is an object, which the SDK expands into three
426
+ result fields. See [Return value](#return-value).
427
+
428
+ ### SDK usage
429
+
430
+ ```js
431
+ const result = await solver.capy(
432
+ 'PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v',
433
+ 'https://example.com/login',
434
+ );
435
+
436
+ // With a non-default API server
437
+ const result = await solver.capy(
438
+ 'PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v',
439
+ 'https://example.com/login',
440
+ { api_server: 'https://jp.api.capy.me/' },
441
+ );
442
+
443
+ // capy_captchakey, capy_challengekey, capy_answer
444
+ result.captchakey, result.challengekey, result.answer;
445
+ ```
446
+
447
+ ### Timing
448
+
449
+ Capy grades the wall-clock gap between issuing the puzzle and verifying the
450
+ answer, and refuses anything superhuman with `CAPTCHA verification failed` — the
451
+ same message a wrong answer gets. CapSkip therefore holds every result until two
452
+ seconds have elapsed since it drew the puzzle. This is invisible if you poll: the
453
+ task simply takes about two seconds instead of a fifth of one. Nothing to
454
+ configure.
455
+
456
+ The challenge key is single-use and short-lived, so submit the three values
457
+ promptly rather than caching them.
458
+
459
+ ---
460
+
461
+ ## 8. CaptchaFox — `captchafox(sitekey, url, { ... })`
462
+
463
+ CaptchaFox scores the browser itself rather than asking the visitor to read
464
+ anything; most visitors never see a puzzle. CapSkip drives the real widget in a
465
+ real browser and returns the verification token.
466
+
467
+ ### POST `/in.php`
468
+
469
+ | Parameter | Type | Required | Description |
470
+ |---|---|---|---|
471
+ | `key` | string | Yes | CapSkip API key |
472
+ | `method` | string | Yes | `captchafox` |
473
+ | `sitekey` | string | Yes | The public key the widget renders with, conventionally prefixed `sk_` |
474
+ | `pageurl` | string | Yes | Full URL of the page the widget appears on |
475
+ | `api_server` | string | No | Widget entry point. Default `https://cdn.captchafox.com/` |
476
+ | `useragent` | string | No | Accepted for compatibility and not applied — CapSkip uses its own browser's identity |
477
+ | `json` | int | No | `0` plain text, `1` JSON |
478
+ | `proxy` | string | No | Proxy address |
479
+ | `proxytype` | string | No | Proxy type |
480
+
481
+ ### Choosing the widget source
482
+
483
+ | `api_server` | Token | Default |
484
+ |---|---|---|
485
+ | `https://cdn.captchafox.com/` | Plain | Yes |
486
+ | `https://s.uicdn.com/mampkg/…` | `MAM_` prefixed | No |
487
+
488
+ Read the value from the `<script>` tag that loads the widget. If you send the
489
+ wrong one the solve still succeeds, but the token comes back in a format the
490
+ target site will not accept — a silent verification failure rather than an error.
491
+
492
+ ### The page URL has to match the key
493
+
494
+ CaptchaFox keys are registered against a list of allowed domains and the service
495
+ checks the host before issuing anything. A correct key used on a page outside
496
+ that list is refused permanently, not intermittently. CapSkip reports that case
497
+ rather than retrying it, because retrying cannot help.
498
+
499
+ ### Challenge types
500
+
501
+ | Challenge | Frequency | Supported |
502
+ |---|---|---|
503
+ | Invisible | Usually | Yes |
504
+ | Slide | Sometimes | Yes |
505
+ | Image select | Rarely | No |
506
+ | Audio | Rarely | No |
507
+
508
+ The two unsupported ones are uncommon and a retry usually draws a different
509
+ challenge, so treat an unsolvable result as a signal to resubmit rather than a
510
+ permanent failure of the key.
511
+
512
+ ### SDK usage
513
+
514
+ ```js
515
+ const result = await solver.captchafox(
516
+ 'sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G',
517
+ 'https://example.com/signup',
518
+ );
519
+
520
+ // Post in the form field named cf-captcha-response
521
+ result.token;
522
+
523
+ // Submit under the UA that minted the token, not your own
524
+ result.userAgent;
525
+ ```
526
+
527
+ Uses `recaptchaTimeout` — it is a real browser session, and longer again when an
528
+ interactive challenge is drawn.
529
+
530
+ ---
531
+
532
+ ## 9. Friendly Captcha — `friendlyCaptcha(sitekey, url, { ... })`
533
+
534
+ Proof-of-work, with nothing shown on screen. The widget searches for values that
535
+ hash below a difficulty the service sets per request.
536
+
537
+ ### POST `/in.php`
538
+
539
+ | Parameter | Type | Required | Description |
540
+ |---|---|---|---|
541
+ | `key` | string | Yes | CapSkip API key |
542
+ | `method` | string | Yes | `friendly_captcha` |
543
+ | `sitekey` | string | Yes | The `data-sitekey` of the element carrying `class="frc-captcha"` |
544
+ | `pageurl` | string | Yes | Full URL of the page the widget appears on |
545
+ | `version` | string | No | `v1` (default) or `v2`; a bare `1` or `2` is accepted |
546
+ | `module_script` | string | No | `src` of the widget script tag carrying `type="module"` |
547
+ | `nomodule_script` | string | No | `src` of the widget script tag carrying `nomodule` |
548
+ | `api_server` | string | No | Data residency endpoint: `global` (default), `eu`, or a full URL |
549
+ | `useragent` | string | No | User-Agent to send with the request |
550
+ | `json` | int | No | `0` plain text, `1` JSON |
551
+ | `proxy` | string | No | Proxy address |
552
+ | `proxytype` | string | No | Proxy type |
553
+
554
+ ### Version 1 and version 2
555
+
556
+ Two entirely different protocols ship under this one name, and a sitekey does not
557
+ tell you which one a site uses — they share a brand and a sitekey namespace and
558
+ nothing else. Both are live. Solve the wrong one and you get a well-formed token
559
+ the target site rejects, with no indication that the version was the problem.
560
+
561
+ | `version` | Widget package | Script |
562
+ |---|---|---|
563
+ | `v1` | `friendly-challenge` | `widget.module.min.js` / `widget.min.js` |
564
+ | `v2` | `@friendlycaptcha/sdk` | `site.min.js` |
565
+
566
+ CapSkip decides in this order, stopping at the first answer: the `version`
567
+ parameter; then the script URL from `moduleScript` or `nomoduleScript`, which is
568
+ the most reliable signal because it is the build the site actually loads; then
569
+ v1. The SDK refuses any other `version` value locally.
570
+
571
+ ### Submitting the token
572
+
573
+ | `version` | Form field |
574
+ |---|---|
575
+ | `v1` | `frc-captcha-solution` |
576
+ | `v2` | `frc-captcha-response` |
577
+
578
+ The field names differ, which is what catches an integration moved from one to
579
+ the other. A v1 token is four dot-separated parts and runs to a few hundred
580
+ characters; a v2 token is a single opaque string beginning `AQQA.` and is roughly
581
+ six kilobytes, so size whatever carries it accordingly.
582
+
583
+ ### SDK usage
584
+
585
+ ```js
586
+ // Say which version
587
+ const result = await solver.friendlyCaptcha(
588
+ 'FCMGEMUD2M567T8G',
589
+ 'https://example.com/signup',
590
+ { version: 'v2' },
591
+ );
592
+
593
+ // …or let the script URL decide it
594
+ const result = await solver.friendlyCaptcha(
595
+ 'FCMGEMUD2M567T8G',
596
+ 'https://example.com/signup',
597
+ { moduleScript: 'https://cdn.example.com/@friendlycaptcha/sdk@0.1.6/site.min.js' },
598
+ );
599
+
600
+ // EU data-residency tenant
601
+ const result = await solver.friendlyCaptcha(
602
+ 'FCMGEMUD2M567T8G',
603
+ 'https://example.com/signup',
604
+ { version: 'v2', api_server: 'eu' },
605
+ );
606
+
607
+ result.token;
608
+ ```
609
+
610
+ Uses `recaptchaTimeout`: the service decides how much work a request is worth at
611
+ the moment it is made, so solve time is not a constant, and v2 always solves in a
612
+ browser.
613
+
614
+ ---
615
+
616
+ ## 10. AWS WAF — `amazonWaf(sitekey, url, { ... })`
617
+
618
+ AWS WAF puts its CAPTCHA either on a challenge page — the HTTP 405 interstitial
619
+ that sets `window.gokuProps` — or in a widget an ordinary page renders with
620
+ `AwsWafCaptcha.renderCaptcha`. CapSkip solves both in a real browser. See the
621
+ [CapSkip API docs](https://capskip.com/api-docs/#aws-waf).
622
+
623
+ ### POST `/in.php`
624
+
625
+ | Parameter | Type | Required | Description |
626
+ |---|---|---|---|
627
+ | `key` | string | Yes | CapSkip API key |
628
+ | `method` | string | Yes | `amazon_waf` |
629
+ | `sitekey` | string | Yes | Challenge page: `gokuProps.key`. Widget page: the widget's `apiKey` |
630
+ | `pageurl` | string | Yes | Full http or https URL of the protected page |
631
+ | `iv` | string | No | Challenge page: `gokuProps.iv`. Send together with `context` |
632
+ | `context` | string | No | Challenge page: `gokuProps.context`. Send together with `iv` |
633
+ | `challenge_script` | string | No | Full URL of the page's `challenge.js` |
634
+ | `captcha_script` | string | No | Full URL of the page's `captcha.js` |
635
+ | `jsapiScript` | string | No | Widget page: full URL of `jsapi.js` |
636
+ | `json` | int | No | `0` plain text, `1` JSON |
637
+ | `proxy` | string | No | Proxy address |
638
+ | `proxytype` | string | No | Proxy type |
639
+
640
+ The SDK also accepts `challengeScript`, `captchaScript` and `jsapi_script`, and
641
+ sends the documented spelling of each. An optional left `undefined`, `null` or
642
+ `''` is not sent — the server reads an empty value as absent.
643
+
644
+ ### Challenge page or widget page
645
+
646
+ | | Challenge page | Widget page |
647
+ |---|---|---|
648
+ | Recognized by | HTTP 405 with `window.gokuProps` in an inline script | `AwsWafCaptcha.renderCaptcha(container, { apiKey, … })` |
649
+ | `sitekey` | `gokuProps.key` | the widget's `apiKey` |
650
+ | `iv` / `context` | `gokuProps.iv` / `gokuProps.context`, or neither | not sent |
651
+ | Scripts | `challenge_script`, `captcha_script` | `jsapiScript` |
652
+
653
+ `iv` and `context` come off the same page and only work as a pair, so the SDK
654
+ refuses one without the other. Both are optional: left out, CapSkip reads them
655
+ from `pageurl` at solve time, which is the safer choice when your copy of the
656
+ page is more than a few minutes old.
657
+
658
+ ### Script URLs
659
+
660
+ CapSkip posts the solution to the host a script URL names, so it refuses any
661
+ script that is not on AWS WAF. The SDK applies the server's rule before the
662
+ request is made: the URL must be `https`, its host must end in `.awswaf.com`, it
663
+ must carry no credentials and no port other than 443, and its path must be one
664
+ or more segments followed by `challenge.js`, `captcha.js` or `jsapi.js`. A query
665
+ string is allowed. Anything else throws `ValidationException`, because CapSkip is
666
+ not an AWS WAF integration for scripts hosted elsewhere.
667
+
668
+ ### GET `/res.php`
669
+
670
+ The answer is **not a token**. With `json=1` it is an object in `request`:
671
+
672
+ ```json
673
+ {"status":1,"request":{"captcha_voucher":"…","existing_token":"…"}}
674
+ ```
675
+
676
+ and in plain text the same object as one line of JSON after `OK|`. The SDK
677
+ expands it into `captcha_voucher` and `existing_token`; see
678
+ [Return value](#return-value).
679
+
680
+ ### Exchanging the voucher
681
+
682
+ The two values only work as a pair. POST them as JSON to the `voucher` endpoint
683
+ beside the page's `challenge.js` (the same directory, with `voucher` in place of
684
+ `challenge.js`). The response's `token` is the `aws-waf-token` cookie. Exchange
685
+ them immediately rather than storing them for later.
686
+
687
+ ### SDK usage
688
+
689
+ ```js
690
+ // Challenge page
691
+ const result = await solver.amazonWaf(
692
+ 'AQIDAHjcYuEXAMPLEghicBgQ/7bFaQZ+m5FKCMDnO+vTbNg96AHDh0IR5vgzHNceHYqZR+GOAAAAfjB8',
693
+ 'https://example.com/protected',
694
+ {
695
+ iv: 'CgAFRDIjfgAAA5yY',
696
+ context: 'Rbp3r8fvbuhmrCqpUe8Mh1TLvs8OSKJZiSs2vCF4dt9rxVZI7xYJa2n5k/9r+XaXe7KUIu2I8nXTDHHkDvIOTDc2',
697
+ challenge_script: 'https://3f2a9c7d41b0.edge.sdk.awswaf.com/3f2a9c7d41b0/8e51d0c7a2f4/challenge.js',
698
+ },
699
+ );
700
+
701
+ // Widget page
702
+ const result = await solver.amazonWaf(
703
+ 'Xr9kEXAMPLE2bWQx1vTq8LmZ0aYc4dHs7nFp3gJt6uKw5eRi9AbCdEfGh=_0_1',
704
+ 'https://example.com/protected',
705
+ { jsapiScript: 'https://5b7e0a19c3d8.us-east-1.captcha-sdk.awswaf.com/5b7e0a19c3d8/jsapi.js' },
706
+ );
707
+
708
+ result.captcha_voucher, result.existing_token;
709
+ ```
710
+
711
+ Uses `recaptchaTimeout`: a real browser mints the token the voucher is issued
712
+ for, and the puzzle rounds go through AWS on top of that.
713
+
714
+ ---
715
+
380
716
  ## Return value
381
717
 
382
718
  Every solve method resolves to:
@@ -401,6 +737,46 @@ form field it goes in) and `number`, the counter that solved it:
401
737
  }
402
738
  ```
403
739
 
740
+ CaptchaFox and Friendly Captcha expose `token`, the same string as `code`, named
741
+ for the form field it goes in. CaptchaFox adds `userAgent` when the solve
742
+ reported one — the UA the browser minted the token under, not one you sent:
743
+
744
+ ```js
745
+ {
746
+ captchaId: '12345',
747
+ code: '177f50c25b845601e5c779cdb51b040d…',
748
+ token: '177f50c25b845601e5c779cdb51b040d…',
749
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) …',
750
+ }
751
+ ```
752
+
753
+ Capy returns an object rather than a token, expanded into the three fields the
754
+ target form takes (`code` keeps the raw answer):
755
+
756
+ ```js
757
+ {
758
+ captchaId: '12345',
759
+ code: { captchakey: 'PUZZLE_…', challengekey: '…', answer: '…', respKey: '' },
760
+ captchakey: 'PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v',
761
+ challengekey: 'BalY2gJaI8uA2SGVOZhqBQ3V0CYSNNGP',
762
+ answer: '0xax8ex0xax84x0xkx7qx0x18x76x…',
763
+ respKey: '',
764
+ }
765
+ ```
766
+
767
+ AWS WAF returns an object too, expanded into the voucher pair the site's
768
+ `voucher` endpoint trades for the `aws-waf-token` cookie (`code` keeps the raw
769
+ answer):
770
+
771
+ ```js
772
+ {
773
+ captchaId: '12345',
774
+ code: { captcha_voucher: '…', existing_token: '…' },
775
+ captcha_voucher: 'eyJ2b3VjaGVyIjoiRVhBTVBMRSJ9.9QwZcEXAMPLEvoucherJk4LmN0pQ',
776
+ existing_token: '7d4c2a91-EXAMPLE-4f0b-9e6d-3a1b5c8e2f70:EQoAeXAMPLEtokenAAAA:Wm9wc2E=',
777
+ }
778
+ ```
779
+
404
780
  GeeTest additionally expands its answer into `challenge`, `validate`, and
405
781
  `seccode` (`code` keeps the raw JSON string):
406
782
 
@@ -431,6 +807,13 @@ Convenience aliases mapped before sending to CapSkip:
431
807
  | `api_subdomain` | `api_server` |
432
808
  | `challengeUrl` / `challengeURL` | `challenge_url` |
433
809
  | `challengeJson` / `challengeJSON` | `challenge_json` |
810
+ | `userAgent` / `user_agent` | `useragent` |
811
+ | `captchaKey` | `captchakey` |
812
+ | `moduleScript` | `module_script` |
813
+ | `nomoduleScript` / `noModuleScript` | `nomodule_script` |
814
+ | `challengeScript` | `challenge_script` |
815
+ | `captchaScript` | `captcha_script` |
816
+ | `jsapi_script` | `jsapiScript` |
434
817
  | `proxy` object | `proxy` + `proxytype` strings |
435
818
 
436
819
  ```js
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Solve an AWS WAF CAPTCHA.
5
+ *
6
+ * AWS WAF shows up in two forms, and the values you need depend on which one
7
+ * the target page uses:
8
+ *
9
+ * * A challenge page -- the HTTP 405 interstitial that sets `window.gokuProps`
10
+ * in an inline script. `sitekey` is `gokuProps.key`; `iv` and `context` are
11
+ * `gokuProps.iv` and `gokuProps.context`. `challenge_script` and
12
+ * `captcha_script` are the full URLs of the page's challenge.js and
13
+ * captcha.js.
14
+ * * A widget page -- an ordinary page that calls
15
+ * `AwsWafCaptcha.renderCaptcha(container, { apiKey, ... })`. `sitekey` is
16
+ * that `apiKey`, and `jsapiScript` is the full URL of jsapi.js.
17
+ *
18
+ * Only `sitekey` and the page URL are required. `iv` and `context` travel
19
+ * together: send both or neither. Left out, CapSkip reads them from the page
20
+ * at solve time, which is the safer choice when your copy of the page is more
21
+ * than a few minutes old.
22
+ *
23
+ * Every script URL must be an https URL on an awswaf.com host. CapSkip posts
24
+ * the solution to the host the script names, so it refuses anything else
25
+ * before the request is made.
26
+ *
27
+ * Unlike reCAPTCHA or Turnstile, the answer is NOT a token you put in a form.
28
+ * It is a voucher pair that AWS trades for the `aws-waf-token` cookie.
29
+ */
30
+
31
+ const { CapSkip } = require('capskip');
32
+
33
+ const solver = new CapSkip({
34
+ apiKey: process.env.CAPSKIP_API_KEY || 'capskip',
35
+ host: process.env.CAPSKIP_HOST || '127.0.0.1',
36
+ port: Number(process.env.CAPSKIP_PORT || 8080),
37
+ });
38
+
39
+ const PAGE_URL = 'https://example.com/protected';
40
+
41
+ // From the challenge page's window.gokuProps.
42
+ const SITEKEY = 'AQIDAHjcYuEXAMPLEghicBgQ/7bFaQZ+m5FKCMDnO+vTbNg96AHDh0IR5vgzHNceHYqZR+GOAAAAfjB8';
43
+ const IV = 'CgAFRDIjfgAAA5yY';
44
+ const CONTEXT = 'Rbp3r8fvbuhmrCqpUe8Mh1TLvs8OSKJZiSs2vCF4dt9rxVZI7xYJa2n5k/9r+XaXe7KUIu2I8nXTDHHkDvIOTDc2';
45
+ const CHALLENGE_JS = 'https://3f2a9c7d41b0.edge.sdk.awswaf.com/3f2a9c7d41b0/8e51d0c7a2f4/challenge.js';
46
+
47
+ async function main() {
48
+ const result = await solver.amazonWaf(SITEKEY, PAGE_URL, {
49
+ iv: IV,
50
+ context: CONTEXT,
51
+ challenge_script: CHALLENGE_JS,
52
+ });
53
+
54
+ console.log('Captcha ID: ', result.captchaId);
55
+ console.log('Voucher: ', `${result.captcha_voucher.slice(0, 32)}...`);
56
+ console.log('Existing token:', `${result.existing_token.slice(0, 32)}...`);
57
+
58
+ // --- A widget page -------------------------------------------------------
59
+ // The widget's apiKey as the sitekey, and the jsapi.js it loads. No iv or
60
+ // context: a widget page has none.
61
+ //
62
+ // await solver.amazonWaf(
63
+ // 'Xr9kEXAMPLE2bWQx1vTq8LmZ0aYc4dHs7nFp3gJt6uKw5eRi9AbCdEfGh=_0_1',
64
+ // PAGE_URL,
65
+ // { jsapiScript: 'https://5b7e0a19c3d8.us-east-1.captcha-sdk.awswaf.com/5b7e0a19c3d8/jsapi.js' },
66
+ // );
67
+ //
68
+ // --- With a per-request proxy --------------------------------------------
69
+ // await solver.amazonWaf(SITEKEY, PAGE_URL, {
70
+ // proxy: { type: 'HTTP', uri: 'login:password@1.2.3.4:8080' },
71
+ // });
72
+
73
+ // Exchange the pair for the aws-waf-token cookie: POST both values as JSON
74
+ // to the `voucher` endpoint that sits beside the page's challenge.js. The
75
+ // response's `token` is the cookie value. Do this immediately -- the voucher
76
+ // is short-lived, so do not store the pair for a later request.
77
+ const voucherUrl = new URL('voucher', CHALLENGE_JS).href;
78
+ console.log('Voucher URL: ', voucherUrl);
79
+ //
80
+ // const response = await fetch(voucherUrl, {
81
+ // method: 'POST',
82
+ // headers: { 'Content-Type': 'application/json' },
83
+ // body: JSON.stringify({
84
+ // captcha_voucher: result.captcha_voucher,
85
+ // existing_token: result.existing_token,
86
+ // }),
87
+ // });
88
+ // const { token } = await response.json();
89
+ //
90
+ // // Then request the protected page with the cookie:
91
+ // await fetch(PAGE_URL, { headers: { Cookie: `aws-waf-token=${token}` } });
92
+ }
93
+
94
+ main().catch((error) => {
95
+ console.error(`${error.name}: ${error.message}`);
96
+ process.exitCode = 1;
97
+ });
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Solve a CaptchaFox challenge.
5
+ *
6
+ * CaptchaFox scores the browser itself rather than asking the visitor to read
7
+ * anything, so most solves draw no puzzle at all. CapSkip drives the real widget
8
+ * in a real browser and returns the verification token it would have produced.
9
+ *
10
+ * You need two values, both read off the target page:
11
+ *
12
+ * * sitekey - the public key the widget renders with, conventionally prefixed
13
+ * `sk_`. It is on the widget container as `data-sitekey`, or in the
14
+ * `captchafox.render(...)` call. If the page builds the widget at runtime,
15
+ * open DevTools -> Network and find the request to api.captchafox.com: the
16
+ * key is the path segment after /captcha/.
17
+ * * pageurl - the full URL of the page the widget appears on
18
+ *
19
+ * The page URL has to match: CaptchaFox keys are registered against a list of
20
+ * allowed domains and the service checks the host before it issues anything. A
21
+ * key that is correct but used on a page outside that list is refused
22
+ * permanently, not intermittently -- so if a sitekey fails immediately and
23
+ * consistently, check that you are sending the page the widget actually runs on
24
+ * rather than a search page, a redirect or a shortened link.
25
+ */
26
+
27
+ const { CapSkip } = require('capskip');
28
+
29
+ const solver = new CapSkip({
30
+ apiKey: process.env.CAPSKIP_API_KEY || 'capskip',
31
+ host: process.env.CAPSKIP_HOST || '127.0.0.1',
32
+ port: Number(process.env.CAPSKIP_PORT || 8080),
33
+ });
34
+
35
+ const SITEKEY = 'sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G';
36
+ const PAGE_URL = 'https://example.com/signup';
37
+
38
+ async function main() {
39
+ const result = await solver.captchafox(SITEKEY, PAGE_URL);
40
+
41
+ console.log('Captcha ID:', result.captchaId);
42
+ console.log('Token: ', `${result.token.slice(0, 60)}...`);
43
+ console.log('User-Agent:', result.userAgent || '(none reported)');
44
+
45
+ // --- Choosing the widget source ------------------------------------------
46
+ // CaptchaFox publishes its widget from two places, and which one a site loads
47
+ // decides the shape of the token it expects back. Send api_server only when
48
+ // the target page does not use the default (https://cdn.captchafox.com/).
49
+ //
50
+ // await solver.captchafox(SITEKEY, PAGE_URL, {
51
+ // api_server: 'https://s.uicdn.com/mampkg/@mamdev/core.frontend.libs.captchafox/',
52
+ // });
53
+ //
54
+ // That one returns a MAM_ prefixed token. Read the value from the <script>
55
+ // tag on the target page: if you send the wrong one the solve still succeeds,
56
+ // but the token comes back in a format the site will not accept, which looks
57
+ // like a silent verification failure rather than an error.
58
+ //
59
+ // --- With a per-request proxy --------------------------------------------
60
+ // CapSkip does not force a proxy, but CaptchaFox scores the network a widget
61
+ // runs on as well as the browser, so repeated solves from one address push
62
+ // that address toward the interactive challenges and then toward refusals.
63
+ //
64
+ // await solver.captchafox(SITEKEY, PAGE_URL, {
65
+ // proxy: { type: 'HTTP', uri: 'login:password@1.2.3.4:8080' },
66
+ // });
67
+
68
+ // Post the token back in the form field the widget uses, named
69
+ // `cf-captcha-response`. Treat it as opaque and pass it through unchanged --
70
+ // it is verified server side against the session that produced it, so any
71
+ // edit invalidates it.
72
+ //
73
+ // await fetch(SIGNUP_URL, {
74
+ // method: 'POST',
75
+ // headers: { 'User-Agent': result.userAgent },
76
+ // body: new URLSearchParams({
77
+ // email: 'someone@example.com',
78
+ // 'cf-captcha-response': result.token,
79
+ // }),
80
+ // });
81
+ //
82
+ // Some integrations read the token from a JSON body field instead, so check
83
+ // what the page's own submit sends and mirror it.
84
+ console.log('Form field:', { 'cf-captcha-response': `${result.token.slice(0, 40)}...` });
85
+
86
+ // Submit under the User-Agent the token was minted with, not your own.
87
+ // CapSkip solves in a real browser and uses that browser's identity, so the
88
+ // UA it reports is the one the token is bound to.
89
+ //
90
+ // Tokens are short lived: submit promptly rather than holding one while a
91
+ // user fills in a form, and solve again if the form is abandoned and resumed.
92
+
93
+ // Two of CaptchaFox's challenge types are not solvable -- image-select and
94
+ // the audio fallback, both rare. They are reported as unsolvable rather than
95
+ // waited out, so treat a failure as a signal to resubmit into a fresh
96
+ // challenge rather than as a permanent failure of the key.
97
+ }
98
+
99
+ main().catch((error) => {
100
+ console.error(`${error.name}: ${error.message}`);
101
+ process.exitCode = 1;
102
+ });