Severity Daily

IT and AI security incidents, checked against the primary source

Tag: Crawlab

  • Crawlab signs administrator JWTs with the string “crawlab,” and its last tagged release was July 2023

    Crawlab signs administrator JWTs with the string “crawlab,” and its last tagged release was July 2023

    A JWT signing secret that is the literal string crawlab sits in the constructor of Crawlab’s user service on main today, in two repositories, with no release carrying a fix and no tagged release of any kind since July 2023.

    What happened

    NVD published CVE-2026-90945 on Monday, September 14, 2026, at 6:20 p.m. UTC. The record’s vulnStatus is “Received,” meaning NVD has not yet done its own analysis. The scores come from the assigning CNA, VulnCheck, which rates it 9.8 under CVSS v3.1 with the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H and 9.3 under CVSS v4.0. The weakness is CWE-321, use of a hard-coded cryptographic key.

    The description reads: “Crawlab through 0.6.3 uses a hard-coded HMAC-SHA256 secret for JWT token signing that cannot be overridden via configuration or environment variables. Unauthenticated attackers can forge valid administrator tokens to access administrative APIs and execute code on worker nodes.”

    Crawlab is a distributed web crawler management platform written in Go. It runs a master node with a web interface and an API, and worker nodes that execute spider tasks. Administrative access to the master is, by design, the ability to schedule code on the workers.

    The code backs the description up. In crawlab-team/crawlab at core/user/service_v2.go on main, the service constructor sets jwtSecret: "crawlab" alongside jwtSigningMethod: jwt.SigningMethodHS256. Token issuance calls token.SignedString([]byte(svc.jwtSecret)). Verification goes through a getSecretFunc that returns the same bytes. The identical pattern — same default string, same signing method, same key function — is present in the separate crawlab-team/crawlab-core repository at user/service.go.

    A setter exists. SetJwtSecret(secret string) is defined immediately below the constructor in both files. VulnCheck’s position is that it is never called, which is what turns a weak default into a hard-coded key: the advisory says the secret “cannot be overridden via configuration or environment variables.” Severity Daily read both service files and found the setter defined and the constant assigned, but could not run a repository-wide search for callers — GitHub’s code search API rejects unauthenticated requests — so the never-invoked half of that claim is VulnCheck’s, reproduced here as theirs.

    The version range does not describe what people run

    “Through 0.6.3” is where this record stops being routine.

    The v0.6.3 tag points at commit 2dbc737, dated July 26, 2023. Its tree contains no core/ directory at all. The Go backend at that point lived in git submodules: .gitmodules at that tag maps backend/core to https://github.com/crawlab-team/crawlab-core, along with four more submodules for the database, filesystem, version control, and logging packages. So the file VulnCheck cites first — core/user/service_v2.go — did not exist in the tagged release the range names.

    It exists because of commit 0485310, a merge of the develop branch dated October 9, 2024, that restructured the project: roughly 8,350 lines added and 16,302 removed, collapsing the submodule layout into a single tree. That is the commit VulnCheck pinned for its second reference. Everything after it is untagged.

    Which means there is no released version of Crawlab that contains the file the advisory quotes. And there has been no tagged release since July 26, 2023 — more than three years.

    Docker is where this lands. On Docker Hub, crawlabteam/crawlab:latest was last pushed on October 9, 2024, the same day as that restructuring merge. The develop tag was pushed on December 3, 2025. The 0.6.3 tag was pushed on July 26, 2023 and has not moved since. The project’s own docker-compose.yml is the documented way to stand Crawlab up, and the image it pulls is the one built from the restructured tree.

    So the population most exposed to this is running an image whose contents match the advisory’s code citation exactly, and whose version string matches the advisory’s affected range not at all. A scanner comparing an installed version against “≤ 0.6.3” will resolve latest or develop to nothing, and report nothing.

    Why it matters

    Hard-coded signing keys are an old bug class, and the usual reason they persist is that the default is meant to be replaced and quietly never is. This one is a step worse in a specific way: the mechanism for replacing it is present in the source and, on VulnCheck’s reading, unreachable. That is the difference between a deployment mistake and a product defect, and it decides who can fix it. If the secret were configurable, every operator could rotate it this morning. If it is not, nobody can, and the only remedies left are network ones.

    The second thing worth sitting with is what a three-year tag gap does to vulnerability reporting. The whole apparatus — CVE affected ranges, CPE match strings, SBOM comparisons, scanner output — assumes software is distributed as versions. Crawlab is distributed as a Docker image that has not carried a version number since 2023. VulnCheck did the honest thing available to it and pinned commit hashes in the references. But the structured field a machine reads still says 0.6.3, and the structured field is what scanners consume.

    This site has seen the same shape twice in the last week: 17 AVideo CVEs whose affected range was a git commit hash, and a snappy-java flaw whose most recent release predated the report by more than a year. Projects that ship from main and tag rarely are now common enough in the dependency graph that “what version are you on” is frequently not a question with an answer.

    Third: Crawlab’s published security policy invites exactly the disclosure that happened. SECURITY.md, identical at the v0.6.3 tag and on main today, reads: “If you encounter a security vulnerability, please submit an issue with tag ‘security’ and highlight the reasons and impact of the security vulnerability.” Issue #1622, which VulnCheck references, is titled “[Security] Hardcoded JWT Secret Allows Arbitrary Token Forgery and Full Admin Takeover.” It is a public issue, because the project asked for public issues. There is no private reporting channel, no security advisory tab in use, and no embargo. Anyone who read the tracker knew before the CVE existed.

    That same file still lists 0.6.x and 0.5.x as supported versions. Both statements can be read as current project policy, and neither has been revised in three years.

    What to do

    There is no fixed version. Upgrading is not an available action, so the actions are all containment:

    • Take the Crawlab web interface and API off any network you do not control. The CVSS vector is AV:N/PR:N — network reachable, no credentials required. An internet-exposed master node is the whole attack. Put it behind a VPN or an authenticating reverse proxy that validates its own session before Crawlab’s JWT is ever parsed.
    • Do not rely on Crawlab’s own authentication as a boundary. Any token it will accept can be minted offline by anyone who has read the source. Treat every request reaching the master as unauthenticated.
    • Check what image you are actually running. docker inspect the container and compare the image digest against Docker Hub. If it resolves to latest or develop, your scanner’s silence on CVE-2026-90945 is not evidence of anything.
    • Assume worker nodes inherit master compromise. Scope their credentials, cloud roles, and network reach as though the master is hostile, because an attacker with a forged admin token has whatever task execution the platform normally offers.
    • Watch issue #1622 and the repository rather than the release feed. If a fix lands, it will land on main first and may never be tagged.

    Sourcing note

    Checked: the NVD record for CVE-2026-90945, which carries VulnCheck’s CVSS v3.1 and v4.0 scores, CWE-321, the affected configuration, and the five reference URLs; the VulnCheck advisory page; core/user/service_v2.go on crawlab-team/crawlab main and user/service.go on crawlab-team/crawlab-core main, both read directly, both containing jwtSecret: "crawlab"; the GitHub tag list and the v0.6.3 tag object, resolving to commit 2dbc737 dated July 26, 2023; the tree of that commit, which contains no core/ path; .gitmodules at that tag; commit 0485310 dated October 9, 2024; SECURITY.md at both the tag and main; and the Docker Hub tag list for crawlabteam/crawlab.

    Not established: whether SetJwtSecret has any caller anywhere in the codebase. GitHub’s code search API refuses unauthenticated requests, so the claim that the secret cannot be overridden is reported as VulnCheck’s, not as verified here. The opening date and current status of issue #1622 could not be read — GitHub’s issues API returned 403 to automated fetching and the rendered page did not expose the timestamp — so the disclosure lag between the public report and the CVE is unknown. No exploitation has been reported by any source, and the CVE is not in CISA’s Known Exploited Vulnerabilities catalog; the catalog was read directly from its published JSON, at version 2026.09.14, released September 14, 2026, at 7:00 p.m. UTC. Crawlab’s maintainers have published no advisory. The KEV catalog was reached through its public JSON file rather than cisa.gov, which returns 403 to automated requests.