vehlo-code-scanner 0.1.1__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.1/.gitignore +53 -0
  2. vehlo_code_scanner-0.1.1/PKG-INFO +198 -0
  3. vehlo_code_scanner-0.1.1/README.md +170 -0
  4. vehlo_code_scanner-0.1.1/pyproject.toml +81 -0
  5. vehlo_code_scanner-0.1.1/src/vcs/__init__.py +8 -0
  6. vehlo_code_scanner-0.1.1/src/vcs/api/__init__.py +1 -0
  7. vehlo_code_scanner-0.1.1/src/vcs/api/app.py +93 -0
  8. vehlo_code_scanner-0.1.1/src/vcs/api/auth.py +95 -0
  9. vehlo_code_scanner-0.1.1/src/vcs/api/deps.py +161 -0
  10. vehlo_code_scanner-0.1.1/src/vcs/api/routes/__init__.py +1 -0
  11. vehlo_code_scanner-0.1.1/src/vcs/api/routes/analytics.py +79 -0
  12. vehlo_code_scanner-0.1.1/src/vcs/api/routes/auth.py +169 -0
  13. vehlo_code_scanner-0.1.1/src/vcs/api/routes/findings.py +141 -0
  14. vehlo_code_scanner-0.1.1/src/vcs/api/routes/groups.py +122 -0
  15. vehlo_code_scanner-0.1.1/src/vcs/api/routes/health.py +11 -0
  16. vehlo_code_scanner-0.1.1/src/vcs/api/routes/ingest.py +49 -0
  17. vehlo_code_scanner-0.1.1/src/vcs/api/routes/overview.py +95 -0
  18. vehlo_code_scanner-0.1.1/src/vcs/api/routes/repos.py +106 -0
  19. vehlo_code_scanner-0.1.1/src/vcs/api/routes/scans.py +79 -0
  20. vehlo_code_scanner-0.1.1/src/vcs/api/routes/tokens.py +178 -0
  21. vehlo_code_scanner-0.1.1/src/vcs/api/schemas.py +172 -0
  22. vehlo_code_scanner-0.1.1/src/vcs/api/services/__init__.py +1 -0
  23. vehlo_code_scanner-0.1.1/src/vcs/api/services/auto_resolve.py +55 -0
  24. vehlo_code_scanner-0.1.1/src/vcs/api/services/ingest.py +205 -0
  25. vehlo_code_scanner-0.1.1/src/vcs/api/session.py +32 -0
  26. vehlo_code_scanner-0.1.1/src/vcs/cli/__init__.py +1 -0
  27. vehlo_code_scanner-0.1.1/src/vcs/cli/admin.py +177 -0
  28. vehlo_code_scanner-0.1.1/src/vcs/cli/app.py +20 -0
  29. vehlo_code_scanner-0.1.1/src/vcs/cli/output.py +68 -0
  30. vehlo_code_scanner-0.1.1/src/vcs/cli/scan_command.py +195 -0
  31. vehlo_code_scanner-0.1.1/src/vcs/client/__init__.py +1 -0
  32. vehlo_code_scanner-0.1.1/src/vcs/client/api.py +108 -0
  33. vehlo_code_scanner-0.1.1/src/vcs/config.py +150 -0
  34. vehlo_code_scanner-0.1.1/src/vcs/db.py +47 -0
  35. vehlo_code_scanner-0.1.1/src/vcs/enums.py +55 -0
  36. vehlo_code_scanner-0.1.1/src/vcs/fingerprint.py +17 -0
  37. vehlo_code_scanner-0.1.1/src/vcs/models/__init__.py +22 -0
  38. vehlo_code_scanner-0.1.1/src/vcs/models/api_token.py +39 -0
  39. vehlo_code_scanner-0.1.1/src/vcs/models/base.py +32 -0
  40. vehlo_code_scanner-0.1.1/src/vcs/models/finding.py +81 -0
  41. vehlo_code_scanner-0.1.1/src/vcs/models/finding_history.py +42 -0
  42. vehlo_code_scanner-0.1.1/src/vcs/models/group.py +35 -0
  43. vehlo_code_scanner-0.1.1/src/vcs/models/organization.py +23 -0
  44. vehlo_code_scanner-0.1.1/src/vcs/models/repo.py +56 -0
  45. vehlo_code_scanner-0.1.1/src/vcs/models/scan.py +62 -0
  46. vehlo_code_scanner-0.1.1/src/vcs/models/user.py +53 -0
  47. vehlo_code_scanner-0.1.1/src/vcs/scanner/__init__.py +1 -0
  48. vehlo_code_scanner-0.1.1/src/vcs/scanner/models.py +67 -0
  49. vehlo_code_scanner-0.1.1/src/vcs/scanner/parser.py +25 -0
  50. vehlo_code_scanner-0.1.1/src/vcs/scanner/runner.py +146 -0
  51. vehlo_code_scanner-0.1.1/src/vcs/services/__init__.py +1 -0
  52. vehlo_code_scanner-0.1.1/src/vcs/services/provisioning.py +144 -0
  53. vehlo_code_scanner-0.1.1/src/vcs/services/tokens.py +74 -0
  54. vehlo_code_scanner-0.1.1/src/vcs/storage/__init__.py +55 -0
  55. vehlo_code_scanner-0.1.1/src/vcs/storage/memory.py +19 -0
  56. vehlo_code_scanner-0.1.1/src/vcs/storage/s3.py +49 -0
  57. vehlo_code_scanner-0.1.1/src/vcs/worker/__init__.py +1 -0
  58. vehlo_code_scanner-0.1.1/src/vcs/worker/celery_app.py +17 -0
  59. vehlo_code_scanner-0.1.1/src/vcs/worker/db.py +33 -0
  60. vehlo_code_scanner-0.1.1/src/vcs/worker/tasks.py +48 -0
@@ -0,0 +1,53 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+
9
+ # Virtual environment
10
+ .venv/
11
+
12
+ # IDE
13
+ .idea/
14
+ .vscode/
15
+ *.swp
16
+ *.swo
17
+
18
+ # Environment
19
+ .env
20
+ .env.*
21
+
22
+ # Testing
23
+ .pytest_cache/
24
+ .coverage
25
+ htmlcov/
26
+
27
+ # Docker
28
+ docker-compose.override.yml
29
+
30
+ # Alembic
31
+ alembic/versions/__pycache__/
32
+
33
+ # OS
34
+ .DS_Store
35
+ Thumbs.db
36
+
37
+ # Office documents (working files + Word lock files)
38
+ *.docx
39
+ ~$*
40
+
41
+ # MinIO data
42
+ minio_data/
43
+
44
+ # Dashboard
45
+ dashboard/node_modules/
46
+ dashboard/dist/
47
+
48
+ # Local tooling caches
49
+ .ash/
50
+ .claude-fuel/
51
+
52
+ # Local Claude context (deployment/operational notes — not public)
53
+ CLAUDE.local.md
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: vehlo-code-scanner
3
+ Version: 0.1.1
4
+ Summary: Multi-tenant security scanning platform wrapping Amazon Security Helper
5
+ Project-URL: Homepage, https://github.com/Vehlo-CyberSec/vehlo-code-scanner
6
+ Project-URL: Repository, https://github.com/Vehlo-CyberSec/vehlo-code-scanner
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: alembic<2.0,>=1.13
9
+ Requires-Dist: authlib<2.0,>=1.3
10
+ Requires-Dist: boto3>=1.34
11
+ Requires-Dist: celery<6.0,>=5.3
12
+ Requires-Dist: fastapi<1.0,>=0.115
13
+ Requires-Dist: httpx<1.0,>=0.27
14
+ Requires-Dist: itsdangerous<3.0,>=2.1
15
+ Requires-Dist: psycopg[binary]<4.0,>=3.1
16
+ Requires-Dist: pydantic<3.0,>=2.0
17
+ Requires-Dist: redis<6.0,>=5.0
18
+ Requires-Dist: rich<14.0,>=13.5
19
+ Requires-Dist: sqlalchemy<3.0,>=2.0
20
+ Requires-Dist: typer<1.0,>=0.16
21
+ Requires-Dist: uvicorn[standard]<1.0,>=0.30
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest-cov<6.0,>=5.0; extra == 'dev'
24
+ Requires-Dist: pytest<9.0,>=8.0; extra == 'dev'
25
+ Provides-Extra: scan
26
+ Requires-Dist: vehlo-ash<4,>=3.2.5; extra == 'scan'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Vehlo Code Scanner
30
+
31
+ Multi-tenant security scanning platform wrapping [Amazon Security Helper (ASH)](https://github.com/awslabs/automated-security-helper). Runs ASH across Vehlo's 500+ repos, centralizes findings in PostgreSQL, and surfaces them through a React dashboard with triage workflows and analytics.
32
+
33
+ ## Architecture
34
+
35
+ ```
36
+ CI/CD pipeline → ASH scan → vcs CLI (--push) → POST /api/v1/scans → FastAPI + PostgreSQL → React Dashboard
37
+ ```
38
+
39
+ Three components:
40
+
41
+ - **CLI / scanner** — Python package (`vcs`) that wraps ASH. Supports container, local, and pre-commit modes. Outputs rich terminal tables, optionally pushes results to the central API, and can fail the build on a severity threshold.
42
+ - **API service** — FastAPI monolith handling ingest, findings lifecycle, and analytics. Backed by PostgreSQL. Auto-resolves findings when they disappear from subsequent scans.
43
+ - **Dashboard** — React + Vite SPA. Overview, findings browser, per-repo drill-down, scan history, and analytics.
44
+
45
+ ## Quick Start
46
+
47
+ ```bash
48
+ # Start all services
49
+ docker compose up
50
+
51
+ # API → http://localhost:8002
52
+ # Dashboard → http://localhost:5175
53
+ ```
54
+
55
+ ### Installation
56
+
57
+ Distribution is **dual-mode**: a public path (PyPI + ECR Public, no AWS
58
+ account needed) and an AWS-gated path (CodeArtifact + private ECR) for
59
+ internal users. The tool itself is open to install — the **API token** gates
60
+ pushing results and **SSO** gates viewing them. The `[scan]` extra adds the
61
+ ASH engine (`vehlo-ash`, a rename-only repackaging of AWS's Apache-2.0
62
+ automated-security-helper — see `packaging/vehlo-ash/`); without it you still
63
+ get the CLI, `--push`, and the client.
64
+
65
+ **Public (no AWS):**
66
+
67
+ ```bash
68
+ # pip from PyPI
69
+ pip install 'vehlo-code-scanner[scan]'
70
+
71
+ # Docker from ECR Public (ASH bundled, nothing else to install)
72
+ docker run --rm -v "$PWD:/src" \
73
+ public.ecr.aws/<alias>/vehlo-code-scanner:latest scan /src
74
+ ```
75
+
76
+ **AWS-gated (internal):** authenticate once with `aws sso login`, then:
77
+
78
+ ```bash
79
+ # pip via CodeArtifact (configures the index, then installs)
80
+ aws codeartifact login --tool pip \
81
+ --domain "$VCS_CA_DOMAIN" --repository "$VCS_CA_REPO"
82
+ pip install 'vehlo-code-scanner[scan]'
83
+
84
+ # Docker via ECR
85
+ aws ecr get-login-password --region "$AWS_REGION" \
86
+ | docker login --username AWS --password-stdin "$ECR_REGISTRY"
87
+ docker run --rm -v "$PWD:/src" \
88
+ "$ECR_REGISTRY/vehlo-code-scanner:latest" scan /src
89
+ ```
90
+
91
+ **GitHub Actions** (the calling job needs `permissions: id-token: write`):
92
+
93
+ ```yaml
94
+ - uses: Vehlo-CyberSec/vehlo-code-scanner@v1
95
+ with:
96
+ api-url: ${{ vars.VCS_API_URL }}
97
+ api-token: ${{ secrets.VCS_API_TOKEN }}
98
+ image: ${{ vars.VCS_ECR_IMAGE }} # full ECR URI
99
+ aws-role: ${{ secrets.VCS_AWS_ROLE }} # OIDC role with ECR pull
100
+ aws-region: ${{ vars.AWS_REGION }}
101
+ fail-on: high
102
+ ```
103
+
104
+ > ASH is git-only upstream (and its PyPI name is squatted), so `vcs scan`
105
+ > without the engine prints install guidance. The container images bundle ASH;
106
+ > pip users get it via the `[scan]` extra (`vehlo-ash` from PyPI or
107
+ > CodeArtifact, depending on the index you install from).
108
+
109
+ For local development from a checkout:
110
+
111
+ ```bash
112
+ uv tool install '.[scan]' # or: uv sync --group local-scan (ASH from git)
113
+ ```
114
+
115
+ After installation, `vcs` (and `vcs-admin`) are available on your PATH.
116
+
117
+ ### Local development
118
+
119
+ ```bash
120
+ # Install Python deps
121
+ uv sync
122
+
123
+ # Apply migrations
124
+ VCS_DATABASE_URL=postgresql+psycopg://vcs:<password>@localhost:5433/vcs alembic upgrade head
125
+ # (dev password: see docker-compose.yml)
126
+
127
+ # Run API
128
+ VCS_DATABASE_URL=... uvicorn vcs.api.app:create_app --factory --reload
129
+
130
+ # Run dashboard (in ./dashboard)
131
+ npm run dev
132
+ ```
133
+
134
+ ### Running a scan
135
+
136
+ ```bash
137
+ # Scan current directory and print results
138
+ vcs scan .
139
+
140
+ # Scan and push results to central API
141
+ VCS_API_URL=http://localhost:8002 VCS_API_TOKEN=<token> vcs scan . --push
142
+
143
+ # Fail CI if critical or high findings exist
144
+ vcs scan . --push --fail-on high
145
+ ```
146
+
147
+ ## Project Structure
148
+
149
+ ```
150
+ src/vcs/
151
+ ├── api/
152
+ │ ├── routes/ # health, ingest, findings, repos, scans, overview, analytics
153
+ │ ├── services/ # ingest logic, auto-resolve
154
+ │ ├── app.py # FastAPI factory
155
+ │ ├── deps.py # DB session injection
156
+ │ └── schemas.py # Pydantic request/response models
157
+ ├── models/ # SQLAlchemy ORM: org, group, user, repo, scan, finding, api_token
158
+ ├── scanner/ # ASH wrapper: runner, parser, result models
159
+ ├── cli/ # Typer CLI: scan command, rich output
160
+ ├── client/ # HTTP client for pushing results to API
161
+ ├── db.py # Database connection factory
162
+ ├── enums.py # Severity, FindingStatus, etc.
163
+ └── config.py # Settings from environment
164
+
165
+ dashboard/src/
166
+ ├── pages/ # Overview, Findings, FindingDetail, Repos, RepoDetail, Scans, Analytics
167
+ ├── components/ # Layout, SeverityBadge, StatusBadge, Pagination, Panel, etc.
168
+ ├── api/ # TanStack Query hooks
169
+ └── types.ts # TypeScript types
170
+
171
+ alembic/versions/ # Database migrations
172
+ tests/ # Unit + integration tests
173
+ ```
174
+
175
+ ## Environment Variables
176
+
177
+ | Variable | Description | Default |
178
+ |---|---|---|
179
+ | `VCS_DATABASE_URL` | PostgreSQL connection string | required |
180
+ | `VCS_API_URL` | Central API base URL (CLI push) | required for `--push` |
181
+ | `VCS_API_TOKEN` | Bearer token for API auth (CLI push) | required for `--push` |
182
+ | `VCS_REDIS_URL` | Celery broker/result backend | `redis://localhost:6380/0` |
183
+ | `VCS_S3_ENDPOINT` | S3/MinIO endpoint for raw-result archiving | `http://localhost:9002` |
184
+ | `VCS_SESSION_SECRET` | Secret for signing dashboard session cookies | dev default (**required** once OIDC is configured — startup fails without it) |
185
+ | `VCS_CORS_ORIGINS` | Comma-separated allowed CORS origins; empty value = deny cross-origin (prod) | unset → any localhost port (dev) |
186
+ | `VCS_OIDC_ISSUER` | OIDC issuer URL (enables dashboard SSO) | unset → SSO disabled |
187
+ | `VCS_OIDC_CLIENT_ID` / `VCS_OIDC_CLIENT_SECRET` | OIDC client credentials | unset |
188
+ | `VCS_OIDC_REDIRECT_URI` | OIDC callback URL | `.../api/v1/auth/callback` |
189
+ | `VCS_OIDC_GROUPS_CLAIM` | Claim holding the user's group names | `groups` |
190
+ | `VCS_DASHBOARD_URL` | Post-login redirect target | `http://localhost:5175` |
191
+ | `VCS_DEV_LOGIN` | **Dev only** — enables `/api/v1/auth/dev-login` (one-click local session, no IdP). Never set in prod. | unset → disabled |
192
+
193
+ ## Running Tests
194
+
195
+ ```bash
196
+ pytest
197
+ pytest --cov=vcs --cov-report=term-missing
198
+ ```
@@ -0,0 +1,170 @@
1
+ # Vehlo Code Scanner
2
+
3
+ Multi-tenant security scanning platform wrapping [Amazon Security Helper (ASH)](https://github.com/awslabs/automated-security-helper). Runs ASH across Vehlo's 500+ repos, centralizes findings in PostgreSQL, and surfaces them through a React dashboard with triage workflows and analytics.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ CI/CD pipeline → ASH scan → vcs CLI (--push) → POST /api/v1/scans → FastAPI + PostgreSQL → React Dashboard
9
+ ```
10
+
11
+ Three components:
12
+
13
+ - **CLI / scanner** — Python package (`vcs`) that wraps ASH. Supports container, local, and pre-commit modes. Outputs rich terminal tables, optionally pushes results to the central API, and can fail the build on a severity threshold.
14
+ - **API service** — FastAPI monolith handling ingest, findings lifecycle, and analytics. Backed by PostgreSQL. Auto-resolves findings when they disappear from subsequent scans.
15
+ - **Dashboard** — React + Vite SPA. Overview, findings browser, per-repo drill-down, scan history, and analytics.
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ # Start all services
21
+ docker compose up
22
+
23
+ # API → http://localhost:8002
24
+ # Dashboard → http://localhost:5175
25
+ ```
26
+
27
+ ### Installation
28
+
29
+ Distribution is **dual-mode**: a public path (PyPI + ECR Public, no AWS
30
+ account needed) and an AWS-gated path (CodeArtifact + private ECR) for
31
+ internal users. The tool itself is open to install — the **API token** gates
32
+ pushing results and **SSO** gates viewing them. The `[scan]` extra adds the
33
+ ASH engine (`vehlo-ash`, a rename-only repackaging of AWS's Apache-2.0
34
+ automated-security-helper — see `packaging/vehlo-ash/`); without it you still
35
+ get the CLI, `--push`, and the client.
36
+
37
+ **Public (no AWS):**
38
+
39
+ ```bash
40
+ # pip from PyPI
41
+ pip install 'vehlo-code-scanner[scan]'
42
+
43
+ # Docker from ECR Public (ASH bundled, nothing else to install)
44
+ docker run --rm -v "$PWD:/src" \
45
+ public.ecr.aws/<alias>/vehlo-code-scanner:latest scan /src
46
+ ```
47
+
48
+ **AWS-gated (internal):** authenticate once with `aws sso login`, then:
49
+
50
+ ```bash
51
+ # pip via CodeArtifact (configures the index, then installs)
52
+ aws codeartifact login --tool pip \
53
+ --domain "$VCS_CA_DOMAIN" --repository "$VCS_CA_REPO"
54
+ pip install 'vehlo-code-scanner[scan]'
55
+
56
+ # Docker via ECR
57
+ aws ecr get-login-password --region "$AWS_REGION" \
58
+ | docker login --username AWS --password-stdin "$ECR_REGISTRY"
59
+ docker run --rm -v "$PWD:/src" \
60
+ "$ECR_REGISTRY/vehlo-code-scanner:latest" scan /src
61
+ ```
62
+
63
+ **GitHub Actions** (the calling job needs `permissions: id-token: write`):
64
+
65
+ ```yaml
66
+ - uses: Vehlo-CyberSec/vehlo-code-scanner@v1
67
+ with:
68
+ api-url: ${{ vars.VCS_API_URL }}
69
+ api-token: ${{ secrets.VCS_API_TOKEN }}
70
+ image: ${{ vars.VCS_ECR_IMAGE }} # full ECR URI
71
+ aws-role: ${{ secrets.VCS_AWS_ROLE }} # OIDC role with ECR pull
72
+ aws-region: ${{ vars.AWS_REGION }}
73
+ fail-on: high
74
+ ```
75
+
76
+ > ASH is git-only upstream (and its PyPI name is squatted), so `vcs scan`
77
+ > without the engine prints install guidance. The container images bundle ASH;
78
+ > pip users get it via the `[scan]` extra (`vehlo-ash` from PyPI or
79
+ > CodeArtifact, depending on the index you install from).
80
+
81
+ For local development from a checkout:
82
+
83
+ ```bash
84
+ uv tool install '.[scan]' # or: uv sync --group local-scan (ASH from git)
85
+ ```
86
+
87
+ After installation, `vcs` (and `vcs-admin`) are available on your PATH.
88
+
89
+ ### Local development
90
+
91
+ ```bash
92
+ # Install Python deps
93
+ uv sync
94
+
95
+ # Apply migrations
96
+ VCS_DATABASE_URL=postgresql+psycopg://vcs:<password>@localhost:5433/vcs alembic upgrade head
97
+ # (dev password: see docker-compose.yml)
98
+
99
+ # Run API
100
+ VCS_DATABASE_URL=... uvicorn vcs.api.app:create_app --factory --reload
101
+
102
+ # Run dashboard (in ./dashboard)
103
+ npm run dev
104
+ ```
105
+
106
+ ### Running a scan
107
+
108
+ ```bash
109
+ # Scan current directory and print results
110
+ vcs scan .
111
+
112
+ # Scan and push results to central API
113
+ VCS_API_URL=http://localhost:8002 VCS_API_TOKEN=<token> vcs scan . --push
114
+
115
+ # Fail CI if critical or high findings exist
116
+ vcs scan . --push --fail-on high
117
+ ```
118
+
119
+ ## Project Structure
120
+
121
+ ```
122
+ src/vcs/
123
+ ├── api/
124
+ │ ├── routes/ # health, ingest, findings, repos, scans, overview, analytics
125
+ │ ├── services/ # ingest logic, auto-resolve
126
+ │ ├── app.py # FastAPI factory
127
+ │ ├── deps.py # DB session injection
128
+ │ └── schemas.py # Pydantic request/response models
129
+ ├── models/ # SQLAlchemy ORM: org, group, user, repo, scan, finding, api_token
130
+ ├── scanner/ # ASH wrapper: runner, parser, result models
131
+ ├── cli/ # Typer CLI: scan command, rich output
132
+ ├── client/ # HTTP client for pushing results to API
133
+ ├── db.py # Database connection factory
134
+ ├── enums.py # Severity, FindingStatus, etc.
135
+ └── config.py # Settings from environment
136
+
137
+ dashboard/src/
138
+ ├── pages/ # Overview, Findings, FindingDetail, Repos, RepoDetail, Scans, Analytics
139
+ ├── components/ # Layout, SeverityBadge, StatusBadge, Pagination, Panel, etc.
140
+ ├── api/ # TanStack Query hooks
141
+ └── types.ts # TypeScript types
142
+
143
+ alembic/versions/ # Database migrations
144
+ tests/ # Unit + integration tests
145
+ ```
146
+
147
+ ## Environment Variables
148
+
149
+ | Variable | Description | Default |
150
+ |---|---|---|
151
+ | `VCS_DATABASE_URL` | PostgreSQL connection string | required |
152
+ | `VCS_API_URL` | Central API base URL (CLI push) | required for `--push` |
153
+ | `VCS_API_TOKEN` | Bearer token for API auth (CLI push) | required for `--push` |
154
+ | `VCS_REDIS_URL` | Celery broker/result backend | `redis://localhost:6380/0` |
155
+ | `VCS_S3_ENDPOINT` | S3/MinIO endpoint for raw-result archiving | `http://localhost:9002` |
156
+ | `VCS_SESSION_SECRET` | Secret for signing dashboard session cookies | dev default (**required** once OIDC is configured — startup fails without it) |
157
+ | `VCS_CORS_ORIGINS` | Comma-separated allowed CORS origins; empty value = deny cross-origin (prod) | unset → any localhost port (dev) |
158
+ | `VCS_OIDC_ISSUER` | OIDC issuer URL (enables dashboard SSO) | unset → SSO disabled |
159
+ | `VCS_OIDC_CLIENT_ID` / `VCS_OIDC_CLIENT_SECRET` | OIDC client credentials | unset |
160
+ | `VCS_OIDC_REDIRECT_URI` | OIDC callback URL | `.../api/v1/auth/callback` |
161
+ | `VCS_OIDC_GROUPS_CLAIM` | Claim holding the user's group names | `groups` |
162
+ | `VCS_DASHBOARD_URL` | Post-login redirect target | `http://localhost:5175` |
163
+ | `VCS_DEV_LOGIN` | **Dev only** — enables `/api/v1/auth/dev-login` (one-click local session, no IdP). Never set in prod. | unset → disabled |
164
+
165
+ ## Running Tests
166
+
167
+ ```bash
168
+ pytest
169
+ pytest --cov=vcs --cov-report=term-missing
170
+ ```
@@ -0,0 +1,81 @@
1
+ [project]
2
+ name = "vehlo-code-scanner"
3
+ version = "0.1.1"
4
+ description = "Multi-tenant security scanning platform wrapping Amazon Security Helper"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "sqlalchemy>=2.0,<3.0",
9
+ "psycopg[binary]>=3.1,<4.0",
10
+ "alembic>=1.13,<2.0",
11
+ "typer>=0.16,<1.0",
12
+ "rich>=13.5,<14.0",
13
+ "httpx>=0.27,<1.0",
14
+ "fastapi>=0.115,<1.0",
15
+ "uvicorn[standard]>=0.30,<1.0",
16
+ "pydantic>=2.0,<3.0",
17
+ "celery>=5.3,<6.0",
18
+ "redis>=5.0,<6.0",
19
+ "boto3>=1.34",
20
+ "authlib>=1.3,<2.0",
21
+ "itsdangerous>=2.1,<3.0",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ scan = [
26
+ "vehlo-ash>=3.2.5,<4",
27
+ ]
28
+ # NOTE: the published wheel also gets a `scan` extra (`vehlo-ash>=3.2.5,<4`),
29
+ # injected by the release workflow at build time — see
30
+ # .github/workflows/release.yml and packaging/vehlo-ash/README.md. It is not
31
+ # declared here because vehlo-ash is published by that same release run, so
32
+ # declaring it would make `uv lock` unsatisfiable in dev. Dev installs ASH via
33
+ # the `local-scan` dependency-group below instead.
34
+ dev = [
35
+ "pytest>=8.0,<9.0",
36
+ "pytest-cov>=5.0,<6.0",
37
+ ]
38
+
39
+ [project.urls]
40
+ Homepage = "https://github.com/Vehlo-CyberSec/vehlo-code-scanner"
41
+ Repository = "https://github.com/Vehlo-CyberSec/vehlo-code-scanner"
42
+
43
+ [build-system]
44
+ requires = ["hatchling"]
45
+ build-backend = "hatchling.build"
46
+
47
+ [tool.hatch.build.targets.wheel]
48
+ packages = ["src/vcs"]
49
+
50
+ # Hatchling's sdist default is "everything not gitignored", which would
51
+ # publish the whole repo (internal docs, CI config, dashboard sources, any
52
+ # stray local files) to PyPI. Ship only what the package needs.
53
+ [tool.hatch.build.targets.sdist]
54
+ only-include = ["src/vcs", "README.md"]
55
+
56
+ [project.scripts]
57
+ vcs = "vcs.cli.app:app"
58
+ vcs-admin = "vcs.cli.admin:app"
59
+
60
+ [tool.pytest.ini_options]
61
+ testpaths = ["tests"]
62
+ pythonpath = ["src"]
63
+
64
+ [tool.ruff]
65
+ line-length = 80
66
+ target-version = "py312"
67
+
68
+ [tool.ruff.lint]
69
+ select = ["E", "F", "I", "UP"]
70
+
71
+ # Dev-only ASH install, straight from AWS's git at a pinned commit. This group
72
+ # is NOT part of published wheel metadata and is not installed by a plain
73
+ # `pip install`, so the squatted PyPI name never reaches end users. The Docker
74
+ # image bakes ASH via this group; published pip users get vehlo-ash instead.
75
+ [dependency-groups]
76
+ local-scan = [
77
+ "automated-security-helper>=3.0",
78
+ ]
79
+
80
+ [tool.uv.sources]
81
+ automated-security-helper = { git = "https://github.com/awslabs/automated-security-helper.git", rev = "968c5f8ce337499577f97e62afcf66c0a2bb9a84" }
@@ -0,0 +1,8 @@
1
+ """Vehlo Code Scanner — multi-tenant security scanning platform."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("vehlo-code-scanner")
7
+ except PackageNotFoundError: # running from an uninstalled source tree
8
+ __version__ = "0.0.0.dev0"
@@ -0,0 +1 @@
1
+ """API module — FastAPI application and routes."""
@@ -0,0 +1,93 @@
1
+ """FastAPI application factory."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+ from fastapi import FastAPI
7
+ from fastapi.middleware.cors import CORSMiddleware
8
+ from fastapi.responses import FileResponse
9
+ from fastapi.staticfiles import StaticFiles
10
+ from starlette.middleware.sessions import SessionMiddleware
11
+
12
+ from vcs import __version__
13
+ from vcs.api.routes import (
14
+ analytics,
15
+ auth,
16
+ findings,
17
+ groups,
18
+ health,
19
+ ingest,
20
+ overview,
21
+ repos,
22
+ scans,
23
+ tokens,
24
+ )
25
+ from vcs.config import get_cors_origins, get_session_secret
26
+
27
+
28
+ def create_app() -> FastAPI:
29
+ """Create and configure the FastAPI application."""
30
+ app = FastAPI(
31
+ title="Vehlo Code Scanner API",
32
+ description="Central API for security scan result ingestion and findings management.",
33
+ version=__version__,
34
+ )
35
+ # SessionMiddleware holds transient OIDC state (CSRF/nonce) during the
36
+ # login handshake. The authenticated session is a separate signed cookie.
37
+ app.add_middleware(SessionMiddleware, secret_key=get_session_secret())
38
+ # CORS: explicit origins from VCS_CORS_ORIGINS when set (empty = deny all
39
+ # cross-origin — right for prod, where the SPA is same-origin). The
40
+ # any-localhost-port regex is a dev-only default; with credentials
41
+ # allowed it must not reach a real deployment.
42
+ origins = get_cors_origins()
43
+ cors_scope = (
44
+ {"allow_origins": origins}
45
+ if origins is not None
46
+ else {"allow_origin_regex": r"http://localhost:\d+"}
47
+ )
48
+ app.add_middleware(
49
+ CORSMiddleware,
50
+ allow_credentials=True,
51
+ allow_methods=["*"],
52
+ allow_headers=["*"],
53
+ **cors_scope,
54
+ )
55
+ app.include_router(health.router)
56
+ app.include_router(auth.router)
57
+ app.include_router(tokens.router)
58
+ app.include_router(groups.router)
59
+ app.include_router(ingest.router)
60
+ app.include_router(findings.router)
61
+ app.include_router(overview.router)
62
+ app.include_router(repos.router)
63
+ app.include_router(scans.router)
64
+ app.include_router(analytics.router)
65
+ _mount_dashboard(app)
66
+ return app
67
+
68
+
69
+ def _mount_dashboard(app: FastAPI) -> None:
70
+ """Serve the built dashboard SPA from the API when bundled.
71
+
72
+ Active only when VCS_DASHBOARD_DIR points at a build (so tests/local
73
+ are unaffected). API routes are registered first and take precedence;
74
+ the catch-all serves real files, else index.html for client-side routes.
75
+ """
76
+ dist = os.environ.get("VCS_DASHBOARD_DIR")
77
+ if not dist:
78
+ return
79
+ root = Path(dist)
80
+ index = root / "index.html"
81
+ if not index.is_file():
82
+ return
83
+ if (root / "assets").is_dir():
84
+ app.mount(
85
+ "/assets", StaticFiles(directory=root / "assets"), name="assets"
86
+ )
87
+
88
+ @app.get("/{full_path:path}", include_in_schema=False)
89
+ def spa(full_path: str) -> FileResponse:
90
+ candidate = root / full_path
91
+ if full_path and candidate.is_file():
92
+ return FileResponse(candidate)
93
+ return FileResponse(index)