cmseek: modules found by a magic comment, four different CMS counts, and a build that clones itself
CMS Detection and Exploitation suite - Scan WordPress, Joomla, Drupal and over 180 other CMSs
At a glance
- What is it?
- A CMS fingerprinting suite with deep scans for WordPress, Joomla and Drupal and a plug-in credential module. Useful in the narrow sense the project itself states: with written permission. Everything below is about how it is built and how its documentation drifts from its own code.
- Who is it for?
- Judgment, and the restriction comes first: this tool performs CMS user enumeration and repeated login attempts against a named target, and the project says plainly that using it on a site without prior agreement is illegal. Anything you scan needs a written scope, and the multi-site list option plus the credential module together turn a single-target reconnaissance tool into a bulk one, which is exactly the case where an engagement document and a rate limit matter.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 79 days 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 4, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Authorisation first, and the typo in the sentence that says so
The disclaimer is the right place to start with this tool, and it has a typo in the middle of its most important phrase.
It reads that usage of the software for testing or exploiting websites without prior mutual consistency can be considered an illegal activity, that it is the final user's responsibility to obey all applicable local, state and federal laws, and that the authors assume no liability and are not responsible for any misuse or damage.
The intended phrase is prior mutual consent. As written, the disclaimer is the only statement in the file about what you are supposed to have before you run it, and the word that carries the condition is misspelled.
The tool's own interface agrees with the intent rather than the wording. A scan needs a target: either you run it interactively and it asks, or you pass a URL argument. There is no mode in which it picks targets itself.
What changes the risk is the list option. You can hand it a file of sites, comma separated or one per line, for a multi-site scan. A single named target with a scope document is a different activity from a file of a thousand hosts, and the same flags cover both.
Modules are discovered by a magic comment, and the documented directory is not the real one
The credential module system is the part of this project to understand before you run anything, and it is also where the documentation is furthest behind the code.
Module discovery is comment parsing. A file is a module because it contains a comment matching the pattern for a CMS name followed by a comment that marks it as a credential module. The tool reads those comments out of the source and builds a cache of available modules, which you rebuild from the main menu with a single keypress.
So dropping a Python file into the right directory is enough for the program to treat it as an executable module. There is no manifest, no signature, no entry point declaration. The trust boundary is the directory.
The documentation for this was promised and never written. The file says a proper guide for creating modules will be made shortly, and then gives five numbered steps instead, ending with a note that the process is pretty easy once you have looked at the pre-made modules.
And step three of those five is wrong. It says to paste the module into a `brutecms` directory. The directory in the repository is `cmsbrute/`. The wordlist directory at the root is the other piece of the same subsystem.
The help output misspells its own flag and its own option name
The built-in help is the only place the flags are enumerated, and it is where you would notice two problems. The visible portion:
USAGE:
python3 cmseek.py (for guided scanning) OR
python3 cmseek.py [OPTIONS] <Target Specification>
SPECIFING TARGET:
-u URL, --url URL Target Url
-l LIST, --list LIST Path of the file containing list of sites
for multi-site scan (comma separated or one-per-line)
MANIPULATING SCAN:
-i cms, --ignore--cms cms Specify which CMS IDs to skip in order to
avoid flase positive. separated by comma ","The long form of the skip option carries two hyphens before the word cms, where every other long option in the block has one. Whatever argparse generated that, the name you would copy from this help text is not the name you would type from memory of the same text, and a flag name that differs between the printed help and a tutorial is a bad afternoon.
The description text below it misspells false positive as flase positive.
The second target option is the one worth reading properly. A file of sites, either comma separated or one per line, feeds the same pipeline as a single URL: detection, then any deep scan for that platform, and then any module you selected. Nothing in the tool distinguishes a list you were handed by a client from a list you generated by scanning a range.
Four different CMS counts, one per file
The project cannot agree on how many platforms it supports, and the disagreement is spread across the files rather than concentrated in one stale place.
The repository description says it can scan WordPress, Joomla, Drupal and over 180 other content management systems.
The packaging script carries the same description string and therefore the same figure, and the script's own comment says the description is package meta-data, so the number you would read on a package index is 180.
The documentation says 170 plus, and points at a file in the definitions directory as the authoritative list rather than printing a count itself.
The container image says 130, in a label rather than in prose.
Four files, three different numbers, and no one place where the supported set is enumerated in a form that gets reviewed. The definitions file the documentation points at is the only one of the four that could be checked, and it is a Python file holding a dictionary per platform rather than a list anyone reads.
The shape of each entry is documented too, and the documentation example does not do what it says:
cmsID = {
'name':'Name Of CMS',
'url':'Official URL of the CMS',
'vd':'Version Detection (0 for no, 1 for yes)',
'deeps':'Deep Scan (0 for no 1 for yes)'
}The two flag fields are shown holding the text that explains them rather than the zero or one they are supposed to hold. Copy that example into a definition and version detection is on, because any non-empty string is truthy.
The version lives in two places and the fallback loader is dead code
There is a file at the repository root called `current_version`, and there is a version string in the packaging script, and they are two sources of truth for the same fact.
The packaging script sets its version to 1.1.3. The root file is the runtime's own record. Nothing in either place reconciles them, and a change to one is a silent inconsistency in the other.
The script also contains a version loader that can never run. It computes a package slug from the project name, and if the version constant were empty it would open a version file inside a package directory, execute it, and take the version from there. Since the constant is set, the branch is dead. The path it names does not exist either, because the project ships a script file of that name rather than a package directory of it.
So a reader looking for the canonical version has three candidates and two of them are the wrong shape.
The dates compound it. The release history in the documentation dates version 1.1.3 to the 25th of July 2020, while the release itself was published on the 24th. The three release tags are named with a dot after the v, and the release names carry codenames. And the newest release is from July 2020, while the default branch was last pushed in July 2026.
The image clones the tool with no pinned ref, and the tool updates itself with git
The container build is nine lines and every one of them is interesting.
It starts from a Python Alpine image, adds git and pip, and clones the repository into the image:
FROM python:3-alpine
LABEL name CMSeeK
LABEL src "https://github.com/Tuhinshubhra/CMSeeK"
LABEL creato Tuhinsubhra
LABEL dockerfile_maintenance khast3x
LABEL desc "CMS Detection and Exploitation suite - Scan WordPress, Joomla, Drupal and 130 other CMSs."
RUN apk add --no-cache git py3-pip && git clone https://github.com/Tuhinshubhra/CMSeeK
WORKDIR CMSeeK
RUN pip install -r requirements.txt
ENTRYPOINT [ "python", "cmseek.py" ]The clone names no tag and no commit. Every build of that image gets whatever the default branch held at that moment, so two images built from the same Dockerfile are different software and there is no way to tell from the image which one you have.
The same repository is also the tool's runtime update channel. There is a flag that checks for an update and applies it automatically, and the file says twice that git must be installed because the tool uses git to apply it. So the code has two independent fetch paths into it, one at build time and one while you are scanning.
The labels are a small comedy of errors. The creator label is spelled with one word, and the value is the author's name misspelled differently from the one in the packaging script. The maintenance label names a different person, unquoted, with no link. Only the source and description labels are quoted, and the description is the one carrying the smallest of the four CMS counts.
Dependencies are one line: `requests`. The whole suite, from version detection to user enumeration, runs on one library, unpinned.
Results land on disk in plain text, and the issue template asks for your target
Everything a scan finds is written to the working directory. Results go into a JSON file, logs go into a directory named after the target site, and module results are written as a text file under that same directory.
That last part is worth pausing on. The output of the credential module, meaning whatever it matched, lands in a plain text file on the disk of whatever machine you ran the scan from. There is nothing about that file being encrypted, and nothing about it being excluded from a backup or a synced folder. If your working directory is inside a synced directory, those results are now in your cloud provider as well.
The documentation also promises an example of the JSON report and then does not show one, so a first-time user has the field names in the code and a screenshot in the help text instead.
The bug report guidelines are the other thing to read carefully. To open an issue you are asked for the target, an exact copy of the error or a screenshot of it, and your operating system and Python version. Issues without that information might not be answered.
Asking for the target in a public issue tracker means publishing the address you scanned, unredacted, to anyone watching the repository. That is a reasonable ask on a normal project and an unusual one on a tool whose targets are supposed to be under a scope agreement.
Editorial conclusion
Judgment, and the restriction comes first: this tool performs CMS user enumeration and repeated login attempts against a named target, and the project says plainly that using it on a site without prior agreement is illegal. Anything you scan needs a written scope, and the multi-site list option plus the credential module together turn a single-target reconnaissance tool into a bulk one, which is exactly the case where an engagement document and a rate limit matter. Nothing here is an argument against using it; a fingerprinting tool with per-target authorisation is legitimate work. Three things to know before you run it. Its extension mechanism executes any Python file dropped into one directory, because modules are discovered by matching a comment, so treat that directory as trusted code. Its build clones the repository at image build time with no pinned reference and the tool updates itself from git at runtime, so pin both if you need provenance. And the schema for adding a CMS, the version and the number of supported platforms are each recorded in more than one place and disagree.
Frequently asked questions
What is CMSeeK and what does it detect?
CMSeeK is a CMS detection and exploitation suite written in Python for Unix-like systems, licensed under the GPL v3. It identifies the platform behind a site using HTTP headers, the generator meta tag, page source, robots.txt and directory checks, and offers deeper scans for WordPress, Joomla and Drupal covering version detection, user enumeration, plugin and theme enumeration, backup file discovery and configuration leak checks. It also has a modular bruteforce system.
How do I install and run CMSeeK?
It needs Python 3 and git, which it relies on for auto-update. Clone the repository, change into the directory, install the requirements with pip or pip3, then run `python3 cmseek.py` for a guided scan or `python3 cmseek.py -u <target_url>` with options. A single dependency is required. An update check and auto-update is available from the main menu or with `python3 cmseek.py --update`.
How do I add a bruteforce module to CMSeeK?
The file containing the module must have a comment naming the CMS followed by the words Bruteforce module, matched by regex, plus a second comment marking it as a bruteforce module. Paste the file into the `cmsbrute` directory, open CMSeeK and rebuild the cache by pressing R in the main menu, and the module will appear in the bruteforce menu on the next run. The documentation notes that a fuller guide will be written and does not yet exist.
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/tuhinshubhra-cmseek)