shieldops-cli 1.0.0__tar.gz → 1.0.2__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.0/shieldops_cli.egg-info → shieldops_cli-1.0.2}/PKG-INFO +26 -15
  2. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/README.md +22 -10
  3. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/pyproject.toml +4 -5
  4. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/__init__.py +1 -1
  5. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/auth.py +1 -1
  6. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/commands/analyze.py +42 -9
  7. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/formatters/sarif.py +1 -1
  8. shieldops_cli-1.0.2/shieldops_cli/local_analyzer.py +218 -0
  9. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2/shieldops_cli.egg-info}/PKG-INFO +26 -15
  10. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli.egg-info/SOURCES.txt +1 -0
  11. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/tests/test_analyze.py +4 -4
  12. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/LICENSE +0 -0
  13. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/setup.cfg +0 -0
  14. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/api_client.py +0 -0
  15. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/commands/__init__.py +0 -0
  16. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/commands/autofix.py +0 -0
  17. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/commands/compose_gen.py +0 -0
  18. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/commands/compose_scan.py +0 -0
  19. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/commands/config_cmd.py +0 -0
  20. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/commands/k8s_scan.py +0 -0
  21. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/commands/sbom.py +0 -0
  22. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/commands/scan_image.py +0 -0
  23. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/commands/tui.py +0 -0
  24. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/config.py +0 -0
  25. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/formatters/__init__.py +0 -0
  26. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/formatters/json_fmt.py +0 -0
  27. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/formatters/summary.py +0 -0
  28. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/formatters/table.py +0 -0
  29. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli/main.py +0 -0
  30. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli.egg-info/dependency_links.txt +0 -0
  31. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli.egg-info/entry_points.txt +0 -0
  32. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli.egg-info/requires.txt +0 -0
  33. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/shieldops_cli.egg-info/top_level.txt +0 -0
  34. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/tests/test_auth.py +0 -0
  35. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/tests/test_formatters.py +0 -0
  36. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/tests/test_phase2_validation.py +0 -0
  37. {shieldops_cli-1.0.0 → shieldops_cli-1.0.2}/tests/test_score_zero_bug.py +0 -0
@@ -1,20 +1,19 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shieldops-cli
3
- Version: 1.0.0
3
+ Version: 1.0.2
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
- License: MIT
7
- Project-URL: Homepage, https://shieldops-ai.onrender.com
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://shieldops-ai.dev
8
8
  Project-URL: Documentation, https://github.com/mohammedabdallahcv-creator/shieldops-cli
9
9
  Project-URL: Repository, https://github.com/mohammedabdallahcv-creator/shieldops-cli
10
- Project-URL: Changelog, https://github.com/mohammedabdallahcv-creator/shieldops-cli/releases
10
+ Project-URL: Changelog, https://github.com/mohammedabdallahcv-creator/shieldops-cli/blob/main/CHANGELOG.md
11
11
  Keywords: docker,kubernetes,security,devsecops,sbom,cli
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Environment :: Console
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: Topic :: Security
16
16
  Classifier: Topic :: Software Development :: Quality Assurance
17
- Classifier: License :: OSI Approved :: MIT License
18
17
  Classifier: Programming Language :: Python :: 3
19
18
  Requires-Python: >=3.9
20
19
  Description-Content-Type: text/markdown
@@ -38,10 +37,14 @@ Dynamic: license-file
38
37
  [![Python](https://img.shields.io/pypi/pyversions/shieldops-cli.svg)](https://pypi.org/project/shieldops-cli/)
39
38
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
40
39
  [![GitHub Stars](https://img.shields.io/github/stars/mohammedabdallahcv-creator/shieldops-cli?style=social)](https://github.com/mohammedabdallahcv-creator/shieldops-cli)
41
- [![Powered by ShieldOps AI](https://img.shields.io/badge/powered%20by-ShieldOps%20AI-8B5CF6)](https://shieldops-ai.onrender.com)
40
+ [![Powered by ShieldOps AI](https://img.shields.io/badge/powered%20by-ShieldOps%20AI-8B5CF6)](https://shieldops-ai.dev)
42
41
 
43
42
  <p align="center">
44
- <img src="docs/screenshots/cli-output.svg" alt="ShieldOps CLI in action" width="800">
43
+ <img src="docs/screenshots/tui-session.svg" alt="ShieldOps TUI interactive session" width="800">
44
+ </p>
45
+
46
+ <p align="center">
47
+ <img src="docs/screenshots/cli-output.svg" alt="ShieldOps CLI scan results" width="800">
45
48
  </p>
46
49
 
47
50
  ---
@@ -61,7 +64,8 @@ Most Dockerfile/K8s scanners tell you **what** is wrong. ShieldOps CLI also tell
61
64
  | Docker image scan | Yes | No | Yes (built-in) |
62
65
  | Interactive TUI | Yes | No | No |
63
66
  | CI/CD ready (`--fail-on`) | Yes | Yes | Yes |
64
- | 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) | — | — |
65
69
 
66
70
  ### What makes it different
67
71
 
@@ -78,14 +82,21 @@ Most Dockerfile/K8s scanners tell you **what** is wrong. ShieldOps CLI also tell
78
82
  # 1. Install
79
83
  pip install shieldops-cli
80
84
 
81
- # 2. Login (free tier 5 scans/day)
82
- shieldops login
83
-
84
- # 3. Scan your Dockerfile
85
+ # 2. Scan your Dockerfile (localno login needed)
85
86
  shieldops analyze Dockerfile
86
87
  ```
87
88
 
88
- 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
+ ```
89
100
 
90
101
  ---
91
102
 
@@ -293,7 +304,7 @@ shieldops-scan:
293
304
  | Policy engine | No | Yes |
294
305
  | Priority queue | No | Yes |
295
306
 
296
- Get your API key at [shieldops-ai.onrender.com](https://shieldops-ai.onrender.com).
307
+ Get your API key at [shieldops-ai.dev](https://shieldops-ai.dev).
297
308
 
298
309
  ---
299
310
 
@@ -348,4 +359,4 @@ MIT
348
359
 
349
360
  ---
350
361
 
351
- ShieldOps CLI is open-source. The analysis backend is proprietary and hosted at [shieldops-ai.onrender.com](https://shieldops-ai.onrender.com).
362
+ ShieldOps CLI is open-source. The analysis backend is proprietary and hosted at [shieldops-ai.dev](https://shieldops-ai.dev).
@@ -6,10 +6,14 @@
6
6
  [![Python](https://img.shields.io/pypi/pyversions/shieldops-cli.svg)](https://pypi.org/project/shieldops-cli/)
7
7
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
8
8
  [![GitHub Stars](https://img.shields.io/github/stars/mohammedabdallahcv-creator/shieldops-cli?style=social)](https://github.com/mohammedabdallahcv-creator/shieldops-cli)
9
- [![Powered by ShieldOps AI](https://img.shields.io/badge/powered%20by-ShieldOps%20AI-8B5CF6)](https://shieldops-ai.onrender.com)
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/cli-output.svg" alt="ShieldOps CLI in action" width="800">
12
+ <img src="docs/screenshots/tui-session.svg" alt="ShieldOps TUI interactive session" width="800">
13
+ </p>
14
+
15
+ <p align="center">
16
+ <img src="docs/screenshots/cli-output.svg" alt="ShieldOps CLI scan results" width="800">
13
17
  </p>
14
18
 
15
19
  ---
@@ -29,7 +33,8 @@ Most Dockerfile/K8s scanners tell you **what** is wrong. ShieldOps CLI also tell
29
33
  | Docker image scan | Yes | No | Yes (built-in) |
30
34
  | Interactive TUI | Yes | No | No |
31
35
  | CI/CD ready (`--fail-on`) | Yes | Yes | Yes |
32
- | 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) | — | — |
33
38
 
34
39
  ### What makes it different
35
40
 
@@ -46,14 +51,21 @@ Most Dockerfile/K8s scanners tell you **what** is wrong. ShieldOps CLI also tell
46
51
  # 1. Install
47
52
  pip install shieldops-cli
48
53
 
49
- # 2. Login (free tier 5 scans/day)
50
- shieldops login
51
-
52
- # 3. Scan your Dockerfile
54
+ # 2. Scan your Dockerfile (localno login needed)
53
55
  shieldops analyze Dockerfile
54
56
  ```
55
57
 
56
- 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
+ ```
57
69
 
58
70
  ---
59
71
 
@@ -261,7 +273,7 @@ shieldops-scan:
261
273
  | Policy engine | No | Yes |
262
274
  | Priority queue | No | Yes |
263
275
 
264
- Get your API key at [shieldops-ai.onrender.com](https://shieldops-ai.onrender.com).
276
+ Get your API key at [shieldops-ai.dev](https://shieldops-ai.dev).
265
277
 
266
278
  ---
267
279
 
@@ -316,4 +328,4 @@ MIT
316
328
 
317
329
  ---
318
330
 
319
- ShieldOps CLI is open-source. The analysis backend is proprietary and hosted at [shieldops-ai.onrender.com](https://shieldops-ai.onrender.com).
331
+ ShieldOps CLI is open-source. The analysis backend is proprietary and hosted at [shieldops-ai.dev](https://shieldops-ai.dev).
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "shieldops-cli"
7
- version = "1.0.0"
7
+ version = "1.0.2"
8
8
  description = "ShieldOps AI — Security scanner CLI for Docker, Kubernetes, Compose, SBOM, and more."
9
9
  readme = "README.md"
10
- license = {text = "MIT"}
10
+ license = "MIT"
11
11
  requires-python = ">=3.9"
12
12
  authors = [{name = "ShieldOps AI", email = "support@shieldops.ai"}]
13
13
  keywords = ["docker", "kubernetes", "security", "devsecops", "sbom", "cli"]
@@ -17,7 +17,6 @@ classifiers = [
17
17
  "Intended Audience :: Developers",
18
18
  "Topic :: Security",
19
19
  "Topic :: Software Development :: Quality Assurance",
20
- "License :: OSI Approved :: MIT License",
21
20
  "Programming Language :: Python :: 3",
22
21
  ]
23
22
  dependencies = [
@@ -34,7 +33,7 @@ tui = ["prompt_toolkit>=3.0.43"]
34
33
  shieldops = "shieldops_cli.main:cli"
35
34
 
36
35
  [project.urls]
37
- Homepage = "https://shieldops-ai.onrender.com"
36
+ Homepage = "https://shieldops-ai.dev"
38
37
  Documentation = "https://github.com/mohammedabdallahcv-creator/shieldops-cli"
39
38
  Repository = "https://github.com/mohammedabdallahcv-creator/shieldops-cli"
40
- Changelog = "https://github.com/mohammedabdallahcv-creator/shieldops-cli/releases"
39
+ Changelog = "https://github.com/mohammedabdallahcv-creator/shieldops-cli/blob/main/CHANGELOG.md"
@@ -1,3 +1,3 @@
1
1
  """ShieldOps AI CLI — Security scanner for Docker, Kubernetes, Compose, SBOM, and more."""
2
2
 
3
- __version__ = "1.0.0"
3
+ __version__ = "1.0.2"
@@ -9,7 +9,7 @@ console = Console()
9
9
 
10
10
  @click.command()
11
11
  @click.option("--key", prompt="API Key", hide_input=True,
12
- help="API key from https://shieldops-ai.onrender.com/settings/api-keys")
12
+ help="API key from https://shieldops-ai.dev/settings/api-keys")
13
13
  @click.option("--url", default=None, help="Override API base URL.")
14
14
  def login(key, url):
15
15
  """Authenticate with your ShieldOps API key."""
@@ -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]")
@@ -50,7 +50,7 @@ def to_sarif(task: str, result: dict) -> str:
50
50
  "tool": {
51
51
  "driver": {
52
52
  "name": "ShieldOps AI",
53
- "informationUri": "https://shieldops-ai.onrender.com",
53
+ "informationUri": "https://shieldops-ai.dev",
54
54
  "version": "1.0.0",
55
55
  "rules": list(rules.values()),
56
56
  }
@@ -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,20 +1,19 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shieldops-cli
3
- Version: 1.0.0
3
+ Version: 1.0.2
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
- License: MIT
7
- Project-URL: Homepage, https://shieldops-ai.onrender.com
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://shieldops-ai.dev
8
8
  Project-URL: Documentation, https://github.com/mohammedabdallahcv-creator/shieldops-cli
9
9
  Project-URL: Repository, https://github.com/mohammedabdallahcv-creator/shieldops-cli
10
- Project-URL: Changelog, https://github.com/mohammedabdallahcv-creator/shieldops-cli/releases
10
+ Project-URL: Changelog, https://github.com/mohammedabdallahcv-creator/shieldops-cli/blob/main/CHANGELOG.md
11
11
  Keywords: docker,kubernetes,security,devsecops,sbom,cli
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Environment :: Console
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: Topic :: Security
16
16
  Classifier: Topic :: Software Development :: Quality Assurance
17
- Classifier: License :: OSI Approved :: MIT License
18
17
  Classifier: Programming Language :: Python :: 3
19
18
  Requires-Python: >=3.9
20
19
  Description-Content-Type: text/markdown
@@ -38,10 +37,14 @@ Dynamic: license-file
38
37
  [![Python](https://img.shields.io/pypi/pyversions/shieldops-cli.svg)](https://pypi.org/project/shieldops-cli/)
39
38
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
40
39
  [![GitHub Stars](https://img.shields.io/github/stars/mohammedabdallahcv-creator/shieldops-cli?style=social)](https://github.com/mohammedabdallahcv-creator/shieldops-cli)
41
- [![Powered by ShieldOps AI](https://img.shields.io/badge/powered%20by-ShieldOps%20AI-8B5CF6)](https://shieldops-ai.onrender.com)
40
+ [![Powered by ShieldOps AI](https://img.shields.io/badge/powered%20by-ShieldOps%20AI-8B5CF6)](https://shieldops-ai.dev)
42
41
 
43
42
  <p align="center">
44
- <img src="docs/screenshots/cli-output.svg" alt="ShieldOps CLI in action" width="800">
43
+ <img src="docs/screenshots/tui-session.svg" alt="ShieldOps TUI interactive session" width="800">
44
+ </p>
45
+
46
+ <p align="center">
47
+ <img src="docs/screenshots/cli-output.svg" alt="ShieldOps CLI scan results" width="800">
45
48
  </p>
46
49
 
47
50
  ---
@@ -61,7 +64,8 @@ Most Dockerfile/K8s scanners tell you **what** is wrong. ShieldOps CLI also tell
61
64
  | Docker image scan | Yes | No | Yes (built-in) |
62
65
  | Interactive TUI | Yes | No | No |
63
66
  | CI/CD ready (`--fail-on`) | Yes | Yes | Yes |
64
- | 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) | — | — |
65
69
 
66
70
  ### What makes it different
67
71
 
@@ -78,14 +82,21 @@ Most Dockerfile/K8s scanners tell you **what** is wrong. ShieldOps CLI also tell
78
82
  # 1. Install
79
83
  pip install shieldops-cli
80
84
 
81
- # 2. Login (free tier 5 scans/day)
82
- shieldops login
83
-
84
- # 3. Scan your Dockerfile
85
+ # 2. Scan your Dockerfile (localno login needed)
85
86
  shieldops analyze Dockerfile
86
87
  ```
87
88
 
88
- 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
+ ```
89
100
 
90
101
  ---
91
102
 
@@ -293,7 +304,7 @@ shieldops-scan:
293
304
  | Policy engine | No | Yes |
294
305
  | Priority queue | No | Yes |
295
306
 
296
- Get your API key at [shieldops-ai.onrender.com](https://shieldops-ai.onrender.com).
307
+ Get your API key at [shieldops-ai.dev](https://shieldops-ai.dev).
297
308
 
298
309
  ---
299
310
 
@@ -348,4 +359,4 @@ MIT
348
359
 
349
360
  ---
350
361
 
351
- ShieldOps CLI is open-source. The analysis backend is proprietary and hosted at [shieldops-ai.onrender.com](https://shieldops-ai.onrender.com).
362
+ ShieldOps CLI is open-source. The analysis backend is proprietary and hosted at [shieldops-ai.dev](https://shieldops-ai.dev).
@@ -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
@@ -35,7 +35,7 @@ def test_analyze_table_output(runner, mock_config, sample_dockerfile):
35
35
  with patch("shieldops_cli.commands.analyze.ShieldOpsClient") as mock_cls:
36
36
  mock_client = MagicMock()
37
37
  mock_client.run_task.return_value = MOCK_ANALYZE_RESPONSE
38
- mock_client.api_url = "https://shieldops-ai.onrender.com"
38
+ mock_client.api_url = "https://shieldops-ai.dev"
39
39
  mock_cls.return_value = mock_client
40
40
 
41
41
  result = runner.invoke(cli, ["analyze", str(sample_dockerfile)])
@@ -50,7 +50,7 @@ def test_analyze_json_output(runner, mock_config, sample_dockerfile):
50
50
  with patch("shieldops_cli.commands.analyze.ShieldOpsClient") as mock_cls:
51
51
  mock_client = MagicMock()
52
52
  mock_client.run_task.return_value = MOCK_ANALYZE_RESPONSE
53
- mock_client.api_url = "https://shieldops-ai.onrender.com"
53
+ mock_client.api_url = "https://shieldops-ai.dev"
54
54
  mock_cls.return_value = mock_client
55
55
 
56
56
  result = runner.invoke(cli, ["analyze", str(sample_dockerfile), "--format", "json"])
@@ -65,7 +65,7 @@ def test_analyze_fail_on_high(runner, mock_config, sample_dockerfile):
65
65
  with patch("shieldops_cli.commands.analyze.ShieldOpsClient") as mock_cls:
66
66
  mock_client = MagicMock()
67
67
  mock_client.run_task.return_value = MOCK_ANALYZE_RESPONSE
68
- mock_client.api_url = "https://shieldops-ai.onrender.com"
68
+ mock_client.api_url = "https://shieldops-ai.dev"
69
69
  mock_cls.return_value = mock_client
70
70
 
71
71
  result = runner.invoke(cli, ["analyze", str(sample_dockerfile), "--fail-on", "high"])
@@ -82,7 +82,7 @@ def test_analyze_save_to_file(runner, mock_config, sample_dockerfile, tmp_path):
82
82
  with patch("shieldops_cli.commands.analyze.ShieldOpsClient") as mock_cls:
83
83
  mock_client = MagicMock()
84
84
  mock_client.run_task.return_value = MOCK_ANALYZE_RESPONSE
85
- mock_client.api_url = "https://shieldops-ai.onrender.com"
85
+ mock_client.api_url = "https://shieldops-ai.dev"
86
86
  mock_cls.return_value = mock_client
87
87
 
88
88
  result = runner.invoke(cli, ["analyze", str(sample_dockerfile), "-f", "json", "-o", str(output_file)])
File without changes
File without changes