Skip to content
Open
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,21 @@ Initial release of SlimStack - Dependency hygiene and waste elimination CLI tool
- **CLI Commands**
- `slim version` - Display version
- `slim help` - Show usage help
- `slim man` - Display detailed manual
- `slim scan -py` - Scan Python dependencies
- `slim scan -node` - Scan Node.js dependencies
- `slim prune -py` - Remove unused Python packages
- `slim prune -node` - Remove unused Node packages
- `slim disk` - Disk usage analysis
- `slim docker` - Dockerfile security and optimization analysis

- **Dockerfile Analysis** (NEW)
- Security anti-pattern detection (secrets in ENV, running as root, etc.)
- Hardened image recommendations (Chainguard, Alpine, distroless)
- Multi-stage build detection
- Best practice suggestions (HEALTHCHECK, COPY vs ADD, etc.)
- Severity filtering and security-only mode
- JSON output for CI/CD pipelines

- **Safety Features**
- Read-only scan operations by default
Expand All @@ -57,6 +67,7 @@ Initial release of SlimStack - Dependency hygiene and waste elimination CLI tool
- **Documentation**
- Comprehensive README with usage examples
- Unix man page (`man slim`)
- Built-in manual (`slim man`)

## [Unreleased]

Expand Down
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,60 @@ Node.js node_modules:
324.1 MB api-server/node_modules
```

### Docker Commands

```bash
# Analyze Dockerfile for security and optimization issues
slim docker

# Output as JSON (for CI/CD)
slim docker --json

# Only show security issues
slim docker --security-only

# Filter by severity (critical, warning, info)
slim docker --severity warning
```

**Example output:**
```text
SlimStack Dockerfile Analysis
================================

Dockerfile: Dockerfile
Base images: 1
Multi-stage: No
Runs as non-root: No
Has HEALTHCHECK: No

Issues Found (4):

🔴 Line 2: Potential secret exposed in ENV instruction
Category: security
→ Use Docker secrets or mount secrets at runtime instead of ENV

🟡 Line 1: Container runs as root (no USER instruction)
Category: security
→ Add 'USER nonroot' or 'USER 1000' to run as non-root user

Image Recommendations:

Current: python:3.12
Recommended: python:3.12-slim
Reason: smaller - Debian slim variant, ~100MB smaller
Size: ~150MB

Current: python:3.12
Recommended: cgr.dev/chainguard/python:latest
Reason: hardened - Chainguard hardened image, zero CVEs
Size: ~50MB

────────────────────────────────
Summary: 1 critical, 2 warnings, 1 info
2 image recommendations
```

## Safety

SlimStack is designed with safety as a priority:
Expand Down
16 changes: 16 additions & 0 deletions man/slim.1
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,22 @@ Scan a specific directory instead of current directory.
.TP
.B slim disk \-\-json
Output disk usage as JSON.
.SS "Docker Commands"
.TP
.B slim docker
Analyze Dockerfile for security issues and optimization opportunities. Suggests hardened image alternatives.
.TP
.B slim docker \-\-json
Output analysis results as JSON for CI/CD pipelines.
.TP
.B slim docker \-\-path \fIPATH\fR
Analyze Dockerfile in a specific directory.
.TP
.B slim docker \-\-severity \fILEVEL\fR
Filter issues by minimum severity (critical, warning, info).
.TP
.B slim docker \-\-security\-only
Only show security-related issues.
.SH OPTIONS
.TP
.B \-py, \-\-py
Expand Down
128 changes: 128 additions & 0 deletions slim/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
slim disk Show disk usage by ecosystem
slim disk --by project Show disk usage by project
slim disk --top 10 Limit results

slim docker Analyze Dockerfile for issues
slim docker --json Output as JSON
"""

import argparse
Expand Down Expand Up @@ -673,6 +676,121 @@ def cmd_disk(args: argparse.Namespace) -> int:
return 0


def cmd_docker_scan(args: argparse.Namespace) -> int:
"""Scan Dockerfile for security issues and optimization opportunities."""
from slim.scanners.docker_scanner import scan_dockerfile, get_scan_result_dict

project_path = Path(args.path) if args.path else None
result = scan_dockerfile(project_path=project_path)

if args.json:
output_json(get_scan_result_dict(result))
return 0

# Human-readable output
colors_enabled = is_tty()

print()
if colors_enabled:
print(f"{Colors.BOLD}SlimStack Dockerfile Analysis{Colors.RESET}")
else:
print("SlimStack Dockerfile Analysis")
print("=" * 32)

# Check if Dockerfile was found
if not result.base_images:
if result.issues and result.issues[0].message == "No Dockerfile found":
error("No Dockerfile found in project.")
return 1

# Show basic info
print(f"\nDockerfile: {result.dockerfile_path}")
print(f"Base images: {len(result.base_images)}")
print(f"Multi-stage: {'Yes' if result.multi_stage else 'No'}")
print(f"Runs as non-root: {'Yes' if result.has_user_instruction else 'No'}")
print(f"Has HEALTHCHECK: {'Yes' if result.has_healthcheck else 'No'}")

# Filter issues by severity if specified
issues = result.issues
min_severity = getattr(args, 'severity', None)
if min_severity:
severity_order = {"critical": 0, "warning": 1, "info": 2}
min_level = severity_order.get(min_severity.lower(), 2)
issues = [i for i in issues if severity_order.get(i.severity, 2) <= min_level]

# Filter by security only if specified
if getattr(args, 'security_only', False):
issues = [i for i in issues if i.category == "security"]

# Show issues
if issues:
print()
if colors_enabled:
print(f"{Colors.YELLOW}Issues Found ({len(issues)}):{Colors.RESET}")
else:
print(f"Issues Found ({len(issues)}):")

for issue in issues:
# Severity icon and color
if issue.severity == "critical":
icon = "🔴" if colors_enabled else "[CRITICAL]"
color = Colors.RED if colors_enabled else ""
elif issue.severity == "warning":
icon = "🟡" if colors_enabled else "[WARNING]"
color = Colors.YELLOW if colors_enabled else ""
else:
icon = "🔵" if colors_enabled else "[INFO]"
color = Colors.CYAN if colors_enabled else ""

reset = Colors.RESET if colors_enabled else ""

print(f"\n {icon} {color}Line {issue.line_number}: {issue.message}{reset}")
print(f" Category: {issue.category}")
if colors_enabled:
print(f" {Colors.DIM}→ {issue.suggestion}{Colors.RESET}")
else:
print(f" → {issue.suggestion}")
else:
print(f"\n{Colors.GREEN if colors_enabled else ''}✓ No issues found!{Colors.RESET if colors_enabled else ''}")

# Show recommendations
if result.recommendations:
print()
if colors_enabled:
print(f"{Colors.CYAN}Image Recommendations:{Colors.RESET}")
else:
print("Image Recommendations:")

shown = set()
for rec in result.recommendations:
if rec.recommended_image in shown:
continue
shown.add(rec.recommended_image)

print(f"\n Current: {rec.current_image}")
if colors_enabled:
print(f" {Colors.GREEN}Recommended: {rec.recommended_image}{Colors.RESET}")
else:
print(f" Recommended: {rec.recommended_image}")
print(f" Reason: {rec.reason} - {rec.description}")
if rec.size_estimate:
print(f" Size: {rec.size_estimate}")

# Summary
critical = sum(1 for i in result.issues if i.severity == "critical")
warning = sum(1 for i in result.issues if i.severity == "warning")
info_count = sum(1 for i in result.issues if i.severity == "info")

print(f"\n{'─' * 32}")
print(f"Summary: {critical} critical, {warning} warnings, {info_count} info")

if result.recommendations:
print(f" {len(result.recommendations)} image recommendations")

# Return non-zero if critical issues found
return 1 if critical > 0 else 0


def create_parser() -> argparse.ArgumentParser:
"""Create the argument parser."""
parser = argparse.ArgumentParser(
Expand Down Expand Up @@ -724,6 +842,16 @@ def create_parser() -> argparse.ArgumentParser:
disk_parser.add_argument("--path", "-p", help="Path to scan (default: current directory)")
disk_parser.set_defaults(func=cmd_disk)

# docker scan command
docker_parser = subparsers.add_parser("docker", help="Dockerfile analysis and optimization")
docker_parser.add_argument("--json", action="store_true", help="Output as JSON")
docker_parser.add_argument("--path", "-p", help="Project path (default: current directory)")
docker_parser.add_argument("--severity", choices=["critical", "warning", "info"],
help="Minimum severity to report")
docker_parser.add_argument("--security-only", action="store_true", dest="security_only",
help="Only show security-related issues")
docker_parser.set_defaults(func=cmd_docker_scan)

return parser


Expand Down
Loading