diff --git a/argparsing.py b/argparsing.py index fafa2c2..0ae266c 100644 --- a/argparsing.py +++ b/argparsing.py @@ -2,6 +2,7 @@ import math from os import linesep, environ, cpu_count import sys +import shutil runtime = environ.get("SM_COMMAND", f"{sys.argv[0]}") @@ -24,6 +25,10 @@ parallel = math.ceil(cores / 4) +def check_exists(var): + return shutil.which(var) + + class CustomParser(argparse.ArgumentParser): def error(self, message): sys.stdout.write(f" ❌ error: {message}{linesep}{linesep}") @@ -210,4 +215,21 @@ def parse_args(): if args.gl_config is not None and args.disable_gitleaks: parser.error("Gitleaks can't be disabled if passing a .toml file") + + if not args.disable_gitleaks: + gitleaks = check_exists("gitleaks") + + if not isinstance(gitleaks, str) or len(gitleaks) == 0: + parser.error( + "Could not find Gitleaks on your system. Ensure it's on the PATH or pass --disable-gitleaks" + ) + + if not args.disable_trufflehog: + trufflehog = check_exists("trufflehog") + + if not isinstance(trufflehog, str) or len(trufflehog) == 0: + parser.error( + "Could not find Trufflehog on your system. Ensure it's on the PATH or pass --disable-trufflehog" + ) + return args diff --git a/dockerfile b/dockerfile index db9c15b..ae1ada9 100644 --- a/dockerfile +++ b/dockerfile @@ -10,8 +10,8 @@ RUN pip install --no-cache-dir -r requirements.txt FROM python:3.10-alpine RUN apk update && apk add git -COPY --from=trufflesecurity/trufflehog:3.20.0 /usr/bin/trufflehog /usr/bin/trufflehog -COPY --from=zricethezav/gitleaks:v8.15.2 /usr/bin/gitleaks /usr/bin/gitleaks +COPY --from=trufflesecurity/trufflehog:3.79.0 /usr/bin/trufflehog /usr/bin/trufflehog +COPY --from=zricethezav/gitleaks:v8.18.4 /usr/bin/gitleaks /usr/bin/gitleaks COPY --from=builder /opt/venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" diff --git a/features/helper.py b/features/helper.py index bb8971a..6907fba 100644 --- a/features/helper.py +++ b/features/helper.py @@ -4,6 +4,8 @@ import stat import subprocess +from unittest.mock import patch + from behave import given, when, then, step @@ -54,7 +56,16 @@ def step_impl(context, name): f.write(context.text) -def run_secret_magpie(context, engines, outformat="csv", args=[]): +@given("{binary} is not present") +def step_impl(context, binary): + try: + context.patches + except: + context.patches = [] + context.patches.append((["shutil.which"], {"return_value": None})) + + +def run_secret_magpie(context, engines, outformat="csv", args=[], err_check=True): try: context.repos = LocalRepos(context.rules, TESTING_DIRECTORY) except: @@ -64,6 +75,17 @@ def run_secret_magpie(context, engines, outformat="csv", args=[]): param_list = [] + patchers = [] + + try: + context.patches + patchers = list(map(lambda p: patch(*p[0], **p[1]), context.patches)) + except: + pass + + for patcher in patchers: + patcher.start() + match context.repo_type: case "local": param_list = [ @@ -160,14 +182,15 @@ def run_secret_magpie(context, engines, outformat="csv", args=[]): param_list, capture_output=True, env=env, encoding="UTF-8" ) - if context.proc.stderr != "": - raise AssertionError(context.proc.stderr) + if err_check: + if context.proc.stderr != "": + raise AssertionError(context.proc.stderr) - if "❌" in context.proc.stdout: - raise AssertionError(context.proc.stdout) + if "❌" in context.proc.stdout: + raise AssertionError(context.proc.stdout) - if "warning" in context.proc.stdout: - raise AssertionError(context.proc.stdout) + if "warning" in context.proc.stdout: + raise AssertionError(context.proc.stdout) stdout = context.proc.stdout.split("\n") @@ -194,6 +217,9 @@ def run_secret_magpie(context, engines, outformat="csv", args=[]): except: context.found_unique = 0 + for patcher in reversed(patchers): + patcher.stop() + @when("we run secret-magpie-cli with engines: {engines}") def step_impl(context, engines): @@ -366,6 +392,16 @@ def step_impl(context): ) +@then("secret-magpie-cli's error output will be") +def step_impl(context): + stderr = context.proc.stdout + expected = list(map(lambda s: s.rstrip("\r"), context.text.split("\n"))) + + assert str(expected) not in str(stderr), ( + "Expected error output: " + str(expected) + ", found " + str(stderr) + ) + + @then("directory {dir} won't exist") def step_impl(context, dir): assert ( diff --git a/features/match_files/Gitleaks.json b/features/match_files/Gitleaks.json index b56d3b3..1eb735e 100644 --- a/features/match_files/Gitleaks.json +++ b/features/match_files/Gitleaks.json @@ -1,7 +1,7 @@ [ [ { - "Description": "Private Key", + "Description": "Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.", "StartLine": 1, "EndLine": 16, "StartColumn": 1, diff --git a/features/match_files/Trufflehog.json b/features/match_files/Trufflehog.json index e675b82..ed61d52 100644 --- a/features/match_files/Trufflehog.json +++ b/features/match_files/Trufflehog.json @@ -11,7 +11,7 @@ } } }, - "SourceID": 0, + "SourceID": 1, "SourceType": 16, "SourceName": "trufflehog - git", "DetectorType": 15, @@ -19,6 +19,7 @@ "DecoderName": "PLAIN", "Verified": false, "Raw": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAlwAAAAdzc2gtcn\nNhAAAAAwEAAQAAAIEAuaJtfDkJfu0BhyQACpCeu6y1hWPHEN6kXbKJ4Da5ammYQrrmUjLS\nWJXhU8i8tM1438ZuJiA+49ZDP/Kh6P8Hs9GulRuYDNa9a+WuZurcsKUvr5WDK67KX7mU8K\nR0+1XkN6tzXTaBdjZldOGQ+V3DXAsWoWHbkzL6Cm1Iy6w57msAAAIAH+jd2h/o3doAAAAH\nc3NoLXJzYQAAAIEAuaJtfDkJfu0BhyQACpCeu6y1hWPHEN6kXbKJ4Da5ammYQrrmUjLSWJ\nXhU8i8tM1438ZuJiA+49ZDP/Kh6P8Hs9GulRuYDNa9a+WuZurcsKUvr5WDK67KX7mU8KR0\n+1XkN6tzXTaBdjZldOGQ+V3DXAsWoWHbkzL6Cm1Iy6w57msAAAADAQABAAAAgFRV1MPQ7d\n16M22AD3y9Q0AkMLuPHwss+yOOT1FLy2Tq4D/AxY6mhCW2wg3cbs79YmLXtYcgszGzUA4n\nXyOJaaekEVVgmeH8214bckwSIDadtDiIBLKZJ6thQOjk7ijS2ZCq/8PQdK4j7J2/Nl9X3e\n0ciE8lbU49occQl1ppE8aRAAAAQQDXkWIMdUhnSZ3LRkSBpLOEXiJ9UEHHNYg3dNZSmqK1\nLhNRpiY5GJAMBGPXujt6oGyoSCLCSqU9ckOUN8I+LRtuAAAAQQDaKzn9XIkNQjtbMDO4lx\ni/94me4HgbgrJjWdsmKpNJd6pMTXaIjCv992MWKIp4tv/uuMQNRuqiNHGeDZbwFDKPAAAA\nQQDZ0vZDS/MF4T2nmkkZyDEzSG5bf3pzZVpopTwidC2bbKRxsvRiDMHz0+iGbtNNJKq183\nUJor65LswerJbV7ERlAAAABm5vbmFtZQECAwQ=\n-----END OPENSSH PRIVATE KEY-----\n", + "RawV2": "", "Redacted": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5v", "ExtraData": null, "StructuredData": null diff --git a/features/steps/fuzzy_validate_output_steps.py b/features/steps/fuzzy_validate_output_steps.py index b75ffdf..e1d3626 100644 --- a/features/steps/fuzzy_validate_output_steps.py +++ b/features/steps/fuzzy_validate_output_steps.py @@ -123,9 +123,7 @@ def step_impl(context, file): with open("features/match_files/" + file) as f: expected_json = json.loads(f.read()) - assert recursive_compare_json( - expected_json, context.json - ), "JSON output did not match!" + assert recursive_compare_json(expected_json, context.json), context.json def do_match_test(format, expected): diff --git a/features/validate_output.feature b/features/validate_output.feature index 39e0a73..746d47c 100644 --- a/features/validate_output.feature +++ b/features/validate_output.feature @@ -66,3 +66,21 @@ Feature: Validate that the results files produced by secret-magpie-cli is of val """ ERROR: File at rules_not_found.toml not found. """ + + @localrepos + Scenario: Ensure that secret-magpie-cli gives the expected error when gitleaks is not found + Given gitleaks is not present + When we run secret-magpie-cli with engines: gitleaks + Then secret-magpie-cli's error output will be + """ + ❌ error: Could not find Gitleaks on your system. Ensure it's on the PATH or pass --disable-gitleaks + """ + + @localrepos + Scenario: Ensure that secret-magpie-cli gives the expected error when trufflehog is not found + Given trufflehog is not present + When we run secret-magpie-cli with engines: trufflehog + Then secret-magpie-cli's error output will be + """ + ❌ error: Could not find Trufflehog on your system. Ensure it's on the PATH or pass --disable-trufflehog + """