diff --git a/.github/workflows/fastapi-test.yml b/.github/workflows/fastapi-test.yml
index e3242152..417d90b4 100644
--- a/.github/workflows/fastapi-test.yml
+++ b/.github/workflows/fastapi-test.yml
@@ -49,7 +49,7 @@ jobs:
run: hatch run lint:format-check
- name: Install pnpm
- uses: pnpm/action-setup@008330803749db0355799c700092d9a85fd074e9
+ uses: pnpm/action-setup@008330803749db0355799c700092d9a85fd074e9 # v4.0.0
- name: Install Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
@@ -76,7 +76,7 @@ jobs:
run: yamllint --config-file .yamllint .github/ infra/ docker-compose*.yml
- name: Run ShellCheck
- run: shellcheck --severity=warning scripts/*.sh entrypoint.sh migrate.sh
+ run: shellcheck --severity=warning scripts/*.sh entrypoint.sh migrate.sh worker/entrypoint-worker.sh
# ============================================
# TYPE CHECK - Static type analysis
@@ -164,7 +164,7 @@ jobs:
image: postgres:15
env:
POSTGRES_USER: test_user
- POSTGRES_PASSWORD: test_pass
+ POSTGRES_PASSWORD: test_pw
POSTGRES_DB: test_db
options: >-
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
@@ -200,20 +200,24 @@ jobs:
- name: Create test environment
run: |
+ SK="$(openssl rand -hex 32)"
cp .env.example .env
- sed -i 's|^DB_PASSWORD=.*|DB_PASSWORD=test_pass|' .env
- sed -i 's|^SECRET_KEY=$|SECRET_KEY=test-secret-key-for-ci-at-least-32-chars-long|' .env
+ sed -i "s|^DB_PASSWORD=.*|DB_PASSWORD=test_pw|" .env
+ sed -i "s|^SECRET_KEY=$|SECRET_KEY=${SK}|" .env
+ echo "PGPASSWORD=test_pw" >> "$GITHUB_ENV"
- name: Create database schema
run: |
uv run python -c "
import asyncio
+ import os
from sqlalchemy.ext.asyncio import create_async_engine
from core.database import Base
from core.models import User, DownloadJob
async def init_db():
- engine = create_async_engine('postgresql+asyncpg://test_user:test_pass@localhost:5432/test_db')
+ db_pass = os.environ['PGPASSWORD']
+ engine = create_async_engine(f'postgresql+asyncpg://test_user:{db_pass}@localhost:5432/test_db')
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
await engine.dispose()
diff --git a/.gitignore b/.gitignore
index 024244e7..7428ed0e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -180,3 +180,15 @@ docs/CRITIQUE-ISSUES.md
.agents/
.kilocode/skills/*
.agents/.story-automator-active
+
+# Local scan and audit artifacts (not checked in)
+.betterleaks.toml
+.betterleaksignore
+betterleaks-setup.md
+betterleaks.sarif
+SOTA-AUDIT-REPORT.md
+fail-console.md
+fail-result.md
+fail.har
+# HAR files and local exports that are ephemeral
+notegpt-clone/
diff --git a/.secrets.baseline b/.secrets.baseline
index 127212f7..2e712109 100644
--- a/.secrets.baseline
+++ b/.secrets.baseline
@@ -146,14 +146,14 @@
{
"type": "Secret Keyword",
"filename": ".github/workflows/fastapi-test.yml",
- "hashed_secret": "c94d65f02a652d11c2e5c2e1ccf38dce5a076e1e",
+ "hashed_secret": "d986d7729a3ca93e8fb8d9c0f1962564aed9f829",
"is_verified": false,
"line_number": 167
},
{
"type": "Secret Keyword",
"filename": ".github/workflows/fastapi-test.yml",
- "hashed_secret": "89edba72d4aef5098771cee787b40b81af666eb3",
+ "hashed_secret": "fac0e4eec4d11a3bbad583ff2d750d900a70bf72",
"is_verified": false,
"line_number": 202
}
diff --git a/Dockerfile b/Dockerfile
index 858f673b..e87213f1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,7 +7,7 @@
# ============================================
# Stage 1: Python Dependency Builder
# ============================================
-FROM python:3.12-slim AS python-builder
+FROM python@sha256:6c4dd321d176d61ea848dc8c73a4f7dbae8f70e0ee48bb411ea2f045b599fa8e AS python-builder
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
@@ -29,7 +29,7 @@ ENV PATH="/opt/venv/bin:$PATH" \
UV_COMPILE_BYTECODE=1
# Install uv binary (single static binary, ~25MB, not copied to final image)
-COPY --from=ghcr.io/astral-sh/uv:0.6 /uv /bin/uv
+COPY --from=ghcr.io/astral-sh/uv@sha256:4a6c9444b126bd325fba904bff796bf91fb777bf6148d60109c4cb1de2ffc497 /uv /bin/uv
# Copy manifest and lockfile first → cacheable dependency layer
COPY pyproject.toml uv.lock ./
@@ -41,7 +41,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
# ============================================
# Stage 2: Frontend Builder
# ============================================
-FROM node:20-alpine AS frontend-builder
+FROM node@sha256:fb4cd12c85ee03686f6af5362a0b0d56d50c58a04632e6c0fb8363f609372293 AS frontend-builder
WORKDIR /app
# Install pnpm for package management (version pinned in frontend/package.json packageManager field)
@@ -102,7 +102,7 @@ RUN mkdir -p /app/app/static/swagger && \
# ============================================
# Stage 4: Runtime Base
# ============================================
-FROM python:3.12-slim AS runtime-base
+FROM python@sha256:6c4dd321d176d61ea848dc8c73a4f7dbae8f70e0ee48bb411ea2f045b599fa8e AS runtime-base
ENV PYTHONDONTWRITEBYTECODE=1
# Install runtime dependencies with apt cache mounts
@@ -115,7 +115,10 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
curl \
gnupg \
&& mkdir -p /etc/apt/keyrings \
- && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
+ && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key -o /tmp/nodesource-repo.gpg.key \
+ && echo "b42e0321dabdc24e892115da705cf061167eac12a317f23d329862d0aa0a271d /tmp/nodesource-repo.gpg.key" | sha256sum -c - \
+ && gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg /tmp/nodesource-repo.gpg.key \
+ && rm /tmp/nodesource-repo.gpg.key \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends nodejs \
diff --git a/app/api/dependencies/__init__.py b/app/api/dependencies/__init__.py
index e5746d24..d0328e37 100644
--- a/app/api/dependencies/__init__.py
+++ b/app/api/dependencies/__init__.py
@@ -77,7 +77,7 @@ async def get_current_user_from_cookie(
if credentials is not None:
token = credentials.credentials
else:
- token = request.cookies.get("access_token")
+ token = request.cookies.get("__Host-access_token")
return await _resolve_user_from_token(db, token, expected_type=ACCESS_TOKEN_TYPE)
diff --git a/app/api/docs.py b/app/api/docs.py
index 9dea1de8..580d6661 100644
--- a/app/api/docs.py
+++ b/app/api/docs.py
@@ -30,7 +30,7 @@ def register_docs_routes(app: FastAPI) -> None:
"""Register custom Swagger UI and ReDoc routes."""
@app.get("/docs", include_in_schema=False)
- async def custom_docs(request: Request):
+ async def custom_docs(request: Request) -> HTMLResponse:
nonce = request.state.nonce
swagger_dir = APP_DIR / "static" / "swagger"
if swagger_dir.exists():
@@ -69,7 +69,7 @@ async def custom_docs(request: Request):
return docs_response
@app.get("/redoc", include_in_schema=False)
- async def custom_redoc(request: Request):
+ async def custom_redoc(request: Request) -> HTMLResponse:
nonce = request.state.nonce
redoc_dir = APP_DIR / "static" / "redoc"
if redoc_dir.exists():
@@ -97,7 +97,7 @@ async def custom_redoc(request: Request):
def _inject_inline_script_nonce(html: str, nonce: str) -> str:
"""Add the request nonce to FastAPI's generated inline docs script."""
return html.replace(
- "
+
{% endblock %}
diff --git a/app/templates/slides/presentation.html b/app/templates/slides/presentation.html
index 13a6ed45..1bf37e0c 100644
--- a/app/templates/slides/presentation.html
+++ b/app/templates/slides/presentation.html
@@ -9,7 +9,7 @@
-
@@ -281,7 +286,7 @@
-
+
Every downloader
@@ -323,7 +328,7 @@ No Observability
-
+
@@ -331,7 +336,7 @@
No Observability
Happy Path — Normal Operation
-
+