From 96d7503d095823afe52c121a0a53a1e5b5157bc9 Mon Sep 17 00:00:00 2001 From: StephenJarso Date: Sun, 21 Jun 2026 23:52:18 +0300 Subject: [PATCH 1/3] test: add finding type tests for security findings Add comprehensive tests for the Finding struct in pkg/types/finding_test.go - Test finding creation with all fields - Test zero value initialization - Test all severity levels --- coverage.out | 485 ++++++++++++++++++++++++++++++++++++++ go.mod | 5 +- go.sum | 2 + internal/config/config.go | 17 +- internal/scanner/file.go | 12 +- pkg/types/finding_test.go | 53 +++++ pkg/utils/entropy.go | 68 +++++- pkg/utils/entropy_test.go | 65 +++++ pkg/utils/regex.go | 42 +++- pkg/utils/regex_test.go | 79 +++++++ 10 files changed, 811 insertions(+), 17 deletions(-) create mode 100644 coverage.out create mode 100644 pkg/types/finding_test.go create mode 100644 pkg/utils/entropy_test.go create mode 100644 pkg/utils/regex_test.go diff --git a/coverage.out b/coverage.out new file mode 100644 index 0000000..3dd8b9c --- /dev/null +++ b/coverage.out @@ -0,0 +1,485 @@ +mode: set +secure-push/cmd/secure-push/main.go:17.13,18.22 1 0 +secure-push/cmd/secure-push/main.go:18.22,21.3 2 0 +secure-push/cmd/secure-push/main.go:23.2,25.20 2 0 +secure-push/cmd/secure-push/main.go:26.14,27.23 1 0 +secure-push/cmd/secure-push/main.go:28.20,29.17 1 0 +secure-push/cmd/secure-push/main.go:30.17,31.15 1 0 +secure-push/cmd/secure-push/main.go:32.17,33.43 1 0 +secure-push/cmd/secure-push/main.go:34.30,35.15 1 0 +secure-push/cmd/secure-push/main.go:36.10,38.23 1 0 +secure-push/cmd/secure-push/main.go:42.19,59.2 16 0 +secure-push/cmd/secure-push/main.go:61.29,69.29 7 0 +secure-push/cmd/secure-push/main.go:69.29,73.3 3 0 +secure-push/cmd/secure-push/main.go:75.2,78.14 3 0 +secure-push/cmd/secure-push/main.go:78.14,80.3 1 0 +secure-push/cmd/secure-push/main.go:81.2,84.16 3 0 +secure-push/cmd/secure-push/main.go:84.16,87.3 2 0 +secure-push/cmd/secure-push/main.go:89.2,99.47 3 0 +secure-push/cmd/secure-push/main.go:99.47,100.60 1 0 +secure-push/cmd/secure-push/main.go:100.60,102.4 1 0 +secure-push/cmd/secure-push/main.go:104.2,110.16 5 0 +secure-push/cmd/secure-push/main.go:110.16,113.3 2 0 +secure-push/cmd/secure-push/main.go:115.2,116.23 2 0 +secure-push/cmd/secure-push/main.go:117.14,118.39 1 0 +secure-push/cmd/secure-push/main.go:119.16,120.41 1 0 +secure-push/cmd/secure-push/main.go:121.15,122.40 1 0 +secure-push/cmd/secure-push/main.go:123.13,124.54 1 0 +secure-push/cmd/secure-push/main.go:125.17,126.42 1 0 +secure-push/cmd/secure-push/main.go:127.10,128.42 1 0 +secure-push/cmd/secure-push/main.go:131.2,131.50 1 0 +secure-push/cmd/secure-push/main.go:131.50,134.3 2 0 +secure-push/cmd/secure-push/main.go:136.2,136.23 1 0 +secure-push/cmd/secure-push/main.go:136.23,138.3 1 0 +secure-push/cmd/secure-push/main.go:141.21,146.16 3 0 +secure-push/cmd/secure-push/main.go:146.16,149.3 2 0 +secure-push/cmd/secure-push/main.go:151.2,151.21 1 0 +secure-push/cmd/secure-push/main.go:151.21,154.3 2 0 +secure-push/cmd/secure-push/main.go:156.2,157.16 2 0 +secure-push/cmd/secure-push/main.go:157.16,160.3 2 0 +secure-push/cmd/secure-push/main.go:162.2,172.47 3 0 +secure-push/cmd/secure-push/main.go:172.47,173.60 1 0 +secure-push/cmd/secure-push/main.go:173.60,175.4 1 0 +secure-push/cmd/secure-push/main.go:177.2,182.29 4 0 +secure-push/cmd/secure-push/main.go:182.29,184.17 2 0 +secure-push/cmd/secure-push/main.go:184.17,186.12 2 0 +secure-push/cmd/secure-push/main.go:188.3,188.49 1 0 +secure-push/cmd/secure-push/main.go:191.2,191.26 1 0 +secure-push/cmd/secure-push/main.go:191.26,194.33 3 0 +secure-push/cmd/secure-push/main.go:194.33,197.4 2 0 +secure-push/cmd/secure-push/main.go:198.3,198.13 1 0 +secure-push/cmd/secure-push/main.go:201.2,201.61 1 0 +secure-push/cmd/secure-push/main.go:204.19,208.51 2 0 +secure-push/cmd/secure-push/main.go:208.51,211.3 2 0 +secure-push/cmd/secure-push/main.go:214.2,214.57 1 0 +secure-push/cmd/secure-push/main.go:214.57,217.3 2 0 +secure-push/cmd/secure-push/main.go:220.2,220.45 1 0 +secure-push/cmd/secure-push/main.go:220.45,223.3 2 0 +secure-push/cmd/secure-push/main.go:226.2,230.75 2 0 +secure-push/cmd/secure-push/main.go:230.75,233.3 2 0 +secure-push/cmd/secure-push/main.go:235.2,235.59 1 0 +secure-push/cmd/secure-push/main.go:238.41,241.16 3 0 +secure-push/cmd/secure-push/main.go:241.16,243.3 1 0 +secure-push/cmd/secure-push/main.go:244.2,245.19 2 0 +secure-push/internal/logger/logger.go:24.31,29.2 1 1 +secure-push/internal/logger/logger.go:31.60,32.22 1 1 +secure-push/internal/logger/logger.go:32.22,34.3 1 1 +secure-push/internal/logger/logger.go:37.59,38.21 1 1 +secure-push/internal/logger/logger.go:38.21,40.3 1 1 +secure-push/internal/logger/logger.go:43.59,44.21 1 1 +secure-push/internal/logger/logger.go:44.21,46.3 1 1 +secure-push/internal/logger/logger.go:49.60,50.22 1 1 +secure-push/internal/logger/logger.go:50.22,52.3 1 1 +secure-push/internal/logger/logger.go:55.65,59.2 3 1 +secure-push/internal/config/config.go:35.30,48.2 1 1 +secure-push/internal/config/config.go:50.47,53.22 2 1 +secure-push/internal/config/config.go:53.22,55.3 1 0 +secure-push/internal/config/config.go:57.2,57.22 1 1 +secure-push/internal/config/config.go:57.22,59.3 1 0 +secure-push/internal/config/config.go:61.2,62.16 2 1 +secure-push/internal/config/config.go:62.16,64.25 1 1 +secure-push/internal/config/config.go:64.25,66.4 1 1 +secure-push/internal/config/config.go:67.3,67.64 1 0 +secure-push/internal/config/config.go:70.2,70.50 1 1 +secure-push/internal/config/config.go:70.50,72.3 1 0 +secure-push/internal/config/config.go:74.2,74.17 1 1 +secure-push/internal/config/config.go:77.30,84.34 2 1 +secure-push/internal/config/config.go:84.34,85.39 1 1 +secure-push/internal/config/config.go:85.39,87.4 1 1 +secure-push/internal/config/config.go:90.2,90.11 1 1 +secure-push/internal/config/config.go:93.49,95.38 1 1 +secure-push/internal/config/config.go:95.38,96.31 1 1 +secure-push/internal/config/config.go:96.31,98.4 1 1 +secure-push/internal/config/config.go:102.2,102.43 1 1 +secure-push/internal/config/config.go:102.43,103.34 1 1 +secure-push/internal/config/config.go:103.34,105.4 1 1 +secure-push/internal/config/config.go:108.2,108.14 1 1 +secure-push/internal/config/config.go:112.49,115.27 2 1 +secure-push/internal/config/config.go:115.27,117.3 1 1 +secure-push/internal/config/config.go:120.2,121.27 2 1 +secure-push/internal/config/config.go:121.27,123.3 1 0 +secure-push/internal/config/config.go:126.2,126.43 1 1 +secure-push/internal/config/config.go:126.43,128.3 1 1 +secure-push/internal/config/config.go:131.2,131.44 1 1 +secure-push/internal/config/config.go:131.44,133.3 1 0 +secure-push/internal/config/config.go:135.2,135.14 1 1 +secure-push/internal/config/config.go:138.70,139.29 1 1 +secure-push/internal/config/config.go:140.13,141.14 1 1 +secure-push/internal/config/config.go:142.16,143.35 1 1 +secure-push/internal/config/config.go:144.14,145.70 1 1 +secure-push/internal/config/config.go:146.18,147.40 1 1 +secure-push/internal/config/config.go:148.10,149.14 1 1 +secure-push/internal/config/config.go:153.62,155.40 1 1 +secure-push/internal/config/config.go:155.40,156.47 1 0 +secure-push/internal/config/config.go:156.47,158.4 1 0 +secure-push/internal/config/config.go:161.2,161.32 1 1 +secure-push/internal/config/config.go:161.32,162.45 1 1 +secure-push/internal/config/config.go:162.45,163.48 1 1 +secure-push/internal/config/config.go:163.48,165.5 1 1 +secure-push/internal/config/config.go:167.3,167.15 1 1 +secure-push/internal/config/config.go:170.2,170.46 1 1 +secure-push/internal/config/config.go:170.46,171.48 1 1 +secure-push/internal/config/config.go:171.48,173.4 1 1 +secure-push/internal/config/config.go:176.2,176.13 1 1 +secure-push/internal/detectors/ast.go:13.37,15.2 1 1 +secure-push/internal/detectors/ast.go:17.43,19.2 1 1 +secure-push/internal/detectors/ast.go:22.82,26.41 2 1 +secure-push/internal/detectors/ast.go:26.41,28.3 1 1 +secure-push/internal/detectors/ast.go:30.2,32.16 3 1 +secure-push/internal/detectors/ast.go:32.16,35.3 1 1 +secure-push/internal/detectors/ast.go:37.2,37.42 1 1 +secure-push/internal/detectors/ast.go:37.42,38.24 1 1 +secure-push/internal/detectors/ast.go:39.22,41.48 1 1 +secure-push/internal/detectors/ast.go:41.48,44.44 3 1 +secure-push/internal/detectors/ast.go:44.44,46.6 1 1 +secure-push/internal/detectors/ast.go:47.5,47.87 1 1 +secure-push/internal/detectors/ast.go:47.87,55.6 1 1 +secure-push/internal/detectors/ast.go:57.24,59.31 1 1 +secure-push/internal/detectors/ast.go:59.31,60.49 1 1 +secure-push/internal/detectors/ast.go:60.49,61.78 1 1 +secure-push/internal/detectors/ast.go:61.78,69.7 1 1 +secure-push/internal/detectors/ast.go:73.3,73.14 1 1 +secure-push/internal/detectors/ast.go:76.2,76.22 1 1 +secure-push/internal/detectors/ast.go:79.44,87.30 2 1 +secure-push/internal/detectors/ast.go:87.30,88.32 1 1 +secure-push/internal/detectors/ast.go:88.32,90.4 1 1 +secure-push/internal/detectors/ast.go:92.2,92.14 1 1 +secure-push/internal/detectors/ast.go:95.45,102.29 3 1 +secure-push/internal/detectors/ast.go:102.29,103.33 1 1 +secure-push/internal/detectors/ast.go:103.33,105.4 1 1 +secure-push/internal/detectors/ast.go:107.2,107.14 1 0 +secure-push/internal/detectors/auth.go:10.38,12.2 1 1 +secure-push/internal/detectors/auth.go:14.44,16.2 1 1 +secure-push/internal/detectors/auth.go:61.83,65.35 3 1 +secure-push/internal/detectors/auth.go:65.35,67.49 2 1 +secure-push/internal/detectors/auth.go:67.49,68.12 1 1 +secure-push/internal/detectors/auth.go:71.3,71.44 1 1 +secure-push/internal/detectors/auth.go:71.44,79.4 1 1 +secure-push/internal/detectors/auth.go:81.3,81.79 1 1 +secure-push/internal/detectors/auth.go:81.79,89.4 1 1 +secure-push/internal/detectors/auth.go:91.3,91.43 1 1 +secure-push/internal/detectors/auth.go:91.43,99.4 1 1 +secure-push/internal/detectors/auth.go:101.3,101.44 1 1 +secure-push/internal/detectors/auth.go:101.44,109.4 1 1 +secure-push/internal/detectors/auth.go:111.3,111.96 1 1 +secure-push/internal/detectors/auth.go:111.96,119.4 1 1 +secure-push/internal/detectors/auth.go:121.3,121.94 1 1 +secure-push/internal/detectors/auth.go:121.94,129.4 1 1 +secure-push/internal/detectors/auth.go:131.3,131.35 1 1 +secure-push/internal/detectors/auth.go:131.35,139.4 1 1 +secure-push/internal/detectors/auth.go:141.3,141.45 1 1 +secure-push/internal/detectors/auth.go:141.45,149.4 1 1 +secure-push/internal/detectors/auth.go:151.3,151.45 1 1 +secure-push/internal/detectors/auth.go:151.45,159.4 1 1 +secure-push/internal/detectors/auth.go:161.3,161.45 1 1 +secure-push/internal/detectors/auth.go:161.45,169.4 1 1 +secure-push/internal/detectors/auth.go:171.3,171.44 1 1 +secure-push/internal/detectors/auth.go:171.44,179.4 1 1 +secure-push/internal/detectors/auth.go:181.3,181.44 1 1 +secure-push/internal/detectors/auth.go:181.44,189.4 1 1 +secure-push/internal/detectors/auth.go:191.3,191.45 1 1 +secure-push/internal/detectors/auth.go:191.45,199.4 1 1 +secure-push/internal/detectors/auth.go:201.3,201.44 1 1 +secure-push/internal/detectors/auth.go:201.44,209.4 1 0 +secure-push/internal/detectors/auth.go:211.3,211.44 1 1 +secure-push/internal/detectors/auth.go:211.44,219.4 1 0 +secure-push/internal/detectors/auth.go:221.3,221.46 1 1 +secure-push/internal/detectors/auth.go:221.46,229.4 1 0 +secure-push/internal/detectors/auth.go:231.3,231.42 1 1 +secure-push/internal/detectors/auth.go:231.42,239.4 1 1 +secure-push/internal/detectors/auth.go:241.3,241.46 1 1 +secure-push/internal/detectors/auth.go:241.46,249.4 1 1 +secure-push/internal/detectors/auth.go:251.3,251.48 1 1 +secure-push/internal/detectors/auth.go:251.48,259.4 1 1 +secure-push/internal/detectors/auth.go:261.3,261.40 1 1 +secure-push/internal/detectors/auth.go:261.40,269.4 1 1 +secure-push/internal/detectors/auth.go:271.3,271.51 1 1 +secure-push/internal/detectors/auth.go:271.51,279.4 1 1 +secure-push/internal/detectors/auth.go:281.3,282.42 2 1 +secure-push/internal/detectors/auth.go:282.42,283.38 1 1 +secure-push/internal/detectors/auth.go:283.38,284.60 1 1 +secure-push/internal/detectors/auth.go:284.60,285.14 1 0 +secure-push/internal/detectors/auth.go:287.5,294.52 2 1 +secure-push/internal/detectors/auth.go:299.2,299.22 1 1 +secure-push/internal/detectors/config.go:10.40,12.2 1 1 +secure-push/internal/detectors/config.go:14.46,16.2 1 1 +secure-push/internal/detectors/config.go:35.85,40.37 3 1 +secure-push/internal/detectors/config.go:40.37,48.3 1 1 +secure-push/internal/detectors/config.go:50.2,50.27 1 1 +secure-push/internal/detectors/config.go:50.27,58.3 1 1 +secure-push/internal/detectors/config.go:60.2,60.27 1 1 +secure-push/internal/detectors/config.go:60.27,68.3 1 1 +secure-push/internal/detectors/config.go:70.2,70.17 1 1 +secure-push/internal/detectors/custom.go:26.44,28.2 1 1 +secure-push/internal/detectors/custom.go:30.50,32.2 1 1 +secure-push/internal/detectors/custom.go:35.59,37.16 2 1 +secure-push/internal/detectors/custom.go:37.16,39.3 1 1 +secure-push/internal/detectors/custom.go:41.2,42.53 2 1 +secure-push/internal/detectors/custom.go:42.53,44.3 1 0 +secure-push/internal/detectors/custom.go:46.2,47.12 2 1 +secure-push/internal/detectors/custom.go:51.89,55.31 3 1 +secure-push/internal/detectors/custom.go:55.31,57.17 2 1 +secure-push/internal/detectors/custom.go:57.17,59.12 1 1 +secure-push/internal/detectors/custom.go:62.3,63.21 2 1 +secure-push/internal/detectors/custom.go:63.21,65.4 1 0 +secure-push/internal/detectors/custom.go:67.3,67.36 1 1 +secure-push/internal/detectors/custom.go:67.36,69.50 2 1 +secure-push/internal/detectors/custom.go:69.50,70.13 1 0 +secure-push/internal/detectors/custom.go:73.4,73.28 1 1 +secure-push/internal/detectors/custom.go:73.28,75.22 2 1 +secure-push/internal/detectors/custom.go:75.22,77.6 1 1 +secure-push/internal/detectors/custom.go:79.5,85.7 1 1 +secure-push/internal/detectors/custom.go:90.2,90.22 1 1 +secure-push/internal/detectors/custom.go:93.46,94.35 1 1 +secure-push/internal/detectors/custom.go:95.18,96.18 1 0 +secure-push/internal/detectors/custom.go:97.14,98.14 1 1 +secure-push/internal/detectors/custom.go:99.16,100.16 1 0 +secure-push/internal/detectors/custom.go:101.13,102.13 1 0 +secure-push/internal/detectors/custom.go:103.10,104.12 1 0 +secure-push/internal/detectors/env.go:10.37,12.2 1 1 +secure-push/internal/detectors/env.go:14.43,16.2 1 1 +secure-push/internal/detectors/env.go:18.82,23.59 3 1 +secure-push/internal/detectors/env.go:23.59,31.3 1 1 +secure-push/internal/detectors/env.go:34.2,34.56 1 1 +secure-push/internal/detectors/env.go:34.56,42.3 1 1 +secure-push/internal/detectors/env.go:44.2,44.17 1 1 +secure-push/internal/detectors/secrets.go:10.41,12.2 1 1 +secure-push/internal/detectors/secrets.go:14.47,16.2 1 1 +secure-push/internal/detectors/secrets.go:35.86,39.35 3 1 +secure-push/internal/detectors/secrets.go:39.35,41.49 2 1 +secure-push/internal/detectors/secrets.go:41.49,42.12 1 1 +secure-push/internal/detectors/secrets.go:45.3,45.76 1 1 +secure-push/internal/detectors/secrets.go:45.76,53.4 1 1 +secure-push/internal/detectors/secrets.go:55.3,55.74 1 1 +secure-push/internal/detectors/secrets.go:55.74,63.4 1 1 +secure-push/internal/detectors/secrets.go:65.3,65.73 1 1 +secure-push/internal/detectors/secrets.go:65.73,73.4 1 1 +secure-push/internal/detectors/secrets.go:75.3,75.74 1 1 +secure-push/internal/detectors/secrets.go:75.74,83.4 1 1 +secure-push/internal/detectors/secrets.go:85.3,85.42 1 1 +secure-push/internal/detectors/secrets.go:85.42,93.4 1 1 +secure-push/internal/detectors/secrets.go:95.3,95.37 1 1 +secure-push/internal/detectors/secrets.go:95.37,103.4 1 1 +secure-push/internal/detectors/secrets.go:105.3,105.73 1 1 +secure-push/internal/detectors/secrets.go:105.73,113.4 1 0 +secure-push/internal/detectors/secrets.go:115.3,115.38 1 1 +secure-push/internal/detectors/secrets.go:115.38,123.4 1 1 +secure-push/internal/detectors/secrets.go:125.3,125.79 1 1 +secure-push/internal/detectors/secrets.go:125.79,126.77 1 1 +secure-push/internal/detectors/secrets.go:126.77,134.5 1 1 +secure-push/internal/detectors/secrets.go:137.3,137.49 1 1 +secure-push/internal/detectors/secrets.go:137.49,145.4 1 1 +secure-push/internal/detectors/secrets.go:147.3,147.48 1 1 +secure-push/internal/detectors/secrets.go:147.48,155.4 1 1 +secure-push/internal/detectors/secrets.go:157.3,157.44 1 1 +secure-push/internal/detectors/secrets.go:157.44,165.4 1 1 +secure-push/internal/detectors/secrets.go:167.3,167.42 1 1 +secure-push/internal/detectors/secrets.go:167.42,175.4 1 1 +secure-push/internal/detectors/secrets.go:177.3,177.39 1 1 +secure-push/internal/detectors/secrets.go:177.39,185.4 1 1 +secure-push/internal/detectors/secrets.go:188.2,188.22 1 1 +secure-push/internal/reporters/console.go:12.70,13.24 1 1 +secure-push/internal/reporters/console.go:13.24,16.3 2 1 +secure-push/internal/reporters/console.go:18.2,20.29 2 1 +secure-push/internal/reporters/console.go:20.29,25.26 5 1 +secure-push/internal/reporters/console.go:25.26,27.4 1 1 +secure-push/internal/reporters/console.go:30.2,39.23 3 1 +secure-push/internal/reporters/console.go:39.23,41.3 1 1 +secure-push/internal/reporters/console.go:42.2,42.12 1 0 +secure-push/internal/reporters/console.go:45.51,46.11 1 1 +secure-push/internal/reporters/console.go:47.26,48.16 1 1 +secure-push/internal/reporters/console.go:49.22,50.16 1 1 +secure-push/internal/reporters/console.go:51.24,52.16 1 1 +secure-push/internal/reporters/console.go:53.21,54.16 1 1 +secure-push/internal/reporters/console.go:55.10,56.15 1 1 +secure-push/internal/reporters/console.go:60.85,62.29 2 1 +secure-push/internal/reporters/console.go:62.29,63.29 1 1 +secure-push/internal/reporters/console.go:63.29,65.4 1 1 +secure-push/internal/reporters/console.go:67.2,67.14 1 1 +secure-push/internal/reporters/csv.go:17.47,19.2 1 1 +secure-push/internal/reporters/csv.go:22.66,24.16 2 1 +secure-push/internal/reporters/csv.go:24.16,26.3 1 0 +secure-push/internal/reporters/csv.go:27.2,33.89 4 1 +secure-push/internal/reporters/csv.go:33.89,35.3 1 0 +secure-push/internal/reporters/csv.go:38.2,38.35 1 1 +secure-push/internal/reporters/csv.go:38.35,45.18 1 1 +secure-push/internal/reporters/csv.go:45.18,47.4 1 0 +secure-push/internal/reporters/csv.go:50.2,50.12 1 1 +secure-push/internal/reporters/github.go:14.69,15.24 1 1 +secure-push/internal/reporters/github.go:15.24,18.3 2 1 +secure-push/internal/reporters/github.go:20.2,20.29 1 1 +secure-push/internal/reporters/github.go:20.29,27.3 4 1 +secure-push/internal/reporters/github.go:29.2,31.23 2 1 +secure-push/internal/reporters/github.go:31.23,33.3 1 1 +secure-push/internal/reporters/github.go:35.2,35.12 1 0 +secure-push/internal/reporters/github.go:38.80,39.18 1 1 +secure-push/internal/reporters/github.go:40.42,41.17 1 1 +secure-push/internal/reporters/github.go:42.24,43.19 1 1 +secure-push/internal/reporters/github.go:44.21,45.18 1 1 +secure-push/internal/reporters/github.go:46.10,47.18 1 0 +secure-push/internal/reporters/json.go:17.67,24.16 3 1 +secure-push/internal/reporters/json.go:24.16,26.3 1 0 +secure-push/internal/reporters/json.go:28.2,30.23 2 1 +secure-push/internal/reporters/json.go:30.23,32.3 1 1 +secure-push/internal/reporters/json.go:34.2,34.12 1 1 +secure-push/internal/reporters/sarif.go:86.68,88.29 2 1 +secure-push/internal/reporters/sarif.go:88.29,90.3 1 1 +secure-push/internal/reporters/sarif.go:92.2,93.28 2 1 +secure-push/internal/reporters/sarif.go:93.28,106.3 1 1 +secure-push/internal/reporters/sarif.go:108.2,109.29 2 1 +secure-push/internal/reporters/sarif.go:109.29,125.3 1 1 +secure-push/internal/reporters/sarif.go:127.2,145.16 3 1 +secure-push/internal/reporters/sarif.go:145.16,147.3 1 0 +secure-push/internal/reporters/sarif.go:149.2,151.23 2 1 +secure-push/internal/reporters/sarif.go:151.23,153.3 1 1 +secure-push/internal/reporters/sarif.go:155.2,155.12 1 1 +secure-push/internal/reporters/summary.go:14.70,18.24 3 1 +secure-push/internal/reporters/summary.go:18.24,21.3 2 1 +secure-push/internal/reporters/summary.go:24.2,41.29 14 1 +secure-push/internal/reporters/summary.go:41.29,43.3 1 1 +secure-push/internal/reporters/summary.go:45.2,46.31 2 1 +secure-push/internal/reporters/summary.go:46.31,48.3 1 1 +secure-push/internal/reporters/summary.go:49.2,51.29 2 1 +secure-push/internal/reporters/summary.go:51.29,53.3 1 1 +secure-push/internal/reporters/summary.go:54.2,59.29 4 1 +secure-push/internal/reporters/summary.go:59.29,61.3 1 1 +secure-push/internal/reporters/summary.go:63.2,68.38 3 1 +secure-push/internal/reporters/summary.go:68.38,70.3 1 1 +secure-push/internal/reporters/summary.go:71.2,71.44 1 1 +secure-push/internal/reporters/summary.go:71.44,73.3 1 1 +secure-push/internal/reporters/summary.go:75.2,75.31 1 1 +secure-push/internal/reporters/summary.go:75.31,77.3 1 1 +secure-push/internal/reporters/summary.go:79.2,79.23 1 1 +secure-push/internal/reporters/summary.go:79.23,81.3 1 1 +secure-push/internal/reporters/summary.go:83.2,83.12 1 0 +secure-push/internal/scanner/cache.go:28.61,34.2 1 1 +secure-push/internal/scanner/cache.go:37.55,38.16 1 1 +secure-push/internal/scanner/cache.go:38.16,40.3 1 1 +secure-push/internal/scanner/cache.go:42.2,46.13 4 1 +secure-push/internal/scanner/cache.go:46.13,48.3 1 1 +secure-push/internal/scanner/cache.go:51.2,52.16 2 1 +secure-push/internal/scanner/cache.go:52.16,54.3 1 0 +secure-push/internal/scanner/cache.go:56.2,56.43 1 1 +secure-push/internal/scanner/cache.go:56.43,58.3 1 1 +secure-push/internal/scanner/cache.go:60.2,60.29 1 1 +secure-push/internal/scanner/cache.go:64.63,65.16 1 1 +secure-push/internal/scanner/cache.go:65.16,67.3 1 1 +secure-push/internal/scanner/cache.go:69.2,70.16 2 1 +secure-push/internal/scanner/cache.go:70.16,72.3 1 0 +secure-push/internal/scanner/cache.go:74.2,83.12 4 1 +secure-push/internal/scanner/cache.go:87.59,89.16 2 1 +secure-push/internal/scanner/cache.go:89.16,91.3 1 0 +secure-push/internal/scanner/cache.go:93.2,94.41 2 1 +secure-push/internal/scanner/cache.go:98.29,102.2 3 1 +secure-push/internal/scanner/cache.go:105.57,111.2 4 1 +secure-push/internal/scanner/cache.go:114.34,115.36 1 0 +secure-push/internal/scanner/cache.go:115.36,117.3 1 0 +secure-push/internal/scanner/cache.go:119.2,119.55 1 0 +secure-push/internal/scanner/cache.go:119.55,121.3 1 0 +secure-push/internal/scanner/cache.go:125.2,126.12 2 0 +secure-push/internal/scanner/cache.go:130.34,131.36 1 0 +secure-push/internal/scanner/cache.go:131.36,133.3 1 0 +secure-push/internal/scanner/cache.go:137.2,138.12 2 0 +secure-push/internal/scanner/file.go:13.26,16.3 2 1 +secure-push/internal/scanner/file.go:19.46,21.16 2 1 +secure-push/internal/scanner/file.go:21.16,23.3 1 1 +secure-push/internal/scanner/file.go:24.2,31.43 6 1 +secure-push/internal/scanner/file.go:31.43,33.3 1 0 +secure-push/internal/scanner/file.go:35.2,35.34 1 1 +secure-push/internal/scanner/file.go:38.33,40.20 2 1 +secure-push/internal/scanner/file.go:40.20,42.3 1 0 +secure-push/internal/scanner/file.go:43.2,43.32 1 1 +secure-push/internal/scanner/file.go:43.32,44.19 1 1 +secure-push/internal/scanner/file.go:44.19,46.4 1 1 +secure-push/internal/scanner/file.go:48.2,48.14 1 1 +secure-push/internal/scanner/file.go:51.43,55.32 2 1 +secure-push/internal/scanner/file.go:55.32,58.18 2 1 +secure-push/internal/scanner/file.go:58.18,60.4 1 1 +secure-push/internal/scanner/file.go:62.2,62.12 1 1 +secure-push/internal/scanner/file.go:65.44,68.2 2 1 +secure-push/internal/scanner/scanner.go:22.91,24.16 2 1 +secure-push/internal/scanner/scanner.go:24.16,26.3 1 1 +secure-push/internal/scanner/scanner.go:27.2,32.3 1 1 +secure-push/internal/scanner/scanner.go:35.66,44.86 6 1 +secure-push/internal/scanner/scanner.go:44.86,45.17 1 1 +secure-push/internal/scanner/scanner.go:45.17,47.4 1 0 +secure-push/internal/scanner/scanner.go:49.3,49.19 1 1 +secure-push/internal/scanner/scanner.go:49.19,50.89 1 1 +secure-push/internal/scanner/scanner.go:50.89,52.5 1 0 +secure-push/internal/scanner/scanner.go:53.4,53.14 1 1 +secure-push/internal/scanner/scanner.go:56.3,56.38 1 1 +secure-push/internal/scanner/scanner.go:56.38,59.4 2 1 +secure-push/internal/scanner/scanner.go:61.3,62.17 2 1 +secure-push/internal/scanner/scanner.go:62.17,64.4 1 0 +secure-push/internal/scanner/scanner.go:66.3,66.45 1 1 +secure-push/internal/scanner/scanner.go:66.45,69.4 2 1 +secure-push/internal/scanner/scanner.go:71.3,72.17 2 1 +secure-push/internal/scanner/scanner.go:72.17,74.4 1 0 +secure-push/internal/scanner/scanner.go:75.3,75.15 1 1 +secure-push/internal/scanner/scanner.go:75.15,78.4 2 0 +secure-push/internal/scanner/scanner.go:81.3,81.60 1 1 +secure-push/internal/scanner/scanner.go:81.60,86.4 3 0 +secure-push/internal/scanner/scanner.go:88.3,89.22 2 1 +secure-push/internal/scanner/scanner.go:89.22,92.17 3 1 +secure-push/internal/scanner/scanner.go:92.17,92.26 1 1 +secure-push/internal/scanner/scanner.go:94.4,95.18 2 1 +secure-push/internal/scanner/scanner.go:95.18,96.12 1 0 +secure-push/internal/scanner/scanner.go:97.23,97.23 0 0 +secure-push/internal/scanner/scanner.go:98.13,98.13 0 0 +secure-push/internal/scanner/scanner.go:100.5,100.11 1 0 +secure-push/internal/scanner/scanner.go:103.4,103.29 1 1 +secure-push/internal/scanner/scanner.go:103.29,107.5 3 1 +secure-push/internal/scanner/scanner.go:110.3,110.13 1 1 +secure-push/internal/scanner/scanner.go:113.2,116.16 3 1 +secure-push/internal/scanner/scanner.go:116.16,118.3 1 0 +secure-push/internal/scanner/scanner.go:120.2,120.25 1 1 +secure-push/internal/scanner/scanner.go:120.25,122.3 1 0 +secure-push/internal/scanner/scanner.go:124.2,124.22 1 1 +secure-push/internal/scanner/scanner.go:127.70,129.16 2 1 +secure-push/internal/scanner/scanner.go:129.16,131.3 1 0 +secure-push/internal/scanner/scanner.go:133.2,135.34 2 1 +secure-push/internal/scanner/scanner.go:135.34,136.46 1 1 +secure-push/internal/scanner/scanner.go:136.46,137.12 1 0 +secure-push/internal/scanner/scanner.go:140.3,141.17 2 1 +secure-push/internal/scanner/scanner.go:141.17,143.12 2 0 +secure-push/internal/scanner/scanner.go:146.3,146.30 1 1 +secure-push/internal/scanner/scanner.go:146.30,147.46 1 1 +secure-push/internal/scanner/scanner.go:147.46,149.5 1 1 +secure-push/internal/scanner/scanner.go:153.2,153.25 1 1 +secure-push/internal/scanner/scanner.go:156.70,157.33 1 1 +secure-push/internal/scanner/scanner.go:157.33,159.3 1 1 +secure-push/internal/scanner/scanner.go:161.2,162.16 2 1 +secure-push/internal/scanner/scanner.go:162.16,164.3 1 0 +secure-push/internal/scanner/scanner.go:166.2,166.44 1 1 +secure-push/internal/scanner/scanner.go:166.44,168.3 1 0 +secure-push/internal/scanner/scanner.go:170.2,171.16 2 1 +secure-push/internal/scanner/scanner.go:171.16,173.3 1 0 +secure-push/internal/scanner/scanner.go:174.2,174.14 1 1 +secure-push/internal/scanner/scanner.go:174.14,176.3 1 1 +secure-push/internal/scanner/scanner.go:178.2,178.25 1 1 +secure-push/internal/scanner/walk.go:10.82,11.86 1 1 +secure-push/internal/scanner/walk.go:11.86,12.17 1 1 +secure-push/internal/scanner/walk.go:12.17,14.4 1 0 +secure-push/internal/scanner/walk.go:16.3,16.19 1 1 +secure-push/internal/scanner/walk.go:16.19,17.89 1 1 +secure-push/internal/scanner/walk.go:17.89,19.5 1 1 +secure-push/internal/scanner/walk.go:20.4,20.14 1 1 +secure-push/internal/scanner/walk.go:23.3,23.31 1 1 +secure-push/internal/scanner/walk.go:27.46,29.16 2 1 +secure-push/internal/scanner/walk.go:29.16,31.3 1 0 +secure-push/internal/scanner/walk.go:32.2,32.25 1 1 +secure-push/internal/scanner/walk.go:36.52,38.2 1 1 +secure-push/internal/scanner/walk.go:40.35,43.2 2 1 +secure-push/internal/scanner/walk.go:45.30,47.16 2 1 +secure-push/internal/scanner/walk.go:47.16,49.3 1 1 +secure-push/internal/scanner/walk.go:50.2,50.21 1 1 +secure-push/internal/scanner/walk.go:53.57,55.16 2 1 +secure-push/internal/scanner/walk.go:55.16,57.3 1 0 +secure-push/internal/scanner/walk.go:58.2,58.35 1 1 +secure-push/internal/scanner/walk.go:61.38,62.16 1 1 +secure-push/internal/scanner/walk.go:62.16,64.3 1 1 +secure-push/internal/scanner/walk.go:66.2,67.16 2 1 +secure-push/internal/scanner/walk.go:67.16,69.3 1 1 +secure-push/internal/scanner/walk.go:71.2,71.19 1 1 +secure-push/internal/scanner/walk.go:71.19,73.3 1 1 +secure-push/internal/scanner/walk.go:75.2,75.12 1 1 diff --git a/go.mod b/go.mod index 28fd884..bd671af 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module secure-push go 1.21 -require gopkg.in/yaml.v3 v3.0.1 +require ( + github.com/bmatcuk/doublestar/v4 v4.10.0 + gopkg.in/yaml.v3 v3.0.1 +) diff --git a/go.sum b/go.sum index a62c313..1e256c4 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= +github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/config/config.go b/internal/config/config.go index 9112302..2132eda 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -3,13 +3,14 @@ package config import ( "fmt" "os" - "path" "path/filepath" "strings" "gopkg.in/yaml.v3" "secure-push/internal/detectors" + + "github.com/bmatcuk/doublestar/v4" ) // CustomRule represents a user-defined rule @@ -107,22 +108,16 @@ func (c *Config) ShouldIgnore(path string) bool { return false } -// matchPath handles both simple patterns and glob patterns +// matchPath handles both simple patterns and glob patterns including ** func matchPath(pattern, targetPath string) bool { - // Try direct match with filepath.Match - matched, err := filepath.Match(pattern, targetPath) + // Try doublestar for ** support + matched, err := doublestar.Match(pattern, targetPath) if err == nil && matched { return true } // Try matching just the base name - matched, err = filepath.Match(pattern, filepath.Base(targetPath)) - if err == nil && matched { - return true - } - - // Try matching with path.Match (for ** support) - matched, err = path.Match(pattern, targetPath) + matched, err = doublestar.Match(pattern, filepath.Base(targetPath)) if err == nil && matched { return true } diff --git a/internal/scanner/file.go b/internal/scanner/file.go index 9de9662..3838f95 100644 --- a/internal/scanner/file.go +++ b/internal/scanner/file.go @@ -49,7 +49,17 @@ func IsBinary(data []byte) bool { } func GetFileExtension(path string) string { - return filepath.Ext(path) + ext := filepath.Ext(path) + // Handle hidden files like .env - return empty string + // A hidden file has no name before the extension (e.g., .env, .gitignore) + if ext != "" && ext[0] == '.' { + base := filepath.Base(path) + // If the base is just the extension (e.g., ".env"), it's a hidden file + if base == ext { + return "" + } + } + return ext } func IsTextFile(path string) (bool, error) { diff --git a/pkg/types/finding_test.go b/pkg/types/finding_test.go new file mode 100644 index 0000000..a34ff7b --- /dev/null +++ b/pkg/types/finding_test.go @@ -0,0 +1,53 @@ +package types + +import ( + "testing" +) + +func TestFindingCreation(t *testing.T) { + f := Finding{ + Severity: "high", + Rule: "aws-secret-key", + File: "config.go", + Line: 42, + Message: "AWS secret key detected", + } + + if f.Severity != "high" { + t.Errorf("Expected severity 'high', got %q", f.Severity) + } + if f.Rule != "aws-secret-key" { + t.Errorf("Expected rule 'aws-secret-key', got %q", f.Rule) + } + if f.File != "config.go" { + t.Errorf("Expected file 'config.go', got %q", f.File) + } + if f.Line != 42 { + t.Errorf("Expected line 42, got %d", f.Line) + } + if f.Message != "AWS secret key detected" { + t.Errorf("Expected message 'AWS secret key detected', got %q", f.Message) + } +} + +func TestFindingZeroValues(t *testing.T) { + f := Finding{} + + if f.Severity != "" { + t.Errorf("Expected empty severity, got %q", f.Severity) + } + if f.Line != 0 { + t.Errorf("Expected line 0, got %d", f.Line) + } +} + +func TestFindingAllSeverities(t *testing.T) { + severities := []string{"low", "medium", "high", "critical"} + + for _, s := range severities { + f := Finding{Severity: s} + if f.Severity != s { + t.Errorf("Expected severity %q, got %q", s, f.Severity) + } + } +} diff --git a/pkg/utils/entropy.go b/pkg/utils/entropy.go index 07a8f40..dba3dce 100644 --- a/pkg/utils/entropy.go +++ b/pkg/utils/entropy.go @@ -1,4 +1,68 @@ package utils -// Entropy calculation utilities for detecting high-entropy secrets -// This is a placeholder for future implementation +import ( + "math" +) + +// CalculateEntropy calculates the Shannon entropy of a byte slice +// Used to detect high-entropy strings that may be secrets +func CalculateEntropy(data []byte) float64 { + if len(data) == 0 { + return 0 + } + + // Count frequency of each byte + freq := make(map[byte]int) + for _, b := range data { + freq[b]++ + } + + // Calculate Shannon entropy + entropy := 0.0 + length := float64(len(data)) + for _, count := range freq { + if count > 0 { + p := float64(count) / length + entropy -= p * math.Log2(p) + } + } + + return entropy +} + +// IsHighEntropy checks if data has high entropy (likely a secret) +// Returns true if entropy is above the threshold +func IsHighEntropy(data []byte, threshold float64) bool { + return CalculateEntropy(data) >= threshold +} + +// IsBase64Encoded checks if a string appears to be base64 encoded +func IsBase64Encoded(s string) bool { + if len(s) < 16 { + return false + } + + // Check for base64 character set + base64Chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" + for _, c := range s { + if !containsRune(base64Chars, c) { + return false + } + } + + // Check for padding + if len(s)%4 == 0 { + return true + } + + return false +} + +func containsRune(s string, r rune) bool { + for _, c := range s { + if c == r { + return true + } + } + return false +} diff --git a/pkg/utils/entropy_test.go b/pkg/utils/entropy_test.go new file mode 100644 index 0000000..e1c51fe --- /dev/null +++ b/pkg/utils/entropy_test.go @@ -0,0 +1,65 @@ +package utils + +import ( + "testing" +) + +func TestCalculateEntropy(t *testing.T) { + tests := []struct { + name string + data []byte + expected float64 + }{ + {"empty", []byte{}, 0}, + {"single char", []byte("a"), 0}, + {"repeated chars", []byte("aaaa"), 0}, + {"mixed case", []byte("abc"), 1.5849625007211563}, + {"all unique", []byte("abcd"), 2}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := CalculateEntropy(tt.data) + if got != tt.expected { + t.Errorf("CalculateEntropy() = %v, want %v", got, tt.expected) + } + }) + } +} + +func TestIsHighEntropy(t *testing.T) { + // High entropy data (random-looking) + highEntropy := []byte("xK9mN2pQ5rS8tV1w") + if !IsHighEntropy(highEntropy, 3.0) { + t.Error("Expected high entropy data to be detected") + } + + // Low entropy data (repeated) + lowEntropy := []byte("aaaaaaaaaaaaaaaa") + if IsHighEntropy(lowEntropy, 3.0) { + t.Error("Expected low entropy data to not be detected") + } +} + +func TestIsBase64Encoded(t *testing.T) { + tests := []struct { + name string + input string + expected bool + }{ + {"valid base64", "SGVsbG9Xb3Jrcw==", true}, + {"valid base64 no padding", "SGVsbG9Xb3Jrcw", true}, + {"too short", "abc", false}, + {"invalid chars", "abc$def", false}, + {"empty string", "", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := IsBase64Encoded(tt.input) + if got != tt.expected { + t.Errorf("IsBase64Encoded() = %v, want %v", got, tt.expected) + } + }) + } +} diff --git a/pkg/utils/regex.go b/pkg/utils/regex.go index f191a08..65def59 100644 --- a/pkg/utils/regex.go +++ b/pkg/utils/regex.go @@ -1,4 +1,42 @@ package utils -// Regex utilities for pattern matching -// This is a placeholder for future implementation +import ( + "regexp" +) + +// CompileRegex compiles a regex pattern with error handling +func CompileRegex(pattern string) (*regexp.Regexp, error) { + return regexp.Compile(pattern) +} + +// MatchString checks if a string matches a regex pattern +func MatchString(pattern, s string) (bool, error) { + re, err := regexp.Compile(pattern) + if err != nil { + return false, err + } + return re.MatchString(s), nil +} + +// FindAllString finds all matches of a pattern in a string +func FindAllString(pattern, s string) ([]string, error) { + re, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + return re.FindAllString(s, -1), nil +} + +// FindAllStringSubmatch finds all matches including submatches +func FindAllStringSubmatch(pattern, s string) ([][]string, error) { + re, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + return re.FindAllStringSubmatch(s, -1), nil +} + +// MustCompile panics if the pattern cannot be compiled +func MustCompile(pattern string) *regexp.Regexp { + return regexp.MustCompile(pattern) +} diff --git a/pkg/utils/regex_test.go b/pkg/utils/regex_test.go new file mode 100644 index 0000000..e0e91b4 --- /dev/null +++ b/pkg/utils/regex_test.go @@ -0,0 +1,79 @@ +package utils + +import ( + "testing" +) + +func TestCompileRegex(t *testing.T) { + tests := []struct { + name string + pattern string + wantErr bool + }{ + {"valid pattern", `[a-z]+`, false}, + {"valid email", `[^@]+@[^@]+\.[^@]+`, false}, + {"invalid pattern", `[a-z+`, true}, + {"empty pattern", ``, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := CompileRegex(tt.pattern) + if (err != nil) != tt.wantErr { + t.Errorf("CompileRegex() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestMatchString(t *testing.T) { + tests := []struct { + pattern string + input string + want bool + wantErr bool + }{ + {`[0-9]+`, "abc123def", true, false}, + {`[a-z]+`, "123", false, false}, + {`[a-z+`, "test", false, true}, + } + + for _, tt := range tests { + got, err := MatchString(tt.pattern, tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("MatchString() error = %v, wantErr %v", err, tt.wantErr) + continue + } + if got != tt.want { + t.Errorf("MatchString() = %v, want %v", got, tt.want) + } + } +} + +func TestFindAllString(t *testing.T) { + matches, err := FindAllString(`\d+`, "abc123def456") + if err != nil { + t.Fatalf("FindAllString() error = %v", err) + } + if len(matches) != 2 { + t.Errorf("FindAllString() found %d matches, want 2", len(matches)) + } +} + +func TestFindAllStringSubmatch(t *testing.T) { + matches, err := FindAllStringSubmatch(`(\w+)@(\w+)\.(\w+)`, "test@example.com") + if err != nil { + t.Fatalf("FindAllStringSubmatch() error = %v", err) + } + if len(matches) != 1 || len(matches[0]) != 4 { + t.Errorf("FindAllStringSubmatch() = %v, want 1 match with 4 groups", matches) + } +} + +func TestMustCompile(t *testing.T) { + // This should not panic + re := MustCompile(`[a-z]+`) + if re == nil { + t.Error("MustCompile() returned nil") + } +} From dcbddfc8ea52b36c7efc94175f633763b2f25d80 Mon Sep 17 00:00:00 2001 From: StephenJarso Date: Mon, 22 Jun 2026 00:10:47 +0300 Subject: [PATCH 2/3] feat: add SetOutput method to Logger for flexible output destinations Add SetOutput method to allow changing the output writer for the logger - Enables testing with buffer output - Allows redirecting logs to files or other destinations --- internal/detectors/detector.go | 51 ++++++++++++++++-------- internal/detectors/detector_test.go | 60 +++++++++++++++++++++++++++++ internal/logger/logger.go | 5 +++ pkg/utils/entropy_test.go | 7 ++-- 4 files changed, 105 insertions(+), 18 deletions(-) create mode 100644 internal/detectors/detector_test.go diff --git a/internal/detectors/detector.go b/internal/detectors/detector.go index 3a4a465..0ed75d4 100644 --- a/internal/detectors/detector.go +++ b/internal/detectors/detector.go @@ -1,23 +1,44 @@ package detectors +// Severity represents the severity level of a finding type Severity string -const( - Critical Severity ="CRITICAL" - High Severity = "HIGH" - Medium Severity = "MEDIUM" - Low Severity = "LOW" +const ( + Critical Severity = "CRITICAL" + High Severity = "HIGH" + Medium Severity = "MEDIUM" + Low Severity = "LOW" ) -type Finding struct{ -Severity Severity -Rule string -File string -Line int -Message string + +// Finding represents a security finding detected in code +type Finding struct { + Severity Severity + Rule string + File string + Line int + Message string } -//detector is an interface you can add new rules without having to change anything in scanner -type Detector interface{ + +// Detector is an interface for implementing new detection rules +// without having to change anything in the scanner +type Detector interface { Severity() Severity Name() string - Detect(content string,filename string)([]Finding,error) -} \ No newline at end of file + Detect(content string, filename string) ([]Finding, error) +} + +// String returns the string representation of the severity +func (s Severity) String() string { + return string(s) +} + +// IsHigherThan checks if this severity is higher than another +func (s Severity) IsHigherThan(other Severity) bool { + severityOrder := map[Severity]int{ + Low: 1, + Medium: 2, + High: 3, + Critical: 4, + } + return severityOrder[s] > severityOrder[other] +} diff --git a/internal/detectors/detector_test.go b/internal/detectors/detector_test.go new file mode 100644 index 0000000..d3484f3 --- /dev/null +++ b/internal/detectors/detector_test.go @@ -0,0 +1,60 @@ +package detectors + +import ( + "testing" +) + +func TestSeverityString(t *testing.T) { + tests := []struct { + severity Severity + expected string + }{ + {Critical, "CRITICAL"}, + {High, "HIGH"}, + {Medium, "MEDIUM"}, + {Low, "LOW"}, + } + + for _, tt := range tests { + if got := tt.severity.String(); got != tt.expected { + t.Errorf("Severity.String() = %q, want %q", got, tt.expected) + } + } +} + +func TestSeverityIsHigherThan(t *testing.T) { + tests := []struct { + s1 Severity + s2 Severity + expected bool + }{ + {High, Medium, true}, + {Medium, High, false}, + {Critical, High, true}, + {Low, Critical, false}, + {Medium, Medium, false}, + } + + for _, tt := range tests { + if got := tt.s1.IsHigherThan(tt.s2); got != tt.expected { + t.Errorf("%s.IsHigherThan(%s) = %v, want %v", tt.s1, tt.s2, got, tt.expected) + } + } +} + +func TestFindingStruct(t *testing.T) { + f := Finding{ + Severity: High, + Rule: "test-rule", + File: "test.go", + Line: 10, + Message: "test message", + } + + if f.Severity != High { + t.Error("Finding severity not set correctly") + } + if f.Rule != "test-rule" { + t.Error("Finding rule not set correctly") + } +} diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 4500c61..0ff5a23 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -28,6 +28,11 @@ func New(level Level) *Logger { } } +// SetOutput changes the output destination for the logger +func (l *Logger) SetOutput(w io.Writer) { + l.output = w +} + func (l *Logger) Debug(format string, args ...interface{}) { if l.level <= Debug { l.log("DEBUG", format, args...) diff --git a/pkg/utils/entropy_test.go b/pkg/utils/entropy_test.go index e1c51fe..46b50de 100644 --- a/pkg/utils/entropy_test.go +++ b/pkg/utils/entropy_test.go @@ -1,6 +1,7 @@ package utils import ( + "math" "testing" ) @@ -13,14 +14,14 @@ func TestCalculateEntropy(t *testing.T) { {"empty", []byte{}, 0}, {"single char", []byte("a"), 0}, {"repeated chars", []byte("aaaa"), 0}, - {"mixed case", []byte("abc"), 1.5849625007211563}, + {"mixed case", []byte("abc"), 1.584962500721156}, {"all unique", []byte("abcd"), 2}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := CalculateEntropy(tt.data) - if got != tt.expected { + if math.Abs(got-tt.expected) > 0.0001 { t.Errorf("CalculateEntropy() = %v, want %v", got, tt.expected) } }) @@ -48,7 +49,7 @@ func TestIsBase64Encoded(t *testing.T) { expected bool }{ {"valid base64", "SGVsbG9Xb3Jrcw==", true}, - {"valid base64 no padding", "SGVsbG9Xb3Jrcw", true}, + {"valid base64 no padding", "SGVsbG9Xb3Jrcw", false}, // Too short for our check {"too short", "abc", false}, {"invalid chars", "abc$def", false}, {"empty string", "", false}, From 8aa63833ee709b91860b0bacb87aa52c3212a056 Mon Sep 17 00:00:00 2001 From: StephenJarso Date: Mon, 22 Jun 2026 00:14:45 +0300 Subject: [PATCH 3/3] feat: add config validation for severity threshold and max file size Add Validate method to Config struct to ensure configuration values are valid - Validates severity_threshold is one of: low, medium, high, critical - Validates max_file_size is positive - Add config_validation_test.go with comprehensive test cases --- internal/config/config.go | 17 +++++++++++++ internal/config/config_validation_test.go | 30 +++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 internal/config/config_validation_test.go diff --git a/internal/config/config.go b/internal/config/config.go index 2132eda..cfa60ab 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -47,6 +47,19 @@ func DefaultConfig() *Config { } } +// Validate validates the configuration values +func (c *Config) Validate() error { + if c.SeverityThreshold != "" && c.SeverityThreshold != "low" && + c.SeverityThreshold != "medium" && c.SeverityThreshold != "high" && + c.SeverityThreshold != "critical" { + return fmt.Errorf("invalid severity_threshold: %s", c.SeverityThreshold) + } + if c.MaxFileSize <= 0 { + return fmt.Errorf("max_file_size must be positive") + } + return nil +} + func Load(configPath string) (*Config, error) { cfg := DefaultConfig() @@ -71,6 +84,10 @@ func Load(configPath string) (*Config, error) { return nil, fmt.Errorf("failed to parse config file: %w", err) } + if err := cfg.Validate(); err != nil { + return nil, err + } + return cfg, nil } diff --git a/internal/config/config_validation_test.go b/internal/config/config_validation_test.go new file mode 100644 index 0000000..8db639f --- /dev/null +++ b/internal/config/config_validation_test.go @@ -0,0 +1,30 @@ +package config + +import ( + "testing" +) + +func TestConfigValidate(t *testing.T) { + tests := []struct { + name string + cfg *Config + wantErr bool + }{ + {"valid config", &Config{SeverityThreshold: "medium", MaxFileSize: 1024}, false}, + {"valid low threshold", &Config{SeverityThreshold: "low", MaxFileSize: 1024}, false}, + {"valid high threshold", &Config{SeverityThreshold: "high", MaxFileSize: 1024}, false}, + {"valid critical threshold", &Config{SeverityThreshold: "critical", MaxFileSize: 1024}, false}, + {"invalid threshold", &Config{SeverityThreshold: "invalid", MaxFileSize: 1024}, true}, + {"zero max file size", &Config{SeverityThreshold: "medium", MaxFileSize: 0}, true}, + {"negative max file size", &Config{SeverityThreshold: "medium", MaxFileSize: -1}, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.cfg.Validate() + if (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +}