From 45f5302e1f8d9b5d686164931e26ccb8daf3fd02 Mon Sep 17 00:00:00 2001 From: Tigah <88289044+TBX3D@users.noreply.github.com> Date: Mon, 22 Jun 2026 17:24:06 -0700 Subject: [PATCH] feat(modules): add aws, npmrc and docker credential file exposure modules (#195) modules/recon/aws-credentials-exposure.yaml flags exposed .aws/credentials, .s3cfg and .boto files on the access and secret key markers, and extracts the AKIA/ASIA access key id. modules/recon/npmrc-exposure.yaml flags a .npmrc only when it carries an auth token or password, not a bare registry config, and extracts the registry the token belongs to. modules/recon/docker-config-exposure.yaml flags .docker/config.json and the legacy .dockercfg on the base64 auth field, and extracts the registry host. each module ands a negative matcher on the usual html markers so a 200 page that merely names a key is not a hit, the same guard the env exposure module uses. internal/modules/credential_exposure_test.go drives the three modules end to end through ExecuteHTTPModule and asserts the leak alongside the near misses a strict review wants pinned: an html doc that only names a key, a plain 200 body, a 404, and a jwt shaped docker auth value, none of which may match. verify: go test ./internal/modules, each matcher, guard and extractor proven to bite (break -> red, restore -> green). --- internal/modules/credential_exposure_test.go | 113 +++++++++++++++++++ modules/recon/aws-credentials-exposure.yaml | 53 +++++++++ modules/recon/docker-config-exposure.yaml | 47 ++++++++ modules/recon/npmrc-exposure.yaml | 50 ++++++++ 4 files changed, 263 insertions(+) create mode 100644 internal/modules/credential_exposure_test.go create mode 100644 modules/recon/aws-credentials-exposure.yaml create mode 100644 modules/recon/docker-config-exposure.yaml create mode 100644 modules/recon/npmrc-exposure.yaml diff --git a/internal/modules/credential_exposure_test.go b/internal/modules/credential_exposure_test.go new file mode 100644 index 0000000..b5e0b84 --- /dev/null +++ b/internal/modules/credential_exposure_test.go @@ -0,0 +1,113 @@ +package modules_test + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/dropalldatabases/sif/internal/modules" +) + +func runCredModule(t *testing.T, file string, status int, body string) *modules.Result { + t.Helper() + def, err := modules.ParseYAMLModule(file) + if err != nil { + t.Fatalf("parse %s: %v", file, err) + } + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(status) + _, _ = w.Write([]byte(body)) + })) + defer srv.Close() + + res, err := modules.ExecuteHTTPModule(context.Background(), srv.URL, def, modules.Options{ + Timeout: 5 * time.Second, + Threads: 2, + }) + if err != nil { + t.Fatalf("execute %s: %v", file, err) + } + return res +} + +func credExtract(res *modules.Result, key string) string { + for _, f := range res.Findings { + if v := f.Extracted[key]; v != "" { + return v + } + } + return "" +} + +func TestCredentialExposureModules(t *testing.T) { + const aws = "../../modules/recon/aws-credentials-exposure.yaml" + const npmrc = "../../modules/recon/npmrc-exposure.yaml" + const docker = "../../modules/recon/docker-config-exposure.yaml" + + t.Run("aws credentials leak the access key id", func(t *testing.T) { + body := "[default]\naws_access_key_id = AKIAIOSFODNN7EXAMPLE\n" + + "aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n" + res := runCredModule(t, aws, 200, body) + if len(res.Findings) == 0 { + t.Fatal("expected an aws credentials finding") + } + if v := credExtract(res, "aws_access_key_id"); v != "AKIAIOSFODNN7EXAMPLE" { + t.Errorf("aws_access_key_id=%q, want AKIAIOSFODNN7EXAMPLE", v) + } + }) + + t.Run("npmrc leaks the registry of an auth token", func(t *testing.T) { + body := "//registry.npmjs.org/:_authToken=npm_AbCdEf0123456789AbCdEf0123456789\n" + res := runCredModule(t, npmrc, 200, body) + if len(res.Findings) == 0 { + t.Fatal("expected an npmrc finding") + } + if v := credExtract(res, "npm_registry"); v != "registry.npmjs.org" { + t.Errorf("npm_registry=%q, want registry.npmjs.org", v) + } + }) + + t.Run("docker config leaks the registry host", func(t *testing.T) { + body := `{"auths":{"registry.example.com":{"auth":"dXNlcm5hbWU6c3VwZXJzZWNyZXRwYXNz"}}}` + res := runCredModule(t, docker, 200, body) + if len(res.Findings) == 0 { + t.Fatal("expected a docker config finding") + } + if v := credExtract(res, "docker_registry"); v != "registry.example.com" { + t.Errorf("docker_registry=%q, want registry.example.com", v) + } + }) + + t.Run("html page mentioning the key name is not a leak", func(t *testing.T) { + body := `