Skip to content

Click'n'Load

Click'n'Load ("CnL") is how a website hands a batch of links straight to a download manager. It is a de-facto standard rather than a specification: it is whatever JDownloader implemented, and every site that offers a "Click'n'Load" button talks to that. KnightLoader answers the same protocol, so those buttons work without the site knowing or caring which program is listening.

How a site actually does it

Three steps, all aimed at 127.0.0.1:9666, the port JDownloader has listened on for two decades, and which every CnL button hard-codes.

1. Detection. The page loads

<script src="http://127.0.0.1:9666/jdcheck.js"></script>

and checks whether the global jdownloader came out true. KnightLoader answers with jdownloader=true; so the button appears.

2. Submission. Clicking posts a form to /flash/addcrypted2:

Field Contents
crypted the link list, AES-128-CBC encrypted, base64 encoded
jk a one-line JavaScript function returning the hex key; the key doubles as the IV
passwords archive passwords for the download, newline separated (optional)
source / package where the links came from, used as the package name (optional)

The encryption is not security, because the key travels with the ciphertext. It exists so a link list is not sitting in plain text in the page source for a scraper to harvest.

KnightLoader extracts the hex key from jk with a regular expression rather than executing the snippet. jk is attacker-controlled JavaScript from an arbitrary website; running it would be an obvious way to lose the machine.

3. Plain variant. Older or simpler sites post an unencrypted list to /flash/add with a urls field.

4. addcrypted (v1). The oldest variant, from before addcrypted2 existed: the site posts a crypted field encrypted against JDownloader's own RSA public key rather than a key that travels with it, so nobody but a real JDownloader can open it. KnightLoader does not hold that key and never will. See "Container files" below for the reason behind that choice, and for how it is answered anyway when a JD backend is configured.

Every one of these four accepts POST only. A GET here would be a browser "simple request" (no preflight, no user gesture, no navigation), so any page in the world, an ad iframe or an email preview image tag included, could queue arbitrary downloads and archive passwords into this instance. A handful of read-only routes some sites and extensions probe before ever trying to submit (/flash/addcnl, /flashgot, /alive, /favicon.ico, /crossdomain.xml, alongside jdcheck.js and the bare detection paths above) answer GET only and never touch a link or a password.

The three things that stop it working

Private Network Access

Chrome will not let a page on the public internet open a connection to a private or loopback address unless the target opts in. The browser sends an OPTIONS preflight first and expects Access-Control-Allow-Private-Network: true back.

Without that header the failure is quiet in the worst way: sites that submit with a plain HTML form still work, while sites that use fetch or XHR fail with nothing in the UI to explain it. KnightLoader answers the preflight.

Cross-origin rules

A page at https://example.com posting to http://127.0.0.1:9666 is a cross-origin request, so the CnL routes send Access-Control-Allow-Origin: *.

That wildcard is confined to the CnL listener. The protocol requires it, since any site must be able to reach the listener, and the listener binds 127.0.0.1 only, so nothing off the machine can talk to it. The main API on port 8749 does the exact opposite and refuses foreign origins outright. Do not "fix" one to match the other.

Mixed content

An HTTPS page loading http://127.0.0.1:9666/jdcheck.js looks like mixed content, which browsers normally block. Loopback is the exception: both Chrome and Firefox treat 127.0.0.1 and localhost as potentially trustworthy, so the script tag is allowed. You do not need to do anything here, but it explains why the address must stay literally 127.0.0.1 rather than a hostname that happens to resolve there.

When KnightLoader is not on your desktop

This is the case that matters for the primary deployment, and the one that plain CnL cannot solve: the container runs on a NAS, and 127.0.0.1 inside that container is not the 127.0.0.1 your browser means. The site posts into the void.

Publishing port 9666 does not help. The address is hard-coded in the site's JavaScript; it will never dial your NAS.

There are two answers, and the browser extension is the one most people want.

The browser extension. It catches the submission inside the page, before it is ever sent, then hands the links to whichever instance you pick, the same chooser every other send from the extension uses. Nothing runs on your desktop, no port is owned, and it works wherever the instance is. It is what most people install the extension for, so the options page that opens after the install leads with it.

This looks impossible at first, since an extension cannot listen on a TCP port and so never receives the POST. Instead it patches the page's own fetch, XHR and form submission at document_start and takes the payload before it leaves (extension/src/cnl-main.js), decodes it in the browser (extension/src/cnl.js, the same AES-128-CBC this file describes), and answers the site with the identical success\r\n a real listener would. The detection step is answered the same way: the interceptor declares jdownloader = true before any script the page brings, so the button appears.

That means running code in every page you visit, which is a real permission. The manifest lists <all_urls> under optional_host_permissions, so the install dialog asks for no website access, and the browser asks for it when Click'n'Load is switched on. The switch shows whether the interception runs, not whether it is wanted, so it never reads "on" while the browser still withholds the access. A fresh install opens the options page with a card that asks for it in one click.

Switching it off really switches it off, which is the part a static content_scripts entry could never offer: the two scripts are unregistered through chrome.scripting.unregisterContentScripts, and chrome.scripting.getRegisteredContentScripts() returns []. Nothing of this extension runs in any page until it is switched back on. (JDownloader's own extension requires the same access and registers eight content scripts fixed in its manifest, which cannot be switched off at all.)

The bridge. This is for someone who wants no extension at all, or a browser without one. Run the same binary on your own machine in bridge mode. It listens on 127.0.0.1:9666, speaks CnL to the website, and forwards what it decodes to the remote instance over the normal REST API:

knightloader -bridge http://nas:8749
# with a password-locked instance:
knightloader -bridge http://nas:8749 -bridge-password 'your-ui-password'
# with an instance a reverse proxy serves under a path:
knightloader -bridge https://example.com/kl

It downloads nothing itself and needs no data directory. It is a few hundred kilobytes of forwarding. The remote does the work, and the links land in its collector exactly as if you had pasted them.

The desktop build needs none of this: it is the server, running on your machine, so its own CnL listener is already the one the browser means.

Configuration

Var / flag Default Meaning
KL_CNL 9666 listener port on 127.0.0.1; 0 disables it
-bridge-clipboard off bridge mode only: watch the OS clipboard for hoster links (needs a -tags bridgeclipboard build)

In the container it defaults to 9666 as well, since 2026-09-07. It used to be 0, on the grounds that a loopback listener inside a container cannot be reached. That is true for a browser on another machine, but not for one on the host itself or for the bridge. An image that switched the feature off produced a switch that read "off" on every container install with no way to tell whether that was a choice or a default.

It stays bound to 127.0.0.1. The Click'n'Load protocol has no authentication, so binding it to the LAN would be an open "add these links to your downloader" endpoint for the whole network.

Checking it by hand

With KnightLoader (or a bridge) running:

curl -s http://127.0.0.1:9666/jdcheck.js
# jdownloader=true;

curl -s -X POST http://127.0.0.1:9666/flash/add \
  --data-urlencode 'urls=https://example.com/file.bin' \
  --data-urlencode 'package=Manual test'
# success

The links appear in the collector. If jdcheck.js answers but a real site's button does nothing, the preflight is the first thing to look at: open the browser's network panel and check whether the OPTIONS request came back with Access-Control-Allow-Private-Network.

Container files, and addcrypted (v1)

A .dlc, .ccf or .rsdf is encrypted the same way addcrypted (v1) is: the key is issued to registered clients, and no open-source client generates or holds one on its own. Rather than borrow somebody else's application key and pretend to be their client, KnightLoader hands the bytes to the headless JDownloader backend it already ships as its catch-all resolver, which has its own key and does this legitimately (see internal/container's package doc). Handing KnightLoader one of these files (POST /api/containers) takes this path; so does a site's addcrypted (v1) submission, by the identical route. The payload is handed to JD as inline content instead of a fetchable URL, because unlike an uploaded file it was never a file anywhere to fetch.

Without KL_JD configured, both are refused with an error that states that reason. See the main README's configuration table.

Ambient clipboard watching (bridge only)

The bridge can also watch the OS clipboard and forward anything that looks like a hoster link, without waiting for a CnL button or an explicit paste, which is useful for a site with no Click'n'Load button at all. It is off by default:

knightloader -bridge http://nas:8749 -bridge-clipboard

This is scoped to the bridge specifically because it is the one build with an unambiguous claim on a user's own OS clipboard: the user started it, by hand, on their own machine. It also needs a build with -tags bridgeclipboard: the ordinary release binary (and the container image) does not carry the clipboard-reading dependency at all, because a headless server has no legitimate reason to touch a clipboard. Passing -bridge-clipboard against a plain build logs that the build has no clipboard support instead of silently doing nothing.

Watching is narrow: only a line that is essentially just a link qualifies, not prose that happens to contain one, because nobody is watching the result to catch an accidental selection before it queues. A small ring of recently-forwarded content is kept so the same clipboard is not resubmitted on every poll tick, and so KnightLoader's own "copy link" writing a link back into the clipboard does not get read in as if it arrived from somewhere else.

What CnL does not cover

Right-click-send-to-KnightLoader on an arbitrary link is a browser extension's job, not CnL's: CnL only exists where a site chose to put a button. See docs/browser-tools.md for that extension, the bookmarklet, and the PWA share target.

The extension now covers both: the right-click entries, which have nothing to do with this protocol, and Click'n'Load interception, which is entirely this protocol. They remain separate features, and the site access belongs to the second one alone: the right-click entries and the toolbar button keep working with Click'n'Load switched off and its content scripts gone.