vehlo-code-scanner 0.1.2__tar.gz → 0.1.4__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 (60) hide show
  1. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/PKG-INFO +7 -4
  2. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/README.md +6 -3
  3. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/pyproject.toml +1 -1
  4. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/cli/scan_command.py +74 -53
  5. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/.gitignore +0 -0
  6. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/__init__.py +0 -0
  7. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/__init__.py +0 -0
  8. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/app.py +0 -0
  9. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/auth.py +0 -0
  10. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/deps.py +0 -0
  11. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/routes/__init__.py +0 -0
  12. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/routes/analytics.py +0 -0
  13. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/routes/auth.py +0 -0
  14. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/routes/findings.py +0 -0
  15. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/routes/groups.py +0 -0
  16. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/routes/health.py +0 -0
  17. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/routes/ingest.py +0 -0
  18. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/routes/overview.py +0 -0
  19. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/routes/repos.py +0 -0
  20. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/routes/scans.py +0 -0
  21. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/routes/tokens.py +0 -0
  22. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/schemas.py +0 -0
  23. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/services/__init__.py +0 -0
  24. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/services/auto_resolve.py +0 -0
  25. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/services/ingest.py +0 -0
  26. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/api/session.py +0 -0
  27. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/cli/__init__.py +0 -0
  28. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/cli/admin.py +0 -0
  29. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/cli/app.py +0 -0
  30. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/cli/output.py +0 -0
  31. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/client/__init__.py +0 -0
  32. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/client/api.py +0 -0
  33. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/config.py +0 -0
  34. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/db.py +0 -0
  35. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/enums.py +0 -0
  36. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/fingerprint.py +0 -0
  37. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/models/__init__.py +0 -0
  38. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/models/api_token.py +0 -0
  39. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/models/base.py +0 -0
  40. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/models/finding.py +0 -0
  41. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/models/finding_history.py +0 -0
  42. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/models/group.py +0 -0
  43. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/models/organization.py +0 -0
  44. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/models/repo.py +0 -0
  45. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/models/scan.py +0 -0
  46. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/models/user.py +0 -0
  47. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/scanner/__init__.py +0 -0
  48. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/scanner/models.py +0 -0
  49. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/scanner/parser.py +0 -0
  50. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/scanner/runner.py +0 -0
  51. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/services/__init__.py +0 -0
  52. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/services/provisioning.py +0 -0
  53. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/services/tokens.py +0 -0
  54. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/storage/__init__.py +0 -0
  55. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/storage/memory.py +0 -0
  56. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/storage/s3.py +0 -0
  57. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/worker/__init__.py +0 -0
  58. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/worker/celery_app.py +0 -0
  59. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/worker/db.py +0 -0
  60. {vehlo_code_scanner-0.1.2 → vehlo_code_scanner-0.1.4}/src/vcs/worker/tasks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vehlo-code-scanner
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Multi-tenant security scanning platform wrapping Amazon Security Helper
5
5
  Project-URL: Homepage, https://github.com/Vehlo-CyberSec/vehlo-code-scanner
6
6
  Project-URL: Repository, https://github.com/Vehlo-CyberSec/vehlo-code-scanner
@@ -65,12 +65,15 @@ get the CLI, `--push`, and the client.
65
65
  **Public (no AWS):**
66
66
 
67
67
  ```bash
68
- # pip from PyPI
68
+ # pipx (recommended — modern macOS/Linux pythons refuse bare pip installs)
69
+ pipx install 'vehlo-code-scanner[scan]' # quotes matter in zsh
70
+
71
+ # or pip, inside a venv
69
72
  pip install 'vehlo-code-scanner[scan]'
70
73
 
71
- # Docker from ECR Public (ASH bundled, nothing else to install)
74
+ # or Docker from ECR Public (ASH bundled, nothing else to install)
72
75
  docker run --rm -v "$PWD:/src" \
73
- public.ecr.aws/<alias>/vehlo-code-scanner:latest scan /src
76
+ public.ecr.aws/w1f0w6h2/vehlo-code-scanner:latest scan /src --mode local
74
77
  ```
75
78
 
76
79
  **AWS-gated (internal):** authenticate once with `aws sso login`, then:
@@ -37,12 +37,15 @@ get the CLI, `--push`, and the client.
37
37
  **Public (no AWS):**
38
38
 
39
39
  ```bash
40
- # pip from PyPI
40
+ # pipx (recommended — modern macOS/Linux pythons refuse bare pip installs)
41
+ pipx install 'vehlo-code-scanner[scan]' # quotes matter in zsh
42
+
43
+ # or pip, inside a venv
41
44
  pip install 'vehlo-code-scanner[scan]'
42
45
 
43
- # Docker from ECR Public (ASH bundled, nothing else to install)
46
+ # or Docker from ECR Public (ASH bundled, nothing else to install)
44
47
  docker run --rm -v "$PWD:/src" \
45
- public.ecr.aws/<alias>/vehlo-code-scanner:latest scan /src
48
+ public.ecr.aws/w1f0w6h2/vehlo-code-scanner:latest scan /src --mode local
46
49
  ```
47
50
 
48
51
  **AWS-gated (internal):** authenticate once with `aws sso login`, then:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "vehlo-code-scanner"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  description = "Multi-tenant security scanning platform wrapping Amazon Security Helper"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -3,9 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import subprocess
6
- import sys
7
6
  from pathlib import Path
8
- from typing import Optional
9
7
 
10
8
  import typer
11
9
  from rich.console import Console
@@ -19,66 +17,69 @@ console = Console()
19
17
  SEVERITY_LEVELS = ["critical", "high", "medium", "low", "info"]
20
18
 
21
19
 
22
- def _get_git_info(source_dir: str) -> tuple[str, str, str, str]:
23
- """Get commit SHA, branch, provider, and org/repo from git remote."""
20
+ def _git(args: list[str], cwd: str) -> str | None:
21
+ """Run a git command, returning stripped stdout or None on failure."""
24
22
  try:
25
- sha = subprocess.run(
26
- ["git", "rev-parse", "HEAD"],
27
- cwd=source_dir,
28
- capture_output=True,
29
- text=True,
30
- check=True,
31
- ).stdout.strip()
32
- branch = subprocess.run(
33
- ["git", "rev-parse", "--abbrev-ref", "HEAD"],
34
- cwd=source_dir,
35
- capture_output=True,
36
- text=True,
37
- check=True,
38
- ).stdout.strip()
39
- remote_url = subprocess.run(
40
- ["git", "remote", "get-url", "origin"],
41
- cwd=source_dir,
23
+ out = subprocess.run(
24
+ ["git", *args],
25
+ cwd=cwd,
42
26
  capture_output=True,
43
27
  text=True,
44
28
  check=True,
45
29
  ).stdout.strip()
46
30
  except (subprocess.CalledProcessError, FileNotFoundError):
47
- return "unknown", "unknown", "github", "unknown"
31
+ return None
32
+ return out or None
33
+
34
+
35
+ def _get_git_info(source_dir: str) -> tuple[str, str, str, str, str | None]:
36
+ """Get commit SHA, branch, provider, org, and repo name from git.
37
+
38
+ Each field degrades independently: a repo without an `origin` remote
39
+ still reports its real SHA and branch, and vice versa. The repo name is
40
+ only known when a remote exists (callers fall back to the directory
41
+ name).
42
+ """
43
+ sha = _git(["rev-parse", "HEAD"], source_dir) or "unknown"
44
+ branch = (
45
+ _git(["rev-parse", "--abbrev-ref", "HEAD"], source_dir) or "unknown"
46
+ )
47
+ remote_url = _git(["remote", "get-url", "origin"], source_dir)
48
+ if not remote_url:
49
+ return sha, branch, "github", "unknown", None
50
+
51
+ provider, org, repo = _parse_remote(remote_url)
52
+ return sha, branch, provider, org, repo
48
53
 
49
- provider, org = _parse_remote(remote_url)
50
- return sha, branch, provider, org
51
54
 
55
+ def _parse_remote(remote_url: str) -> tuple[str, str, str | None]:
56
+ """Extract provider, org, and repo name from a git remote URL.
52
57
 
53
- def _parse_remote(remote_url: str) -> tuple[str, str]:
54
- """Extract provider and org from a git remote URL."""
58
+ Handles git@host:org/repo.git, https://host/org/repo(.git), and
59
+ ssh://git@host/org/repo.git forms.
60
+ """
55
61
  url = remote_url.lower()
56
- if "github.com" in url:
57
- provider = "github"
58
- elif "bitbucket.org" in url:
62
+ if "bitbucket.org" in url:
59
63
  provider = "bitbucket"
60
64
  elif "gitlab.com" in url:
61
65
  provider = "gitlab"
62
66
  else:
63
67
  provider = "github"
64
68
 
65
- # Extract org from URLs like:
66
- # git@github.com:org/repo.git
67
- # https://github.com/org/repo.git
68
- org = "unknown"
69
- if ":" in remote_url and "@" in remote_url:
70
- # SSH format: git@host:org/repo.git
71
- path = remote_url.split(":")[-1]
72
- parts = path.replace(".git", "").split("/")
73
- if parts:
74
- org = parts[0]
75
- elif "/" in remote_url:
76
- # HTTPS format: https://host/org/repo.git
77
- parts = remote_url.replace(".git", "").split("/")
78
- if len(parts) >= 2:
79
- org = parts[-2]
80
-
81
- return provider, org
69
+ # Normalize every form to host/org/.../repo, then read the tail.
70
+ path = remote_url
71
+ if "://" in path:
72
+ path = path.split("://", 1)[1] # strip scheme
73
+ if "@" in path:
74
+ path = path.split("@", 1)[1] # strip user
75
+ path = path.replace(":", "/", 1) # scp-like host:org/repo
76
+ parts = [p for p in path.removesuffix(".git").split("/") if p]
77
+
78
+ if len(parts) >= 3: # [host, org..., repo]
79
+ return provider, parts[-2], parts[-1]
80
+ if len(parts) == 2: # [host, org]
81
+ return provider, parts[1], None
82
+ return provider, "unknown", None
82
83
 
83
84
 
84
85
  def _create_client(api_url: str, api_token: str) -> VCSClient:
@@ -91,7 +92,7 @@ def scan(
91
92
  ".",
92
93
  help="Directory to scan. Defaults to current directory.",
93
94
  ),
94
- scanners: Optional[str] = typer.Option(
95
+ scanners: str | None = typer.Option(
95
96
  None,
96
97
  help="Comma-separated list of scanners to run (e.g., semgrep,bandit).",
97
98
  ),
@@ -99,7 +100,7 @@ def scan(
99
100
  "container",
100
101
  help="ASH execution mode: container (default, all scanners included), local, or precommit.",
101
102
  ),
102
- fail_on: Optional[str] = typer.Option(
103
+ fail_on: str | None = typer.Option(
103
104
  None,
104
105
  help=(
105
106
  "Exit non-zero if findings at this severity or above exist "
@@ -110,16 +111,34 @@ def scan(
110
111
  False,
111
112
  help="Push results to the central API.",
112
113
  ),
113
- api_url: Optional[str] = typer.Option(
114
+ api_url: str | None = typer.Option(
114
115
  None,
115
116
  envvar="VCS_API_URL",
116
117
  help="Central API URL (required with --push).",
117
118
  ),
118
- api_token: Optional[str] = typer.Option(
119
+ api_token: str | None = typer.Option(
119
120
  None,
120
121
  envvar="VCS_API_TOKEN",
121
122
  help="API token for authentication (required with --push).",
122
123
  ),
124
+ repo_name: str | None = typer.Option(
125
+ None,
126
+ "--repo-name",
127
+ envvar="VCS_REPO_NAME",
128
+ help=(
129
+ "Repository name for attribution (defaults to the git remote's "
130
+ "repo name, then the directory name)."
131
+ ),
132
+ ),
133
+ org: str | None = typer.Option(
134
+ None,
135
+ "--org",
136
+ envvar="VCS_PROVIDER_ORG",
137
+ help=(
138
+ "Provider org/owner for attribution (defaults to the git "
139
+ "remote's org)."
140
+ ),
141
+ ),
123
142
  ) -> None:
124
143
  """Run a security scan against a directory using ASH."""
125
144
  source_path = Path(source_dir).resolve()
@@ -154,14 +173,16 @@ def scan(
154
173
  format_findings_table(result, console)
155
174
 
156
175
  if push:
157
- sha, branch, provider, org = _get_git_info(str(source_path))
176
+ sha, branch, provider, git_org, git_repo = _get_git_info(
177
+ str(source_path)
178
+ )
158
179
  client = _create_client(api_url, api_token)
159
180
  try:
160
181
  scan_id = client.push(
161
182
  result=result,
162
183
  repo_provider=provider,
163
- repo_org=org,
164
- repo_name=source_path.name,
184
+ repo_org=org or git_org,
185
+ repo_name=repo_name or git_repo or source_path.name,
165
186
  commit_sha=sha,
166
187
  branch=branch,
167
188
  trigger_type="manual_cli",