diff --git a/CHANGELOG.txt b/CHANGELOG.txt
@@ -1,5 +1,6 @@
## v0.0.7
+- feat(scan): gitleaks secret scanning of crawled responses ~ @djnnvx
- feat(osint): ASN/prefix lookup over DNS (cymru, shadowserver fallback), flags provider-owned ranges ~ @djnnvx
- feat(scan): cdncheck annotates resolved IPs, skips port-scanning WAF edges ~ @djnnvx
- fix(osint): shodan dedupes by resolved IP instead of querying once per hostname ~ @djnnvx
diff --git a/README.md b/README.md
@@ -33,233 +33,115 @@
Based on yelaa: https://github.com/matro7sh/Yelaa.
-# 1. Disclaimer
+## Disclaimer
This software is provided as-is, at the discretion of professionals.
Developpers assume no responsability for your lack of morals or overall stupidity.
Please use at your own risk, in a controlled environnement. Thanks<3
-
-# 2. Roadmap
-
-Contributions would be appreciated for these ones:
-
-* osint: add support for more dorks
-
-## 2.a Bugfixes
-
-* sitemap should be stored to a file
-
-## 2.b Integrations
-
-Every tool is a `core.Module` (see `core/module.go`), so adding one is just
-implementing the interface and appending it to a pipeline. yellow leans on the
-projectdiscovery ecosystem and a few other golang libraries, wired in as
-libraries (not shelled out).
-
-Still on the radar, roughly by area:
-
-
-other:
-
-* `subjack` / `subzy` subdomain takeover detection on enumerated subdomains
-
-feel free to suggest more ideas. :)~
-if you'd like to do so, reach me by mail or on social media: https://djnn.sh/pgp
-
-## 2.b Contributing
-
-This software's code is public, but not open to contributions.
-The reason for that is that if something is integrated, I want to make sure I
-am able to maintain it afterwards.
-
-# 3. Installing
+## Installing
Using go v1.26.
```bash
git clone https://evil.djnn.sh/yellow.git
-
cd yellow/
-
-make
+make # or: make docker
```
-## 3.a Using docker
-
-```bash
-cd yellow/
-
-make docker
-```
+The docker image ships chromium. On the host, katana and httpx download their own
+(~150MB) on first run if no chrome is installed.
-# 4. Running
+## Running
-## 4.a Create your directory tree
+Every tool is a `core.Module` (see `core/module.go`), wired in as a library rather
+than shelled out. Run any subcommand with `--help` for its flags.
-At the beginning of your mission, you might want to have a nice little dir tree.
-Easy enough:
+### Directory tree
```bash
./yellow -d djnn.sh
-
-# example tree output for djnn.sh/
-djnn.sh
-├── extracted
-│ ├── assets
-│ ├── code
-│ └── creds
-├── scans
-│ ├── infra
-│ ├── nessus
-│ ├── screenshots
-│ └── ssl
-└── www
- ├── exploits
- └── tools
-
-13 directories, 0 files
```
-## 4.b Run passive enumeration
+Creates `extracted/{assets,code,creds}`, `scans/{infra,nessus,screenshots,ssl}` and
+`www/{exploits,tools}` under `djnn.sh/`.
-Run various scans to retrieve more targets, using OSINT techniques.
+### Passive enumeration
```bash
-./yellow osint --help
-
-# or, if in a hurry
./yellow osint -d djnn.sh
```
-osint also runs `gau` to pull historical URLs (Wayback, CommonCrawl, OTX, URLScan).
-
-osint also runs `alterx`: it generates permutations of the discovered subdomains, resolves
-them (time-bounded), and merges the live ones into `domains.txt` (and `alterx.txt`).
-
-osint also maps every discovered IP to its ASN and BGP prefix (`asn.json`), over DNS via
-Team Cymru with Shadowserver as a fallback. No API key needed.
+subfinder, assetfinder, dnsx, gau, alterx, dorks, leaker, shodan. Discovered assets
+land in `scans/domains.txt`. Each IP is also mapped to its ASN and BGP prefix
+(`asn.json`) over DNS via Team Cymru, with Shadowserver as a fallback.
-This output is **informational only and is never fed back into scanning**. For a
-cloud-hosted target the prefix belongs to the provider, not the client, and can span
-millions of unrelated addresses. Those ranges are flagged `hosting_provider: true` so it is
-obvious they sit outside client scope. ASN data pays off when the target owns its ASN;
-otherwise treat it as a scope boundary, not a target list.
+That ASN data is informational and never feeds back into scanning. For a cloud-hosted
+target the prefix belongs to the provider and can span millions of unrelated addresses,
+so those are flagged `hosting_provider: true`. Treat it as a scope boundary, not a
+target list.
-For very large scopes, use Team Cymru's bulk netcat interface rather than hammering the
-DNS one.
+### Prune dead hosts
-## 4.c Run active scans
-
-Run scans against the target actively. (You might want to use a proxy for this !)
+osint returns historical domains, plenty of which are gone. Filter to the live ones:
```bash
-./yellow scan --help
-
-# run scan on ports 80, 443, 8080 & 8443
-nmap -T4 -Pn -p 80,443,8080,8443 --open -oA domains -iL djnn.sh/scans/domains.txt
-cat *.gnmap | grep -i "open/tcp" | cut -d " " -f2 | sort -u > djnn.sh/scans/web-targets.txt
-
-# you can also just run the domains.txt file directly
-
-./yellow scan -d djnn.sh/scans/infra --file djnn.sh/scans/web-targets.txt
-```
-
-By default the scan runs sitemap, robots.txt, a katana headless crawl, tlsx, wappalyzergo,
-cvemap and httpx. Katana drives a real browser, so it also picks up endpoints that only
-appear once JavaScript has run. Its URLs are fed into gobuster and nuclei when those are
-enabled.
-
-```bash
-# skip the crawl (it is the slowest default step)
-./yellow scan -d djnn.sh --no-katana
-
-# tune the crawl
-./yellow scan -d djnn.sh --katana-depth 4 --katana-duration 10m --katana-max-urls 1000
+./yellow prune -f djnn.sh/scans/domains.txt -o djnn.sh/scans/web-targets.txt
```
-The heavier steps are opt-in:
+### Active scans
```bash
-# add directory bruteforce, rooted at every directory katana found
-./yellow scan -d djnn.sh --gobuster
-
-# add nuclei template scanning (downloads ~hundreds of MB of templates on first run)
-./yellow scan -d djnn.sh --nuclei
+./yellow scan -d djnn.sh/scans/infra --file djnn.sh/scans/web-targets.txt
```
-Katana needs a Chrome or Chromium. It uses an installed one if present, otherwise it
-downloads its own (~150MB) on first run. The docker image ships chromium, so nothing is
-downloaded there.
-
-#### Running port scans:
-
-You can run a TCP port scan with service fingerprinting as part of the scan command:
+Default pipeline: sitemap, robots.txt, katana crawl, secret scan, tlsx, wappalyzergo,
+cvemap, httpx. Opt in to more:
```bash
-./yellow scan -d djnn.sh --port-scan
-# or with custom ports
-./yellow scan -d djnn.sh --port-scan --ports "22,80,443,8080-8090"
+./yellow scan -d djnn.sh --port-scan # TCP + service fingerprinting
+./yellow scan -d djnn.sh --gobuster # dir bruteforce, rooted at crawled dirs
+./yellow scan -d djnn.sh --nuclei # downloads ~hundreds of MB of templates
```
-Every resolved IP is checked against `cdncheck` first. Hosts sitting behind a WAF
-(cloudflare, incapsula) are annotated in `portscan.json` and **not** port-scanned, since
-you would only be scanning the reverse proxy. CDN and cloud ranges are annotated but still
-scanned: cdncheck files ordinary GCE compute under `cdn/google`, so skipping on that would
-drop real origins. The same WAF check skips pointless shodan lookups.
+Disable the slow defaults with `--no-katana` and `--no-secrets`.
-#### Filter inactive web domains from a list of domains:
+Katana drives a real browser, so it picks up endpoints that only appear once JavaScript
+has run. Its URLs feed the secret scan, gobuster and nuclei, bounded by
+`--katana-max-urls`.
-The `osint` subcommand is nice, but as it retrieves historical domains, it means there are
-some domains that are not reachable anymore. To filter them out, you can run:
-
-```bash
-./yellow prune -f djnn.sh/scans/domains.txt -o djnn.sh/scans/cleaned-web-targets.txt
-```
+The secret scan re-fetches each crawled URL and runs the `gitleaks` rule set over it,
+writing `secrets.json`. **That file holds raw secret values.** gitleaks is tuned for
+source repos, so minified JS throws high-entropy false positives: treat findings as
+leads. Rule ID and entropy are recorded so noisy rules are easy to spot.
-#### Retrieving CVEs automatically:
+Port scanning checks every resolved IP against `cdncheck` first. Hosts behind a WAF are
+annotated in `portscan.json` and skipped, since you would only be scanning the reverse
+proxy. CDN and cloud ranges are annotated but still scanned, because cdncheck files
+ordinary cloud compute under `cdn`, and skipping that would drop real origins.
-CVE lookups use the [NVD API v2](https://nvd.nist.gov/developers/vulnerabilities) (NIST National
-Vulnerability Database), no account required. Results are queried by detected technology name and
-saved to `cves.json` in your scan path.
+### Fingerprint only
-Without an API key, NVD allows 5 requests per 30 seconds (yellow sleeps 7s between queries to stay
-safe). For faster scans, grab a free key at https://nvd.nist.gov/developers/request-an-api-key
-and set it:
+Wappalyzer plus CVE lookup, without the rest of the scan:
```bash
-export NVD_API_KEY=your-key-here
-```
-
-#### Credential Leak Checking (Leaker)
-
-The `osint` subcommand can check for credential leaks using the integrated
-[leaker](https://github.com/vflame6/leaker) library.
-
-Some leaker sources (like LeakCheck) require API keys. Create a provider config file:
-
-```yaml
-# ~/.config/leaker/provider-config.yml
-leakcheck: [your-api-key-here]
+./yellow fingerprint -d djnn.sh/scans/infra --file djnn.sh/scans/web-targets.txt
```
-Set the config path via environment variable:
-
-```bash
-export LEAKER_PROVIDER_CONFIG=~/.config/leaker/provider-config.yml
-```
+## Environment
-Usage:
+All optional. Modules self-skip when their key is absent.
-```bash
-./yellow osint -d target.com --emails /path/to/emails.txt
-```
+| variable | unlocks |
+|---|---|
+| `NVD_API_KEY` | faster CVE lookups ([free key](https://nvd.nist.gov/developers/request-an-api-key)); without one NVD allows 5 requests per 30s and yellow sleeps 7s between queries |
+| `SHODAN_API_KEY` | the shodan module |
+| `LEAKER_PROVIDER_CONFIG` | path to a leaker provider config, e.g. `~/.config/leaker/provider-config.yml` containing `leakcheck: [your-key]` |
+| `HTTP_PROXY` | set for you by `-p/--proxy` |
-#### Running fingerprinting
+## Contributing
-If you don't want to scan the whole website, but just run the fingerprint and retrieve the CVEs,
-you can also run this:
+This software's code is public, but not open to contributions. If something is
+integrated, I want to be sure I can maintain it afterwards.
-```bash
-./yellow fingerprint -d djnn.sh/scans/infra --file djnn.sh/scans/web-targets.txt
-```
+Ideas are welcome though: https://djnn.sh/pgp
diff --git a/cli/parser.go b/cli/parser.go
@@ -95,7 +95,7 @@ func GetParser(opts *StandardOptions) *cobra.Command {
var scanCmd = &cobra.Command{
Use: "scan",
Short: "Run active scanning tools to perform enumeration",
- Long: "Runs active scanning (sitemap, robots.txt, katana crawl, tlsx, wappalyzergo, cvemap, httpx, optional gobuster, optional TCP port-scan and optional nuclei)",
+ Long: "Runs active scanning (sitemap, robots.txt, katana crawl, tlsx, wappalyzergo, cvemap, httpx, gitleaks secret scan, optional gobuster, optional TCP port-scan and optional nuclei)",
Args: cobra.MinimumNArgs(0),
Run: func(cmd *cobra.Command, args []string) {
@@ -128,6 +128,7 @@ func GetParser(opts *StandardOptions) *cobra.Command {
KatanaDepth: opts.KatanaDepth,
KatanaMaxTime: opts.KatanaMaxTime,
KatanaMaxURLs: opts.KatanaMaxURLs,
+ NoSecrets: opts.NoSecrets,
}
if opts.TargetFilePath == "" {
@@ -248,6 +249,7 @@ func GetParser(opts *StandardOptions) *cobra.Command {
scanCmd.Flags().IntVarP(&opts.KatanaDepth, "katana-depth", "", defaults.KatanaDepth, "Katana crawl depth")
scanCmd.Flags().DurationVarP(&opts.KatanaMaxTime, "katana-duration", "", defaults.KatanaMaxTime, "Katana crawl time budget per target")
scanCmd.Flags().IntVarP(&opts.KatanaMaxURLs, "katana-max-urls", "", defaults.KatanaMaxURLs, "Cap on crawled URLs fed into nuclei/gobuster (0 = unlimited)")
+ scanCmd.Flags().BoolVarP(&opts.NoSecrets, "no-secrets", "", defaults.NoSecrets, "Disable gitleaks secret scanning of crawled responses (runs by default)")
rootCmd.AddCommand(pruneCmd)
pruneCmd.Flags().StringVarP(&opts.Proxy, "proxy", "p", defaults.Proxy, "Proxy URL (used for the tools supporting it. Other will prompt a warning msg)")
diff --git a/cli/types.go b/cli/types.go
@@ -51,6 +51,9 @@ type StandardOptions struct {
// Cap on crawled URLs fed into nuclei/gobuster
KatanaMaxURLs int
+ // Disable gitleaks secret scanning of crawled responses (runs by default)
+ NoSecrets bool
+
// File containing list of emails for credential leak checking
EmailsFilePath string
}
diff --git a/go.mod b/go.mod
@@ -24,6 +24,7 @@ require (
github.com/spiral-sec/assetfinder v0.2.0
github.com/valyala/fasthttp v1.73.0
github.com/vflame6/leaker v1.6.9
+ github.com/zricethezav/gitleaks/v8 v8.30.1
)
require (
@@ -40,8 +41,11 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect
github.com/Azure/go-ntlmssp v0.1.1 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
+ github.com/BobuSumisu/aho-corasick v1.0.3 // indirect
github.com/FalconOpsLLC/goexec v0.3.0 // indirect
+ github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.5.0 // indirect
+ github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 // indirect
github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect
@@ -142,16 +146,19 @@ require (
github.com/emirpasic/gods v1.18.1 // indirect
github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 // indirect
github.com/fatih/color v1.19.0 // indirect
+ github.com/fatih/semgroup v1.2.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/felixge/fgprof v0.9.5 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b // indirect
+ github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gaissmai/bart v0.28.0 // indirect
github.com/geoffgarside/ber v1.2.0 // indirect
github.com/getkin/kin-openapi v0.132.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
+ github.com/gitleaks/go-gitdiff v0.9.1 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-faker/faker/v4 v4.9.0 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect
@@ -205,8 +212,10 @@ require (
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.9.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
+ github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf // indirect
github.com/hdm/jarm-go v0.0.8 // indirect
+ github.com/huandu/xstrings v1.5.0 // indirect
github.com/iangcarroll/cookiemonster v1.6.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
@@ -247,6 +256,7 @@ require (
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect
github.com/lukasbob/srcset v0.0.0-20190730101422-86b742e617f3 // indirect
github.com/mackerelio/go-osstat v0.2.6 // indirect
+ github.com/magiconair/properties v1.8.9 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.22 // indirect
@@ -262,8 +272,10 @@ require (
github.com/mikelolasagasti/xz v1.0.1 // indirect
github.com/minio/minlz v1.1.0 // indirect
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect
+ github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/moby/api v1.54.2 // indirect
github.com/moby/moby/client v0.4.1 // indirect
@@ -289,7 +301,7 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/openrdap/rdap v0.9.1 // indirect
- github.com/pelletier/go-toml/v2 v2.0.8 // indirect
+ github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
github.com/pjbgf/sha1cd v0.6.0 // indirect
@@ -342,6 +354,8 @@ require (
github.com/rs/xid v1.6.0 // indirect
github.com/rs/zerolog v1.34.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
+ github.com/sagikazarmark/locafero v0.7.0 // indirect
+ github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/sashabaranov/go-openai v1.41.2 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect
@@ -349,18 +363,23 @@ require (
github.com/sergi/go-diff v1.4.0 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/shirou/gopsutil/v4 v4.26.3 // indirect
+ github.com/shopspring/decimal v1.4.0 // indirect
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect
github.com/sijms/go-ora/v2 v2.9.0 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/sorairolake/lzip-go v0.3.8 // indirect
+ github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
+ github.com/spf13/viper v1.19.0 // indirect
github.com/stoewer/go-strcase v1.3.1 // indirect
+ github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/temoto/robotstxt v1.1.2 // indirect
+ github.com/tetratelabs/wazero v1.9.0 // indirect
github.com/tidwall/btree v1.8.1 // indirect
github.com/tidwall/buntdb v1.3.2 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
@@ -387,6 +406,8 @@ require (
github.com/vmihailenco/tagparser v0.1.2 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/vulncheck-oss/go-exploit v1.58.0 // indirect
+ github.com/wasilibs/go-re2 v1.10.0 // indirect
+ github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect
github.com/weppos/publicsuffix-go v0.50.3 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
diff --git a/go.sum b/go.sum
@@ -71,14 +71,20 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
+github.com/BobuSumisu/aho-corasick v1.0.3 h1:uuf+JHwU9CHP2Vx+wAy6jcksJThhJS9ehR8a+4nPE9g=
+github.com/BobuSumisu/aho-corasick v1.0.3/go.mod h1:hm4jLcvZKI2vRF2WDU1N4p/jpWtpOzp3nLmi9AzX/XE=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/FalconOpsLLC/goexec v0.3.0 h1:ryLMkrGT6asnkqdc5rFMNOSTYdMH/iCfyEuwu0D6ZhA=
github.com/FalconOpsLLC/goexec v0.3.0/go.mod h1:kiyxVbmFCGbbwXRyZmOSKlOy7PiK+JH2gq07Ztag/k8=
+github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
+github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
+github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
+github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
@@ -360,6 +366,8 @@ github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731 h1:R/ZjJpjQK
github.com/ericlagergren/decimal v0.0.0-20240411145413-00de7ca16731/go.mod h1:M9R1FoZ3y//hwwnJtO51ypFGwm8ZfpxPT/ZLtO1mcgQ=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
+github.com/fatih/semgroup v1.2.0 h1:h/OLXwEM+3NNyAdZEpMiH1OzfplU09i2qXPVThGZvyg=
+github.com/fatih/semgroup v1.2.0/go.mod h1:1KAD4iIYfXjE4U13B48VM4z9QUwV5Tt8O4rS879kgm8=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
@@ -373,8 +381,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b h1:XMw3j+4AEXLeL/uyiZ7/qYE1X7Ul05RTwWBhzxCLi+0=
github.com/free5gc/util v1.0.5-0.20230511064842-2e120956883b/go.mod h1:l2Jrml4vojDomW5jdDJhIS60KdbrE9uPYhyAq/7OnF4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
-github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
+github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gaissmai/bart v0.28.0 h1:89yZLo8NmyqD0RYgJ3QO9HhqqGGw+oWhf90cZm69Lko=
@@ -387,6 +395,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
+github.com/gitleaks/go-gitdiff v0.9.1 h1:ni6z6/3i9ODT685OLCTf+s/ERlWUNWQF4x1pvoNICw0=
+github.com/gitleaks/go-gitdiff v0.9.1/go.mod h1:pKz0X4YzCKZs30BL+weqBIG7mx0jl4tF1uXV9ZyNvrA=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
@@ -605,6 +615,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf h1:umfGUaWdFP2s6457fz1+xXYIWDxdGc7HdkLS9aJ1skk=
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf/go.mod h1:V99KdStnMHZsvVOwIvhfcUzYgYkRZeQWUtumtL+SKxA=
github.com/hdm/jarm-go v0.0.7/go.mod h1:kinGoS0+Sdn1Rr54OtanET5E5n7AlD6T6CrJAKDjJSQ=
@@ -613,6 +625,8 @@ github.com/hdm/jarm-go v0.0.8/go.mod h1:memdt/YMMW3811nvANDJsrtc8Fy2e1685aDWdI7G
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
+github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/iangcarroll/cookiemonster v1.6.0 h1:NPFkn/ZZYZgzXhJ1awRnYhZ3fJK3hKWgbctfTW21kew=
github.com/iangcarroll/cookiemonster v1.6.0/go.mod h1:n3MvoAq56NkNyCEyhcYs3ZJMzTc9rL3w7IaITI0apMg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -730,6 +744,8 @@ github.com/lukasbob/srcset v0.0.0-20190730101422-86b742e617f3 h1:l1rIRmxNhzeQM+q
github.com/lukasbob/srcset v0.0.0-20190730101422-86b742e617f3/go.mod h1:j16TYl5p17+vBMyaL6Nu4ojlOnfX8lc2k2cfmw6m5TQ=
github.com/mackerelio/go-osstat v0.2.6 h1:gs4U8BZeS1tjrL08tt5VUliVvSWP26Ai2Ob8Lr7f2i0=
github.com/mackerelio/go-osstat v0.2.6/go.mod h1:lRy8V9ZuHpuRVZh+vyTkODeDPl3/d5MgXHtLSaqG8bA=
+github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
+github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
@@ -769,10 +785,14 @@ github.com/minio/minlz v1.1.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wa
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 h1:yRZGarbxsRytL6EGgbqK2mCY+Lk5MWKQYKJT2gEglhc=
github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
+github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
+github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
+github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
@@ -849,8 +869,8 @@ github.com/openrdap/rdap v0.9.1/go.mod h1:vKSiotbsENrjM/vaHXLddXbW8iQkBfa+ldEuYE
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw=
github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE=
-github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
-github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
+github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
+github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
@@ -1015,6 +1035,10 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
+github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
+github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/sashabaranov/go-openai v1.41.2 h1:vfPRBZNMpnqu8ELsclWcAvF19lDNgh1t6TVfFFOPiSM=
@@ -1031,6 +1055,8 @@ github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKl
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc=
github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
+github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
+github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0=
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE=
github.com/sijms/go-ora/v2 v2.9.0 h1:+iQbUeTeCOFMb5BsOMgUhV8KWyrv9yjKpcK4x7+MFrg=
@@ -1053,6 +1079,8 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -1065,6 +1093,8 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
+github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/spiral-sec/assetfinder v0.2.0 h1:Z49ze7/sVZ6ZjuCRDqFpQt3T78VGW3P+SPzC57/HnAI=
github.com/spiral-sec/assetfinder v0.2.0/go.mod h1:Btbw+b0afgtwSL+YcFXdyrq9cHm46v3bimm4sJyHi/M=
github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs=
@@ -1085,17 +1115,20 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9 h1:GXIyLuIJ5Qk46lI8WJ83qHBZKUI3zhmMmuoY9HICUIQ=
github.com/tarunKoyalwar/goleak v0.0.0-20240429141123-0efa90dbdcf9/go.mod h1:uQdBQGrE1fZ2EyOs0pLcCDd1bBV4rSThieuIIGhXZ50=
github.com/temoto/robotstxt v1.1.2 h1:W2pOjSJ6SWvldyEuiFXNxz3xZ8aiWX5LbfDiOFd7Fxg=
github.com/temoto/robotstxt v1.1.2/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
+github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
+github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA=
@@ -1161,6 +1194,10 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/vulncheck-oss/go-exploit v1.58.0 h1:bl573fFbcxPs/FBc3MpIUd9uNIa1x0W94/mttYfr/WQ=
github.com/vulncheck-oss/go-exploit v1.58.0/go.mod h1:IC3YcPlt2RZp7untK29Gnf3gnYOjjhDzlSm2e1+VCLQ=
+github.com/wasilibs/go-re2 v1.10.0 h1:vQZEBYZOCA9jdBMmrO4+CvqyCj0x4OomXTJ4a5/urQ0=
+github.com/wasilibs/go-re2 v1.10.0/go.mod h1:k+5XqO2bCJS+QpGOnqugyfwC04nw0jaglmjrrkG8U6o=
+github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4=
+github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52/go.mod h1:jMeV4Vpbi8osrE/pKUxRZkVaA0EX7NZN0A9/oRzgpgY=
github.com/weppos/publicsuffix-go v0.12.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/weppos/publicsuffix-go v0.30.0/go.mod h1:kBi8zwYnR0zrbm8RcuN1o9Fzgpnnn+btVN8uWPMyXAY=
@@ -1246,6 +1283,8 @@ github.com/zmap/zflags v1.4.0-beta.1.0.20200204220219-9d95409821b6/go.mod h1:HXD
github.com/zmap/zgrab2 v0.1.8 h1:PFnXrIBcGjYFec1JNbxMKQuSXXzS+SbqE89luuF4ORY=
github.com/zmap/zgrab2 v0.1.8/go.mod h1:5d8HSmUwvllx4q1qG50v/KXphkg45ZzWdaQtgTFnegE=
github.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8=
+github.com/zricethezav/gitleaks/v8 v8.30.1 h1:PmEvCfVI7ti9dV3s5aMZUY7sS2GxRvG3yzih7E+cS3w=
+github.com/zricethezav/gitleaks/v8 v8.30.1/go.mod h1:rTDwxRjufMKAkhTI/Mijd07nday1yOhf9qywjwz5Irw=
gitlab.com/gitlab-org/api/client-go v1.9.1 h1:tZm+URa36sVy8UCEHQyGGJ8COngV4YqMHpM6k9O5tK8=
gitlab.com/gitlab-org/api/client-go v1.9.1/go.mod h1:71yTJk1lnHCWcZLvM5kPAXzeJ2fn5GjaoV8gTOPd4ME=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
diff --git a/scan/run.go b/scan/run.go
@@ -25,6 +25,7 @@ type ScanOpts struct {
KatanaDepth int
KatanaMaxTime time.Duration
KatanaMaxURLs int
+ NoSecrets bool
}
// context builds a per-target Context from the current options.
@@ -50,6 +51,9 @@ func (opts *ScanOpts) webModules() []core.Module {
if !opts.NoKatana {
modules = append(modules, &Katana{Depth: opts.KatanaDepth, Duration: opts.KatanaMaxTime})
}
+ if !opts.NoSecrets {
+ modules = append(modules, &Secrets{MaxURLs: opts.KatanaMaxURLs})
+ }
modules = append(modules, &Tlsx{}, &WappalyzerGo{}, &Cvemap{}, &Httpx{})
if opts.Nuclei {
diff --git a/scan/secrets.go b/scan/secrets.go
@@ -0,0 +1,171 @@
+package scan
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "strings"
+ "sync"
+
+ "evil.djnn.sh/djnn/yellow/core"
+ helper "evil.djnn.sh/djnn/yellow/helpers"
+ "github.com/zricethezav/gitleaks/v8/detect"
+)
+
+const maxSecretBodySize = 10 << 20
+
+type SecretFinding struct {
+ URL string `json:"url"`
+ RuleID string `json:"rule_id"`
+ Description string `json:"description"`
+ Secret string `json:"secret"`
+ Match string `json:"match"`
+ Line int `json:"line"`
+ Entropy float32 `json:"entropy"`
+ Fingerprint string `json:"fingerprint"`
+}
+
+type Secrets struct {
+ MaxURLs int
+}
+
+var (
+ detectorOnce sync.Once
+ detector *detect.Detector
+)
+
+func secretDetector() *detect.Detector {
+ detectorOnce.Do(func() {
+ d, err := detect.NewDetectorDefaultConfig()
+ if err != nil {
+ fmt.Printf("[!] secrets: could not load gitleaks rules: %v\n", err)
+ return
+ }
+ detector = d
+ })
+ return detector
+}
+
+var scannableTypes = []string{
+ "javascript", "json", "html", "css", "xml", "text/plain", "ecmascript",
+}
+
+func shouldScanBody(contentType string) bool {
+ ct := strings.ToLower(contentType)
+ for _, t := range scannableTypes {
+ if strings.Contains(ct, t) {
+ return true
+ }
+ }
+ return false
+}
+
+func (*Secrets) Name() string { return "secrets" }
+
+func (s *Secrets) Run(ctx *core.Context) error {
+ fmt.Println("[+] Running secrets scan on", ctx.Domain)
+ if ctx.DryRun {
+ return nil
+ }
+
+ d := secretDetector()
+ if d == nil {
+ return nil
+ }
+
+ targets := capURLs(ctx.URLs, s.MaxURLs, "secrets")
+ if len(targets) == 0 {
+ targets = []string{ctx.Domain}
+ }
+
+ client := helper.GetHttpClient(true)
+
+ var (
+ mu sync.Mutex
+ seen = map[string]bool{}
+ findings = []SecretFinding{}
+ scanned int
+ wg sync.WaitGroup
+ )
+ sem := make(chan struct{}, 10)
+
+ for _, target := range targets {
+ wg.Add(1)
+ sem <- struct{}{}
+ go func(url string) {
+ defer wg.Done()
+ defer func() { <-sem }()
+
+ body, ok := fetchScannable(client, url)
+ if !ok {
+ return
+ }
+
+ mu.Lock()
+ scanned++
+ mu.Unlock()
+
+ for _, f := range d.DetectBytes(body) {
+ rec := SecretFinding{
+ URL: url,
+ RuleID: f.RuleID,
+ Description: f.Description,
+ Secret: f.Secret,
+ Match: strings.TrimSpace(f.Match),
+ Line: f.StartLine,
+ Entropy: f.Entropy,
+ Fingerprint: f.Fingerprint,
+ }
+ key := rec.Fingerprint
+ if key == "" {
+ key = rec.RuleID + ":" + rec.Secret
+ }
+
+ mu.Lock()
+ if !seen[key] {
+ seen[key] = true
+ findings = append(findings, rec)
+ fmt.Printf("[SCAN %s] secrets: [%s] %s in %s\n", ctx.Domain, rec.RuleID, rec.Description, url)
+ }
+ mu.Unlock()
+ }
+ }(target)
+ }
+ wg.Wait()
+
+ outfile := fmt.Sprintf("%s/secrets.json", ctx.ScanPath)
+ data, _ := json.MarshalIndent(findings, "", "\t")
+ if err := os.WriteFile(outfile, data, 0644); err != nil {
+ fmt.Printf("[!] secrets: could not write %s: %v\n", outfile, err)
+ }
+
+ fmt.Printf("[SCAN %s] secrets: %d finding(s) across %d URL(s) in %s\n\n",
+ ctx.Domain, len(findings), scanned, outfile)
+ return nil
+}
+
+func fetchScannable(client *http.Client, url string) ([]byte, bool) {
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return nil, false
+ }
+ req.Header.Set("User-Agent", helper.GetUserAgent())
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, false
+ }
+ defer resp.Body.Close()
+
+ if !shouldScanBody(resp.Header.Get("Content-Type")) {
+ return nil, false
+ }
+
+ body, err := io.ReadAll(io.LimitReader(resp.Body, maxSecretBodySize))
+ if err != nil {
+ return nil, false
+ }
+ return body, true
+}
diff --git a/scan/secrets_test.go b/scan/secrets_test.go
@@ -0,0 +1,201 @@
+package scan
+
+import (
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "testing"
+
+ "evil.djnn.sh/djnn/yellow/core"
+ helper "evil.djnn.sh/djnn/yellow/helpers"
+)
+
+// Fabricated, non-functional credentials, assembled at runtime so no
+// secret-shaped literal sits in the source and trips push protection.
+// AWS key ids are base32, hence the restricted charset there.
+const (
+ charsAlnum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+ charsDigits = "0123456789"
+ charsBase32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
+)
+
+func mkFake(prefix, charset string, n int) string {
+ b := make([]byte, n)
+ for i := range b {
+ b[i] = charset[(i*7+3)%len(charset)]
+ }
+ return prefix + string(b)
+}
+
+var (
+ fakeSlackToken = mkFake("xoxb-", charsDigits, 12) + "-" + mkFake("", charsDigits, 13)
+ fakeGithubPAT = mkFake("ghp_", charsAlnum, 36)
+ fakeAWSKeyID = mkFake("AKIA", charsBase32, 16)
+ malformedAWSID = mkFake("AKIA", charsBase32, 15) + "9" // 9 is not base32
+)
+
+func TestShouldScanBody(t *testing.T) {
+ tests := []struct {
+ ct string
+ want bool
+ }{
+ {"application/javascript", true},
+ {"text/javascript; charset=utf-8", true},
+ {"application/json", true},
+ {"text/html; charset=utf-8", true},
+ {"text/css", true},
+ {"application/xml", true},
+ {"text/plain", true},
+ {"image/png", false},
+ {"font/woff2", false},
+ {"video/mp4", false},
+ {"application/octet-stream", false},
+ {"", false},
+ }
+
+ for _, tt := range tests {
+ if got := shouldScanBody(tt.ct); got != tt.want {
+ t.Errorf("shouldScanBody(%q) = %v, want %v", tt.ct, got, tt.want)
+ }
+ }
+}
+
+func TestFetchScannableSkipsBinary(t *testing.T) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "image/png")
+ _, _ = w.Write([]byte("token = \"" + fakeSlackToken + "\""))
+ }))
+ defer srv.Close()
+
+ if _, ok := fetchScannable(helper.GetHttpClient(true), srv.URL); ok {
+ t.Error("binary content-type must not be fetched for scanning")
+ }
+}
+
+func TestSecretsDetectsPlantedCredentials(t *testing.T) {
+ body := "var cfg={\n slack:\"" + fakeSlackToken + "\",\n" +
+ " github:\"" + fakeGithubPAT + "\",\n" +
+ " awsKeyId:\"" + fakeAWSKeyID + "\"\n};\n"
+
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/javascript")
+ _, _ = w.Write([]byte(body))
+ }))
+ defer srv.Close()
+
+ dir := t.TempDir()
+ ctx := &core.Context{Domain: srv.URL, ScanPath: dir, URLs: []string{srv.URL + "/app.js"}}
+
+ if err := (&Secrets{MaxURLs: 10}).Run(ctx); err != nil {
+ t.Fatalf("Run returned %v", err)
+ }
+
+ byRule := map[string]SecretFinding{}
+ for _, f := range readSecrets(t, dir) {
+ byRule[f.RuleID] = f
+ }
+
+ want := map[string]string{
+ "slack-bot-token": fakeSlackToken,
+ "github-pat": fakeGithubPAT,
+ "aws-access-token": fakeAWSKeyID,
+ }
+ for rule, secret := range want {
+ got, ok := byRule[rule]
+ if !ok {
+ t.Errorf("rule %s did not fire; got rules %v", rule, keysOf(byRule))
+ continue
+ }
+ if got.Secret != secret {
+ t.Errorf("%s: secret = %q, want %q", rule, got.Secret, secret)
+ }
+ if got.URL != srv.URL+"/app.js" {
+ t.Errorf("%s: url = %q, want the source URL", rule, got.URL)
+ }
+ }
+}
+
+// a key id with a char outside base32 is not a real AWS key and must not match
+func TestSecretsIgnoresMalformedAWSKey(t *testing.T) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/javascript")
+ _, _ = w.Write([]byte("var k=\"" + malformedAWSID + "\";\n"))
+ }))
+ defer srv.Close()
+
+ dir := t.TempDir()
+ ctx := &core.Context{Domain: srv.URL, ScanPath: dir, URLs: []string{srv.URL + "/bad.js"}}
+
+ if err := (&Secrets{MaxURLs: 10}).Run(ctx); err != nil {
+ t.Fatalf("Run returned %v", err)
+ }
+ for _, f := range readSecrets(t, dir) {
+ if f.RuleID == "aws-access-token" {
+ t.Errorf("matched a non-base32 key id: %q", f.Secret)
+ }
+ }
+}
+
+func keysOf(m map[string]SecretFinding) []string {
+ out := []string{}
+ for k := range m {
+ out = append(out, k)
+ }
+ return out
+}
+
+func TestSecretsDedupesSameSecretAcrossURLs(t *testing.T) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/javascript")
+ _, _ = w.Write([]byte("var t=\"" + fakeGithubPAT + "\";\n"))
+ }))
+ defer srv.Close()
+
+ dir := t.TempDir()
+ ctx := &core.Context{
+ Domain: srv.URL,
+ ScanPath: dir,
+ URLs: []string{srv.URL + "/a.js", srv.URL + "/b.js", srv.URL + "/c.js"},
+ }
+
+ if err := (&Secrets{MaxURLs: 10}).Run(ctx); err != nil {
+ t.Fatalf("Run returned %v", err)
+ }
+
+ findings := readSecrets(t, dir)
+ if len(findings) != 1 {
+ t.Errorf("same secret on 3 URLs produced %d records, want 1 deduped", len(findings))
+ }
+}
+
+func TestSecretsCleanBodyYieldsNoFindings(t *testing.T) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/javascript")
+ _, _ = w.Write([]byte("function add(a,b){return a+b}\n"))
+ }))
+ defer srv.Close()
+
+ dir := t.TempDir()
+ ctx := &core.Context{Domain: srv.URL, ScanPath: dir, URLs: []string{srv.URL + "/clean.js"}}
+
+ if err := (&Secrets{MaxURLs: 10}).Run(ctx); err != nil {
+ t.Fatalf("Run returned %v", err)
+ }
+ if findings := readSecrets(t, dir); len(findings) != 0 {
+ t.Errorf("clean body produced %d findings: %+v", len(findings), findings)
+ }
+}
+
+func readSecrets(t *testing.T, dir string) []SecretFinding {
+ t.Helper()
+ data, err := os.ReadFile(dir + "/secrets.json")
+ if err != nil {
+ t.Fatalf("secrets.json not written: %v", err)
+ }
+ var findings []SecretFinding
+ if err := json.Unmarshal(data, &findings); err != nil {
+ t.Fatalf("secrets.json is not valid json: %v", err)
+ }
+ return findings
+}
| | | | | | | | |