Security: WebDecoy/FCaptcha

Security

SECURITY.md

Security Policy

Reporting a vulnerability

Please do not open a public issue for a security problem.

Use GitHub's private vulnerability reporting, which is enabled on this repository. If that is unavailable to you, email hello@webdecoy.com with FCaptcha security in the subject.

What helps most, roughly in order:

  • What the issue lets an attacker do, stated plainly.
  • A reproduction. A curl command or a short script beats prose.
  • Which server implementations you checked. Go, Python and Node are separate codebases kept in sync by hand, so a finding in one is often — but not always — present in the other two.
  • The version or commit.

You do not need a working exploit, and you do not need to be sure. A clear description of something that looks wrong is worth sending.

What to expect

  • Acknowledgement within 3 working days. If you have not heard back, assume the mail went astray and ping the issue tracker without details.
  • An assessment, including whether we agree it is a vulnerability and why.
  • A fix in a release, with credit in the release notes unless you would rather not be named.

This is a small project without a paid security team; there is no bug bounty. What there is, is a genuine interest in being told.

Scope

In scope: anything in this repository — the three servers, the browser widget, the benchmark harness, and the published @webdecoy/fcaptcha and @webdecoy/fcaptcha-client packages and container images.

Particularly interesting:

  • Token forgery or replay — minting a token without completing a challenge, or spending one twice.
  • Verification bypass — anything that gets a passing verdict without doing the work. See the note below on scoring.
  • Signal spoofing that is cheap and general — a technique that makes any automated browser score like a human, as distinct from a hand-tuned evasion of one detector.
  • Anything that harms legitimate visitors — a false positive that can be induced remotely, a way to get another user blocked, or a denial of service against the scoring path.

A note on what counts as a vulnerability here

This is a detection system, so "I evaded a detector" and "I broke the security model" are different claims, and it is worth being explicit about which is which.

Individual detector evasion is expected. Every behavioural and environmental signal can be defeated by someone willing to spend enough effort — that is the nature of the problem, and the scoring is designed around accumulating evidence rather than relying on any single tell. A patched Chromium that hides navigator.webdriver is not a vulnerability report; it is the adversary the roadmap already assumes. Please do send it if the technique is cheap, general, and defeats many signals at once.

Structural bypasses are vulnerabilities. Anything that skips the mechanism rather than beating it: forging a token, replaying a challenge, getting a token without a valid proof of work, making the server trust a header it should not.

A worked example of the second kind, fixed in v1.23.0: the proof of work was scored as evidence rather than enforced as a precondition, and because the final score is a weighted sum in which any one category contributes at most its own weight, a bare curl sending {"siteKey":"x","signals":{}} was issued a valid token. Every detector fired correctly; the aggregation discarded the verdict. That is the shape of a report worth sending.

Supported versions

The latest minor release receives security fixes. Given how young this project is, older lines are not maintained — upgrade rather than expect a backport.

VersionSupported
1.34.xYes
< 1.34No

Deployment notes that are security-relevant

Several ways to make a correct build insecure, documented because they are easy to get wrong and fail quietly:

  • FCAPTCHA_SECRET signs every token and is required. Servers refuse to start without it or when it equals the public development key. The explicit FCAPTCHA_INSECURE_DEV_MODE=1 escape hatch exists only for local development; a deployment using it can have tokens minted by anyone.
  • TRUSTED_PROXIES decides whose X-Forwarded-For is believed. Left unset behind a proxy, every visitor is attributed to the proxy and rate limiting collapses onto one address. Set wrong, a client can claim any IP. See Trusted proxies.
  • FCAPTCHA_LEGACY_UNAUTH_VERIFY disables the secret check on token verification. It exists only as one release of migration cover and should not be on.
  • Check hostname and action from the verification response. They are signed into the token so that a token minted on another site, or for another action, can be rejected — but only if you look.
  • Pin and integrity-check the widget if you load it from a CDN. A captcha is the control deciding whether a request is trusted; a CDN that can silently replace it can switch it off. Digests are published with each release.

There aren't any published security advisories

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Security: WebDecoy/FCaptcha

Security

SECURITY.md

Security Policy

Reporting a vulnerability

Please do not open a public issue for a security problem.

Use GitHub's private vulnerability reporting, which is enabled on this repository. If that is unavailable to you, email hello@webdecoy.com with FCaptcha security in the subject.

What helps most, roughly in order:

  • What the issue lets an attacker do, stated plainly.
  • A reproduction. A curl command or a short script beats prose.
  • Which server implementations you checked. Go, Python and Node are separate codebases kept in sync by hand, so a finding in one is often — but not always — present in the other two.
  • The version or commit.

You do not need a working exploit, and you do not need to be sure. A clear description of something that looks wrong is worth sending.

What to expect

  • Acknowledgement within 3 working days. If you have not heard back, assume the mail went astray and ping the issue tracker without details.
  • An assessment, including whether we agree it is a vulnerability and why.
  • A fix in a release, with credit in the release notes unless you would rather not be named.

This is a small project without a paid security team; there is no bug bounty. What there is, is a genuine interest in being told.

Scope

In scope: anything in this repository — the three servers, the browser widget, the benchmark harness, and the published @webdecoy/fcaptcha and @webdecoy/fcaptcha-client packages and container images.

Particularly interesting:

  • Token forgery or replay — minting a token without completing a challenge, or spending one twice.
  • Verification bypass — anything that gets a passing verdict without doing the work. See the note below on scoring.
  • Signal spoofing that is cheap and general — a technique that makes any automated browser score like a human, as distinct from a hand-tuned evasion of one detector.
  • Anything that harms legitimate visitors — a false positive that can be induced remotely, a way to get another user blocked, or a denial of service against the scoring path.

A note on what counts as a vulnerability here

This is a detection system, so "I evaded a detector" and "I broke the security model" are different claims, and it is worth being explicit about which is which.

Individual detector evasion is expected. Every behavioural and environmental signal can be defeated by someone willing to spend enough effort — that is the nature of the problem, and the scoring is designed around accumulating evidence rather than relying on any single tell. A patched Chromium that hides navigator.webdriver is not a vulnerability report; it is the adversary the roadmap already assumes. Please do send it if the technique is cheap, general, and defeats many signals at once.

Structural bypasses are vulnerabilities. Anything that skips the mechanism rather than beating it: forging a token, replaying a challenge, getting a token without a valid proof of work, making the server trust a header it should not.

A worked example of the second kind, fixed in v1.23.0: the proof of work was scored as evidence rather than enforced as a precondition, and because the final score is a weighted sum in which any one category contributes at most its own weight, a bare curl sending {"siteKey":"x","signals":{}} was issued a valid token. Every detector fired correctly; the aggregation discarded the verdict. That is the shape of a report worth sending.

Supported versions

The latest minor release receives security fixes. Given how young this project is, older lines are not maintained — upgrade rather than expect a backport.

VersionSupported
1.34.xYes
< 1.34No

Deployment notes that are security-relevant

Several ways to make a correct build insecure, documented because they are easy to get wrong and fail quietly:

  • FCAPTCHA_SECRET signs every token and is required. Servers refuse to start without it or when it equals the public development key. The explicit FCAPTCHA_INSECURE_DEV_MODE=1 escape hatch exists only for local development; a deployment using it can have tokens minted by anyone.
  • TRUSTED_PROXIES decides whose X-Forwarded-For is believed. Left unset behind a proxy, every visitor is attributed to the proxy and rate limiting collapses onto one address. Set wrong, a client can claim any IP. See Trusted proxies.
  • FCAPTCHA_LEGACY_UNAUTH_VERIFY disables the secret check on token verification. It exists only as one release of migration cover and should not be on.
  • Check hostname and action from the verification response. They are signed into the token so that a token minted on another site, or for another action, can be rejected — but only if you look.
  • Pin and integrity-check the widget if you load it from a CDN. A captcha is the control deciding whether a request is trusted; a CDN that can silently replace it can switch it off. Digests are published with each release.

There aren't any published security advisories

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Security: WebDecoy/FCaptcha

Security

SECURITY.md

Security Policy

Reporting a vulnerability

Please do not open a public issue for a security problem.

Use GitHub's private vulnerability reporting, which is enabled on this repository. If that is unavailable to you, email hello@webdecoy.com with FCaptcha security in the subject.

What helps most, roughly in order:

  • What the issue lets an attacker do, stated plainly.
  • A reproduction. A curl command or a short script beats prose.
  • Which server implementations you checked. Go, Python and Node are separate codebases kept in sync by hand, so a finding in one is often — but not always — present in the other two.
  • The version or commit.

You do not need a working exploit, and you do not need to be sure. A clear description of something that looks wrong is worth sending.

What to expect

  • Acknowledgement within 3 working days. If you have not heard back, assume the mail went astray and ping the issue tracker without details.
  • An assessment, including whether we agree it is a vulnerability and why.
  • A fix in a release, with credit in the release notes unless you would rather not be named.

This is a small project without a paid security team; there is no bug bounty. What there is, is a genuine interest in being told.

Scope

In scope: anything in this repository — the three servers, the browser widget, the benchmark harness, and the published @webdecoy/fcaptcha and @webdecoy/fcaptcha-client packages and container images.

Particularly interesting:

  • Token forgery or replay — minting a token without completing a challenge, or spending one twice.
  • Verification bypass — anything that gets a passing verdict without doing the work. See the note below on scoring.
  • Signal spoofing that is cheap and general — a technique that makes any automated browser score like a human, as distinct from a hand-tuned evasion of one detector.
  • Anything that harms legitimate visitors — a false positive that can be induced remotely, a way to get another user blocked, or a denial of service against the scoring path.

A note on what counts as a vulnerability here

This is a detection system, so "I evaded a detector" and "I broke the security model" are different claims, and it is worth being explicit about which is which.

Individual detector evasion is expected. Every behavioural and environmental signal can be defeated by someone willing to spend enough effort — that is the nature of the problem, and the scoring is designed around accumulating evidence rather than relying on any single tell. A patched Chromium that hides navigator.webdriver is not a vulnerability report; it is the adversary the roadmap already assumes. Please do send it if the technique is cheap, general, and defeats many signals at once.

Structural bypasses are vulnerabilities. Anything that skips the mechanism rather than beating it: forging a token, replaying a challenge, getting a token without a valid proof of work, making the server trust a header it should not.

A worked example of the second kind, fixed in v1.23.0: the proof of work was scored as evidence rather than enforced as a precondition, and because the final score is a weighted sum in which any one category contributes at most its own weight, a bare curl sending {"siteKey":"x","signals":{}} was issued a valid token. Every detector fired correctly; the aggregation discarded the verdict. That is the shape of a report worth sending.

Supported versions

The latest minor release receives security fixes. Given how young this project is, older lines are not maintained — upgrade rather than expect a backport.

VersionSupported
1.34.xYes
< 1.34No

Deployment notes that are security-relevant

Several ways to make a correct build insecure, documented because they are easy to get wrong and fail quietly:

  • FCAPTCHA_SECRET signs every token and is required. Servers refuse to start without it or when it equals the public development key. The explicit FCAPTCHA_INSECURE_DEV_MODE=1 escape hatch exists only for local development; a deployment using it can have tokens minted by anyone.
  • TRUSTED_PROXIES decides whose X-Forwarded-For is believed. Left unset behind a proxy, every visitor is attributed to the proxy and rate limiting collapses onto one address. Set wrong, a client can claim any IP. See Trusted proxies.
  • FCAPTCHA_LEGACY_UNAUTH_VERIFY disables the secret check on token verification. It exists only as one release of migration cover and should not be on.
  • Check hostname and action from the verification response. They are signed into the token so that a token minted on another site, or for another action, can be rejected — but only if you look.
  • Pin and integrity-check the widget if you load it from a CDN. A captcha is the control deciding whether a request is trusted; a CDN that can silently replace it can switch it off. Digests are published with each release.

There aren't any published security advisories

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Security: WebDecoy/FCaptcha

Security

SECURITY.md

Security Policy

Reporting a vulnerability

Please do not open a public issue for a security problem.

Use GitHub's private vulnerability reporting, which is enabled on this repository. If that is unavailable to you, email hello@webdecoy.com with FCaptcha security in the subject.

What helps most, roughly in order:

  • What the issue lets an attacker do, stated plainly.
  • A reproduction. A curl command or a short script beats prose.
  • Which server implementations you checked. Go, Python and Node are separate codebases kept in sync by hand, so a finding in one is often — but not always — present in the other two.
  • The version or commit.

You do not need a working exploit, and you do not need to be sure. A clear description of something that looks wrong is worth sending.

What to expect

  • Acknowledgement within 3 working days. If you have not heard back, assume the mail went astray and ping the issue tracker without details.
  • An assessment, including whether we agree it is a vulnerability and why.
  • A fix in a release, with credit in the release notes unless you would rather not be named.

This is a small project without a paid security team; there is no bug bounty. What there is, is a genuine interest in being told.

Scope

In scope: anything in this repository — the three servers, the browser widget, the benchmark harness, and the published @webdecoy/fcaptcha and @webdecoy/fcaptcha-client packages and container images.

Particularly interesting:

  • Token forgery or replay — minting a token without completing a challenge, or spending one twice.
  • Verification bypass — anything that gets a passing verdict without doing the work. See the note below on scoring.
  • Signal spoofing that is cheap and general — a technique that makes any automated browser score like a human, as distinct from a hand-tuned evasion of one detector.
  • Anything that harms legitimate visitors — a false positive that can be induced remotely, a way to get another user blocked, or a denial of service against the scoring path.

A note on what counts as a vulnerability here

This is a detection system, so "I evaded a detector" and "I broke the security model" are different claims, and it is worth being explicit about which is which.

Individual detector evasion is expected. Every behavioural and environmental signal can be defeated by someone willing to spend enough effort — that is the nature of the problem, and the scoring is designed around accumulating evidence rather than relying on any single tell. A patched Chromium that hides navigator.webdriver is not a vulnerability report; it is the adversary the roadmap already assumes. Please do send it if the technique is cheap, general, and defeats many signals at once.

Structural bypasses are vulnerabilities. Anything that skips the mechanism rather than beating it: forging a token, replaying a challenge, getting a token without a valid proof of work, making the server trust a header it should not.

A worked example of the second kind, fixed in v1.23.0: the proof of work was scored as evidence rather than enforced as a precondition, and because the final score is a weighted sum in which any one category contributes at most its own weight, a bare curl sending {"siteKey":"x","signals":{}} was issued a valid token. Every detector fired correctly; the aggregation discarded the verdict. That is the shape of a report worth sending.

Supported versions

The latest minor release receives security fixes. Given how young this project is, older lines are not maintained — upgrade rather than expect a backport.

VersionSupported
1.34.xYes
< 1.34No

Deployment notes that are security-relevant

Several ways to make a correct build insecure, documented because they are easy to get wrong and fail quietly:

  • FCAPTCHA_SECRET signs every token and is required. Servers refuse to start without it or when it equals the public development key. The explicit FCAPTCHA_INSECURE_DEV_MODE=1 escape hatch exists only for local development; a deployment using it can have tokens minted by anyone.
  • TRUSTED_PROXIES decides whose X-Forwarded-For is believed. Left unset behind a proxy, every visitor is attributed to the proxy and rate limiting collapses onto one address. Set wrong, a client can claim any IP. See Trusted proxies.
  • FCAPTCHA_LEGACY_UNAUTH_VERIFY disables the secret check on token verification. It exists only as one release of migration cover and should not be on.
  • Check hostname and action from the verification response. They are signed into the token so that a token minted on another site, or for another action, can be rejected — but only if you look.
  • Pin and integrity-check the widget if you load it from a CDN. A captcha is the control deciding whether a request is trusted; a CDN that can silently replace it can switch it off. Digests are published with each release.

There aren't any published security advisories

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Security: WebDecoy/FCaptcha

Security

SECURITY.md

Security Policy

Reporting a vulnerability

Please do not open a public issue for a security problem.

Use GitHub's private vulnerability reporting, which is enabled on this repository. If that is unavailable to you, email hello@webdecoy.com with FCaptcha security in the subject.

What helps most, roughly in order:

  • What the issue lets an attacker do, stated plainly.
  • A reproduction. A curl command or a short script beats prose.
  • Which server implementations you checked. Go, Python and Node are separate codebases kept in sync by hand, so a finding in one is often — but not always — present in the other two.
  • The version or commit.

You do not need a working exploit, and you do not need to be sure. A clear description of something that looks wrong is worth sending.

What to expect

  • Acknowledgement within 3 working days. If you have not heard back, assume the mail went astray and ping the issue tracker without details.
  • An assessment, including whether we agree it is a vulnerability and why.
  • A fix in a release, with credit in the release notes unless you would rather not be named.

This is a small project without a paid security team; there is no bug bounty. What there is, is a genuine interest in being told.

Scope

In scope: anything in this repository — the three servers, the browser widget, the benchmark harness, and the published @webdecoy/fcaptcha and @webdecoy/fcaptcha-client packages and container images.

Particularly interesting:

  • Token forgery or replay — minting a token without completing a challenge, or spending one twice.
  • Verification bypass — anything that gets a passing verdict without doing the work. See the note below on scoring.
  • Signal spoofing that is cheap and general — a technique that makes any automated browser score like a human, as distinct from a hand-tuned evasion of one detector.
  • Anything that harms legitimate visitors — a false positive that can be induced remotely, a way to get another user blocked, or a denial of service against the scoring path.

A note on what counts as a vulnerability here

This is a detection system, so "I evaded a detector" and "I broke the security model" are different claims, and it is worth being explicit about which is which.

Individual detector evasion is expected. Every behavioural and environmental signal can be defeated by someone willing to spend enough effort — that is the nature of the problem, and the scoring is designed around accumulating evidence rather than relying on any single tell. A patched Chromium that hides navigator.webdriver is not a vulnerability report; it is the adversary the roadmap already assumes. Please do send it if the technique is cheap, general, and defeats many signals at once.

Structural bypasses are vulnerabilities. Anything that skips the mechanism rather than beating it: forging a token, replaying a challenge, getting a token without a valid proof of work, making the server trust a header it should not.

A worked example of the second kind, fixed in v1.23.0: the proof of work was scored as evidence rather than enforced as a precondition, and because the final score is a weighted sum in which any one category contributes at most its own weight, a bare curl sending {"siteKey":"x","signals":{}} was issued a valid token. Every detector fired correctly; the aggregation discarded the verdict. That is the shape of a report worth sending.

Supported versions

The latest minor release receives security fixes. Given how young this project is, older lines are not maintained — upgrade rather than expect a backport.

VersionSupported
1.34.xYes
< 1.34No

Deployment notes that are security-relevant

Several ways to make a correct build insecure, documented because they are easy to get wrong and fail quietly:

  • FCAPTCHA_SECRET signs every token and is required. Servers refuse to start without it or when it equals the public development key. The explicit FCAPTCHA_INSECURE_DEV_MODE=1 escape hatch exists only for local development; a deployment using it can have tokens minted by anyone.
  • TRUSTED_PROXIES decides whose X-Forwarded-For is believed. Left unset behind a proxy, every visitor is attributed to the proxy and rate limiting collapses onto one address. Set wrong, a client can claim any IP. See Trusted proxies.
  • FCAPTCHA_LEGACY_UNAUTH_VERIFY disables the secret check on token verification. It exists only as one release of migration cover and should not be on.
  • Check hostname and action from the verification response. They are signed into the token so that a token minted on another site, or for another action, can be rejected — but only if you look.
  • Pin and integrity-check the widget if you load it from a CDN. A captcha is the control deciding whether a request is trusted; a CDN that can silently replace it can switch it off. Digests are published with each release.

There aren't any published security advisories

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Security: WebDecoy/FCaptcha

Security

SECURITY.md

Security Policy

Reporting a vulnerability

Please do not open a public issue for a security problem.

Use GitHub's private vulnerability reporting, which is enabled on this repository. If that is unavailable to you, email hello@webdecoy.com with FCaptcha security in the subject.

What helps most, roughly in order:

  • What the issue lets an attacker do, stated plainly.
  • A reproduction. A curl command or a short script beats prose.
  • Which server implementations you checked. Go, Python and Node are separate codebases kept in sync by hand, so a finding in one is often — but not always — present in the other two.
  • The version or commit.

You do not need a working exploit, and you do not need to be sure. A clear description of something that looks wrong is worth sending.

What to expect

  • Acknowledgement within 3 working days. If you have not heard back, assume the mail went astray and ping the issue tracker without details.
  • An assessment, including whether we agree it is a vulnerability and why.
  • A fix in a release, with credit in the release notes unless you would rather not be named.

This is a small project without a paid security team; there is no bug bounty. What there is, is a genuine interest in being told.

Scope

In scope: anything in this repository — the three servers, the browser widget, the benchmark harness, and the published @webdecoy/fcaptcha and @webdecoy/fcaptcha-client packages and container images.

Particularly interesting:

  • Token forgery or replay — minting a token without completing a challenge, or spending one twice.
  • Verification bypass — anything that gets a passing verdict without doing the work. See the note below on scoring.
  • Signal spoofing that is cheap and general — a technique that makes any automated browser score like a human, as distinct from a hand-tuned evasion of one detector.
  • Anything that harms legitimate visitors — a false positive that can be induced remotely, a way to get another user blocked, or a denial of service against the scoring path.

A note on what counts as a vulnerability here

This is a detection system, so "I evaded a detector" and "I broke the security model" are different claims, and it is worth being explicit about which is which.

Individual detector evasion is expected. Every behavioural and environmental signal can be defeated by someone willing to spend enough effort — that is the nature of the problem, and the scoring is designed around accumulating evidence rather than relying on any single tell. A patched Chromium that hides navigator.webdriver is not a vulnerability report; it is the adversary the roadmap already assumes. Please do send it if the technique is cheap, general, and defeats many signals at once.

Structural bypasses are vulnerabilities. Anything that skips the mechanism rather than beating it: forging a token, replaying a challenge, getting a token without a valid proof of work, making the server trust a header it should not.

A worked example of the second kind, fixed in v1.23.0: the proof of work was scored as evidence rather than enforced as a precondition, and because the final score is a weighted sum in which any one category contributes at most its own weight, a bare curl sending {"siteKey":"x","signals":{}} was issued a valid token. Every detector fired correctly; the aggregation discarded the verdict. That is the shape of a report worth sending.

Supported versions

The latest minor release receives security fixes. Given how young this project is, older lines are not maintained — upgrade rather than expect a backport.

VersionSupported
1.34.xYes
< 1.34No

Deployment notes that are security-relevant

Several ways to make a correct build insecure, documented because they are easy to get wrong and fail quietly:

  • FCAPTCHA_SECRET signs every token and is required. Servers refuse to start without it or when it equals the public development key. The explicit FCAPTCHA_INSECURE_DEV_MODE=1 escape hatch exists only for local development; a deployment using it can have tokens minted by anyone.
  • TRUSTED_PROXIES decides whose X-Forwarded-For is believed. Left unset behind a proxy, every visitor is attributed to the proxy and rate limiting collapses onto one address. Set wrong, a client can claim any IP. See Trusted proxies.
  • FCAPTCHA_LEGACY_UNAUTH_VERIFY disables the secret check on token verification. It exists only as one release of migration cover and should not be on.
  • Check hostname and action from the verification response. They are signed into the token so that a token minted on another site, or for another action, can be rejected — but only if you look.
  • Pin and integrity-check the widget if you load it from a CDN. A captcha is the control deciding whether a request is trusted; a CDN that can silently replace it can switch it off. Digests are published with each release.

There aren't any published security advisories

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Security: WebDecoy/FCaptcha

Security

SECURITY.md

Security Policy

Reporting a vulnerability

Please do not open a public issue for a security problem.

Use GitHub's private vulnerability reporting, which is enabled on this repository. If that is unavailable to you, email hello@webdecoy.com with FCaptcha security in the subject.

What helps most, roughly in order:

  • What the issue lets an attacker do, stated plainly.
  • A reproduction. A curl command or a short script beats prose.
  • Which server implementations you checked. Go, Python and Node are separate codebases kept in sync by hand, so a finding in one is often — but not always — present in the other two.
  • The version or commit.

You do not need a working exploit, and you do not need to be sure. A clear description of something that looks wrong is worth sending.

What to expect

  • Acknowledgement within 3 working days. If you have not heard back, assume the mail went astray and ping the issue tracker without details.
  • An assessment, including whether we agree it is a vulnerability and why.
  • A fix in a release, with credit in the release notes unless you would rather not be named.

This is a small project without a paid security team; there is no bug bounty. What there is, is a genuine interest in being told.

Scope

In scope: anything in this repository — the three servers, the browser widget, the benchmark harness, and the published @webdecoy/fcaptcha and @webdecoy/fcaptcha-client packages and container images.

Particularly interesting:

  • Token forgery or replay — minting a token without completing a challenge, or spending one twice.
  • Verification bypass — anything that gets a passing verdict without doing the work. See the note below on scoring.
  • Signal spoofing that is cheap and general — a technique that makes any automated browser score like a human, as distinct from a hand-tuned evasion of one detector.
  • Anything that harms legitimate visitors — a false positive that can be induced remotely, a way to get another user blocked, or a denial of service against the scoring path.

A note on what counts as a vulnerability here

This is a detection system, so "I evaded a detector" and "I broke the security model" are different claims, and it is worth being explicit about which is which.

Individual detector evasion is expected. Every behavioural and environmental signal can be defeated by someone willing to spend enough effort — that is the nature of the problem, and the scoring is designed around accumulating evidence rather than relying on any single tell. A patched Chromium that hides navigator.webdriver is not a vulnerability report; it is the adversary the roadmap already assumes. Please do send it if the technique is cheap, general, and defeats many signals at once.

Structural bypasses are vulnerabilities. Anything that skips the mechanism rather than beating it: forging a token, replaying a challenge, getting a token without a valid proof of work, making the server trust a header it should not.

A worked example of the second kind, fixed in v1.23.0: the proof of work was scored as evidence rather than enforced as a precondition, and because the final score is a weighted sum in which any one category contributes at most its own weight, a bare curl sending {"siteKey":"x","signals":{}} was issued a valid token. Every detector fired correctly; the aggregation discarded the verdict. That is the shape of a report worth sending.

Supported versions

The latest minor release receives security fixes. Given how young this project is, older lines are not maintained — upgrade rather than expect a backport.

VersionSupported
1.34.xYes
< 1.34No

Deployment notes that are security-relevant

Several ways to make a correct build insecure, documented because they are easy to get wrong and fail quietly:

  • FCAPTCHA_SECRET signs every token and is required. Servers refuse to start without it or when it equals the public development key. The explicit FCAPTCHA_INSECURE_DEV_MODE=1 escape hatch exists only for local development; a deployment using it can have tokens minted by anyone.
  • TRUSTED_PROXIES decides whose X-Forwarded-For is believed. Left unset behind a proxy, every visitor is attributed to the proxy and rate limiting collapses onto one address. Set wrong, a client can claim any IP. See Trusted proxies.
  • FCAPTCHA_LEGACY_UNAUTH_VERIFY disables the secret check on token verification. It exists only as one release of migration cover and should not be on.
  • Check hostname and action from the verification response. They are signed into the token so that a token minted on another site, or for another action, can be rejected — but only if you look.
  • Pin and integrity-check the widget if you load it from a CDN. A captcha is the control deciding whether a request is trusted; a CDN that can silently replace it can switch it off. Digests are published with each release.

There aren't any published security advisories

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Security: WebDecoy/FCaptcha

Security

SECURITY.md

Security Policy

Reporting a vulnerability

Please do not open a public issue for a security problem.

Use GitHub's private vulnerability reporting, which is enabled on this repository. If that is unavailable to you, email hello@webdecoy.com with FCaptcha security in the subject.

What helps most, roughly in order:

  • What the issue lets an attacker do, stated plainly.
  • A reproduction. A curl command or a short script beats prose.
  • Which server implementations you checked. Go, Python and Node are separate codebases kept in sync by hand, so a finding in one is often — but not always — present in the other two.
  • The version or commit.

You do not need a working exploit, and you do not need to be sure. A clear description of something that looks wrong is worth sending.

What to expect

  • Acknowledgement within 3 working days. If you have not heard back, assume the mail went astray and ping the issue tracker without details.
  • An assessment, including whether we agree it is a vulnerability and why.
  • A fix in a release, with credit in the release notes unless you would rather not be named.

This is a small project without a paid security team; there is no bug bounty. What there is, is a genuine interest in being told.

Scope

In scope: anything in this repository — the three servers, the browser widget, the benchmark harness, and the published @webdecoy/fcaptcha and @webdecoy/fcaptcha-client packages and container images.

Particularly interesting:

  • Token forgery or replay — minting a token without completing a challenge, or spending one twice.
  • Verification bypass — anything that gets a passing verdict without doing the work. See the note below on scoring.
  • Signal spoofing that is cheap and general — a technique that makes any automated browser score like a human, as distinct from a hand-tuned evasion of one detector.
  • Anything that harms legitimate visitors — a false positive that can be induced remotely, a way to get another user blocked, or a denial of service against the scoring path.

A note on what counts as a vulnerability here

This is a detection system, so "I evaded a detector" and "I broke the security model" are different claims, and it is worth being explicit about which is which.

Individual detector evasion is expected. Every behavioural and environmental signal can be defeated by someone willing to spend enough effort — that is the nature of the problem, and the scoring is designed around accumulating evidence rather than relying on any single tell. A patched Chromium that hides navigator.webdriver is not a vulnerability report; it is the adversary the roadmap already assumes. Please do send it if the technique is cheap, general, and defeats many signals at once.

Structural bypasses are vulnerabilities. Anything that skips the mechanism rather than beating it: forging a token, replaying a challenge, getting a token without a valid proof of work, making the server trust a header it should not.

A worked example of the second kind, fixed in v1.23.0: the proof of work was scored as evidence rather than enforced as a precondition, and because the final score is a weighted sum in which any one category contributes at most its own weight, a bare curl sending {"siteKey":"x","signals":{}} was issued a valid token. Every detector fired correctly; the aggregation discarded the verdict. That is the shape of a report worth sending.

Supported versions

The latest minor release receives security fixes. Given how young this project is, older lines are not maintained — upgrade rather than expect a backport.

VersionSupported
1.34.xYes
< 1.34No

Deployment notes that are security-relevant

Several ways to make a correct build insecure, documented because they are easy to get wrong and fail quietly:

  • FCAPTCHA_SECRET signs every token and is required. Servers refuse to start without it or when it equals the public development key. The explicit FCAPTCHA_INSECURE_DEV_MODE=1 escape hatch exists only for local development; a deployment using it can have tokens minted by anyone.
  • TRUSTED_PROXIES decides whose X-Forwarded-For is believed. Left unset behind a proxy, every visitor is attributed to the proxy and rate limiting collapses onto one address. Set wrong, a client can claim any IP. See Trusted proxies.
  • FCAPTCHA_LEGACY_UNAUTH_VERIFY disables the secret check on token verification. It exists only as one release of migration cover and should not be on.
  • Check hostname and action from the verification response. They are signed into the token so that a token minted on another site, or for another action, can be rejected — but only if you look.
  • Pin and integrity-check the widget if you load it from a CDN. A captcha is the control deciding whether a request is trusted; a CDN that can silently replace it can switch it off. Digests are published with each release.

There aren't any published security advisories