# gshark and the problem of secrets in repositories you do not own

> GShark is a sensitive information detection and management platform whose distinguishing choice is what it scans, repositories exposed through configured providers such as GitHub, GitLab, Sourcegraph and Postman rather than a local working copy. The deployment is a four service Docker Compose stack with a deliberately memory constrained scanner, and the first thing to do after installation is change the default credentials.

**madneal/gshark** — Scan for sensitive information easily and effectively.

- Repository: https://github.com/madneal/gshark
- Stars: 1,066 · Forks: 157
- Language: Go
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/madneal-gshark

## It scans what a provider exposes, not what you have

GShark is described as a sensitive information detection and management platform, and the sentence that distinguishes it from every other secret scanner is in the README. GShark scans repositories exposed by configured providers rather than local source trees.

That is a different job. A scanner that runs over a checkout finds secrets in code you already have, which is useful for preventing a commit. GShark goes looking at the repositories a token can reach, which means an organisation's own history, a developer's public repositories, a shared team space, or a workspace where the credentials are already published. That second category is where most of the damage comes from, and it is the category a local scanner will never touch.

The platform is a three part system. The backend is Go with Gin, and the current frontend is Vue 3 with Vite, Vue Router 4, Vuex 4 and Element Plus. The repositories also contain a build.sh, a sql.md, a template.csv and a Chinese README, so the project has a Chinese speaking user base as well as the English one.

The feature list adds up to something closer to a platform than a tool. There is multi-platform support across GitHub, GitLab, Sourcegraph, Postman and more. There is flexible rule management with custom scanning rules and whitelist and blacklist filtering. There is fine-grained access control, with configurable menu and API permissions, which is the feature that makes it usable by a team rather than by one person. There is integrated subdomain discovery through gobuster. And there is Docker deployment with a Vue 3 console for managing tasks and results.

## Two provider limits the scanner works around

The resilient scanning item in the feature list is the one worth reading closely, because it names two concrete failure modes of the upstream platforms rather than talking about robustness in general.

The first is GitHub rate limiting, and the behaviour is automatic retry after it. A scan of a large organisation is many API calls, and an unauthenticated or lightly authenticated token will hit the limit, and the scanner retries rather than failing the task.

The second is that GitLab global search is not always available, and the fallback is an automatic per-project crawl. GitLab's global search is the efficient way to search across an organisation, and when it is not offered, the only alternative is to enumerate the projects and search each one. The scanner does that without being asked.

These two behaviours matter because they describe what the tool is actually good at. A scan that silently stopped at the first rate limit would be worse than no scan, since the result would look complete. A scanner that quietly fell back to a slower path without saying so would waste a night of compute. The README's claim is that both are handled automatically.

The other way to read the same feature list is as a set of boundaries. Subdomain discovery is present, but it is a gobuster integration rather than a full discovery workflow. Access control exists at the menu and API level, which is a permission model for the console, not a roles engine for the findings. And the rule system is custom and file based, with whitelist and blacklist support, which is where the tuning effort goes.

## The backend is two commands, serve and scan

The architecture is visible in the deployment instructions, and it is a split of responsibilities rather than a single process.

The server side is started with the serve command. The README shows the sequence, copying a template configuration, editing it to set the MySQL connection, and then running the service:

```shell
cp config-temp.yaml config.yaml
# Edit config.yaml and set the MySQL connection before starting the service.
./gshark serve
```

The backend listens on port 8888, and when Nginx is in front of it the web interface is reached through the frontend port, for example 8080. If the database has not been initialised, you are redirected to the database initialisation page first, which is a sensible guard against starting a service that cannot store anything.

The scanner is a separate command:

```shell
./gshark scan
```

Splitting them is the right shape for this workload. A scan is a batch job that can run for hours, while the API and the console are interactive and need to stay responsive. Running them as separate containers, with the scan container carrying its own resource limits, is the same decision expressed in the compose file.

The configuration file is the contract between them, and the environment variable GSHARK_CONFIG points at which one to use, config.docker.yaml in the containerised setup. The same binary serves both roles, which keeps the deployment simple, but it also means the configuration has to be understood before either command is useful.

## Three services, a private subnet and a MySQL that has to be healthy first

The compose file is the clearest description of the system, and it does some unusual things that are worth understanding before deploying.

The network is defined explicitly, with a subnet of 177.7.0.0/16 and a static IP address for each service, the MySQL container at 177.7.0.13, the server at 177.7.0.12 and the web container at 177.7.0.11. A static address per service is not something compose requires, and it is there because the containers talk to each other by a known address.

The MySQL service uses the mysql/mysql-server:8.0.21 image, is configured for utf8mb4 with the unicode collation, maps host port 13306 to 3306, and has a healthcheck that runs mysqladmin ping with a two second timeout and ten retries. The server service depends on that healthcheck with a condition of service_healthy, which is the detail that makes the whole thing reliable. The web service depends on the server, and the server has to wait for the database to be genuinely ready rather than merely started.

The web service runs Nginx with the daemon off, publishes 8080, and is built from the web/ directory. The server and scan services are both built from server/, with different Dockerfiles under deploy/serve and deploy/scan.

The quick script follows the same order manually. It starts MySQL first, initialises the database, and only then starts the scanner when the with-scan flag is used. The README is explicit that if you start the scanner manually with Docker Compose you must wait until database initialisation completes, and it also gives the alternative path, sign in at http://localhost:8080, configure tokens and rules, then run docker compose up -d scan.

## The scanner is deliberately squeezed, and the results are written page by page

The resource guardrails in the scanner container are the most honest part of the deployment documentation.

The scanner container has a 512 MB memory limit, a 1 CPU limit and Go's GOMEMLIMIT set to 384MiB, with GOGC at 75 and GOMAXPROCS at 1. Scan results are persisted page by page, so a large repository search does not remain fully resident in memory. If the scanner is OOM-killed, Compose restarts it automatically. And the advice is to monitor real usage with docker stats gshark-scanner before lowering the limits further.

That combination is a considered design rather than a default. A search that returns a million results cannot be held in memory and written at the end, so the results are streamed to the database in pages, and the Go runtime is given a memory ceiling below the container limit so that the garbage collector works before the kernel runs out of memory and kills the process. Restarting on OOM is the safety net for the case where the estimate was wrong.

The consequence for an adopter is that a scan of a very large organisation will be slow, and that the tool is designed for that rather than against it. The one CPU limit means the scan is deliberately not parallelised, which trades wall clock time for a predictable memory footprint on a small machine.

It is also why the rest of the documentation is so insistent about ordering. A scanner that starts before the database exists will fail, and with the restart policy it will keep failing, and the log will be about the database rather than about the scanner.

## The default credentials are documented, so change them first

The quick start section states the default login after initialisation, if it has not been customised, and it is a pair of identical words:

```text
gshark / gshark
```

The instruction that follows is that the default password should be changed immediately after a non-local deployment. That is a small sentence with a large consequence, because the tool is designed to be pointed at an organisation's repositories and its console exposes the results of that search, and the shipped credential is in the public README.

There is a way to avoid the window in which the default exists. A custom administrator can be set through script or CLI flags, with no browser initialisation page required:

```bash
./scripts/quick-docker.sh --admin-user myadmin --admin-password 'S3cret!'
```

The same thing is available on the binary, where the init command takes the host, the database user and password and the database name alongside the new administrator account.

The other credential question is the provider tokens, which are configured in the console after you sign in. Those are the sensitive ones, because a token that can read an organisation's private repositories is a credential worth a great deal, and the console stores it. GShark is a scanning tool, and the tools that scan other people's infrastructure are only as safe as the credentials they hold.

## Apache-2.0, a patch every week or so, and a wiki instead of a manual

The licence is Apache 2.0, with the text in the LICENSE file at the root of the repository, and the project is published under the madneal organisation.

The release pattern is steady. v2.1.27 was published on 2026-10-03, v2.1.26 on 2026-09-30 and v2.1.25 on 2026-08-23, so patch releases on the 2.1 line with gaps measured in weeks. The repository is not archived, and the last push was on 2026-10-06, which is a few days after the newest tag.

The documentation strategy is worth noting, because it says something about who the project expects to use it. The README points at a wiki for usage, and at a set of articles and videos hosted on WeChat for the full introduction, with separate video links for deployment tutorials on bilibili and YouTube. The English README is a summary and a set of pointers rather than a manual, which is normal for a project whose primary audience writes and reads Chinese.

The three deployment routes reflect that audience's practical needs. Docker for the quick path, a release package for production hosts that need MySQL, Nginx, curl, jq and unzip, and a manual deployment for people who want to build it themselves with Go 1.25 or later for the backend and Node.js 20 or later for the frontend. For an adopter, the choice is between convenience and control, and the constraint in all three is the same, which is a MySQL database and a web console that somebody has to run.

## Conclusion

Adopt GShark if your job includes looking for leaked credentials in repositories you can reach but do not own, since that is a job a local secret scanner cannot do, and if you can live with a web console and a MySQL backend. Do not adopt it as a pre-commit hook for your own code, where a scanner that runs over your working copy is the better fit. Verify three things first: that the default account is changed before the service is reachable by anyone else, since the shipped credentials are documented, that the provider tokens you configure are scoped to what the scan needs, and that the scanner's memory guardrails match your largest repository, since the container is limited to 512 MB and results are persisted page by page. The last push was on 2026-10-06 and the newest release is v2.1.27 from 2026-10-03.

## FAQ

### What is GShark?

A sensitive information detection and management platform with a Go and Gin backend and a Vue 3, Vite, Vue Router 4, Vuex 4 and Element Plus frontend. It supports GitHub, GitLab, Sourcegraph, Postman and more, and the licence is Apache 2.0.

### What does GShark scan, local repositories or remote ones?

It scans repositories exposed by the configured providers rather than local source trees. That is the distinguishing design choice, and it is why the tool needs provider tokens and why it handles GitHub rate limiting and the absence of GitLab global search.

### How do I start GShark with Docker?

Clone the repository and run ./scripts/quick-docker.sh, which starts MySQL first and initialises the database. Sign in at http://localhost:8080, configure tokens and rules, then run docker compose up -d scan, or pass --with-scan so the scanner starts as part of the same command.

### What are the default GShark credentials and how do I change them?

The default login after initialisation is gshark / gshark, and the README says to change the default password immediately after a non-local deployment. A custom administrator can be set with the --admin-user and --admin-password flags on the quick script, or with the gshark init command.

### What are the resource limits on the GShark scanner container?

A 512 MB memory limit, a 1 CPU limit and GOMEMLIMIT set to 384MiB, with GOGC at 75 and GOMAXPROCS at 1. Scan results are persisted page by page, Compose restarts the scanner if it is OOM-killed, and the README suggests watching docker stats gshark-scanner before lowering the limits.

### What does a manual GShark deployment need?

Nginx, MySQL 8.0 or newer, Go 1.25 or later to build the backend, and Node.js 20 or later with npm to build the frontend. The backend listens on 8888, the interface is served through Nginx, for example on 8080, and the server is started with gshark serve and the scanner with gshark scan.

## Sources

- [Issues](https://github.com/madneal/gshark/issues)
- [License: Apache-2.0](https://github.com/madneal/gshark/blob/master/LICENSE)
- [madneal/gshark on GitHub](https://github.com/madneal/gshark)
- [README](https://github.com/madneal/gshark/blob/master/README.md)
- [Releases](https://github.com/madneal/gshark/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/madneal-gshark
