[
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 UndeadSec\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "SRC_DIR = src/\nDEST_DIR = /etc/dockerspy\nBIN_DIR = /usr/local/bin\n\nGREEN = \\033[0;32m\nYELLOW = \\033[0;33m\nBLUE = \\033[0;34m\nNC = \\033[0m\n\nall: build cleanup\n\nbuild: copy-deps\n\t@echo \"$(BLUE)[#] Building the dockerspy binary...$(NC)\"\n\tsudo go build -o dockerspy\n\t@echo \"$(BLUE)[#] Copying the dockerspy binary to $(BIN_DIR)...$(NC)\"\n\tsudo cp dockerspy $(BIN_DIR)\n\tsudo chmod +x $(BIN_DIR)/dockerspy\n\ncopy-deps:\n\t@echo \"$(YELLOW)[#] Copying configuration files...$(NC)\"\n\tsudo mkdir -p $(DEST_DIR)\n\tsudo cp -R $(SRC_DIR)* $(DEST_DIR)\n\ncleanup:\n\t@echo \"$(BLUE)[#] Cleaning up...$(NC)\"\n\tsudo rm -f dockerspy\n\n.PHONY: all build copy-deps cleanup\n\nall: build cleanup\n\t@echo \"$(GREEN)[###] Build complete. You can now run dockerspy from the terminal.$(NC)\"\n"
  },
  {
    "path": "README.md",
    "content": "# DockerSpy\nDockerSpy searches for images on Docker Hub and extracts sensitive information such as authentication secrets, private keys, and more.\n\n<p align=\"center\">\n<img src=\"https://github.com/UndeadSec/DockerSpy/blob/main/screenshot/sc.png?raw=true\"/>\n</p>\n\n### What is Docker?\n\nDocker is an open-source platform that automates the deployment, scaling, and management of applications using containerization technology. Containers allow developers to package an application and its dependencies into a single, portable unit that can run consistently across various computing environments. Docker simplifies the development and deployment process by ensuring that applications run the same way regardless of where they are deployed.\n\n### About Docker Hub\n\nDocker Hub is a cloud-based repository where developers can store, share, and distribute container images. It serves as the largest library of container images, providing access to both official images created by Docker and community-contributed images. Docker Hub enables developers to easily find, download, and deploy pre-built images, facilitating rapid application development and deployment.\n\n### Why OSINT on Docker Hub?\n\nOpen Source Intelligence (OSINT) on Docker Hub involves using publicly available information to gather insights and data from container images and repositories hosted on Docker Hub. This is particularly important for identifying exposed secrets for several reasons:\n\n1. **Security Audits**: By analyzing Docker images, organizations can uncover exposed secrets such as API keys, authentication tokens, and private keys that might have been inadvertently included. This helps in mitigating potential security risks.\n\n2. **Incident Prevention**: Proactively searching for exposed secrets in Docker images can prevent security breaches before they happen, protecting sensitive information and maintaining the integrity of applications.\n\n3. **Compliance**: Ensuring that container images do not expose secrets is crucial for meeting regulatory and organizational security standards. OSINT helps verify that no sensitive information is unintentionally disclosed.\n\n4. **Vulnerability Assessment**: Identifying exposed secrets as part of regular security assessments allows organizations to address these vulnerabilities promptly, reducing the risk of exploitation by malicious actors.\n\n5. **Enhanced Security Posture**: Continuously monitoring Docker Hub for exposed secrets strengthens an organization's overall security posture, making it more resilient against potential threats.\n\nUtilizing OSINT on Docker Hub to find exposed secrets enables organizations to enhance their security measures, prevent data breaches, and ensure the confidentiality of sensitive information within their containerized applications.\n\n- [Thousands of images on Docker Hub leak auth secrets, private keys](https://www.bleepingcomputer.com/news/security/thousands-of-images-on-docker-hub-leak-auth-secrets-private-keys/)\n- [Docker Hub images found to expose secrets and private keys](https://www.threatdown.com/blog/docker-hub-images-found-to-expose-secrets-and-private-keys/)\n\n## How DockerSpy Works\n\nDockerSpy obtains information from Docker Hub and uses regular expressions to inspect the content for sensitive information, such as secrets.\n\n## Getting Started\n\nTo use DockerSpy, follow these steps:\n\n1. **Installation:** Clone the DockerSpy repository and install the required dependencies.\n\n```bash\ngit clone https://github.com/UndeadSec/DockerSpy.git && cd DockerSpy && make\n```\n\n2. **Usage:** Run DockerSpy from terminal.\n\n```bash\ndockerspy\n```\n\n## Custom Configurations\n\nTo customize DockerSpy configurations, edit the following files:\n- [Regular Expressions](src/configs/regex_patterns.json)\n- [Ignored File Extensions](src/configs/ignore_extensions.json)\n\n## Disclaimer\n\nDockerSpy is intended for educational and research purposes only. Users are responsible for ensuring that their use of this tool complies with applicable laws and regulations.\n\n## Contribution\n\nContributions to DockerSpy are welcome! Feel free to submit issues, feature requests, or pull requests to help improve this tool.\n\n## About the Author\n\nDockerSpy is developed and maintained by *Alisson Moretto* (UndeadSec)\n\nI'm a passionate cyber threat intelligence pro who loves sharing insights and crafting cybersecurity tools.\n\nConsider following me:\n\n[![X](https://img.shields.io/badge/X-%23000000.svg?style=for-the-badge&logo=X&logoColor=white)](https://twitter.com/UndeadSec)\n[![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://linkedin.com/in/alissonmoretto)\n[![GitHub](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/UndeadSec)\n\n## TODO\n\n### Regular Expressions Enhancement\n\n- [ ] Review and improve existing regular expressions.\n- [ ] Ensure that regular expressions adhere to best practices.\n- [ ] Check for any potential optimizations in the regex patterns.\n- [ ] Test regular expressions with various input scenarios for accuracy.\n- [ ] Document any complex or non-trivial regex patterns for better understanding.\n\n## License\n\nDockerSpy is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\n\n### Thanks\n\nSpecial thanks to [@akaclandestine](https://x.com/akaclandestine) \n"
  },
  {
    "path": "go.mod",
    "content": "module dockerspy\n\ngo 1.22\n\nrequire github.com/fatih/color v1.17.0\n\nrequire (\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgolang.org/x/sys v0.22.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=\ngithub.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=\ngolang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=\ngolang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t\"archive/tar\"\n\t\"bufio\"\n\t\"compress/gzip\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/fatih/color\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype IgnoreExtensions struct {\n\tExtensions []string `json:\"extensions\"`\n}\n\ntype SearchResult struct {\n\tNumResults int    `json:\"count\"`\n\tNext       string `json:\"next\"`\n\tResults    []struct {\n\t\tName        string `json:\"repo_name\"`\n\t\tDescription string `json:\"short_description\"`\n\t\tPullCount   int    `json:\"pull_count\"`\n\t\tStarCount   int    `json:\"star_count\"`\n\t\tIsOfficial  bool   `json:\"is_official\"`\n\t} `json:\"results\"`\n}\n\ntype TagsResult struct {\n\tCount    int    `json:\"count\"`\n\tNext     string `json:\"next\"`\n\tPrevious string `json:\"previous\"`\n\tResults  []struct {\n\t\tName string `json:\"name\"`\n\t} `json:\"results\"`\n}\n\nconst (\n\tdockerHubAPI = \"https://registry-1.docker.io/v2/\"\n)\n\ntype TokenResponse struct {\n\tToken string `json:\"token\"`\n}\n\ntype Manifest struct {\n\tConfig    Descriptor   `json:\"config\"`\n\tLayers    []Descriptor `json:\"layers\"`\n\tMediaType string       `json:\"mediaType\"`\n}\n\ntype Descriptor struct {\n\tMediaType string `json:\"mediaType\"`\n\tSize      int64  `json:\"size\"`\n\tDigest    string `json:\"digest\"`\n}\n\nfunc getDockerHubToken(repo string) (string, error) {\n\tauthURL := fmt.Sprintf(\"https://auth.docker.io/token?service=registry.docker.io&scope=repository:%s:pull\", repo)\n\tresp, err := http.Get(authURL)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"failed to authenticate: %s\", resp.Status)\n\t}\n\n\tvar tokenResponse TokenResponse\n\tif err := json.NewDecoder(resp.Body).Decode(&tokenResponse); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn tokenResponse.Token, nil\n}\n\nfunc getManifest(repo, tag, token string) (*Manifest, error) {\n\tclient := &http.Client{}\n\turl := fmt.Sprintf(\"%s%s/manifests/%s\", dockerHubAPI, repo, tag)\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+token)\n\treq.Header.Set(\"Accept\", \"application/vnd.docker.distribution.manifest.v2+json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to get manifest: %s\", resp.Status)\n\t}\n\n\tvar manifest Manifest\n\tif err := json.NewDecoder(resp.Body).Decode(&manifest); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &manifest, nil\n}\n\nfunc downloadLayer(repo, token, digest, outputPath string, size int64) error {\n\tclient := &http.Client{}\n\turl := fmt.Sprintf(\"%s%s/blobs/%s\", dockerHubAPI, repo, digest)\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+token)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"failed to download layer: %s\", resp.Status)\n\t}\n\n\tfile, err := os.Create(outputPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\n\tprogressWriter := &ProgressWriter{Writer: file, Total: size}\n\t_, err = io.Copy(progressWriter, resp.Body)\n\treturn err\n}\n\ntype ProgressWriter struct {\n\tWriter     io.Writer\n\tTotal      int64\n\tDownloaded int64\n}\n\nfunc (pw *ProgressWriter) Write(p []byte) (int, error) {\n\tn, err := pw.Writer.Write(p)\n\tpw.Downloaded += int64(n)\n\tpw.printProgress()\n\treturn n, err\n}\n\nfunc (pw *ProgressWriter) printProgress() {\n\tpercent := float64(pw.Downloaded) / float64(pw.Total) * 100\n\tfmt.Printf(\"\\rDownloading... %.2f%% complete\", percent)\n}\n\nfunc loadRegexPatterns(filename string) (map[string]*regexp.Regexp, error) {\n\tfile, err := os.Open(filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer file.Close()\n\n\tvar patterns map[string]string\n\tdecoder := json.NewDecoder(file)\n\tif err := decoder.Decode(&patterns); err != nil {\n\t\treturn nil, err\n\t}\n\n\tregexPatterns := make(map[string]*regexp.Regexp)\n\tfor name, pattern := range patterns {\n\t\tre, err := regexp.Compile(pattern)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to compile regex %s: %v\", name, err)\n\t\t}\n\t\tregexPatterns[name] = re\n\t}\n\n\treturn regexPatterns, nil\n}\n\nfunc checkPatterns(content string, patterns map[string]*regexp.Regexp) map[string][]string {\n\tmatches := make(map[string][]string)\n\tfor name, re := range patterns {\n\t\tfoundMatches := re.FindAllString(content, -1)\n\t\tif foundMatches != nil {\n\t\t\tmatches[name] = foundMatches\n\t\t}\n\t}\n\treturn matches\n}\n\nfunc extractTarGz(tarGzPath, outputDir string) error {\n\tfile, err := os.Open(tarGzPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\n\tgzr, err := gzip.NewReader(file)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer gzr.Close()\n\n\ttarReader := tar.NewReader(gzr)\n\tfor {\n\t\theader, err := tarReader.Next()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ttarget := filepath.Join(outputDir, header.Name)\n\t\tswitch header.Typeflag {\n\t\tcase tar.TypeDir:\n\t\t\tif err := os.MkdirAll(target, os.ModePerm); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase tar.TypeReg:\n\t\t\toutFile, err := os.Create(target)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif _, err := io.Copy(outFile, tarReader); err != nil {\n\t\t\t\toutFile.Close()\n\t\t\t\treturn err\n\t\t\t}\n\t\t\toutFile.Close()\n\t\tdefault:\n\t\t\t//fmt.Printf(\"Unable to untar type: %c in file %s\", header.Typeflag, header.Name)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc loadIgnoreExtensions(filename string) ([]string, error) {\n\tfile, err := os.Open(filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer file.Close()\n\n\tvar ignoreExtensions IgnoreExtensions\n\tdecoder := json.NewDecoder(file)\n\tif err := decoder.Decode(&ignoreExtensions); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ignoreExtensions.Extensions, nil\n}\n\nfunc shouldSkipFile(filename string, ignoreExtensions []string) bool {\n\tfor _, ext := range ignoreExtensions {\n\t\tif strings.HasSuffix(strings.ToLower(filename), ext) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc removeDir(dir string) error {\n\tif _, err := os.Stat(dir); os.IsNotExist(err) {\n\t\treturn nil\n\t}\n\treturn os.RemoveAll(dir)\n}\n\nfunc printBanner() {\n\tbanner := `\n╭━━━━━━━━╮┏━╮╭━┓\n┃┈┈┈┈┈┈┈┈┃╰╮╰╯╭╯   v1.1\n┃╰╯┈┈┈┈┈┈╰╮╰╮╭╯┈   DOCKERSPY by Alisson Moretto (UndeadSec)\n┣━━╯┈┈┈┈┈┈╰━╯┃┈┈         AUTOMATED OSINT ON DOCKER HUB     \n╰━━━━━━━━━━━━╯┈┈`\n\tfmt.Println(color.New(color.FgGreen).Sprint(banner))\n}\n\nfunc fetchPaginatedResults(url string) ([]struct {\n\tName        string `json:\"repo_name\"`\n\tDescription string `json:\"short_description\"`\n\tPullCount   int    `json:\"pull_count\"`\n\tStarCount   int    `json:\"star_count\"`\n\tIsOfficial  bool   `json:\"is_official\"`\n}, error) {\n\tvar allResults []struct {\n\t\tName        string `json:\"repo_name\"`\n\t\tDescription string `json:\"short_description\"`\n\t\tPullCount   int    `json:\"pull_count\"`\n\t\tStarCount   int    `json:\"star_count\"`\n\t\tIsOfficial  bool   `json:\"is_official\"`\n\t}\n\n\tcount := 0\n\tfor {\n\t\tif count >= 100 {\n\t\t\tbreak\n\t\t}\n\n\t\tresp, err := http.Get(url)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\treturn nil, fmt.Errorf(\"API response error: %s\", resp.Status)\n\t\t}\n\n\t\tvar searchResult SearchResult\n\t\tif err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tallResults = append(allResults, searchResult.Results...)\n\t\tcount += len(searchResult.Results)\n\n\t\tif searchResult.Next == \"\" {\n\t\t\tbreak\n\t\t}\n\n\t\turl = searchResult.Next\n\t}\n\n\tif len(allResults) > 100 {\n\t\tallResults = allResults[:100]\n\t}\n\n\treturn allResults, nil\n}\n\nfunc main() {\n\tprintBanner()\n\n\terr := removeDir(\"docker_image\")\n\tif err != nil {\n\t\tfmt.Println(\"\\nError removing docker_image directory:\", err)\n\t\treturn\n\t}\n\n\tregexPatterns, err := loadRegexPatterns(\"/etc/dockerspy/configs/regex_patterns.json\")\n\tif err != nil {\n\t\tfmt.Println(\"\\nError loading regex patterns:\", err)\n\t\treturn\n\t}\n\n\tignoreExtensions, err := loadIgnoreExtensions(\"/etc/dockerspy/configs/ignore_extensions.json\")\n\tif err != nil {\n\t\tfmt.Println(\"\\nError loading ignore extensions:\", err)\n\t\treturn\n\t}\n\n\tscanner := bufio.NewScanner(os.Stdin)\n\tinfo := color.New(color.FgCyan).SprintFunc()\n\twarning := color.New(color.FgYellow).SprintFunc()\n\terrorColor := color.New(color.FgRed).SprintFunc()\n\tsuccess := color.New(color.FgGreen).SprintFunc()\n\thighlight := color.New(color.FgHiMagenta, color.Bold).SprintFunc()\n\n\tfor {\n\t\tfmt.Print(info(\"\\nEnter search term (or 'exit' to quit): \"))\n\t\tscanner.Scan()\n\t\tsearchTerm := scanner.Text()\n\n\t\tif strings.ToLower(searchTerm) == \"exit\" {\n\t\t\tbreak\n\t\t}\n\n\t\tdockerHubURL := \"https://hub.docker.com/v2/search/repositories\"\n\t\tparams := url.Values{}\n\t\tparams.Add(\"query\", searchTerm)\n\n\t\tsearchURL := fmt.Sprintf(\"%s?%s\", dockerHubURL, params.Encode())\n\t\tresults, err := fetchPaginatedResults(searchURL)\n\t\tif err != nil {\n\t\t\tfmt.Println(errorColor(\"\\nError fetching search results:\"), err)\n\t\t\tcontinue\n\t\t}\n\n\t\tfmt.Printf(info(\"\\nFound %d results for '%s':\"), len(results), searchTerm)\n\t\tfor i, result := range results {\n\t\t\tfmt.Printf(\"\\n%s - Name: %s\\nDescription: %s\\nStars: %d\\nOfficial: %t\", highlight(i+1), result.Name, result.Description, result.StarCount, result.IsOfficial)\n\t\t}\n\n\t\tfmt.Print(info(\"\\nChoose a number or enter the full name to view repository tags (or 'cancel' to search again): \"))\n\t\tscanner.Scan()\n\t\tchoice := scanner.Text()\n\n\t\tif strings.ToLower(choice) == \"cancel\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar selectedRepo string\n\t\tchoiceNum, err := strconv.Atoi(choice)\n\t\tif err == nil && choiceNum >= 1 && choiceNum <= len(results) {\n\t\t\tselectedRepo = results[choiceNum-1].Name\n\t\t} else {\n\t\t\tselectedRepo = choice\n\t\t}\n\n\t\ttagsURL := fmt.Sprintf(\"https://hub.docker.com/v2/repositories/%s/tags\", selectedRepo)\n\t\tresp, err := http.Get(tagsURL)\n\t\tif err != nil {\n\t\t\tfmt.Println(errorColor(\"\\nError fetching tags:\"), err)\n\t\t\tcontinue\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tvar tagsResult TagsResult\n\t\tif err := json.NewDecoder(resp.Body).Decode(&tagsResult); err != nil {\n\t\t\tfmt.Println(errorColor(\"\\nError decoding JSON response:\"), err)\n\t\t\tcontinue\n\t\t}\n\n\t\tfmt.Printf(info(\"Available tags for repository '%s':\"), selectedRepo)\n\t\tfor i, tag := range tagsResult.Results {\n\t\t\tfmt.Printf(\"\\n%s - %s\", highlight(i+1), tag.Name)\n\t\t}\n\n\t\tfmt.Print(info(\"\\nChoose a number to download the tag (or 'cancel' to search again): \"))\n\t\tscanner.Scan()\n\t\ttagChoice := scanner.Text()\n\n\t\tif strings.ToLower(tagChoice) == \"cancel\" {\n\t\t\tcontinue\n\t\t}\n\n\t\ttagChoiceNum, err := strconv.Atoi(tagChoice)\n\t\tif err != nil || tagChoiceNum < 1 || tagChoiceNum > len(tagsResult.Results) {\n\t\t\tfmt.Println(warning(\"\\nInvalid choice. Please try again.\"))\n\t\t\tcontinue\n\t\t}\n\n\t\ttag := tagsResult.Results[tagChoiceNum-1].Name\n\n\t\trepo := selectedRepo\n\t\toutputDir := \"./docker_image\"\n\n\t\ttoken, err := getDockerHubToken(repo)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"\\nError getting token:\", err)\n\t\t\treturn\n\t\t}\n\n\t\tmanifest, err := getManifest(repo, tag, token)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"\\nError getting manifest:\", err)\n\t\t\treturn\n\t\t}\n\n\t\tos.MkdirAll(outputDir, os.ModePerm)\n\n\t\tvar envContent string\n\t\tmatchesResult := make(map[string]map[string][]string)\n\n\t\tfor _, layer := range manifest.Layers {\n\t\t\tdigestParts := strings.Split(layer.Digest, \":\")\n\t\t\tif len(digestParts) != 2 {\n\t\t\t\tfmt.Println(\"\\nInvalid digest format:\", layer.Digest)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\toutputPath := filepath.Join(outputDir, digestParts[1]+\".tar.gz\")\n\t\t\tfmt.Println(\"\\nDownloading layer:\", layer.Digest)\n\t\t\tif err := downloadLayer(repo, token, layer.Digest, outputPath, layer.Size); err != nil {\n\t\t\t\tfmt.Println(\"\\nError downloading layer:\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\textractedDir := filepath.Join(outputDir, digestParts[1])\n\t\t\tfmt.Println(\"\\nExtracting layer:\", outputPath)\n\t\t\tif err := extractTarGz(outputPath, extractedDir); err != nil {\n\t\t\t\tfmt.Println(\"\\nError extracting layer:\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfilepath.Walk(extractedDir, func(path string, info os.FileInfo, err error) error {\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif !info.IsDir() && !shouldSkipFile(path, ignoreExtensions) {\n\t\t\t\t\tcontent, err := os.ReadFile(path)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"\\nError reading file:\", err)\n\t\t\t\t\t\treturn nil\n\t\t\t\t\t}\n\t\t\t\t\tif filepath.Base(path) == \".env\" {\n\t\t\t\t\t\tfmt.Println(success(\"\\nFound .env file:\"))\n\t\t\t\t\t\tenvContent = string(content)\n\t\t\t\t\t\tfmt.Println(envContent)\n\t\t\t\t\t}\n\t\t\t\t\tmatches := checkPatterns(string(content), regexPatterns)\n\t\t\t\t\tif len(matches) > 0 {\n\t\t\t\t\t\tfmt.Println(success(\"\\nMatches found in file:\"), path)\n\t\t\t\t\t\tmatchesResult[path] = matches\n\t\t\t\t\t\tfor pattern, matchedStrings := range matches {\n\t\t\t\t\t\t\tfmt.Printf(\"  Pattern: %s\\n\", pattern)\n\t\t\t\t\t\t\tfor _, match := range matchedStrings {\n\t\t\t\t\t\t\t\tfmt.Printf(\"    %s\\n\", match)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t})\n\t\t}\n\n\t\tfmt.Println(success(\"\\nImage downloaded and extracted successfully\\n\"))\n\n\t\tresultData := map[string]interface{}{\n\t\t\t\"selectedRepo\": selectedRepo,\n\t\t\t\"selectedTag\":  tag,\n\t\t\t\"envContent\":   envContent,\n\t\t\t\"matches\":      matchesResult,\n\t\t}\n\n\t\tjsonFile, err := os.Create(\"results.json\")\n\t\tif err != nil {\n\t\t\tfmt.Println(errorColor(\"\\nError creating JSON file:\"), err)\n\t\t\treturn\n\t\t}\n\t\tdefer jsonFile.Close()\n\n\t\tencoder := json.NewEncoder(jsonFile)\n\t\tif err := encoder.Encode(resultData); err != nil {\n\t\t\tfmt.Println(errorColor(\"\\nError encoding JSON:\"), err)\n\t\t\treturn\n\t\t}\n\n\t\tfmt.Println(success(\"Results saved to results.json\"))\n\t}\n}\n"
  },
  {
    "path": "src/configs/ignore_extensions.json",
    "content": "{\n  \"extensions\": [\".md\",\".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\", \".tiff\", \".tif\", \".mp4\", \".mp3\", \".avi\", \".mkv\", \".mov\", \".exe\", \".dll\", \".so\", \".bin\", \".dmg\", \".iso\", \".jar\", \".bat\", \".sh\", \".msi\"]\n}\n"
  },
  {
    "path": "src/configs/regex_patterns.json",
    "content": "{\n  \"amazon_mws_auth_token\": \"amzn\\\\\\\\.mws\\\\\\\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\",\n  \"amazon_aws_url\": \"s3\\\\.amazonaws.com[/]+|[a-zA-Z0-9_-]*\\\\.s3\\\\.amazonaws.com\",\n  \"authorization_bearer\": \"bearer [a-zA-Z0-9_\\\\-\\\\.=:_\\\\+/]{5,100}\",\n  \"github_access_token\": \"[a-zA-Z0-9_-]*:[a-zA-Z0-9_\\\\-]+@github\\\\.com*\",\n  \"rsa_private_key\": \"-----BEGIN RSA PRIVATE KEY-----\",\n  \"ssh_dsa_private_key\": \"-----BEGIN DSA PRIVATE KEY-----\",\n  \"ssh_dc_private_key\": \"-----BEGIN EC PRIVATE KEY-----\",\n  \"pgp_private_block\": \"-----BEGIN PGP PRIVATE KEY BLOCK-----\",\n  \"slack_token\": \"\\\\\\\"api_token\\\\\\\":\\\\\\\"(xox[a-zA-Z]-[a-zA-Z0-9-]+)\\\\\\\"\",\n  \"SSH_privKey\": \"([-]+BEGIN [^\\\\s]+ PRIVATE KEY[-]+[\\\\s]*[^-]*[-]+END [^\\\\s]+ PRIVATE KEY[-]+)\"\n}"
  }
]