Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,17 @@ docksec -i myapp:latest --image-only --baseline .docksec-baseline.json --fail-on
# Reduce output to warnings, errors, and the result summary
docksec Dockerfile --scan-only --quiet

# Show INFO-level diagnostic logs on stderr for troubleshooting
docksec Dockerfile --scan-only --verbose

# Disable colored output (also honors the NO_COLOR env var)
docksec Dockerfile --no-color
```

Every scan ends with a result summary: a severity table, the security score with a
rating, a "Quick take" action block, the generated reports, and a suggested next
command. Use `--quiet` for a compact result and `--no-color` for plain output.
command. Use `--quiet` for a compact result, `--verbose` for INFO-level
diagnostic logs, and `--no-color` for plain output.

### Machine-readable output

Expand Down
6 changes: 5 additions & 1 deletion docksec/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def main() -> None:
parser.add_argument('--baseline', dest='baseline', metavar='FILE', help='Path to a baseline file; with --fail-on, only findings not present in the baseline trigger the gate')
parser.add_argument('--update-baseline', dest='update_baseline', action='store_true', help='Write the current scan findings to --baseline instead of gating against it')
parser.add_argument('--quiet', action='store_true', help='Reduce output to warnings, errors, and the result summary')
parser.add_argument('-v', '--verbose', action='store_true', help='Show INFO-level log lines on stderr')
parser.add_argument('--no-color', action='store_true', help='Disable colored output (also honors the NO_COLOR env var)')
parser.add_argument('--version', action='version', version=f'DockSec {get_version()}')

Expand All @@ -98,6 +99,9 @@ def main() -> None:
if args.compact_output:
os.environ["DOCKSEC_COMPACT_OUTPUT"] = "true"

if args.verbose and not os.getenv("DOCKSEC_LOG_LEVEL"):
os.environ["DOCKSEC_LOG_LEVEL"] = "INFO"

# Resolve the severity filter: CLI flag > DOCKSEC_DEFAULT_SEVERITY env > default.
from docksec.config_manager import get_config
from docksec.enums import Severity
Expand Down Expand Up @@ -679,4 +683,4 @@ def _suggest_next_command(args, results, run_ai, run_compose_analysis):
return ""

if __name__ == "__main__":
main()
main()
44 changes: 44 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,50 @@ def test_quiet_and_no_color_flags_are_accepted(self):
main()
self.assertNotEqual(ctx.exception.code, 2)

@patch("sys.argv", ["docksec", "--image-only", "-i", "test:latest", "--verbose"])
@patch("docksec.docker_scanner.DockerSecurityScanner")
def test_verbose_flag_sets_info_log_level(self, mock_scanner_class):
"""--verbose is a shortcut for DOCKSEC_LOG_LEVEL=INFO."""
from docksec.cli import main

scanner = Mock()
mock_scanner_class.return_value = scanner
scanner.run_image_only_scan.return_value = {
"json_data": [],
"dockerfile_scan": {"skipped": True},
"image_scan": {"skipped": False},
"scan_mode": "image_only",
}
scanner.get_security_score.return_value = 90.0
scanner.generate_all_reports.return_value = {}
scanner.RESULTS_DIR = "/tmp"

with patch.dict(os.environ, {}, clear=True):
main()
self.assertEqual(os.environ["DOCKSEC_LOG_LEVEL"], "INFO")

@patch("sys.argv", ["docksec", "--image-only", "-i", "test:latest", "-v"])
@patch("docksec.docker_scanner.DockerSecurityScanner")
def test_verbose_flag_preserves_explicit_log_level(self, mock_scanner_class):
"""An existing DOCKSEC_LOG_LEVEL takes priority over -v."""
from docksec.cli import main

scanner = Mock()
mock_scanner_class.return_value = scanner
scanner.run_image_only_scan.return_value = {
"json_data": [],
"dockerfile_scan": {"skipped": True},
"image_scan": {"skipped": False},
"scan_mode": "image_only",
}
scanner.get_security_score.return_value = 90.0
scanner.generate_all_reports.return_value = {}
scanner.RESULTS_DIR = "/tmp"

with patch.dict(os.environ, {"DOCKSEC_LOG_LEVEL": "DEBUG"}, clear=True):
main()
self.assertEqual(os.environ["DOCKSEC_LOG_LEVEL"], "DEBUG")


class TestCLIHelpers(unittest.TestCase):
"""Test cases for the CLI summary helper functions."""
Expand Down
Loading