badsecrets checks whether a cookie or token was signed with a known key
A library for detecting known secrets across many web frameworks
At a glance
- What is it?
- badsecrets is a pure Python library and command line tool that detects the use of known or very weak cryptographic secrets across web frameworks, from ASP.NET viewstate to Django and Rails session cookies. It tells you a product was signed with a key that appears in its database of weak keys.
- Who is it for?
- Application security teams that need to answer whether a captured cookie, JWT or signed URL was signed with a published default key should adopt badsecrets, since the passive module list covers a wide set of frameworks and works offline against artifacts you already hold. It will not tell you whether a key you invented is weak, and its active modules send forged artefacts to a live target, so scope them to systems you are authorised to test.
- Can I use it commercially?
- Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A repository of known keys wrapped in a language-agnostic abstraction
badsecrets answers a narrow question: was this cryptographic product signed with a secret that is publicly known?
The typical case is a cookie, a token or a signed URL that has already crossed the wire and landed in a log, a proxy record or a ticket. To check it, you need the secret that produced it, and the way to test a guess is to attempt decryption or to verify the signature. The difficulty is that every platform does this differently, with its own key derivation, its own encoding rules and its own implementation quirks. The README describes this as the problem the library aims to alleviate.
The design has two halves. The first is a repository of known secrets, the kind that turn up in tutorial code, in sample applications and in shipped defaults. The second is a language-agnostic abstraction layer for identifying their use, so the caller does not have to know which framework produced the artefact.
The project describes itself as a pure Python library, and it is inspired by Blacklist3r with the stated aim of expanding the supported platforms and removing language and operating system dependencies. That inheritance shows in the module list, which follows the original tool's habit of pairing a framework name with the specific artefact it checks.
Passive modules decrypt and verify what you already hold
Passive modules are the bulk of the library, and the README describes them precisely: they analyze cryptographic products such as cookies, tokens and signed URLs that you already have, and they work offline by attempting to decrypt or verify the product against a database of known secrets.
The list is long enough to be useful in practice. There is ASPNET_Viewstate, which checks the viewstate generator against a list of known machine keys, and ASPNET_Resource, which checks encrypted WebResource.axd and ScriptResource.axd URLs when __VIEWSTATE is not present on the page. There are cookie modules for Flask, Django in signed_cookie mode, Rails across multiple major releases, Rack 2.x, Yii2, Laravel, express-session, cookie-session, and NextAuth.js or Auth.js JWE session cookies against a known or weak NEXTAUTH_SECRET or AUTH_SECRET.
Others target framework specific products rather than session cookies. Generic_JWT checks JWTs for known HMAC secrets or RSA private keys. Symfony_SignedURL works on symfony _fragment URLs including the hash. Shiro_RememberMe checks Apache Shiro rememberMe cookies against known AES encryption keys, and LTPA_Token handles IBM WebSphere LtpaToken and LtpaToken2 cookies against known LTPA encryption keys. Telerik modules look for a known Telerik.Upload.ConfigurationHashKey and a known Telerik.Web.UI.DialogParametersEncryptionKey in patched 2017 and later versions of Telerik UI.
One module is worth calling out because it checks a different kind of object entirely: ASPNET_Compressedviewstate looks for a once popular custom compressed viewstate snippet that was vulnerable to remote code execution. The finding there is about the format, not about the key.
Active modules forge artefacts and let the target answer
Active modules go past detection. The README describes a two stage mechanism: YARA-based prefiltering fingerprints a live target using HTTP response headers, cookies and body content, and then the module forges cryptographic products with known keys and sends them to the target to confirm whether the key is accepted.
The point is a gap that passive checking cannot close. If you hold a token you can test it. If you have no token but suspect the target runs on a default key, active modules let the target tell you. The active module list covers Apache Shiro rememberMe cookies forged with known AES keys, tested for deserialization remote code execution, PAN-OS GlobalProtect portals tested for the default master encryption key, and IBM WebSphere LtpaToken2 cookies forged with known LTPA keys and tested for whether the target grants authentication.
The trade-off is that active modules touch a live system and forge artefacts that can trigger deserialisation paths. They are for targets you are authorised to test, and the README says so plainly about the Shiro case in the module description. It also gives you a switch. Active modules are enabled by default in URL mode, and passing --passive-only disables them.
If you intend to use this in a pipeline that scans systems you do not own, the passive path is the one to keep on.
Installing with pip and pointing the CLI at a token
The package is on PyPI, so installation is a single pip command, and the README gives the same command as the way to use the tool:
pip install badsecrets
badsecrets eyJhbGciOiJIUzI1NiJ9.eyJJc3N1ZXIiOiJJc3N1ZXIiLCJVc2VybmFtZSI6IkJhZFNlY3JldHMiLCJleHAiOjE1OTMxMzM0ODMsImlhdCI6MTQ2NjkwMzA4M30.ovqRikAo_0kKJ0GVrAwQlezymxrLGjcEiW_s3UJMMCoThe second line is the example token from the README, and passing a value on the command line is how the tool selects the modules to try. The CLI entry point is declared in the package metadata:
badsecrets = "badsecrets.examples.cli:main"Two more entry points are registered alongside it, telerik-knownkey and symfony-knownkey, both pointing at their own example modules.
If you would rather not install anything, the README shows the clone and run path, which uses the same example CLI directly from the repository:
git clone https://github.com/blacklanternsecurity/badsecrets.git
cd badsecrets
python ./badsecrets/examples/cli.py eyJhbGciOiJIUzI1NiJ9.eyJJc3N1ZXIiOiJJc3N1ZXIiLCJVc2VybmFtZSI6IkJhZFNlY3JldHMiLCJleHAiOjE1OTMxMzM0ODMsImlhdCI6MTQ2NjkwMzA4M30.ovqRikAo_0kKJ0GVrAwQlezymxrLGjcEiW_s3UJMMCoThe examples directory in the cloned repository assumes you are in that directory when you run the commands in the examples section. If you use the Badsecrets BBOT module instead, the README notes that there is nothing else to do, because BBOT installs the package for you.
The container image wraps the same entrypoint
A Dockerfile sits at the root of the repository and does almost nothing, which is a good sign. It starts from the python:alpine3.22 base image, installs git and build-base with apk, creates a dedicated unprivileged user, copies the project in, and installs it with pip.
The resulting image runs the same command you would type locally, so the container and the pip install path behave identically:
ENTRYPOINT ["badsecrets"]
CMD ["-h"]The default command prints the help text, so a container run with no arguments gives you usage rather than a scan. Passing a token or a URL to the container replaces that default.
Because the image runs as a non-root user and carries only the project plus its Python dependencies, it drops into an existing scanning pipeline without granting shell access to the host. The one thing to plan for is the dependency set itself, which pulls in more than a pure text parser would. The project requires Python 3.10 or later and depends on pycryptodome, flask-unsign, Django 5.2 or later, pyjwt with crypto extras, blasthttp, colorama and yara-python. Django in particular is a large dependency to carry for a tool that only needs to check a signed session cookie.
What badsecrets cannot tell you
The scope is narrow in a way that decides whether the tool fits your problem.
badsecrets answers whether a secret is known. It does not answer whether a secret is weak. If a team generates a random key and stores it in a public repository, no module in this library will flag it, because the detection works by matching against a database of published keys. The README frames the library as identifying known or very weak secrets, but the mechanism is a lookup against published keys, not an entropy measurement.
It also needs an artefact. Passive modules analyse something you already have, so a clean result only tells you about that one cookie or token. A target with default keys on one endpoint and strong keys on another will look clean or dirty depending on what you feed in.
The active path partly fixes the second problem but introduces its own limits. It depends on YARA-based fingerprinting of response headers, cookies and body content, so a target that hides its identifying headers may never reach the stage where a forged key is tested. The README does not document how to tune or extend the fingerprint rules.
Framework coverage is uneven too, and the module list is the honest measure of it. A framework with no module simply cannot be checked, and adding a framework means writing a module rather than configuring one.
Licence and release history worth reading before you vendor it
The repository records its licence as AGPL-3.0, the package metadata declares GPL-3.0, and the README badge refers to GPLv3. Those are close relatives, but they are not the same licence, and the difference matters if you intend to run badsecrets as a network service or to build it into a product you distribute. Read the LICENSE file rather than the badge.
The version numbering tells you the project is moving quickly and has changed shape. The releases listed are v0.13.47 on 2025-09-25, 1.0.170 on 2026-03-20, and 1.2.0 on 2026-06-19. A jump from a 0.13 series to a 1.0 line and then to 1.2 within a year is a sign of API churn, so pin an exact version in a deployment and read the changelog before upgrading.
The repository is not archived and the last push was on 2026-10-19, so the code is still receiving work. Maintenance of the underlying knowledge is the other half of the risk here. A database of known keys is only as good as its update process, and the README does not describe a schedule for adding newly disclosed keys.
For teams that want this inside a broader scanning setup, the BBOT module is the integration point the README points to, and BBOT handles installing the package for you.
Editorial conclusion
Application security teams that need to answer whether a captured cookie, JWT or signed URL was signed with a published default key should adopt badsecrets, since the passive module list covers a wide set of frameworks and works offline against artifacts you already hold. It will not tell you whether a key you invented is weak, and its active modules send forged artefacts to a live target, so scope them to systems you are authorised to test. Before adopting, check that a module exists for the framework you care about, pin the release you test against because the version numbering jumped from 0.13 to 1.0 to 1.2, and read LICENSE since the repository records AGPL-3.0 while the package metadata declares GPL-3.0.
Frequently asked questions
How do I install badsecrets and scan a token with it?
Install the PyPI package with pip install badsecrets, then run the badsecrets command with the token or cookie you want to check. The CLI entry point is badsecrets.examples.cli:main.
What is the difference between passive and active modules in badsecrets?
Passive modules work offline against cryptographic products you already have, attempting to decrypt or verify them against a database of known secrets. Active modules fingerprint a live target with YARA-based prefiltering and then forge products with known keys to confirm whether the target accepts them; they can be turned off with --passive-only.
Which frameworks does badsecrets support?
The passive module list covers ASP.NET viewstate and resource URLs, Telerik UI hash and encryption keys, Flask, Django, Rails, Rack, Yii2, Laravel, express-session, cookie-session, NextAuth.js and Auth.js cookies, Shiro rememberMe cookies, WebSphere LTPA tokens, Symfony signed URLs and generic JWTs. The active modules cover Shiro rememberMe, PAN-OS GlobalProtect and WebSphere LTPA tokens.
Does badsecrets detect secrets that are weak but not publicly known?
The library works by attempting to decrypt or verify a product against a database of known secrets, so it identifies published or known keys. It does not measure the strength of a key your team invented, so a weak key that has never been published is outside what the modules can report.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/blacklanternsecurity-badsecrets)Was this useful?