Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
80fea73
docs(backlog): add DEBT-024 security sweep from post-release review
fvadicamo Jul 11, 2026
be52b0d
docs(backlog): refresh DEBT-024 to 2026-07-11 alert state
fvadicamo Jul 11, 2026
219075a
chore(deps): re-lock vulnerable transitives for DEBT-024 security sweep
fvadicamo Jul 11, 2026
a2857d1
ci(security): add minimal permissions blocks to all workflows
fvadicamo Jul 11, 2026
bcd5dd5
fix(admin): validate token format before setting login cookie
fvadicamo Jul 11, 2026
faefe78
fix(admin): strip token whitespace; assert lookup skipped in login test
fvadicamo Jul 11, 2026
ef162e8
fix(app): replace starlette-prometheus with prometheus-fastapi-instru…
fvadicamo Jul 11, 2026
f12d152
docs(changelog): log admin login hardening under v0.5.1 security
fvadicamo Jul 11, 2026
ace0816
Merge pull request #80 from vektralabs/chore/v0.5.1-security
fvadicamo Jul 11, 2026
dd1f159
chore(deps-dev): Bump the development-dependencies group across 1 dir…
dependabot[bot] Jul 12, 2026
f81a143
Merge pull request #78 from vektralabs/dependabot/pip/develop/develop…
fvadicamo Jul 12, 2026
f585f0f
chore(deps): Bump actions/checkout from 6 to 7
dependabot[bot] Jul 12, 2026
a04e81b
Merge pull request #79 from vektralabs/dependabot/github_actions/deve…
fvadicamo Jul 12, 2026
f111601
chore(deps): Bump dorny/paths-filter from 3 to 4
dependabot[bot] Jul 12, 2026
43d31fc
Merge pull request #39 from vektralabs/dependabot/github_actions/deve…
fvadicamo Jul 12, 2026
4a7dd3c
Merge branch 'main' into chore/v0.5.1-release
fvadicamo Jul 12, 2026
cd76653
chore(release): finalize v0.5.1
fvadicamo Jul 12, 2026
fd2ef7c
Merge pull request #81 from vektralabs/chore/v0.5.1-release
fvadicamo Jul 12, 2026
434d3cc
docs(backlog): add DEBT-026 for Prometheus instrumentation exclusions
fvadicamo Jul 12, 2026
0beb32a
docs(backlog): reorder DEBT-026 after DEBT-025; anchor regex examples
fvadicamo Jul 12, 2026
ca25717
Merge pull request #83 from vektralabs/docs/backlog-debt-026
fvadicamo Jul 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions .github/workflows/ci-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ on:
push:
branches: [develop, main]

permissions:
contents: read

jobs:
# Detect which components have changed to skip unaffected test jobs.
# Changes to vektra-shared trigger all components (it is a shared dependency).
detect-changes:
name: Detect changed components
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read # dorny/paths-filter reads PR files on pull_request events
outputs:
shared: ${{ steps.filter.outputs.shared }}
core: ${{ steps.filter.outputs.core }}
Expand All @@ -20,8 +26,8 @@ jobs:
analytics: ${{ steps.filter.outputs.analytics }}
learn: ${{ steps.filter.outputs.learn }}
steps:
- uses: actions/checkout@v6
- uses: dorny/paths-filter@v3
- uses: actions/checkout@v7
- uses: dorny/paths-filter@v4
id: filter
with:
filters: |
Expand Down Expand Up @@ -52,7 +58,7 @@ jobs:
if: needs.detect-changes.outputs.shared == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
Expand All @@ -69,7 +75,7 @@ jobs:
if: needs.detect-changes.outputs.core == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
Expand All @@ -86,7 +92,7 @@ jobs:
if: needs.detect-changes.outputs.ingest == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
Expand All @@ -103,7 +109,7 @@ jobs:
if: needs.detect-changes.outputs.index == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
Expand All @@ -120,7 +126,7 @@ jobs:
if: needs.detect-changes.outputs.admin == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
Expand All @@ -137,7 +143,7 @@ jobs:
if: needs.detect-changes.outputs.analytics == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
Expand All @@ -154,7 +160,7 @@ jobs:
if: needs.detect-changes.outputs.learn == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ on:
push:
branches: [develop, main]

permissions:
contents: read

jobs:
integration:
name: Integration tests + NFR gates
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- uses: astral-sh/setup-uv@v7
with:
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ on:
push:
pull_request:

permissions:
contents: read

jobs:
lint:
name: Lint and type check
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- uses: astral-sh/setup-uv@v7
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/shell-scripts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ name: Shell scripts
on:
pull_request:

permissions:
contents: read

jobs:
shell-scripts:
name: Validate shell scripts
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- name: Check shell scripts exist
id: check
Expand Down
95 changes: 94 additions & 1 deletion .s2s/BACKLOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Vektra Backlog

**Updated**: 2026-04-09
**Updated**: 2026-07-12
**Format**: Single markdown file for tracking work items

---
Expand Down Expand Up @@ -830,6 +830,99 @@ Three near-duplicates is the threshold where extraction starts to pay off (a fou

---

### DEBT-024: Security hardening — Dependabot + code scanning sweep (post v0.5.0)

**Status**: completed | **Priority**: high | **Created**: 2026-05-06 | **Updated**: 2026-07-11 | **Completed**: 2026-07-12 (v0.5.1) | **PR**: #80
**Origin**: post-release security review (`gh api repos/.../dependabot/alerts`, `repos/.../code-scanning/alerts`) on 2026-05-06; re-swept 2026-07-11

**Resolution**: PR #80 (merged to develop 2026-07-11, released with v0.5.1): uv.lock re-lock closing 61 of 62 Dependabot alerts (torch low has no patched release, stays open by design), minimal `permissions:` blocks on all workflows (12 code-scanning alerts), `py/cookie-injection` closed by a token-format guard on `POST /admin/login`. Fallout handled in the same PR: starlette 1.x broke starlette-prometheus (unmaintained), replaced with prometheus-fastapi-instrumentator (ARCH-014 updated). Dependabot PRs: #78, #79, #39, #77 merged; #76 closed as superseded. Alert closure happens on the default branch, verified after the v0.5.1 release merge.

**Context**: v0.5.0 closed the critical/high litellm CVEs and the lxml/pillow/pypdf transitive bumps that were active at release time. The original sweep (2026-05-06) found 14 open Dependabot alerts. Two months of inactivity later (2026-07-11) the set has grown to **62 open Dependabot alerts** (1 critical, 15 high, 29 medium, 17 low — all transitive in `uv.lock`) and 13 code-scanning warnings. Priority raised medium → high: the critical litellm alert is an authentication bypass.

**Open Dependabot alerts** (state=open, 2026-07-11, all in `uv.lock`, grouped by package):

| Package | Alerts (severity) | Fix version | Notes |
|---------|-------------------|-------------|-------|
| litellm | 1 critical, 1 high | 1.84.0 | **Auth bypass via Host header injection** — top priority |
| PyJWT / pyjwt | 2 high, 2 medium, 1 low | 2.13.0 | Public-key JWK accepted as HMAC secret (forged tokens); `crit` header |
| starlette / Starlette | 2 high, 2 medium, 1 low | 1.3.1 | SSRF + NTLM credential theft via UNC in StaticFiles; form limits ignored |
| transformers | 1 high | 5.3.0 | RCE |
| urllib3 | 2 high | 2.7.0 | Decompression bomb bypass; header leak across origins |
| cryptography | 1 high | 48.0.1 | Vulnerable OpenSSL in wheels |
| Mako | 2 high | 1.3.12 | Path traversal in TemplateLookup |
| python-multipart | 2 high, 3 low | 0.0.31 | Multipart DoS |
| soupsieve | 2 high | 2.8.4 | ReDoS / memory exhaustion |
| aiohttp | 21 alerts (11 medium, 10 low) | 3.14.1 | Single bump clears the whole cluster |
| pypdf | 9 medium | 6.13.3 | |
| idna | 1 medium | 3.15 | |
| onnx | 1 medium | 1.22.0 | |
| pydantic-settings | 1 medium | 2.14.2 | |
| python-dotenv | 1 medium | 1.2.2 | |
| Pygments | 1 low | 2.20.0 | |
| torch | 1 low | none published | Cannot be resolved by re-lock; leave open, re-check at v0.5.2 |

**Open code-scanning alerts** (unchanged since 2026-05-06):

| # | Severity | Rule | File |
|---|----------|------|------|
| 1 | medium | `py/cookie-injection` | `vektra-admin/src/vektra_admin/ui.py` |
| 3–15 | medium (warning) | `actions/missing-workflow-permissions` | `.github/workflows/ci-unit.yml` (9 jobs), `integration.yml`, `lint.yml`, `shell-scripts.yml` |

**Approach**:

1. **litellm (critical) and PyJWT first**: auth-adjacent surface. Bump constraints where needed and re-lock.
2. **Remaining high cluster** (starlette, transformers, urllib3, cryptography, Mako, python-multipart, soupsieve): same re-lock pass, verifying each first-patched version is reached.
3. **Medium/low clusters** (aiohttp ×21, pypdf ×9, idna, onnx, pydantic-settings, python-dotenv, Pygments): expected to clear in the same re-lock.
4. **`actions/missing-workflow-permissions`**: one commit adding minimal `permissions:` blocks (`contents: read` at workflow level, granular at job level where needed) to all four workflow files.
5. **`py/cookie-injection`** in `vektra-admin/ui.py`: requires code review (not just bump). Inspect cookie write path in admin UI for tainted input from user-controlled fields; harden or annotate as intentional only if false positive.

**Acceptance criteria**:
- [ ] litellm critical alert (#92) and high (#95) resolved
- [ ] All PyJWT alerts (#1, #68, …) resolved at >= 2.13.0
- [ ] All remaining high alerts resolved (starlette, transformers, urllib3, cryptography, Mako, python-multipart, soupsieve)
- [ ] aiohttp (×21), pypdf (×9) and remaining medium/low alerts resolved; torch documented as no-fix-available
- [ ] All 12 `actions/missing-workflow-permissions` code-scanning alerts dismissed/closed
- [ ] `py/cookie-injection` alert either resolved by code change or documented as false positive in `vektra-admin/ui.py` with justification
- [ ] `make lint` and `make test` green after re-lock; CI passes on the resulting PR

**Notes**:
- Open Dependabot PRs to re-evaluate as part of this sweep: #39 (paths-filter v3→v4), #76 (uv group), #77 (esbuild, widget npm), #78 (dev-deps), #79 (actions/checkout 7). The uv re-lock here likely supersedes #76/#78.
- Three transitive deps alerts mentioned in v0.5.0 release notes (lxml/onnx/pillow) were fixed 2026-05-05; onnx has since reopened with a new advisory (see table).

---

### DEBT-025: Isolate unit tests from the developer's local .env

**Status**: planned | **Priority**: low | **Created**: 2026-07-12
**Origin**: discovered during the DEBT-024 sweep (PR #80): `make test` fails locally with 4 errors while CI is green.

**Context**: 4 tests in `vektra-shared/tests/test_config.py` (`TestLLMConfig::test_defaults`, `TestQueryPipelineConfig::test_eval_mode_default_false`, `test_debug_log_queries_default_false`, `TestVektraSettings::test_defaults_with_required_only`) assert configuration defaults, but when the full suite runs from the workspace root the developer's `.env` leaks into `os.environ` (something imported during collection loads dotenv, e.g. litellm), so machine-specific values (eval_mode=true, custom port, LLM keys) override the defaults and the assertions fail. CI never sees this because runners have no `.env`. Current workaround: temporarily move `.env` away before `make test`.

**Proposed approach**: a `vektra-shared/tests` (or workspace-level) autouse fixture that snapshots and scrubs `VEKTRA_*` variables from `os.environ` for the default-assertion tests, or `monkeypatch.delenv` on the specific vars. Alternatively point pydantic-settings at a nonexistent env file in tests via `_env_file=None`.

**Acceptance criteria**:
- [ ] `make test` passes on a dev machine with a populated `.env`
- [ ] Default-assertion tests are hermetic (no dependency on ambient `VEKTRA_*` vars)
- [ ] No change to production settings loading behavior

---

### DEBT-026: Tune Prometheus instrumentation exclusions (health/docs endpoints)

**Status**: planned | **Priority**: low | **Created**: 2026-07-12
**Origin**: Gemini review on PR #82 (v0.5.1 release), `vektra-app/src/vektra_app/main.py:680`

**Context**: the prometheus-fastapi-instrumentator setup (introduced in v0.5.1 when it replaced starlette-prometheus) excludes only `/metrics` from instrumentation. Health endpoints (`/health`, `/health/{component}`, `/health/memory`) and docs endpoints (`/docs`, `/openapi.json`) are polled by load balancers, orchestrators, and monitoring systems; instrumenting them inflates request counters and histogram cardinality with traffic that carries no signal about API usage.

**Proposed approach**: extend `excluded_handlers` with anchored, escaped regexes — the instrumentator compiles each pattern and matches with `re.match`, so an unanchored `/docs` would also match `/docsomething` and an unescaped `.` matches any character. Example: `["^/metrics$", "^/health", "^/docs$", "^/openapi\\.json$"]`. Consider whether health-endpoint latency is itself worth tracking (it can reveal DB/LLM check slowness) before excluding it wholesale — an alternative is excluding only `/docs`/`/openapi.json` and keeping `/health` instrumented.

**Acceptance criteria**:
- [ ] Decision recorded on which endpoints stay instrumented (with rationale)
- [ ] `excluded_handlers` updated accordingly
- [ ] `/metrics` output verified: excluded handlers no longer appear in `http_requests_total`

---

### INFRA-005: Docker log persistence across container restarts

**Status**: planned | **Priority**: medium | **Created**: 2026-03-23
Expand Down
2 changes: 1 addition & 1 deletion .s2s/CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Split criteria defined in [ADR-0002](decisions/ADR-0002-repo-split-criteria.md).
- **Authentication**: API key authentication with argon2id hashing, scoped permissions (admin/ingest/query per REQ-031, multiple scopes per key). Single trust boundary at vektra-core gateway. Rate limiting enforced via per-key RPM in middleware. See [ADR-0010](decisions/ADR-0010-authentication-gateway.md).
- **Authorization**: Namespace isolation via PostgreSQL RLS policies. Application-level filtering for Phase 1, RLS binding via feature flag for multi-tenant activation. Namespace as first-class entity with metadata (ARCH-047). See [ADR-0009](decisions/ADR-0009-namespace-isolation-rls.md).
- **Logging**: structlog with JSON output, PII redaction processors. Correlation ID propagation across sync calls and arq jobs. OpenTelemetry spans at module boundaries. QueryTrace (ARCH-041) for RAG-specific observability, separate from audit log.
- **Monitoring**: Prometheus metrics on /metrics via starlette-prometheus. Hierarchical health endpoints (GET /health, GET /health/{component}). Memory observability via GET /health/memory.
- **Monitoring**: Prometheus metrics on /metrics via prometheus-fastapi-instrumentator. Hierarchical health endpoints (GET /health, GET /health/{component}). Memory observability via GET /health/memory.
- **Security**: TLS termination at reverse proxy layer (NFR-012). Encryption at rest via pgcrypto for conversations (ARCH-031) and PostgreSQL TDE for database. Soft delete for compliance (REQ-057). SafeguardResult supports content modification for PII anonymization (ARCH-049). See [architecture.md](architecture.md#security).
- **Extensibility**: 9 Protocol interfaces with ProviderRegistry (ARCH-039). Forward-compatible data model with Phase 2 fields present from Phase 1 (ARCH-040). EventEmitter for internal hooks (ARCH-038). LlamaIndex not adopted for Phase 1-2, standalone evaluation via RAGAS/DeepEval (ARCH-046). Three-tier evaluation strategy: CI synthetic tests, staging eval mode, production metrics-only (ARCH-050).
- **Pipeline quality**: SimpleQueryPipeline includes retrieval quality controls (relevance threshold, overlap deduplication, no-relevant-context detection per ARCH-056) and token budget allocation (ARCH-055) for prompt construction. Prompt templates are composable Jinja2 files (system, context, conversation per ARCH-054) with configurable path. Startup validation sequence (ARCH-057) ensures clear error reporting on misconfiguration.
Expand Down
2 changes: 1 addition & 1 deletion .s2s/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ See [section 8.4](#84-deployment) for Docker Compose specification and resource

- **ARCH-012 - Tech stack**: FastAPI 0.115+ with Pydantic v2, sentence-transformers (all-MiniLM-L6-v2), Python 3.11+.
- **ARCH-013 - Structured logging**: structlog with JSON output, PII redaction processors.
- **ARCH-014 - Prometheus metrics**: starlette-prometheus on /metrics endpoint. Histogram buckets aligned with latency NFRs.
- **ARCH-014 - Prometheus metrics**: prometheus-fastapi-instrumentator on /metrics endpoint (replaced starlette-prometheus in v0.5.1, unmaintained and incompatible with starlette >= 1.0). Histogram buckets aligned with latency NFRs.
- **ARCH-028 - LLM abstraction: litellm**: ~5MB footprint, native async streaming. Wrapped behind LLMProvider Protocol.
- **ARCH-029 - Protocol method signatures**: Standardized async Protocols for LLMProvider, VectorStoreProvider, DocumentExtractor, SafeguardHook.
- **ARCH-030 - PDF extraction: pdfplumber**: Pure Python, ~5MB footprint. Implements DocumentExtractor Protocol.
Expand Down
2 changes: 1 addition & 1 deletion .s2s/validation-scenarios.md
Original file line number Diff line number Diff line change
Expand Up @@ -1154,7 +1154,7 @@ Phase: 1 | 2

**Actor**: Platform Operator / Prometheus
**Trigger**: Monitoring system scrapes metrics
**Preconditions**: Vektra stack running with starlette-prometheus
**Preconditions**: Vektra stack running with prometheus-fastapi-instrumentator

**Flow**:
1. Prometheus scrapes GET /metrics
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ Convention (Keep a Changelog 1.1.0):

<!-- Add entries under: Added, Changed, Deprecated, Removed, Fixed, Security -->

## [0.5.1] - 2026-07-12

Security hardening: DEBT-024 dependency sweep, workflow permissions, admin login hardening.

### Security

- **deps**: re-lock of all transitive dependencies flagged by Dependabot (DEBT-024, 61 of 62 open alerts). Highlights: litellm 1.83.10 → 1.91.2 (critical: authentication bypass via Host header injection), pyjwt 2.11 → 2.13 (public-key JWK accepted as HMAC secret), starlette 0.52 → 1.3.1 with fastapi 0.129 → 0.139 (StaticFiles SSRF/NTLM credential theft), transformers 5.2 → 5.3 (RCE), plus urllib3, cryptography, Mako, python-multipart, soupsieve, aiohttp (×21 advisories), pypdf (×9), idna, onnx, pydantic-settings, python-dotenv, Pygments. The remaining open alert (torch, low) has no patched release yet.
- **ci**: minimal `permissions:` blocks added to all GitHub workflows (`contents: read`), closing the 12 `actions/missing-workflow-permissions` code-scanning alerts.
- **admin**: login hardening: `POST /admin/login` validates the token against the urlsafe-base64 charset before key-store lookup (closes the `py/cookie-injection` code-scanning alert) and strips accidental surrounding whitespace from pasted tokens.
- **deps-dev**: esbuild 0.25 → 0.28 in the widget build toolchain (Dependabot security update); development-dependencies group refresh; GitHub Actions bumps (checkout v7, dorny/paths-filter v4).

### Changed

- **metrics**: `/metrics` is now served by prometheus-fastapi-instrumentator instead of starlette-prometheus (unmaintained, incompatible with starlette >= 1.0: every request failed with `AttributeError` on included routers). Metric names change accordingly (e.g. `http_request_duration_seconds` replaces the `starlette_*` families); no in-repo dashboards or tests referenced the old names. ARCH-014 updated.

## [0.5.0] - 2026-04-27

Widget production-ready + instructor configuration.
Expand Down
19 changes: 9 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ explicit = true

[dependency-groups]
dev = [
"alembic>=1.13",
"pyyaml>=6.0",
"testcontainers[postgres]>=4.8",
"pytest>=9.0.3",
"pytest-asyncio>=0.24",
"pytest-cov>=5.0",
"ruff>=0.15",
"mypy>=1.13",
"import-linter>=2.7",
"alembic>=1.18.5",
"pyyaml>=6.0.3",
"testcontainers[postgres]>=4.14.2",
"pytest>=9.1.1",
"pytest-asyncio>=1.4.0",
"pytest-cov>=7.1.0",
"ruff>=0.15.21",
"mypy>=2.2.0",
"import-linter>=2.13",
]

# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -85,7 +85,6 @@ module = [
"docx.*",
"testcontainers.*",
"psutil.*",
"starlette_prometheus.*",
"uvicorn.*",
"qdrant_client",
"qdrant_client.*",
Expand Down
Loading
Loading