shieldops-cli 1.0.1__tar.gz → 1.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {shieldops_cli-1.0.1/shieldops_cli.egg-info → shieldops_cli-1.0.3}/PKG-INFO +24 -13
  2. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/README.md +23 -12
  3. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/pyproject.toml +1 -1
  4. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/__init__.py +1 -1
  5. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/commands/analyze.py +42 -9
  6. shieldops_cli-1.0.3/shieldops_cli/local_analyzer.py +218 -0
  7. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3/shieldops_cli.egg-info}/PKG-INFO +24 -13
  8. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli.egg-info/SOURCES.txt +1 -0
  9. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/LICENSE +0 -0
  10. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/setup.cfg +0 -0
  11. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/api_client.py +0 -0
  12. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/auth.py +0 -0
  13. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/commands/__init__.py +0 -0
  14. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/commands/autofix.py +0 -0
  15. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/commands/compose_gen.py +0 -0
  16. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/commands/compose_scan.py +0 -0
  17. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/commands/config_cmd.py +0 -0
  18. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/commands/k8s_scan.py +0 -0
  19. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/commands/sbom.py +0 -0
  20. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/commands/scan_image.py +0 -0
  21. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/commands/tui.py +0 -0
  22. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/config.py +0 -0
  23. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/formatters/__init__.py +0 -0
  24. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/formatters/json_fmt.py +0 -0
  25. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/formatters/sarif.py +0 -0
  26. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/formatters/summary.py +0 -0
  27. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/formatters/table.py +0 -0
  28. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli/main.py +0 -0
  29. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli.egg-info/dependency_links.txt +0 -0
  30. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli.egg-info/entry_points.txt +0 -0
  31. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli.egg-info/requires.txt +0 -0
  32. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/shieldops_cli.egg-info/top_level.txt +0 -0
  33. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/tests/test_analyze.py +0 -0
  34. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/tests/test_auth.py +0 -0
  35. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/tests/test_formatters.py +0 -0
  36. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/tests/test_phase2_validation.py +0 -0
  37. {shieldops_cli-1.0.1 → shieldops_cli-1.0.3}/tests/test_score_zero_bug.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shieldops-cli
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: ShieldOps AI — Security scanner CLI for Docker, Kubernetes, Compose, SBOM, and more.
5
5
  Author-email: ShieldOps AI <support@shieldops.ai>
6
6
  License-Expression: MIT
@@ -40,11 +40,11 @@ Dynamic: license-file
40
40
  [![Powered by ShieldOps AI](https://img.shields.io/badge/powered%20by-ShieldOps%20AI-8B5CF6)](https://shieldops-ai.dev)
41
41
 
42
42
  <p align="center">
43
- <img src="docs/screenshots/tui-session.svg" alt="ShieldOps TUI interactive session" width="800">
43
+ <img src="https://raw.githubusercontent.com/mohammedabdallahcv-creator/shieldops-cli/main/docs/screenshots/tui-session.png" alt="ShieldOps TUI interactive session" width="800">
44
44
  </p>
45
45
 
46
46
  <p align="center">
47
- <img src="docs/screenshots/cli-output.svg" alt="ShieldOps CLI scan results" width="800">
47
+ <img src="https://raw.githubusercontent.com/mohammedabdallahcv-creator/shieldops-cli/main/docs/screenshots/cli-output.png" alt="ShieldOps CLI scan results" width="800">
48
48
  </p>
49
49
 
50
50
  ---
@@ -64,7 +64,8 @@ Most Dockerfile/K8s scanners tell you **what** is wrong. ShieldOps CLI also tell
64
64
  | Docker image scan | Yes | No | Yes (built-in) |
65
65
  | Interactive TUI | Yes | No | No |
66
66
  | CI/CD ready (`--fail-on`) | Yes | Yes | Yes |
67
- | Free tier | Yes (5 scans/day) | Yes | Yes |
67
+ | Free tier (local) | Unlimited scans, no signup | Yes | Yes |
68
+ | Cloud AI analysis | With API key (5 free/day) | — | — |
68
69
 
69
70
  ### What makes it different
70
71
 
@@ -81,14 +82,21 @@ Most Dockerfile/K8s scanners tell you **what** is wrong. ShieldOps CLI also tell
81
82
  # 1. Install
82
83
  pip install shieldops-cli
83
84
 
84
- # 2. Login (free tier 5 scans/day)
85
- shieldops login
86
-
87
- # 3. Scan your Dockerfile
85
+ # 2. Scan your Dockerfile (localno login needed)
88
86
  shieldops analyze Dockerfile
89
87
  ```
90
88
 
91
- That's it. You get severity-graded findings, compliance mapping (CIS, SOC 2, NIST), and AI remediation guidance.
89
+ That's it. You get severity-graded findings with 10+ built-in rules no signup, no API key.
90
+
91
+ For AI-powered analysis with deeper scanning:
92
+
93
+ ```bash
94
+ # 3. Login (free tier — 5 scans/day)
95
+ shieldops login
96
+
97
+ # 4. Scan with cloud AI
98
+ shieldops analyze Dockerfile --api
99
+ ```
92
100
 
93
101
  ---
94
102
 
@@ -118,11 +126,14 @@ pip install shieldops-cli
118
126
 
119
127
  ### `analyze` — Dockerfile Security Scan
120
128
 
129
+ Runs locally by default (no API key). Use `--api` for cloud AI analysis.
130
+
121
131
  ```bash
122
- shieldops analyze Dockerfile
123
- shieldops analyze Dockerfile --format json
124
- shieldops analyze Dockerfile --fail-on high # CI/CD gate
125
- shieldops analyze Dockerfile --open-report # open browser report
132
+ shieldops analyze Dockerfile # local (free, unlimited)
133
+ shieldops analyze Dockerfile --api # cloud AI (requires login)
134
+ shieldops analyze Dockerfile --format json --output report.json
135
+ shieldops analyze Dockerfile --fail-on high # CI/CD gate
136
+ shieldops analyze Dockerfile --open-report # open browser report
126
137
  ```
127
138
 
128
139
  ### `autofix` — AI-Powered Dockerfile Fix
@@ -9,11 +9,11 @@
9
9
  [![Powered by ShieldOps AI](https://img.shields.io/badge/powered%20by-ShieldOps%20AI-8B5CF6)](https://shieldops-ai.dev)
10
10
 
11
11
  <p align="center">
12
- <img src="docs/screenshots/tui-session.svg" alt="ShieldOps TUI interactive session" width="800">
12
+ <img src="https://raw.githubusercontent.com/mohammedabdallahcv-creator/shieldops-cli/main/docs/screenshots/tui-session.png" alt="ShieldOps TUI interactive session" width="800">
13
13
  </p>
14
14
 
15
15
  <p align="center">
16
- <img src="docs/screenshots/cli-output.svg" alt="ShieldOps CLI scan results" width="800">
16
+ <img src="https://raw.githubusercontent.com/mohammedabdallahcv-creator/shieldops-cli/main/docs/screenshots/cli-output.png" alt="ShieldOps CLI scan results" width="800">
17
17
  </p>
18
18
 
19
19
  ---
@@ -33,7 +33,8 @@ Most Dockerfile/K8s scanners tell you **what** is wrong. ShieldOps CLI also tell
33
33
  | Docker image scan | Yes | No | Yes (built-in) |
34
34
  | Interactive TUI | Yes | No | No |
35
35
  | CI/CD ready (`--fail-on`) | Yes | Yes | Yes |
36
- | Free tier | Yes (5 scans/day) | Yes | Yes |
36
+ | Free tier (local) | Unlimited scans, no signup | Yes | Yes |
37
+ | Cloud AI analysis | With API key (5 free/day) | — | — |
37
38
 
38
39
  ### What makes it different
39
40
 
@@ -50,14 +51,21 @@ Most Dockerfile/K8s scanners tell you **what** is wrong. ShieldOps CLI also tell
50
51
  # 1. Install
51
52
  pip install shieldops-cli
52
53
 
53
- # 2. Login (free tier 5 scans/day)
54
- shieldops login
55
-
56
- # 3. Scan your Dockerfile
54
+ # 2. Scan your Dockerfile (localno login needed)
57
55
  shieldops analyze Dockerfile
58
56
  ```
59
57
 
60
- That's it. You get severity-graded findings, compliance mapping (CIS, SOC 2, NIST), and AI remediation guidance.
58
+ That's it. You get severity-graded findings with 10+ built-in rules no signup, no API key.
59
+
60
+ For AI-powered analysis with deeper scanning:
61
+
62
+ ```bash
63
+ # 3. Login (free tier — 5 scans/day)
64
+ shieldops login
65
+
66
+ # 4. Scan with cloud AI
67
+ shieldops analyze Dockerfile --api
68
+ ```
61
69
 
62
70
  ---
63
71
 
@@ -87,11 +95,14 @@ pip install shieldops-cli
87
95
 
88
96
  ### `analyze` — Dockerfile Security Scan
89
97
 
98
+ Runs locally by default (no API key). Use `--api` for cloud AI analysis.
99
+
90
100
  ```bash
91
- shieldops analyze Dockerfile
92
- shieldops analyze Dockerfile --format json
93
- shieldops analyze Dockerfile --fail-on high # CI/CD gate
94
- shieldops analyze Dockerfile --open-report # open browser report
101
+ shieldops analyze Dockerfile # local (free, unlimited)
102
+ shieldops analyze Dockerfile --api # cloud AI (requires login)
103
+ shieldops analyze Dockerfile --format json --output report.json
104
+ shieldops analyze Dockerfile --fail-on high # CI/CD gate
105
+ shieldops analyze Dockerfile --open-report # open browser report
95
106
  ```
96
107
 
97
108
  ### `autofix` — AI-Powered Dockerfile Fix
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "shieldops-cli"
7
- version = "1.0.1"
7
+ version = "1.0.3"
8
8
  description = "ShieldOps AI — Security scanner CLI for Docker, Kubernetes, Compose, SBOM, and more."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """ShieldOps AI CLI — Security scanner for Docker, Kubernetes, Compose, SBOM, and more."""
2
2
 
3
- __version__ = "1.0.1"
3
+ __version__ = "1.0.3"
@@ -1,11 +1,13 @@
1
- """shieldops analyze — Dockerfile analysis."""
1
+ """shieldops analyze — Dockerfile analysis (local or cloud)."""
2
2
  import sys
3
3
  import click
4
4
  from pathlib import Path
5
5
  from rich.console import Console
6
6
 
7
+ from shieldops_cli import config as cfg
7
8
  from shieldops_cli.api_client import ShieldOpsClient, ApiError
8
9
  from shieldops_cli.formatters import format_result
10
+ from shieldops_cli.local_analyzer import analyze_dockerfile as local_analyze
9
11
 
10
12
  console = Console()
11
13
 
@@ -19,23 +21,58 @@ SEVERITY_ORDER = {"critical": 0, "high": 1, "medium": 2, "low": 3}
19
21
  @click.option("-o", "--output", type=click.Path(), default=None,
20
22
  help="Write output to file instead of stdout.")
21
23
  @click.option("--open-report", is_flag=True, default=False,
22
- help="Open the full report in browser after scan.")
24
+ help="Open the full report in browser after scan (cloud only).")
23
25
  @click.option("--fail-on", type=click.Choice(["critical", "high", "medium", "low", "none"]),
24
26
  default="none", help="Exit with code 1 if issues >= severity (for CI/CD).")
25
- def analyze(file, fmt, output, open_report, fail_on):
27
+ @click.option("--api", "force_api", is_flag=True, default=False,
28
+ help="Force cloud analysis (requires login).")
29
+ def analyze(file, fmt, output, open_report, fail_on, force_api):
26
30
  """Analyze a Dockerfile for security and best-practice issues.
27
31
 
32
+ Runs locally by default (no API key needed). Use --api for cloud analysis.
33
+
28
34
  \b
29
35
  Examples:
30
36
  shieldops analyze Dockerfile
37
+ shieldops analyze Dockerfile --api # cloud (requires login)
31
38
  shieldops analyze Dockerfile --format json --output report.json
32
- shieldops analyze Dockerfile --fail-on high # CI/CD gate
33
- shieldops analyze Dockerfile --open-report
39
+ shieldops analyze Dockerfile --fail-on high # CI/CD gate
34
40
  """
35
41
  path = Path(file)
36
42
  content = path.read_text(encoding="utf-8")
37
43
  filename = path.name
38
44
 
45
+ api_key = cfg.get_api_key()
46
+
47
+ if api_key or force_api:
48
+ if not api_key:
49
+ console.print("[red]Cloud analysis requires login. Run: shieldops login --key <YOUR_KEY>[/red]")
50
+ sys.exit(2)
51
+ _run_cloud_analysis(content, filename, fmt, output, open_report, fail_on)
52
+ else:
53
+ _run_local_analysis(content, filename, fmt, output, fail_on)
54
+
55
+
56
+ def _run_local_analysis(content: str, filename: str, fmt, output, fail_on):
57
+ result = local_analyze(content, filename)
58
+ formatted = format_result("analyze", result, fmt=fmt or "table")
59
+
60
+ if output:
61
+ Path(output).write_text(formatted, encoding="utf-8")
62
+ console.print(f"[green]\u2705 Report saved to {output}[/green]")
63
+ else:
64
+ console.print(formatted)
65
+
66
+ console.print("\n[dim][Local analysis - 10 rules] Sign up for cloud analysis with AI:[/dim]")
67
+ console.print(f"[dim] {cfg.get_api_url()}/settings/api-keys[/dim]")
68
+
69
+ if fail_on != "none":
70
+ if check_severity_gate(result, fail_on):
71
+ console.print(f"\n[red]\u274c Issues found at {fail_on.upper()} or above. Failing.[/red]")
72
+ sys.exit(1)
73
+
74
+
75
+ def _run_cloud_analysis(content: str, filename: str, fmt, output, open_report, fail_on):
39
76
  client = ShieldOpsClient()
40
77
 
41
78
  with console.status("[bold blue]Analyzing...", spinner="dots"):
@@ -47,7 +84,6 @@ def analyze(file, fmt, output, open_report, fail_on):
47
84
 
48
85
  result = payload.get("result", {})
49
86
 
50
- # ── Format output ──
51
87
  formatted = format_result("analyze", result, fmt=fmt or "table")
52
88
 
53
89
  if output:
@@ -56,7 +92,6 @@ def analyze(file, fmt, output, open_report, fail_on):
56
92
  else:
57
93
  console.print(formatted)
58
94
 
59
- # ── Report URL (canonical: result.report_url → top-level route → scan_id fallback) ──
60
95
  report_url = (
61
96
  result.get("report_url")
62
97
  or payload.get("route")
@@ -70,14 +105,12 @@ def analyze(file, fmt, output, open_report, fail_on):
70
105
  base = client.api_url
71
106
  full_url = report_url if report_url.startswith("http") else f"{base}{report_url}"
72
107
  print(f"\nFull report: {full_url}")
73
-
74
108
  if open_report:
75
109
  import webbrowser
76
110
  webbrowser.open(full_url)
77
111
  else:
78
112
  print("\nNo report URL returned for this scan.")
79
113
 
80
- # ── CI/CD exit code ──
81
114
  if fail_on != "none":
82
115
  if check_severity_gate(result, fail_on):
83
116
  console.print(f"\n[red]\u274c Issues found at {fail_on.upper()} or above. Failing.[/red]")
@@ -0,0 +1,218 @@
1
+ """Local Dockerfile analyzer — works offline, no API key needed."""
2
+ from __future__ import annotations
3
+ import re
4
+ from pathlib import Path
5
+
6
+ RULES: list[dict] = [
7
+ # ── DL3007: latest tag ──
8
+ {
9
+ "rule_id": "DL3007",
10
+ "severity": "critical",
11
+ "pattern": re.compile(r"^FROM\s+\S+?:\s*latest\s*$", re.IGNORECASE | re.MULTILINE),
12
+ "message": "Using `latest` tag is dangerous — pinned versions ensure reproducible builds",
13
+ "fix": "Replace `latest` with a specific version tag (e.g. `python:3.11-slim`)",
14
+ "category": "best_practice",
15
+ },
16
+ # ── DL3008: pin apt-get versions ──
17
+ {
18
+ "rule_id": "DL3008",
19
+ "severity": "critical",
20
+ "pattern": re.compile(
21
+ r"RUN\s+(apt-get\s+install\s+)(?!.*\s=\s)",
22
+ re.IGNORECASE | re.MULTILINE,
23
+ ),
24
+ "message": "Pin package versions in `apt-get install` for reproducible builds",
25
+ "fix": "Add `=version` to each package (e.g. `build-essential=12.9`)",
26
+ "category": "best_practice",
27
+ },
28
+ # ── DL3009: apt-get lists not cleaned ──
29
+ {
30
+ "rule_id": "DL3009",
31
+ "severity": "medium",
32
+ "pattern": re.compile(
33
+ r"RUN\s+apt-get\s+install",
34
+ re.IGNORECASE,
35
+ ),
36
+ "message": "Delete apt-get lists after install to reduce image size",
37
+ "fix": "Add `&& rm -rf /var/lib/apt/lists/*` after apt-get install",
38
+ "category": "best_practice",
39
+ "_needs_clean": True,
40
+ },
41
+ # ── DL3013: pin pip versions ──
42
+ {
43
+ "rule_id": "DL3013",
44
+ "severity": "high",
45
+ "pattern": re.compile(
46
+ r"RUN\s+pip\s+install\s+(?!.*==)",
47
+ re.IGNORECASE | re.MULTILINE,
48
+ ),
49
+ "message": "Pin package versions in pip install for reproducible builds",
50
+ "fix": "Use `pip install flask==3.0.0` instead of `pip install flask`",
51
+ "category": "best_practice",
52
+ },
53
+ # ── DL4006: SHELL selected by default ──
54
+ {
55
+ "rule_id": "DL4006",
56
+ "severity": "high",
57
+ "pattern": re.compile(r"^SHELL\s+\[", re.IGNORECASE | re.MULTILINE),
58
+ "message": "SHELL is already selected by default — remove redundant SHELL directive",
59
+ "fix": "Remove the SHELL directive unless you need a non-default shell",
60
+ "category": "style",
61
+ },
62
+ # ── DL4001: Windows line endings ──
63
+ {
64
+ "rule_id": "DL4001",
65
+ "severity": "low",
66
+ "pattern": re.compile(r"\r\n"),
67
+ "message": "Windows-style line endings detected — use LF for Dockerfiles",
68
+ "fix": "Convert to Unix line endings (LF)",
69
+ "category": "style",
70
+ },
71
+ # ── DL3003: no WORKDIR before COPY ──
72
+ {
73
+ "rule_id": "DL3003",
74
+ "severity": "low",
75
+ "pattern": re.compile(r"^COPY\s+\.\s+/"),
76
+ "message": "COPY without prior WORKDIR — files may land in unexpected location",
77
+ "fix": "Add `WORKDIR /app` before COPY",
78
+ "category": "best_practice",
79
+ },
80
+ # ── USER root ──
81
+ {
82
+ "rule_id": "SC1001",
83
+ "severity": "critical",
84
+ "pattern": re.compile(r"^USER\s+root\b", re.IGNORECASE | re.MULTILINE),
85
+ "message": "Running as root increases blast radius in case of container escape",
86
+ "fix": "Use `USER nonroot` or create a dedicated user with `RUN adduser -D appuser && USER appuser`",
87
+ "category": "security",
88
+ },
89
+ # ── EXPOSE without port number ──
90
+ {
91
+ "rule_id": "DL4000",
92
+ "severity": "low",
93
+ "pattern": re.compile(r"^EXPOSE\s*$", re.MULTILINE),
94
+ "message": "EXPOSE without port number has no effect",
95
+ "fix": "Specify a port: `EXPOSE 8080`",
96
+ "category": "best_practice",
97
+ },
98
+ # ── ENV with debug mode in production ──
99
+ {
100
+ "rule_id": "SC1002",
101
+ "severity": "medium",
102
+ "pattern": re.compile(
103
+ r"ENV\s+(FLASK_DEBUG|NODE_ENV|DJANGO_DEBUG|APP_DEBUG)\s*=\s*(1|true|development)\b",
104
+ re.IGNORECASE | re.MULTILINE,
105
+ ),
106
+ "message": "Debug/development mode enabled in production image",
107
+ "fix": "Set `ENV FLASK_DEBUG=0` or remove the ENV line for production images",
108
+ "category": "security",
109
+ },
110
+ # ── No HEALTHCHECK ──
111
+ {
112
+ "rule_id": "DL4002",
113
+ "severity": "low",
114
+ "pattern": re.compile(r"^(?!.*HEALTHCHECK)", re.MULTILINE),
115
+ "message": "No HEALTHCHECK defined — container health won't be monitored",
116
+ "fix": "Add `HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1`",
117
+ "category": "best_practice",
118
+ "_negate": True,
119
+ },
120
+ # ── RUN npm install without cache cleanup ──
121
+ {
122
+ "rule_id": "DL3014",
123
+ "severity": "medium",
124
+ "pattern": re.compile(
125
+ r"RUN\s+npm\s+install\s+(?!.*npm\s+cache\s+clean)",
126
+ re.IGNORECASE | re.MULTILINE,
127
+ ),
128
+ "message": "npm install without cache cleanup increases image size",
129
+ "fix": "Add `&& npm cache clean --force` to the RUN command",
130
+ "category": "best_practice",
131
+ },
132
+ ]
133
+
134
+ FINDING_TEMPLATE = {
135
+ "type": "dockerfile",
136
+ "category": "",
137
+ "severity": "info",
138
+ "rule_id": "",
139
+ "message": "",
140
+ "fix": "",
141
+ "line": 1,
142
+ "column": 1,
143
+ "source": "shieldops-local",
144
+ }
145
+
146
+
147
+ def analyze_dockerfile(content: str, filename: str = "Dockerfile") -> dict:
148
+ lines = content.split("\n")
149
+ findings = []
150
+
151
+ for rule in RULES:
152
+ pattern = rule["pattern"]
153
+ negate = rule.get("_negate", False)
154
+
155
+ if negate:
156
+ if not pattern.search(content):
157
+ finding = dict(FINDING_TEMPLATE)
158
+ finding.update(
159
+ severity=rule["severity"],
160
+ rule_id=rule["rule_id"],
161
+ message=rule["message"],
162
+ fix=rule["fix"],
163
+ category=rule["category"],
164
+ line=1,
165
+ )
166
+ findings.append(finding)
167
+ continue
168
+
169
+ for match in pattern.finditer(content):
170
+ line_num = content[: match.start()].count("\n") + 1
171
+ raw_line = lines[line_num - 1] if line_num <= len(lines) else ""
172
+
173
+ if rule.get("_needs_clean"):
174
+ block = content[match.start() : min(match.start() + 300, len(content))]
175
+ if "rm -rf" in block and "apt/lists" in block:
176
+ continue
177
+
178
+ if rule["rule_id"] == "DL3013":
179
+ rest_of_line = raw_line[match.end() - len(raw_line) :]
180
+ if "==" in rest_of_line or "requirements.txt" in rest_of_line or "-r" in rest_of_line:
181
+ continue
182
+
183
+ if rule["rule_id"] == "DL3008":
184
+ rest = raw_line[match.end() - len(raw_line) :]
185
+ if "=" in rest:
186
+ continue
187
+
188
+ finding = dict(FINDING_TEMPLATE)
189
+ finding.update(
190
+ severity=rule["severity"],
191
+ rule_id=rule["rule_id"],
192
+ message=rule["message"],
193
+ fix=rule["fix"],
194
+ category=rule["category"],
195
+ line=line_num,
196
+ )
197
+ findings.append(finding)
198
+
199
+ critical = sum(1 for f in findings if f["severity"] == "critical")
200
+ high = sum(1 for f in findings if f["severity"] == "high")
201
+ medium = sum(1 for f in findings if f["severity"] == "medium")
202
+ low = sum(1 for f in findings if f["severity"] == "low")
203
+
204
+ score = max(0, 100 - (critical * 25 + high * 10 + medium * 5 + low * 2))
205
+
206
+ return {
207
+ "findings": findings,
208
+ "report_contract": {
209
+ "critical_count": critical,
210
+ "high_count": high,
211
+ "medium_count": medium,
212
+ "low_count": low,
213
+ },
214
+ "security_score": score,
215
+ "security_score_grade": "A" if score >= 90 else "B" if score >= 70 else "C" if score >= 50 else "D" if score >= 30 else "F",
216
+ "source": "local",
217
+ "filename": filename,
218
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shieldops-cli
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: ShieldOps AI — Security scanner CLI for Docker, Kubernetes, Compose, SBOM, and more.
5
5
  Author-email: ShieldOps AI <support@shieldops.ai>
6
6
  License-Expression: MIT
@@ -40,11 +40,11 @@ Dynamic: license-file
40
40
  [![Powered by ShieldOps AI](https://img.shields.io/badge/powered%20by-ShieldOps%20AI-8B5CF6)](https://shieldops-ai.dev)
41
41
 
42
42
  <p align="center">
43
- <img src="docs/screenshots/tui-session.svg" alt="ShieldOps TUI interactive session" width="800">
43
+ <img src="https://raw.githubusercontent.com/mohammedabdallahcv-creator/shieldops-cli/main/docs/screenshots/tui-session.png" alt="ShieldOps TUI interactive session" width="800">
44
44
  </p>
45
45
 
46
46
  <p align="center">
47
- <img src="docs/screenshots/cli-output.svg" alt="ShieldOps CLI scan results" width="800">
47
+ <img src="https://raw.githubusercontent.com/mohammedabdallahcv-creator/shieldops-cli/main/docs/screenshots/cli-output.png" alt="ShieldOps CLI scan results" width="800">
48
48
  </p>
49
49
 
50
50
  ---
@@ -64,7 +64,8 @@ Most Dockerfile/K8s scanners tell you **what** is wrong. ShieldOps CLI also tell
64
64
  | Docker image scan | Yes | No | Yes (built-in) |
65
65
  | Interactive TUI | Yes | No | No |
66
66
  | CI/CD ready (`--fail-on`) | Yes | Yes | Yes |
67
- | Free tier | Yes (5 scans/day) | Yes | Yes |
67
+ | Free tier (local) | Unlimited scans, no signup | Yes | Yes |
68
+ | Cloud AI analysis | With API key (5 free/day) | — | — |
68
69
 
69
70
  ### What makes it different
70
71
 
@@ -81,14 +82,21 @@ Most Dockerfile/K8s scanners tell you **what** is wrong. ShieldOps CLI also tell
81
82
  # 1. Install
82
83
  pip install shieldops-cli
83
84
 
84
- # 2. Login (free tier 5 scans/day)
85
- shieldops login
86
-
87
- # 3. Scan your Dockerfile
85
+ # 2. Scan your Dockerfile (localno login needed)
88
86
  shieldops analyze Dockerfile
89
87
  ```
90
88
 
91
- That's it. You get severity-graded findings, compliance mapping (CIS, SOC 2, NIST), and AI remediation guidance.
89
+ That's it. You get severity-graded findings with 10+ built-in rules no signup, no API key.
90
+
91
+ For AI-powered analysis with deeper scanning:
92
+
93
+ ```bash
94
+ # 3. Login (free tier — 5 scans/day)
95
+ shieldops login
96
+
97
+ # 4. Scan with cloud AI
98
+ shieldops analyze Dockerfile --api
99
+ ```
92
100
 
93
101
  ---
94
102
 
@@ -118,11 +126,14 @@ pip install shieldops-cli
118
126
 
119
127
  ### `analyze` — Dockerfile Security Scan
120
128
 
129
+ Runs locally by default (no API key). Use `--api` for cloud AI analysis.
130
+
121
131
  ```bash
122
- shieldops analyze Dockerfile
123
- shieldops analyze Dockerfile --format json
124
- shieldops analyze Dockerfile --fail-on high # CI/CD gate
125
- shieldops analyze Dockerfile --open-report # open browser report
132
+ shieldops analyze Dockerfile # local (free, unlimited)
133
+ shieldops analyze Dockerfile --api # cloud AI (requires login)
134
+ shieldops analyze Dockerfile --format json --output report.json
135
+ shieldops analyze Dockerfile --fail-on high # CI/CD gate
136
+ shieldops analyze Dockerfile --open-report # open browser report
126
137
  ```
127
138
 
128
139
  ### `autofix` — AI-Powered Dockerfile Fix
@@ -5,6 +5,7 @@ shieldops_cli/__init__.py
5
5
  shieldops_cli/api_client.py
6
6
  shieldops_cli/auth.py
7
7
  shieldops_cli/config.py
8
+ shieldops_cli/local_analyzer.py
8
9
  shieldops_cli/main.py
9
10
  shieldops_cli.egg-info/PKG-INFO
10
11
  shieldops_cli.egg-info/SOURCES.txt
File without changes
File without changes