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.
- vehlo_code_scanner-0.1.1/.gitignore +53 -0
- vehlo_code_scanner-0.1.1/PKG-INFO +198 -0
- vehlo_code_scanner-0.1.1/README.md +170 -0
- vehlo_code_scanner-0.1.1/pyproject.toml +81 -0
- vehlo_code_scanner-0.1.1/src/vcs/__init__.py +8 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/__init__.py +1 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/app.py +93 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/auth.py +95 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/deps.py +161 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/routes/__init__.py +1 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/routes/analytics.py +79 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/routes/auth.py +169 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/routes/findings.py +141 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/routes/groups.py +122 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/routes/health.py +11 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/routes/ingest.py +49 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/routes/overview.py +95 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/routes/repos.py +106 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/routes/scans.py +79 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/routes/tokens.py +178 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/schemas.py +172 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/services/__init__.py +1 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/services/auto_resolve.py +55 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/services/ingest.py +205 -0
- vehlo_code_scanner-0.1.1/src/vcs/api/session.py +32 -0
- vehlo_code_scanner-0.1.1/src/vcs/cli/__init__.py +1 -0
- vehlo_code_scanner-0.1.1/src/vcs/cli/admin.py +177 -0
- vehlo_code_scanner-0.1.1/src/vcs/cli/app.py +20 -0
- vehlo_code_scanner-0.1.1/src/vcs/cli/output.py +68 -0
- vehlo_code_scanner-0.1.1/src/vcs/cli/scan_command.py +195 -0
- vehlo_code_scanner-0.1.1/src/vcs/client/__init__.py +1 -0
- vehlo_code_scanner-0.1.1/src/vcs/client/api.py +108 -0
- vehlo_code_scanner-0.1.1/src/vcs/config.py +150 -0
- vehlo_code_scanner-0.1.1/src/vcs/db.py +47 -0
- vehlo_code_scanner-0.1.1/src/vcs/enums.py +55 -0
- vehlo_code_scanner-0.1.1/src/vcs/fingerprint.py +17 -0
- vehlo_code_scanner-0.1.1/src/vcs/models/__init__.py +22 -0
- vehlo_code_scanner-0.1.1/src/vcs/models/api_token.py +39 -0
- vehlo_code_scanner-0.1.1/src/vcs/models/base.py +32 -0
- vehlo_code_scanner-0.1.1/src/vcs/models/finding.py +81 -0
- vehlo_code_scanner-0.1.1/src/vcs/models/finding_history.py +42 -0
- vehlo_code_scanner-0.1.1/src/vcs/models/group.py +35 -0
- vehlo_code_scanner-0.1.1/src/vcs/models/organization.py +23 -0
- vehlo_code_scanner-0.1.1/src/vcs/models/repo.py +56 -0
- vehlo_code_scanner-0.1.1/src/vcs/models/scan.py +62 -0
- vehlo_code_scanner-0.1.1/src/vcs/models/user.py +53 -0
- vehlo_code_scanner-0.1.1/src/vcs/scanner/__init__.py +1 -0
- vehlo_code_scanner-0.1.1/src/vcs/scanner/models.py +67 -0
- vehlo_code_scanner-0.1.1/src/vcs/scanner/parser.py +25 -0
- vehlo_code_scanner-0.1.1/src/vcs/scanner/runner.py +146 -0
- vehlo_code_scanner-0.1.1/src/vcs/services/__init__.py +1 -0
- vehlo_code_scanner-0.1.1/src/vcs/services/provisioning.py +144 -0
- vehlo_code_scanner-0.1.1/src/vcs/services/tokens.py +74 -0
- vehlo_code_scanner-0.1.1/src/vcs/storage/__init__.py +55 -0
- vehlo_code_scanner-0.1.1/src/vcs/storage/memory.py +19 -0
- vehlo_code_scanner-0.1.1/src/vcs/storage/s3.py +49 -0
- vehlo_code_scanner-0.1.1/src/vcs/worker/__init__.py +1 -0
- vehlo_code_scanner-0.1.1/src/vcs/worker/celery_app.py +17 -0
- vehlo_code_scanner-0.1.1/src/vcs/worker/db.py +33 -0
- 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)
|