Skip to content

Commit a404bfb

Browse files
Merge pull request #189 from brownjuly2003-code/audit-gpt-p0
Audit hardening: tenant isolation, TLS-enforcing production profile, PG pooling & migrations, hash-pinned supply chain, real SLIs
2 parents 5328367 + 790742a commit a404bfb

131 files changed

Lines changed: 16874 additions & 2148 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.dockerignore‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@
44
.env.*
55
*.pem
66
*.key
7+
# Secret-bearing config: mirrors pyproject.toml's sdist exclude and
8+
# scripts/check_release_artifacts.py's FORBIDDEN_MEMBER_PATTERNS (audit
9+
# P1-4). Dockerfile.api does `COPY config /app/config`; without these entries
10+
# that copy would bake credential material (bcrypt key hashes, webhook
11+
# secrets) into an immutable image layer. Runtime supplies the real files via
12+
# a docker-compose bind mount or a Helm Secret mount, never from the image.
13+
config/api_keys.yaml
14+
config/webhooks.yaml
15+
config/tenants.yaml
716
*.duckdb
817
*.duckdb.*
918
.artifacts

‎.github/dependabot.yml‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,17 @@ version: 2
66
# matches CONTRIBUTING.md, and a grouped minor+patch PR so the review
77
# queue does not get spammed. Major bumps stay individual.
88
#
9-
# Security advisories from GitHub bypass the schedule and open
10-
# immediately regardless of group / interval — that is Dependabot
11-
# default and is what we want.
9+
# Dependabot version updates (the schedule below) are a different feature
10+
# from Dependabot SECURITY updates. Security updates are a separate
11+
# repo-level toggle, and audit P1-3 (2026-07-11) confirmed via the GitHub
12+
# API that it is currently DISABLED on this repo — an advisory on any
13+
# ecosystem below waits for the next scheduled run like any other bump; it
14+
# does not open a PR "immediately" as an earlier version of this comment
15+
# claimed. Enabling it is a repo-settings change gated on the repo owner:
16+
#
17+
# gh api --method PUT repos/brownjuly2003-code/agentflow/automated-security-fixes
18+
#
19+
# This file cannot flip that toggle by itself.
1220

1321
updates:
1422
# ── Python: root runtime (agentflow-runtime) ───────────────────────

‎.github/workflows/backup.yml‎

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
name: Nightly Backup
1+
name: Backup/Restore Regression Test
22

3+
# This does not back up a deployed environment. It builds synthetic DuckDB
4+
# fixtures on an ephemeral runner, backs *those* up, verifies the manifest,
5+
# and restores into a scratch directory — a regression test for
6+
# scripts/backup.py, scripts/verify_backup.py and scripts/restore.py, not a
7+
# disaster-recovery control (audit P1-2). See docs/disaster-recovery.md for
8+
# what backup coverage actually exists today.
39
on:
410
schedule:
511
- cron: "0 2 * * *"
@@ -22,11 +28,12 @@ jobs:
2228
- name: Prepare backup fixture
2329
run: |
2430
python - <<'PY'
25-
import re
2631
from pathlib import Path
2732
2833
import duckdb
2934
35+
from src.serving.backends.duckdb_backend import DuckDBBackend
36+
3037
pipeline_db = Path("agentflow_demo.duckdb")
3138
usage_db = Path("agentflow_api.duckdb")
3239
for path in (pipeline_db, usage_db):
@@ -36,36 +43,28 @@ jobs:
3643
if wal_path.exists():
3744
wal_path.unlink()
3845
39-
tenants_content = Path("config/tenants.yaml").read_text(encoding="utf-8")
40-
tenant_schemas = re.findall(
41-
r'^\s*duckdb_schema:\s*["\']?([^"\']+)["\']?\s*$',
42-
tenants_content,
43-
flags=re.MULTILINE,
44-
)
45-
46-
conn = duckdb.connect(str(pipeline_db))
46+
# The product's own DDL, not a copy of it. This step used to hand-write
47+
# a `CREATE SCHEMA` per tenant, parsed out of the `duckdb_schema` field
48+
# of config/tenants.yaml -- the schema-per-tenant model that ADR-004
49+
# retired. Nothing in src/ ever created those schemas, so the fixture
50+
# was fabricating the very thing the restore smoke test then went
51+
# looking for. Two tenants now share one table, keyed by `tenant_id`,
52+
# which is what a restored store actually has to bring back.
53+
backend = DuckDBBackend(db_path=str(pipeline_db))
54+
backend.ensure_schema()
55+
conn = backend.connection
4756
try:
48-
for schema in tenant_schemas:
49-
conn.execute(f'CREATE SCHEMA IF NOT EXISTS "{schema}"')
50-
conn.execute(
51-
f'''
52-
CREATE TABLE IF NOT EXISTS "{schema}".orders_v2 (
53-
order_id VARCHAR PRIMARY KEY,
54-
user_id VARCHAR,
55-
status VARCHAR,
56-
total_amount DECIMAL(10,2),
57-
currency VARCHAR,
58-
created_at TIMESTAMP
59-
)
60-
'''
61-
)
62-
conn.execute(
63-
f'''
64-
INSERT INTO "{schema}".orders_v2 VALUES
65-
(?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
66-
''',
67-
[f"ORD-{schema.upper()}-001", f"USR-{schema.upper()}", "confirmed", 42.0, "RUB"],
68-
)
57+
conn.execute(
58+
"""
59+
INSERT OR REPLACE INTO orders_v2
60+
(tenant_id, order_id, user_id, status, total_amount, currency, created_at)
61+
VALUES
62+
('acme-corp', 'ORD-ACME-001', 'USR-ACME', 'confirmed', 42.00, 'RUB',
63+
CURRENT_TIMESTAMP),
64+
('demo', 'ORD-DEMO-001', 'USR-DEMO', 'confirmed', 17.00, 'RUB',
65+
CURRENT_TIMESTAMP)
66+
"""
67+
)
6968
finally:
7069
conn.close()
7170
@@ -90,7 +89,7 @@ jobs:
9089
finally:
9190
conn.close()
9291
PY
93-
- name: Create backup archive
92+
- name: Create backup archive (synthetic fixture)
9493
run: python scripts/backup.py --output backup-artifacts
9594
- name: Resolve backup path
9695
id: backup
@@ -105,9 +104,9 @@ jobs:
105104
python scripts/restore.py \
106105
--backup "${{ steps.backup.outputs.archive_path }}" \
107106
--target-root /tmp/agentflow-restore
108-
- name: Upload backup artifact
107+
- name: Upload regression-test archive
109108
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
110109
with:
111-
name: agentflow-nightly-backup
110+
name: agentflow-backup-restore-regression-fixture
112111
path: ${{ steps.backup.outputs.archive_path }}
113112
retention-days: 7

‎.github/workflows/ci.yml‎

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ concurrency:
1616
jobs:
1717
lint:
1818
runs-on: ubuntu-latest
19+
timeout-minutes: 10
1920
steps:
2021
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
2122
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
@@ -24,15 +25,46 @@ jobs:
2425
- name: Install dependencies
2526
run: pip install -e ".[dev]"
2627
- name: Ruff check
27-
run: ruff check src/ tests/ scripts/ sdk/
28+
run: ruff check src/ tests/ scripts/ sdk/ integrations/ warehouse/
2829
- name: Ruff format check
29-
run: ruff format --check src/ tests/ scripts/ sdk/
30+
run: ruff format --check src/ tests/ scripts/ sdk/ integrations/ warehouse/
3031
- name: Type check
3132
run: mypy src/ --ignore-missing-imports
3233

34+
# Audit P1-3: the lock chain stays honest end to end — uv.lock matches
35+
# pyproject.toml, requirements-docker.lock (what Dockerfile.api actually
36+
# installs) matches uv.lock, and a fresh hash-verified install from it is
37+
# a consistent environment per pip check. Runs independently of lint: a
38+
# dependency drift should be visible even while lint is red.
39+
lock-check:
40+
runs-on: ubuntu-latest
41+
timeout-minutes: 15
42+
steps:
43+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
44+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
45+
with:
46+
python-version: "3.11"
47+
- name: Install uv
48+
# Exact pin: a newer uv may rewrite the lock format and turn this
49+
# gate into noise. Bump deliberately, together with uv.lock.
50+
run: pip install uv==0.8.23
51+
- name: uv.lock matches pyproject.toml
52+
run: uv lock --check
53+
- name: requirements-docker.lock matches uv.lock
54+
run: |
55+
uv export --format requirements-txt --extra cloud --extra postgres \
56+
--no-emit-project -o requirements-docker.lock
57+
git diff --exit-code requirements-docker.lock
58+
- name: Locked install is consistent
59+
run: |
60+
python -m venv /tmp/lockenv
61+
/tmp/lockenv/bin/pip install --quiet --require-hashes -r requirements-docker.lock
62+
/tmp/lockenv/bin/pip check
63+
3364
schema-check:
3465
runs-on: ubuntu-latest
3566
needs: lint
67+
timeout-minutes: 10
3668
steps:
3769
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
3870
with:
@@ -48,6 +80,7 @@ jobs:
4880
test-unit:
4981
runs-on: ubuntu-latest
5082
needs: lint
83+
timeout-minutes: 25
5184
permissions:
5285
contents: read
5386
id-token: write
@@ -161,6 +194,7 @@ jobs:
161194
test-integration:
162195
runs-on: ubuntu-latest
163196
needs: lint
197+
timeout-minutes: 25
164198
services:
165199
kafka:
166200
image: confluentinc/cp-kafka:7.7.0
@@ -320,6 +354,7 @@ jobs:
320354

321355
terraform-validate:
322356
runs-on: ubuntu-latest
357+
timeout-minutes: 10
323358
steps:
324359
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
325360
- uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
@@ -335,3 +370,31 @@ jobs:
335370
run: |
336371
cd infrastructure/terraform
337372
terraform validate
373+
374+
sdk-ts:
375+
# Audit P1-5: typecheck/tests/build for the TypeScript SDK previously ran
376+
# only in publish-npm.yml (tag pushes) and mutation.yml; every PR only got
377+
# `npm audit` (security.yml). This job gives PRs the same build-shaped
378+
# gate the publish workflow already trusts, so a broken SDK cannot merge.
379+
# Branch protection required-context wiring is a repo-settings change the
380+
# owner still has to make (gh api repos/{owner}/{repo}/branches/main/protection).
381+
runs-on: ubuntu-latest
382+
timeout-minutes: 15
383+
defaults:
384+
run:
385+
working-directory: sdk-ts
386+
steps:
387+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
388+
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
389+
with:
390+
node-version: "20"
391+
- name: Install dependencies from lockfile
392+
run: npm ci
393+
- name: Typecheck
394+
run: npm run typecheck
395+
- name: Test
396+
run: npm test
397+
- name: Build
398+
run: npm run build
399+
- name: Verify package is publishable
400+
run: npm pack --dry-run

‎.github/workflows/container-attestation.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ jobs:
5050
# never blocks docker-free PRs.
5151
if: ${{ github.event_name == 'pull_request' }}
5252
runs-on: ubuntu-latest
53+
timeout-minutes: 15
5354
steps:
5455
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
5556
with:
@@ -89,6 +90,7 @@ jobs:
8990
build-push-sign-attest:
9091
if: ${{ github.event_name == 'workflow_dispatch' && inputs.confirm == 'SIGN' && inputs.mode == 'build-and-sign' }}
9192
runs-on: ubuntu-latest
93+
timeout-minutes: 25
9294
permissions:
9395
contents: read
9496
packages: write
@@ -133,6 +135,7 @@ jobs:
133135
attest-and-sign:
134136
if: ${{ github.event_name == 'workflow_dispatch' && inputs.confirm == 'SIGN' && inputs.mode == 'sign-existing-digest' }}
135137
runs-on: ubuntu-latest
138+
timeout-minutes: 10
136139
permissions:
137140
contents: read
138141
packages: write

‎.github/workflows/contract.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ jobs:
3535
contract:
3636
name: contract
3737
runs-on: ubuntu-latest
38+
timeout-minutes: 15
3839
steps:
3940
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
4041
with:

‎.github/workflows/dora.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ permissions:
1616
jobs:
1717
dora-report:
1818
runs-on: ubuntu-latest
19+
timeout-minutes: 10
1920
steps:
2021
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
2122
with:

‎.github/workflows/pages.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ concurrency:
2121
jobs:
2222
deploy:
2323
runs-on: ubuntu-latest
24+
timeout-minutes: 10
2425
environment:
2526
name: github-pages
2627
url: ${{ steps.deployment.outputs.page_url }}

‎.github/workflows/publish-npm.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ permissions:
1919
jobs:
2020
publish:
2121
runs-on: ubuntu-latest
22+
timeout-minutes: 15
2223
steps:
2324
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
2425

‎.github/workflows/publish-pypi.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ permissions:
1313
jobs:
1414
publish:
1515
runs-on: ubuntu-latest
16+
timeout-minutes: 15
1617
environment: pypi
1718
permissions:
1819
id-token: write

0 commit comments

Comments
 (0)