77 "variable name matched password/pwd/passwd pattern and value met minimum length"
88)
99TOKEN_REASON = "variable name matched token pattern and value met minimum length"
10+ AWS_REASON = "value matched AKIA-prefixed AWS access key pattern"
1011
1112
1213def run_cli (* args ):
@@ -333,4 +334,258 @@ def test_cli_json_severity_filter_preserves_schema(tmp_path):
333334 "value" : "abcdef" ,
334335 "reason" : PASSWORD_REASON ,
335336 }
336- ]
337+ ]
338+
339+
340+ # Ensure normal text output shows original values when --redact is not used
341+ def test_cli_text_output_is_unredacted_by_default (tmp_path ):
342+ password_file = tmp_path / "password_file.py"
343+ password_file .write_text ('password = "abcdef"\n ' , encoding = "utf-8" )
344+
345+ result = run_cli (str (tmp_path ))
346+
347+ assert result .returncode == 0
348+
349+ assert "abcdef" in result .stdout
350+ assert "a****f" not in result .stdout
351+ assert "[REDACTED]" not in result .stdout
352+
353+
354+ # Ensure text output redacts detected values when --redact is used
355+ def test_cli_text_redact_masks_secret_value (tmp_path ):
356+ password_file = tmp_path / "password_file.py"
357+ password_file .write_text ('password = "abcdef"\n ' , encoding = "utf-8" )
358+
359+ result = run_cli (str (tmp_path ), "--redact" )
360+
361+ assert result .returncode == 0
362+
363+ assert "a****f" in result .stdout
364+ assert "abcdef" not in result .stdout
365+ assert "Reason:" in result .stdout
366+ assert "Password" in result .stdout
367+
368+
369+ # Ensure JSON output shows original values when --redact is not used
370+ def test_cli_json_output_is_unredacted_by_default (tmp_path ):
371+ password_file = tmp_path / "password_file.py"
372+ password_file .write_text ('password = "abcdef"\n ' , encoding = "utf-8" )
373+
374+ result = run_cli (str (tmp_path ), "--json" )
375+
376+ assert result .returncode == 0
377+
378+ data = parse_json_output (result )
379+
380+ assert data == [
381+ {
382+ "line" : 1 ,
383+ "file" : str (password_file ),
384+ "var_name" : "password" ,
385+ "rule_id" : "PASSWORD" ,
386+ "rule" : "Password" ,
387+ "severity" : "HIGH" ,
388+ "value" : "abcdef" ,
389+ "reason" : PASSWORD_REASON ,
390+ }
391+ ]
392+
393+
394+ # Ensure JSON output redacts values when --redact is used
395+ def test_cli_json_redact_masks_secret_value (tmp_path ):
396+ password_file = tmp_path / "password_file.py"
397+ password_file .write_text ('password = "abcdef"\n ' , encoding = "utf-8" )
398+
399+ result = run_cli (str (tmp_path ), "--json" , "--redact" )
400+
401+ assert result .returncode == 0
402+
403+ data = parse_json_output (result )
404+
405+ assert data == [
406+ {
407+ "line" : 1 ,
408+ "file" : str (password_file ),
409+ "var_name" : "password" ,
410+ "rule_id" : "PASSWORD" ,
411+ "rule" : "Password" ,
412+ "severity" : "HIGH" ,
413+ "value" : "a****f" ,
414+ "reason" : PASSWORD_REASON ,
415+ }
416+ ]
417+
418+
419+ # Ensure JSON redaction keeps output machine-readable and pure JSON
420+ def test_cli_json_redact_output_is_pure_json (tmp_path ):
421+ password_file = tmp_path / "password_file.py"
422+ password_file .write_text ('password = "abcdef"\n ' , encoding = "utf-8" )
423+
424+ result = run_cli (str (tmp_path ), "--json" , "--redact" )
425+
426+ assert result .returncode == 0
427+
428+ assert "Scanning" not in result .stdout
429+ assert "--- Findings ---" not in result .stdout
430+ assert "Total findings" not in result .stdout
431+ assert "Reason:" not in result .stdout
432+
433+ data = parse_json_output (result )
434+
435+ assert isinstance (data , list )
436+ assert data [0 ]["value" ] == "a****f"
437+
438+
439+ # Ensure --json, --severity, and --redact work together
440+ def test_cli_json_severity_and_redact_combined (tmp_path ):
441+ findings_file = tmp_path / "findings.py"
442+ findings_file .write_text (
443+ 'password = "abcdef"\n '
444+ 'token = "qwerty123"\n ' ,
445+ encoding = "utf-8" ,
446+ )
447+
448+ result = run_cli (str (tmp_path ), "--json" , "--severity" , "HIGH" , "--redact" )
449+
450+ assert result .returncode == 0
451+
452+ data = parse_json_output (result )
453+
454+ assert data == [
455+ {
456+ "line" : 1 ,
457+ "file" : str (findings_file ),
458+ "var_name" : "password" ,
459+ "rule_id" : "PASSWORD" ,
460+ "rule" : "Password" ,
461+ "severity" : "HIGH" ,
462+ "value" : "a****f" ,
463+ "reason" : PASSWORD_REASON ,
464+ }
465+ ]
466+
467+
468+ # Ensure text severity filtering and redaction work together
469+ def test_cli_text_severity_and_redact_combined (tmp_path ):
470+ findings_file = tmp_path / "findings.py"
471+ findings_file .write_text (
472+ 'password = "abcdef"\n '
473+ 'token = "qwerty123"\n ' ,
474+ encoding = "utf-8" ,
475+ )
476+
477+ result = run_cli (str (tmp_path ), "--severity" , "HIGH" , "--redact" )
478+
479+ assert result .returncode == 0
480+
481+ assert "[HIGH]" in result .stdout
482+ assert "[MEDIUM]" not in result .stdout
483+ assert "a****f" in result .stdout
484+ assert "abcdef" not in result .stdout
485+ assert "qwerty123" not in result .stdout
486+
487+
488+ # Ensure short detected values are fully redacted
489+ def test_cli_json_redact_short_boundary_value (tmp_path ):
490+ password_file = tmp_path / "password_file.py"
491+ password_file .write_text ('password = "abcd"\n ' , encoding = "utf-8" )
492+
493+ result = run_cli (str (tmp_path ), "--json" , "--redact" )
494+
495+ assert result .returncode == 0
496+
497+ data = parse_json_output (result )
498+
499+ assert data == [
500+ {
501+ "line" : 1 ,
502+ "file" : str (password_file ),
503+ "var_name" : "password" ,
504+ "rule_id" : "PASSWORD" ,
505+ "rule" : "Password" ,
506+ "severity" : "HIGH" ,
507+ "value" : "[REDACTED]" ,
508+ "reason" : PASSWORD_REASON ,
509+ }
510+ ]
511+
512+
513+ # Ensure longer detected values preserve only limited prefix/suffix context
514+ def test_cli_json_redact_long_value (tmp_path ):
515+ password_file = tmp_path / "password_file.py"
516+ password_file .write_text ('password = "abc_def-123#$%^&*()"\n ' , encoding = "utf-8" )
517+
518+ result = run_cli (str (tmp_path ), "--json" , "--redact" )
519+
520+ assert result .returncode == 0
521+
522+ data = parse_json_output (result )
523+
524+ assert data == [
525+ {
526+ "line" : 1 ,
527+ "file" : str (password_file ),
528+ "var_name" : "password" ,
529+ "rule_id" : "PASSWORD" ,
530+ "rule" : "Password" ,
531+ "severity" : "HIGH" ,
532+ "value" : "ab***************()" ,
533+ "reason" : PASSWORD_REASON ,
534+ }
535+ ]
536+
537+
538+ # Ensure AWS access key values are redacted in JSON output
539+ def test_cli_json_redact_aws_access_key (tmp_path ):
540+ aws_file = tmp_path / "aws_file.py"
541+ aws_file .write_text (
542+ 'random_var = "AKIAEXAMPLE123456789"\n ' ,
543+ encoding = "utf-8" ,
544+ )
545+
546+ result = run_cli (str (tmp_path ), "--json" , "--redact" )
547+
548+ assert result .returncode == 0
549+
550+ data = parse_json_output (result )
551+
552+ assert data == [
553+ {
554+ "line" : 1 ,
555+ "file" : str (aws_file ),
556+ "var_name" : "random_var" ,
557+ "rule_id" : "AWS_ACCESS_KEY" ,
558+ "rule" : "AWS Access Key" ,
559+ "severity" : "HIGH" ,
560+ "value" : "AK****************89" ,
561+ "reason" : AWS_REASON ,
562+ }
563+ ]
564+
565+
566+ # Ensure no-finding JSON output remains an empty list even with --redact
567+ def test_cli_no_findings_json_with_redact (tmp_path ):
568+ benign_file = tmp_path / "safe.py"
569+ benign_file .write_text ('username = "notsecret"\n ' , encoding = "utf-8" )
570+
571+ result = run_cli (str (tmp_path ), "--json" , "--redact" )
572+
573+ assert result .returncode == 0
574+
575+ data = parse_json_output (result )
576+
577+ assert data == []
578+
579+
580+ # Ensure no-finding text output does not print redaction artifacts
581+ def test_cli_no_findings_text_with_redact (tmp_path ):
582+ benign_file = tmp_path / "safe.py"
583+ benign_file .write_text ('username = "notsecret"\n ' , encoding = "utf-8" )
584+
585+ result = run_cli (str (tmp_path ), "--redact" )
586+
587+ assert result .returncode == 0
588+
589+ assert "No vulnerabilities found." in result .stdout
590+ assert "[REDACTED]" not in result .stdout
591+ assert "Reason:" not in result .stdout
0 commit comments