Skip to content

Bump typescript from 5.9.3 to 6.0.3 in /frontend#2

Open
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/npm_and_yarn/frontend/typescript-6.0.3
Open

Bump typescript from 5.9.3 to 6.0.3 in /frontend#2
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/npm_and_yarn/frontend/typescript-6.0.3

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Jun 1, 2026

Copy link
Copy Markdown

Bumps typescript from 5.9.3 to 6.0.3.

Release notes

Sourced from typescript's releases.

TypeScript 6.0.3

For release notes, check out the release announcement blog post.

Downloads are available on:

TypeScript 6.0

For release notes, check out the release announcement blog post.

Downloads are available on:

TypeScript 6.0 Beta

For release notes, check out the release announcement.

Downloads are available on:

Commits
  • 050880c Bump version to 6.0.3 and LKG
  • eeae9dd 🤖 Pick PR #63401 (Also check package name validity in...) into release-6.0 (#...
  • ad1c695 🤖 Pick PR #63368 (Harden ATA package name filtering) into release-6.0 (#63372)
  • 0725fb4 🤖 Pick PR #63310 (Mark class property initializers as...) into release-6.0 (#...
  • 607a22a Bump version to 6.0.2 and LKG
  • 9e72ab7 🤖 Pick PR #63239 (Fix missing lib files in reused pro...) into release-6.0 (#...
  • 35ff23d 🤖 Pick PR #63163 (Port anyFunctionType subtype fix an...) into release-6.0 (#...
  • e175b69 Bump version to 6.0.1-rc and LKG
  • af4caac Update LKG
  • 8efd7e8 Merge remote-tracking branch 'origin/main' into release-6.0
  • Additional commits viewable in compare view

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.3 to 6.0.3.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](microsoft/TypeScript@v5.9.3...v6.0.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 6.0.3
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code labels Jun 1, 2026
dmichael-fastly added a commit that referenced this pull request Jun 3, 2026
Closes the Phase 1 trivial sweep from security_remediation_final_v6.md.
Fourteen audit findings, all single-function or single-decorator scope.

Tenant scope:
  #7 /api/sources: filter the storage-metadata list by the caller's
       analyst session scope. Admin requests (no session) still see the
       full list.
  #008 /api/log-fields/catalog: 403 when an analyst queries with a
       service_id not in their invite.
  #019 PATCH /api/provision/services/{id}/ngwaf-workspace: require a
       caller-supplied Fastly token. Accepts either the stored
       fastly_api_key (constant-time match — keeps the existing admin
       UX) OR a token whose /tokens/self response shows 'global' scope
       and access to the service. Frontend NGWAF dialog prompts for it.

DuckDB / SQL surface:
  #1 iceberg.py time_range interpolation: validate tr["start"] /
       tr["end"] via dateutil.isoparse before string-interpolating into
       the WHERE clause. Stops the "2024-01-01'; ATTACH ...; --"
       multi-statement payload.

share_db:
  #2 claim_token TOCTOU: replace check-then-update with one atomic
       UPDATE...WHERE claimed_at IS NULL AND expires_at >= ?. Whichever
       transaction commits first wins (rowcount==1); the loser returns
       None. No more concurrent double-redemption window.
  #3 quarantine narrowing: the previous DatabaseError catch fired on
       any sqlite3 subclass — including OperationalError (lock timeouts,
       FD exhaustion, disk full). A single transient error under load
       would silently rename the share DB and wipe every invite /
       session / audit row. Now: only quarantine on the corruption
       substrings ("malformed", "not a database", "unsupported file
       format", "image is malformed"). Anything else re-raises; bump a
       metric counter so near-misses are observable.
  #4 email enumeration timing: when no invite matches the email, run
       one scrypt verify against a process-cached dummy hash so the
       miss-branch latency matches the hit-branch (~30ms). Closes the
       600x timing oracle.

Path traversal:
  #6 config.py service_id validation: regex check at config_path /
       duckdb_path entry; allow [A-Za-z0-9_-]{1,64} (matches real
       Fastly IDs and the test-suite's hyphenated stubs). load_config
       returns None gracefully on invalid input rather than raising,
       so existing callers don't need to learn a new exception.

Auth / DB / DoS:
  #010 tunnel.validate_session re-syncs pii_policy /
       query_window_hours / query_start_time / query_end_time /
       service_ids from the live invite on every call. Tightened
       permissions take effect on the analyst's next request instead
       of waiting for session expiry.
  #014 _StaticAssetLimiter sweeps stale entries when the tracked-IP
       count exceeds 10k and clears outright above that. Prevents OOM
       via high-cardinality XFF spoofing (the spoofing surface was
       closed in Phase 0; this is defense-in-depth so a future bug
       can't reintroduce it).
  #016 log_fields.py UA / referer VCL fallback: restored the substr
       cap that was previously stripped 'because the edge does it'.
       Without the cap a 100KB header truncates the 16KB Fastly log
       line, dropping the request from audit entirely (repudiation).
  #026 deps.py get_con / get_meta_con: removed the read_only parameter
       from the signature so FastAPI can't auto-convert it to a query
       param. Hardcoded read_only=True inside the holder. Closes the
       ?read_only=false write-lock-acquisition DoS.

CSRF + trace leakage:
  #020 logging-settings/update GET -> POST/PATCH: a state-changing GET
       was reachable via <img src=...> CSRF. useSSE already supports
       POST-with-streaming-response, so the frontend change is just
       passing an empty body to start().
  #027 + #028 query_errors trace key removal: log the traceback via
       logger.exception (server-side, for triage) but drop the 'trace'
       key from HTTPException.detail. Stops public callers from
       reading internal file paths / module structure / secret
       fragments embedded in exception messages.

Supporting fixtures:
  - tests/test_no_trace_leakage_sweep.py: parametrized sweep that
    forces a 500 across real routes and asserts no 'Traceback' or
    'trace' key surfaces. Cheap regression guard against #027/#028
    being silently undone.
  - tests/routers/test_provision.py: two new tests asserting the
    NGWAF workspace PATCH rejects no-token (401) and wrong-token (401
    after /tokens/self fails).
  - Updated existing logging-settings tests to POST instead of GET.

The full test sweep is 180+ assertions passing across the
remote_access, router, and security-regression suites. One pre-existing
test isolation issue (test_invite_analyst depends on test ordering with
test_core_get_endpoints) is unrelated to this change; verified by
stashing the diff and reproducing the same failure on the
pre-Phase-1 tree.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dmichael-fastly added a commit that referenced this pull request Jun 3, 2026
…ediation

This branch ships two large workstreams squashed from 67 commits.

## Session scoring (Phases A/B/C)

End-to-end edge anomaly-detection pipeline for Fastly Compute. Layer 1
behavioral (cookie compliance, impossibly-fast browsing, robotic dwell
patterns) + Layer 2 transition-matrix scoring (Laplace-smoothed
prev→current and prev_anchor→current with skipgram discount) + combined
score quantization. Dual-implemented in Python (backend.scoring) and Rust
(compute/scorer) with cross-language wire-format tests pinning the AES-GCM
cookie codec byte-for-byte.

  - Phase A: cookie codec, route normalization, fixture extractor.
  - Phase B: matrix builder + PageRank visit weighting + ROC/PR/AUC eval.
  - Phase C: Rust/WASM port for Compute@Edge with parity tests.

Backend additions: training pipeline, FOS-published matrix versioning,
labelled session retrain loop, /scoring/evaluation + /scoring/health
endpoints, threshold-matrix admin UI with per-reason AUC, ROC/PR curves,
session-events viewer, audit logging of every scoring config change, key
rotation procedure with grace window, sliding cookie lifetime, in-flight
collapse for hot endpoints, telemetry-proxy hardening so dashboard
queries can't leak into the scoring-call attribution.

Frontend additions: dashboard composite score + per-reason breakdown,
labels UI, threshold slider with live AUC, matrix-version history,
audit-log viewer, descriptive help popups across the scoring panel.

## Security remediation (40 audit findings + 3 extras)

Closed every finding from a 40-item security audit, organised into
five phases for safe rollout. All deployed and verified in production.

Phase 0 — stop-the-bleeding (critical / high blast radius):
  - #034 docker-compose uvicorn now runs with
    `--proxy-headers --forwarded-allow-ips=127.0.0.1` so request.client.host
    is populated from Caddy's authoritative XFF rewrite; TRUSTED_PROXY_IPS
    env var pinned for startup-assertion regression guard.
  - #012 admin Host-spoof bypass: backend/utils/remote_access.py
    `is_request_remote` now uses request.client.host (the trust signal)
    instead of the forgeable Host header.
  - #013 / #029 leftmost-XFF spoofing: removed in-app XFF parsing;
    uvicorn does it under trusted-loopback rules.
  - #017 unauthenticated infrastructure teardown: validates a
    caller-supplied Fastly token via /tokens/self before any destructive
    op, requires the `global` scope, enforces service binding, never
    falls back to server-stored credentials.
  - E1 Caddyfile peer-IP gate: only rewrites XFF from Fastly-Client-IP
    when remote_ip matches Fastly's published edge ranges; injects
    X-Proxied-By-Caddy: true unconditionally.
  - E2 docker json-file log rotation (50MB × 10 files compressed).
  - Startup assertion + integration test prevent silent regression of
    the proxy-headers flag.

Phase 1 — trivial sweep (14 findings):
  - #7 / #008 / #019 tenant-scoping on /api/sources +
    /api/log-fields/catalog + NGWAF workspace mutation (token-gated
    via stored-key constant-time match OR /tokens/self global-scope
    validation).
  - #2 claim_token TOCTOU → atomic UPDATE ... WHERE claimed_at IS NULL.
  - #3 share_db quarantine narrowed to actual SQLite corruption
    signatures (was firing on transient OperationalError → wiping
    every invite).
  - #4 email-enumeration timing equalisation via dummy scrypt.
  - #1 iceberg.py time_range dateutil.isoparse validation.
  - #6 service_id alphanumeric / dash / underscore validation in
    backend.config (path traversal).
  - #010 tunnel.validate_session re-syncs pii_policy / window /
    service_ids from live invite on every call.
  - #014 _StaticAssetLimiter bounds at 10k tracked IPs.
  - #016 log_fields VCL ua/referer keeps substr cap.
  - #020 logging-settings/update moved GET → POST/PATCH.
  - #026 deps.get_con / get_meta_con dropped read_only param.
  - #027 / #028 query_errors logs traceback server-side, strips
    `trace` key from HTTPException.detail.
  - Sweep fixture in tests/test_no_trace_leakage_sweep.py.

Phase 2 — DuckDB safe-query module + escape helper + path traversal +
SSH host-key pinning + VCL preamble:
  - #031 / #033 / #035 backend/utils/sql_validator.py implements
    Decision B: statement-type whitelist + recursive parse-tree walker
    with catalog blocklist (duckdb_/pg_ prefixes, information_schema/
    pg_catalog/system schemas, non-main catalogs) + function denylist
    (read_csv/parquet/json/text/blob, iceberg_scan, glob/lsdir, getenv,
    current_setting, duckdb_secrets, postgres/sqlite/mysql scanners) +
    fail-closed parse + audit logging + perf budget. 60+ acceptance
    tests including the v5 information_schema regression.
  - #009 escape_sql_literal helper applied at four ingest call sites;
    characterisation tests cover audit's PoC payload, multi-byte UTF-8,
    backslash, empty, long-with-many-quotes.
  - #5 /api/download path traversal: realpath + commonpath check.
  - #022 cache cleanup: bucket separator rejection + realpath cage.
  - #015 origin-metric VCL log injection: numeric regex gates +
    json.escape on string fields.
  - #021 internal-routing header scrub preamble in vcl_recv.
  - #011 SSH host-key pinning via configs/ssh_known_hosts; fail-safe
    when file missing.

Phase 3 — architectural:
  - #024 Fastly vcl_hash: req.url (full path + query) instead of
    req.url.path; auth key already stripped earlier so the cache key
    contains no secrets.
  - #032 frontend middleware: gate /admin on Caddy-injected
    X-Proxied-By-Caddy marker instead of the forgeable Host header.
  - #036 / #037 scorer parity (Python + Rust):
    L1_SCORE_COOKIE_TAMPERED = 100 (was sharing the 75 missing/expired
    cap), L1_ROBOTIC_DWELL_LOW_S 0.5 → 0.20 (closes the bot-friendly
    threshold gap).
  - #038 sliding-window mean documented as tracked follow-up in
    scorer.rs; partial mitigations (30min idle expire + 24h hard cap
    + session-max scoring) bound exploitation.

Phase 4 — cross-tenant + failed-audit re-run:
  - #018 unauthenticated NGWAF workspace listing: same token-gate as #019.
  - #039 / #040 backend/routers/alerts.py + views.py: every read /
    mutation consults the analyst-session scope; pre-flight scope
    check on PATCH/DELETE via new get_alert_by_id / get_view_by_id
    helpers so unauthorised mutations never land.
  - #025 teardown CSRF covered by #017 token requirement.
  - Cache-layer audit confirmed all per-tenant caches
    (session_scoring._cached, iceberg, bot_sources) include
    service_id in the key.

## Test coverage

3,059 backend pytest tests + 9 vcl tests + 265 frontend vitest tests
all passing. Net new security test files: sql_validator (60 tests),
proxy_headers_regression (10), no_trace_leakage_sweep (4),
provision_teardown_auth (9), cross_tenant_scope (9).

`make ci` (lint + format + mypy + pytest + vcl-test + verify-deps +
typecheck-frontend + test-frontend + osv) is green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dmichael-fastly added a commit that referenced this pull request Jun 3, 2026
…ediation

This branch ships two large workstreams squashed from 67 commits.

## Session scoring (Phases A/B/C)

End-to-end edge anomaly-detection pipeline for Fastly Compute. Layer 1
behavioral (cookie compliance, impossibly-fast browsing, robotic dwell
patterns) + Layer 2 transition-matrix scoring (Laplace-smoothed
prev→current and prev_anchor→current with skipgram discount) + combined
score quantization. Dual-implemented in Python (backend.scoring) and Rust
(compute/scorer) with cross-language wire-format tests pinning the AES-GCM
cookie codec byte-for-byte.

  - Phase A: cookie codec, route normalization, fixture extractor.
  - Phase B: matrix builder + PageRank visit weighting + ROC/PR/AUC eval.
  - Phase C: Rust/WASM port for Compute@Edge with parity tests.

Backend additions: training pipeline, FOS-published matrix versioning,
labelled session retrain loop, /scoring/evaluation + /scoring/health
endpoints, threshold-matrix admin UI with per-reason AUC, ROC/PR curves,
session-events viewer, audit logging of every scoring config change, key
rotation procedure with grace window, sliding cookie lifetime, in-flight
collapse for hot endpoints, telemetry-proxy hardening so dashboard
queries can't leak into the scoring-call attribution.

Frontend additions: dashboard composite score + per-reason breakdown,
labels UI, threshold slider with live AUC, matrix-version history,
audit-log viewer, descriptive help popups across the scoring panel.

## Security remediation (40 audit findings + 3 extras)

Closed every finding from a 40-item security audit, organised into
five phases for safe rollout. All deployed and verified in production.

Phase 0 — stop-the-bleeding (critical / high blast radius):
  - #034 docker-compose uvicorn now runs with
    `--proxy-headers --forwarded-allow-ips=127.0.0.1` so request.client.host
    is populated from Caddy's authoritative XFF rewrite; TRUSTED_PROXY_IPS
    env var pinned for startup-assertion regression guard.
  - #012 admin Host-spoof bypass: backend/utils/remote_access.py
    `is_request_remote` now uses request.client.host (the trust signal)
    instead of the forgeable Host header.
  - #013 / #029 leftmost-XFF spoofing: removed in-app XFF parsing;
    uvicorn does it under trusted-loopback rules.
  - #017 unauthenticated infrastructure teardown: validates a
    caller-supplied Fastly token via /tokens/self before any destructive
    op, requires the `global` scope, enforces service binding, never
    falls back to server-stored credentials.
  - E1 Caddyfile peer-IP gate: only rewrites XFF from Fastly-Client-IP
    when remote_ip matches Fastly's published edge ranges; injects
    X-Proxied-By-Caddy: true unconditionally.
  - E2 docker json-file log rotation (50MB × 10 files compressed).
  - Startup assertion + integration test prevent silent regression of
    the proxy-headers flag.

Phase 1 — trivial sweep (14 findings):
  - #7 / #008 / #019 tenant-scoping on /api/sources +
    /api/log-fields/catalog + NGWAF workspace mutation (token-gated
    via stored-key constant-time match OR /tokens/self global-scope
    validation).
  - #2 claim_token TOCTOU → atomic UPDATE ... WHERE claimed_at IS NULL.
  - #3 share_db quarantine narrowed to actual SQLite corruption
    signatures (was firing on transient OperationalError → wiping
    every invite).
  - #4 email-enumeration timing equalisation via dummy scrypt.
  - #1 iceberg.py time_range dateutil.isoparse validation.
  - #6 service_id alphanumeric / dash / underscore validation in
    backend.config (path traversal).
  - #010 tunnel.validate_session re-syncs pii_policy / window /
    service_ids from live invite on every call.
  - #014 _StaticAssetLimiter bounds at 10k tracked IPs.
  - #016 log_fields VCL ua/referer keeps substr cap.
  - #020 logging-settings/update moved GET → POST/PATCH.
  - #026 deps.get_con / get_meta_con dropped read_only param.
  - #027 / #028 query_errors logs traceback server-side, strips
    `trace` key from HTTPException.detail.
  - Sweep fixture in tests/test_no_trace_leakage_sweep.py.

Phase 2 — DuckDB safe-query module + escape helper + path traversal +
SSH host-key pinning + VCL preamble:
  - #031 / #033 / #035 backend/utils/sql_validator.py implements
    Decision B: statement-type whitelist + recursive parse-tree walker
    with catalog blocklist (duckdb_/pg_ prefixes, information_schema/
    pg_catalog/system schemas, non-main catalogs) + function denylist
    (read_csv/parquet/json/text/blob, iceberg_scan, glob/lsdir, getenv,
    current_setting, duckdb_secrets, postgres/sqlite/mysql scanners) +
    fail-closed parse + audit logging + perf budget. 60+ acceptance
    tests including the v5 information_schema regression.
  - #009 escape_sql_literal helper applied at four ingest call sites;
    characterisation tests cover audit's PoC payload, multi-byte UTF-8,
    backslash, empty, long-with-many-quotes.
  - #5 /api/download path traversal: realpath + commonpath check.
  - #022 cache cleanup: bucket separator rejection + realpath cage.
  - #015 origin-metric VCL log injection: numeric regex gates +
    json.escape on string fields.
  - #021 internal-routing header scrub preamble in vcl_recv.
  - #011 SSH host-key pinning via configs/ssh_known_hosts; fail-safe
    when file missing.

Phase 3 — architectural:
  - #024 Fastly vcl_hash: req.url (full path + query) instead of
    req.url.path; auth key already stripped earlier so the cache key
    contains no secrets.
  - #032 frontend middleware: gate /admin on Caddy-injected
    X-Proxied-By-Caddy marker instead of the forgeable Host header.
  - #036 / #037 scorer parity (Python + Rust):
    L1_SCORE_COOKIE_TAMPERED = 100 (was sharing the 75 missing/expired
    cap), L1_ROBOTIC_DWELL_LOW_S 0.5 → 0.20 (closes the bot-friendly
    threshold gap).
  - #038 sliding-window mean documented as tracked follow-up in
    scorer.rs; partial mitigations (30min idle expire + 24h hard cap
    + session-max scoring) bound exploitation.

Phase 4 — cross-tenant + failed-audit re-run:
  - #018 unauthenticated NGWAF workspace listing: same token-gate as #019.
  - #039 / #040 backend/routers/alerts.py + views.py: every read /
    mutation consults the analyst-session scope; pre-flight scope
    check on PATCH/DELETE via new get_alert_by_id / get_view_by_id
    helpers so unauthorised mutations never land.
  - #025 teardown CSRF covered by #017 token requirement.
  - Cache-layer audit confirmed all per-tenant caches
    (session_scoring._cached, iceberg, bot_sources) include
    service_id in the key.

## Test coverage

3,059 backend pytest tests + 9 vcl tests + 265 frontend vitest tests
all passing. Net new security test files: sql_validator (60 tests),
proxy_headers_regression (10), no_trace_leakage_sweep (4),
provision_teardown_auth (9), cross_tenant_scope (9).

`make ci` (lint + format + mypy + pytest + vcl-test + verify-deps +
typecheck-frontend + test-frontend + osv) is green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dmichael-fastly added a commit that referenced this pull request Jun 3, 2026
…ediation

This branch ships two large workstreams squashed from 67 commits.

## Session scoring (Phases A/B/C)

End-to-end edge anomaly-detection pipeline for Fastly Compute. Layer 1
behavioral (cookie compliance, impossibly-fast browsing, robotic dwell
patterns) + Layer 2 transition-matrix scoring (Laplace-smoothed
prev→current and prev_anchor→current with skipgram discount) + combined
score quantization. Dual-implemented in Python (backend.scoring) and Rust
(compute/scorer) with cross-language wire-format tests pinning the AES-GCM
cookie codec byte-for-byte.

  - Phase A: cookie codec, route normalization, fixture extractor.
  - Phase B: matrix builder + PageRank visit weighting + ROC/PR/AUC eval.
  - Phase C: Rust/WASM port for Compute@Edge with parity tests.

Backend additions: training pipeline, FOS-published matrix versioning,
labelled session retrain loop, /scoring/evaluation + /scoring/health
endpoints, threshold-matrix admin UI with per-reason AUC, ROC/PR curves,
session-events viewer, audit logging of every scoring config change, key
rotation procedure with grace window, sliding cookie lifetime, in-flight
collapse for hot endpoints, telemetry-proxy hardening so dashboard
queries can't leak into the scoring-call attribution.

Frontend additions: dashboard composite score + per-reason breakdown,
labels UI, threshold slider with live AUC, matrix-version history,
audit-log viewer, descriptive help popups across the scoring panel.

## Security remediation (40 audit findings + 3 extras)

Closed every finding from a 40-item security audit, organised into
five phases for safe rollout. All deployed and verified in production.

Phase 0 — stop-the-bleeding (critical / high blast radius):
  - #034 docker-compose uvicorn now runs with
    `--proxy-headers --forwarded-allow-ips=127.0.0.1` so request.client.host
    is populated from Caddy's authoritative XFF rewrite; TRUSTED_PROXY_IPS
    env var pinned for startup-assertion regression guard.
  - #012 admin Host-spoof bypass: backend/utils/remote_access.py
    `is_request_remote` now uses request.client.host (the trust signal)
    instead of the forgeable Host header.
  - #013 / #029 leftmost-XFF spoofing: removed in-app XFF parsing;
    uvicorn does it under trusted-loopback rules.
  - #017 unauthenticated infrastructure teardown: validates a
    caller-supplied Fastly token via /tokens/self before any destructive
    op, requires the `global` scope, enforces service binding, never
    falls back to server-stored credentials.
  - E1 Caddyfile peer-IP gate: only rewrites XFF from Fastly-Client-IP
    when remote_ip matches Fastly's published edge ranges; injects
    X-Proxied-By-Caddy: true unconditionally.
  - E2 docker json-file log rotation (50MB × 10 files compressed).
  - Startup assertion + integration test prevent silent regression of
    the proxy-headers flag.

Phase 1 — trivial sweep (14 findings):
  - #7 / #008 / #019 tenant-scoping on /api/sources +
    /api/log-fields/catalog + NGWAF workspace mutation (token-gated
    via stored-key constant-time match OR /tokens/self global-scope
    validation).
  - #2 claim_token TOCTOU → atomic UPDATE ... WHERE claimed_at IS NULL.
  - #3 share_db quarantine narrowed to actual SQLite corruption
    signatures (was firing on transient OperationalError → wiping
    every invite).
  - #4 email-enumeration timing equalisation via dummy scrypt.
  - #1 iceberg.py time_range dateutil.isoparse validation.
  - #6 service_id alphanumeric / dash / underscore validation in
    backend.config (path traversal).
  - #010 tunnel.validate_session re-syncs pii_policy / window /
    service_ids from live invite on every call.
  - #014 _StaticAssetLimiter bounds at 10k tracked IPs.
  - #016 log_fields VCL ua/referer keeps substr cap.
  - #020 logging-settings/update moved GET → POST/PATCH.
  - #026 deps.get_con / get_meta_con dropped read_only param.
  - #027 / #028 query_errors logs traceback server-side, strips
    `trace` key from HTTPException.detail.
  - Sweep fixture in tests/test_no_trace_leakage_sweep.py.

Phase 2 — DuckDB safe-query module + escape helper + path traversal +
SSH host-key pinning + VCL preamble:
  - #031 / #033 / #035 backend/utils/sql_validator.py implements
    Decision B: statement-type whitelist + recursive parse-tree walker
    with catalog blocklist (duckdb_/pg_ prefixes, information_schema/
    pg_catalog/system schemas, non-main catalogs) + function denylist
    (read_csv/parquet/json/text/blob, iceberg_scan, glob/lsdir, getenv,
    current_setting, duckdb_secrets, postgres/sqlite/mysql scanners) +
    fail-closed parse + audit logging + perf budget. 60+ acceptance
    tests including the v5 information_schema regression.
  - #009 escape_sql_literal helper applied at four ingest call sites;
    characterisation tests cover audit's PoC payload, multi-byte UTF-8,
    backslash, empty, long-with-many-quotes.
  - #5 /api/download path traversal: realpath + commonpath check.
  - #022 cache cleanup: bucket separator rejection + realpath cage.
  - #015 origin-metric VCL log injection: numeric regex gates +
    json.escape on string fields.
  - #021 internal-routing header scrub preamble in vcl_recv.
  - #011 SSH host-key pinning via configs/ssh_known_hosts; fail-safe
    when file missing.

Phase 3 — architectural:
  - #024 Fastly vcl_hash: req.url (full path + query) instead of
    req.url.path; auth key already stripped earlier so the cache key
    contains no secrets.
  - #032 frontend middleware: gate /admin on Caddy-injected
    X-Proxied-By-Caddy marker instead of the forgeable Host header.
  - #036 / #037 scorer parity (Python + Rust):
    L1_SCORE_COOKIE_TAMPERED = 100 (was sharing the 75 missing/expired
    cap), L1_ROBOTIC_DWELL_LOW_S 0.5 → 0.20 (closes the bot-friendly
    threshold gap).
  - #038 sliding-window mean documented as tracked follow-up in
    scorer.rs; partial mitigations (30min idle expire + 24h hard cap
    + session-max scoring) bound exploitation.

Phase 4 — cross-tenant + failed-audit re-run:
  - #018 unauthenticated NGWAF workspace listing: same token-gate as #019.
  - #039 / #040 backend/routers/alerts.py + views.py: every read /
    mutation consults the analyst-session scope; pre-flight scope
    check on PATCH/DELETE via new get_alert_by_id / get_view_by_id
    helpers so unauthorised mutations never land.
  - #025 teardown CSRF covered by #017 token requirement.
  - Cache-layer audit confirmed all per-tenant caches
    (session_scoring._cached, iceberg, bot_sources) include
    service_id in the key.

## Test coverage

3,059 backend pytest tests + 9 vcl tests + 265 frontend vitest tests
all passing. Net new security test files: sql_validator (60 tests),
proxy_headers_regression (10), no_trace_leakage_sweep (4),
provision_teardown_auth (9), cross_tenant_scope (9).

`make ci` (lint + format + mypy + pytest + vcl-test + verify-deps +
typecheck-frontend + test-frontend + osv) is green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dmichael-fastly added a commit that referenced this pull request Jun 3, 2026
…ediation

This branch ships two large workstreams squashed from 67 commits.

## Session scoring (Phases A/B/C)

End-to-end edge anomaly-detection pipeline for Fastly Compute. Layer 1
behavioral (cookie compliance, impossibly-fast browsing, robotic dwell
patterns) + Layer 2 transition-matrix scoring (Laplace-smoothed
prev→current and prev_anchor→current with skipgram discount) + combined
score quantization. Dual-implemented in Python (backend.scoring) and Rust
(compute/scorer) with cross-language wire-format tests pinning the AES-GCM
cookie codec byte-for-byte.

  - Phase A: cookie codec, route normalization, fixture extractor.
  - Phase B: matrix builder + PageRank visit weighting + ROC/PR/AUC eval.
  - Phase C: Rust/WASM port for Compute@Edge with parity tests.

Backend additions: training pipeline, FOS-published matrix versioning,
labelled session retrain loop, /scoring/evaluation + /scoring/health
endpoints, threshold-matrix admin UI with per-reason AUC, ROC/PR curves,
session-events viewer, audit logging of every scoring config change, key
rotation procedure with grace window, sliding cookie lifetime, in-flight
collapse for hot endpoints, telemetry-proxy hardening so dashboard
queries can't leak into the scoring-call attribution.

Frontend additions: dashboard composite score + per-reason breakdown,
labels UI, threshold slider with live AUC, matrix-version history,
audit-log viewer, descriptive help popups across the scoring panel.

## Security remediation (40 audit findings + 3 extras)

Closed every finding from a 40-item security audit, organised into
five phases for safe rollout. All deployed and verified in production.

Phase 0 — stop-the-bleeding (critical / high blast radius):
  - #034 docker-compose uvicorn now runs with
    `--proxy-headers --forwarded-allow-ips=127.0.0.1` so request.client.host
    is populated from Caddy's authoritative XFF rewrite; TRUSTED_PROXY_IPS
    env var pinned for startup-assertion regression guard.
  - #012 admin Host-spoof bypass: backend/utils/remote_access.py
    `is_request_remote` now uses request.client.host (the trust signal)
    instead of the forgeable Host header.
  - #013 / #029 leftmost-XFF spoofing: removed in-app XFF parsing;
    uvicorn does it under trusted-loopback rules.
  - #017 unauthenticated infrastructure teardown: validates a
    caller-supplied Fastly token via /tokens/self before any destructive
    op, requires the `global` scope, enforces service binding, never
    falls back to server-stored credentials.
  - E1 Caddyfile peer-IP gate: only rewrites XFF from Fastly-Client-IP
    when remote_ip matches Fastly's published edge ranges; injects
    X-Proxied-By-Caddy: true unconditionally.
  - E2 docker json-file log rotation (50MB × 10 files compressed).
  - Startup assertion + integration test prevent silent regression of
    the proxy-headers flag.

Phase 1 — trivial sweep (14 findings):
  - #7 / #008 / #019 tenant-scoping on /api/sources +
    /api/log-fields/catalog + NGWAF workspace mutation (token-gated
    via stored-key constant-time match OR /tokens/self global-scope
    validation).
  - #2 claim_token TOCTOU → atomic UPDATE ... WHERE claimed_at IS NULL.
  - #3 share_db quarantine narrowed to actual SQLite corruption
    signatures (was firing on transient OperationalError → wiping
    every invite).
  - #4 email-enumeration timing equalisation via dummy scrypt.
  - #1 iceberg.py time_range dateutil.isoparse validation.
  - #6 service_id alphanumeric / dash / underscore validation in
    backend.config (path traversal).
  - #010 tunnel.validate_session re-syncs pii_policy / window /
    service_ids from live invite on every call.
  - #014 _StaticAssetLimiter bounds at 10k tracked IPs.
  - #016 log_fields VCL ua/referer keeps substr cap.
  - #020 logging-settings/update moved GET → POST/PATCH.
  - #026 deps.get_con / get_meta_con dropped read_only param.
  - #027 / #028 query_errors logs traceback server-side, strips
    `trace` key from HTTPException.detail.
  - Sweep fixture in tests/test_no_trace_leakage_sweep.py.

Phase 2 — DuckDB safe-query module + escape helper + path traversal +
SSH host-key pinning + VCL preamble:
  - #031 / #033 / #035 backend/utils/sql_validator.py implements
    Decision B: statement-type whitelist + recursive parse-tree walker
    with catalog blocklist (duckdb_/pg_ prefixes, information_schema/
    pg_catalog/system schemas, non-main catalogs) + function denylist
    (read_csv/parquet/json/text/blob, iceberg_scan, glob/lsdir, getenv,
    current_setting, duckdb_secrets, postgres/sqlite/mysql scanners) +
    fail-closed parse + audit logging + perf budget. 60+ acceptance
    tests including the v5 information_schema regression.
  - #009 escape_sql_literal helper applied at four ingest call sites;
    characterisation tests cover audit's PoC payload, multi-byte UTF-8,
    backslash, empty, long-with-many-quotes.
  - #5 /api/download path traversal: realpath + commonpath check.
  - #022 cache cleanup: bucket separator rejection + realpath cage.
  - #015 origin-metric VCL log injection: numeric regex gates +
    json.escape on string fields.
  - #021 internal-routing header scrub preamble in vcl_recv.
  - #011 SSH host-key pinning via configs/ssh_known_hosts; fail-safe
    when file missing.

Phase 3 — architectural:
  - #024 Fastly vcl_hash: req.url (full path + query) instead of
    req.url.path; auth key already stripped earlier so the cache key
    contains no secrets.
  - #032 frontend middleware: gate /admin on Caddy-injected
    X-Proxied-By-Caddy marker instead of the forgeable Host header.
  - #036 / #037 scorer parity (Python + Rust):
    L1_SCORE_COOKIE_TAMPERED = 100 (was sharing the 75 missing/expired
    cap), L1_ROBOTIC_DWELL_LOW_S 0.5 → 0.20 (closes the bot-friendly
    threshold gap).
  - #038 sliding-window mean documented as tracked follow-up in
    scorer.rs; partial mitigations (30min idle expire + 24h hard cap
    + session-max scoring) bound exploitation.

Phase 4 — cross-tenant + failed-audit re-run:
  - #018 unauthenticated NGWAF workspace listing: same token-gate as #019.
  - #039 / #040 backend/routers/alerts.py + views.py: every read /
    mutation consults the analyst-session scope; pre-flight scope
    check on PATCH/DELETE via new get_alert_by_id / get_view_by_id
    helpers so unauthorised mutations never land.
  - #025 teardown CSRF covered by #017 token requirement.
  - Cache-layer audit confirmed all per-tenant caches
    (session_scoring._cached, iceberg, bot_sources) include
    service_id in the key.

## Test coverage

3,059 backend pytest tests + 9 vcl tests + 265 frontend vitest tests
all passing. Net new security test files: sql_validator (60 tests),
proxy_headers_regression (10), no_trace_leakage_sweep (4),
provision_teardown_auth (9), cross_tenant_scope (9).

`make ci` (lint + format + mypy + pytest + vcl-test + verify-deps +
typecheck-frontend + test-frontend + osv) is green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dmichael-fastly added a commit that referenced this pull request Jun 3, 2026
…ediation

This branch ships two large workstreams squashed from 67 commits.

## Session scoring (Phases A/B/C)

End-to-end edge anomaly-detection pipeline for Fastly Compute. Layer 1
behavioral (cookie compliance, impossibly-fast browsing, robotic dwell
patterns) + Layer 2 transition-matrix scoring (Laplace-smoothed
prev→current and prev_anchor→current with skipgram discount) + combined
score quantization. Dual-implemented in Python (backend.scoring) and Rust
(compute/scorer) with cross-language wire-format tests pinning the AES-GCM
cookie codec byte-for-byte.

  - Phase A: cookie codec, route normalization, fixture extractor.
  - Phase B: matrix builder + PageRank visit weighting + ROC/PR/AUC eval.
  - Phase C: Rust/WASM port for Compute@Edge with parity tests.

Backend additions: training pipeline, FOS-published matrix versioning,
labelled session retrain loop, /scoring/evaluation + /scoring/health
endpoints, threshold-matrix admin UI with per-reason AUC, ROC/PR curves,
session-events viewer, audit logging of every scoring config change, key
rotation procedure with grace window, sliding cookie lifetime, in-flight
collapse for hot endpoints, telemetry-proxy hardening so dashboard
queries can't leak into the scoring-call attribution.

Frontend additions: dashboard composite score + per-reason breakdown,
labels UI, threshold slider with live AUC, matrix-version history,
audit-log viewer, descriptive help popups across the scoring panel.

## Security remediation (40 audit findings + 3 extras)

Closed every finding from a 40-item security audit, organised into
five phases for safe rollout. All deployed and verified in production.

Phase 0 — stop-the-bleeding (critical / high blast radius):
  - #034 docker-compose uvicorn now runs with
    `--proxy-headers --forwarded-allow-ips=127.0.0.1` so request.client.host
    is populated from Caddy's authoritative XFF rewrite; TRUSTED_PROXY_IPS
    env var pinned for startup-assertion regression guard.
  - #012 admin Host-spoof bypass: backend/utils/remote_access.py
    `is_request_remote` now uses request.client.host (the trust signal)
    instead of the forgeable Host header.
  - #013 / #029 leftmost-XFF spoofing: removed in-app XFF parsing;
    uvicorn does it under trusted-loopback rules.
  - #017 unauthenticated infrastructure teardown: validates a
    caller-supplied Fastly token via /tokens/self before any destructive
    op, requires the `global` scope, enforces service binding, never
    falls back to server-stored credentials.
  - E1 Caddyfile peer-IP gate: only rewrites XFF from Fastly-Client-IP
    when remote_ip matches Fastly's published edge ranges; injects
    X-Proxied-By-Caddy: true unconditionally.
  - E2 docker json-file log rotation (50MB × 10 files compressed).
  - Startup assertion + integration test prevent silent regression of
    the proxy-headers flag.

Phase 1 — trivial sweep (14 findings):
  - #7 / #008 / #019 tenant-scoping on /api/sources +
    /api/log-fields/catalog + NGWAF workspace mutation (token-gated
    via stored-key constant-time match OR /tokens/self global-scope
    validation).
  - #2 claim_token TOCTOU → atomic UPDATE ... WHERE claimed_at IS NULL.
  - #3 share_db quarantine narrowed to actual SQLite corruption
    signatures (was firing on transient OperationalError → wiping
    every invite).
  - #4 email-enumeration timing equalisation via dummy scrypt.
  - #1 iceberg.py time_range dateutil.isoparse validation.
  - #6 service_id alphanumeric / dash / underscore validation in
    backend.config (path traversal).
  - #010 tunnel.validate_session re-syncs pii_policy / window /
    service_ids from live invite on every call.
  - #014 _StaticAssetLimiter bounds at 10k tracked IPs.
  - #016 log_fields VCL ua/referer keeps substr cap.
  - #020 logging-settings/update moved GET → POST/PATCH.
  - #026 deps.get_con / get_meta_con dropped read_only param.
  - #027 / #028 query_errors logs traceback server-side, strips
    `trace` key from HTTPException.detail.
  - Sweep fixture in tests/test_no_trace_leakage_sweep.py.

Phase 2 — DuckDB safe-query module + escape helper + path traversal +
SSH host-key pinning + VCL preamble:
  - #031 / #033 / #035 backend/utils/sql_validator.py implements
    Decision B: statement-type whitelist + recursive parse-tree walker
    with catalog blocklist (duckdb_/pg_ prefixes, information_schema/
    pg_catalog/system schemas, non-main catalogs) + function denylist
    (read_csv/parquet/json/text/blob, iceberg_scan, glob/lsdir, getenv,
    current_setting, duckdb_secrets, postgres/sqlite/mysql scanners) +
    fail-closed parse + audit logging + perf budget. 60+ acceptance
    tests including the v5 information_schema regression.
  - #009 escape_sql_literal helper applied at four ingest call sites;
    characterisation tests cover audit's PoC payload, multi-byte UTF-8,
    backslash, empty, long-with-many-quotes.
  - #5 /api/download path traversal: realpath + commonpath check.
  - #022 cache cleanup: bucket separator rejection + realpath cage.
  - #015 origin-metric VCL log injection: numeric regex gates +
    json.escape on string fields.
  - #021 internal-routing header scrub preamble in vcl_recv.
  - #011 SSH host-key pinning via configs/ssh_known_hosts; fail-safe
    when file missing.

Phase 3 — architectural:
  - #024 Fastly vcl_hash: req.url (full path + query) instead of
    req.url.path; auth key already stripped earlier so the cache key
    contains no secrets.
  - #032 frontend middleware: gate /admin on Caddy-injected
    X-Proxied-By-Caddy marker instead of the forgeable Host header.
  - #036 / #037 scorer parity (Python + Rust):
    L1_SCORE_COOKIE_TAMPERED = 100 (was sharing the 75 missing/expired
    cap), L1_ROBOTIC_DWELL_LOW_S 0.5 → 0.20 (closes the bot-friendly
    threshold gap).
  - #038 sliding-window mean documented as tracked follow-up in
    scorer.rs; partial mitigations (30min idle expire + 24h hard cap
    + session-max scoring) bound exploitation.

Phase 4 — cross-tenant + failed-audit re-run:
  - #018 unauthenticated NGWAF workspace listing: same token-gate as #019.
  - #039 / #040 backend/routers/alerts.py + views.py: every read /
    mutation consults the analyst-session scope; pre-flight scope
    check on PATCH/DELETE via new get_alert_by_id / get_view_by_id
    helpers so unauthorised mutations never land.
  - #025 teardown CSRF covered by #017 token requirement.
  - Cache-layer audit confirmed all per-tenant caches
    (session_scoring._cached, iceberg, bot_sources) include
    service_id in the key.

## Test coverage

3,059 backend pytest tests + 9 vcl tests + 265 frontend vitest tests
all passing. Net new security test files: sql_validator (60 tests),
proxy_headers_regression (10), no_trace_leakage_sweep (4),
provision_teardown_auth (9), cross_tenant_scope (9).

`make ci` (lint + format + mypy + pytest + vcl-test + verify-deps +
typecheck-frontend + test-frontend + osv) is green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dmichael-fastly added a commit that referenced this pull request Jun 3, 2026
…ecv exclusion override

This branch ships v1.1.0, three workstreams squashed from 70+ original
commits. 170+ files changed; `make ci` green (lint + format + mypy +
3,087 backend pytest + 9 vcl + 65 Rust scorer + 265 frontend vitest +
OSV no-vulns). Deployed and verified on the dev VM.

## 1 — Session scoring (Phases A / B / C)

End-to-end edge anomaly-detection pipeline for Fastly Compute. Layer 1
behavioural (cookie compliance, impossibly-fast browsing, robotic
dwell) + Layer 2 transition-matrix scoring + combined 0–100 quantized
score. Dual-implemented in Python (backend/scoring) and Rust
(compute/scorer); cross-language wire-format tests pin the AES-GCM
cookie codec byte-for-byte.

- Edge: Compute scorer, 6-snippet VCL preflight (recv / pass / fetch /
  deliver / miss / enforce), AES-GCM cookie carrying rotating sid +
  transition state, `fastly.ddos_detected` bypass.
- Backend: training pipeline, FOS-published matrix versioning,
  labelled-session retrain loop, /scoring/evaluation + /scoring/health
  + composite /scoring/dashboard, matrix version history + rollback,
  AES key rotation with grace window, sliding cookie lifetime,
  scoring audit log, threshold enforcement that 429s flagged requests
  at the edge within seconds of commit.
- Admin UI at /admin/session-scoring: StatusPanel with live ROC-AUC
  against accumulated labels, ScoringHealthCard, ThresholdSlider with
  counterfactual flag/pass preview + precision/recall, RocPrCurves,
  TopFlaggedTable, LabelsTab with click-to-view-events, RetrainButton,
  RotateKeyButton, MatrixVersionsCard, per-reason AUC breakdown,
  session-events viewer, ExcludeRegexCard (see §3), help popups.

See docs/session_scoring_runbook.md + docs/features.md for the runbook
and feature reference.

## 2 — Security remediation (Phases 0–4)

40-finding security audit closed in five phases. All fixes deployed
and verified. Full breakdown in the `### Security` block of
CHANGELOG.md 1.1.0; one-line summary per phase:

- Phase 0 — uvicorn `--proxy-headers` + Host-spoof bypass /
  leftmost-XFF / teardown-auth (#012, #013, #029, #017, #034). Three
  extras spotted during the IR sweep:
  - E1 Caddyfile peer-IP gate on `Fastly-Client-IP → XFF` rewrite
    (port 80 was open to `0.0.0.0/0`).
  - E2 docker json-file log rotation (50 MB × 10 compressed).
  - E3 `generate_analyst_invite` fail-fast on missing token +
    defensive Fastly-response shape check.
- Phase 1 — 14-finding trivial sweep: tenant scoping (#7 / #008 /
  #019), TOCTOU (#2), quarantine narrowing (#3), email-enum
  timing equalisation (#4), `isoparse` validation (#1),
  `service_id` path-traversal regex (#6), session re-sync (#010),
  rate-limiter bounds (#014), VCL UA/referer cap (#016), GET→POST CSRF
  (#020), read_only query-param removal (#026), stack-trace strip
  (#027 / #028) + sweep fixture.
- Phase 2 — backend/utils/sql_validator.py implements Decision B:
  statement-type whitelist + recursive parse-tree walker (catalog +
  function blocklists) + fail-closed parse + audit log + perf budget
  (#031 / #033 / #035). escape_sql_literal helper + characterisation
  tests at four ingest sites (#009). VCL preamble unsetting
  client-spoofable internal headers (#021). Origin-metric VCL
  log-injection gates (#015). Path-traversal cages in /api/download
  (#5) + cache cleanup (#022). SSH host-key pinning via
  configs/ssh_known_hosts with fail-safe _ensure_known_hosts (#011).
- Phase 3 — Fastly vcl_hash keys on full req.url not just path
  (#024). Next.js /admin middleware gates on Caddy-injected
  X-Proxied-By-Caddy marker not Host header (#032). Scorer
  Python+Rust parity: L1_SCORE_COOKIE_TAMPERED=100,
  L1_ROBOTIC_DWELL_LOW_S 0.5 → 0.20 (#036 / #037). #038 sliding-
  window mean documented as tracked follow-up.
- Phase 4 — cross-tenant scope enforcement on /api/alerts/* and
  /api/views/* with pre-flight get_alert_by_id / get_view_by_id
  helpers so unauthorised mutations never land (#039 / #040). NGWAF
  workspace listing token-gated (#018). #025 covered by Phase 0 #017.
  Cache-layer audit confirmed every per-tenant cache includes
  service_id in the key.

## 3 — Scoring-recv URL exclusion regex (new operator control)

The "which requests get sent to Compute" condition was previously a
hard-coded _ASSET_EXT_REGEX in code. Operators can now override it
per-service from the Session Scoring page; the default static-asset
extension list still ships as the fallback.

- Backend — recv_snippet + generate_scoring_vcl accept an
  exclude_url_regex parameter; persisted in
  cfg.scoring.exclude_url_regex (None / "" = use default).
  update_recv_exclusion_regex orchestrator clones only the active
  version, swaps the recv snippet, validates, activates — ~5–15s vs.
  the full enable_scoring flow.
- New endpoints — GET /api/services/{id}/scoring/exclude-regex
  (returns current + default + effective) and PUT
  /api/services/{id}/scoring/exclude-regex?confirm=true (token-gated;
  audit-logged as scoring_exclude_regex_changed).
- Three-layer validation before any VCL ships:
  1. Input policy — length cap (2 KB), no double-quote / control
     chars, must compile under Python re.
  2. falco static analysis (github.com/ysugimoto/falco) on the
     assembled recv snippet (catches composition errors that slip past
     Python's compiler).
  3. Fastly's own VCL compiler at activate time.
- Frontend — ExcludeRegexCard on the overview tab: textarea
  pre-populated with current value, "Show default" toggle, "Reset to
  default" button, inline lint-error display, confirm-dialog before
  publish.
- Infra — falco v2.3.0 baked into the backend Docker image; production
  sets SCORING_REQUIRE_FALCO=1 so a missing binary fails closed
  instead of degrading to input-policy-only.

## Infrastructure

- Backend + frontend Docker base: python:3.12-slim-bullseye →
  python:3.12-slim-bookworm (cuts CVE-laden Debian 11 base; remaining
  13 high CVEs are deep-dependency / OpenSSL CVEs every major Python
  base inherits).
- Falco v2.3.0 in the backend image — required by the scoring-recv-
  snippet validator.
- Dependency freshness sweep on all four ecosystems:
  - Python: aiohttp 3.13.5 → 3.14.0, cfn-lint 1.51.2 → 1.51.4,
    distlib, filelock, idna 3.17 → 3.18, joserfc 1.6.8 → 1.7.0.
  - Frontend: @tanstack/react-query 5.100.14 → 5.101.0 (+ devtools),
    @types/react 19.2.15 → 19.2.16, eslint-config-next 16.2.6 →
    16.2.7, next 16.2.6 → 16.2.7, react / react-dom 19.2.6 → 19.2.7.
  - Rust: bitflags 2.11.1 → 2.12.1.
  - Deferred (major bumps reserved for 1.2): TypeScript 5.9 → 6.0
    (compiler-API breaking changes); Fastly Rust SDK 0.11 → 0.12
    (Compute@Edge API churn); jsdom / eslint / vitest where we're
    already ahead of the npm "latest" tag.

## Versioning

Bumped to 1.1.0 in pyproject.toml, frontend/package.json, and the
FastAPI app.version. CHANGELOG updated under [1.1.0] - 2026-06-03
with Security + Infrastructure sections.

## Test coverage

  backend pytest    3,087   (+321 vs v1.0.0)
  Rust scorer          65   (+8)
  frontend vitest     265   (+13)
  VCL tests             9   (same)

New test files for this release:
  tests/utils/test_sql_validator.py            (60)
  tests/utils/test_vcl_validator.py            (18)
  tests/test_proxy_headers_regression.py       (10)
  tests/test_no_trace_leakage_sweep.py          (4)
  tests/routers/test_provision_teardown_auth.py (9)
  tests/routers/test_cross_tenant_scope.py      (9)
  tests/routers/test_scoring_exclude_regex.py   (9)

## Notes for reviewers

- Branch was squashed from 70+ commits; full per-commit history is in
  git reflog locally. The squash makes this reviewable as one
  semantic unit (v1.1.0 release) instead of paging through unrelated
  intermediate work.
- Every security fix has acceptance tests. OpenAPI snapshot
  regenerated.
- Audit-finding working docs (docs/security_remediation_final_*.md,
  audit-findings/) were intentionally .gitignored and cleaned up at
  the end of the cycle — a fresh audit will produce fresh artifacts.
- Stale v1.1.0 tag was deleted before the squash. After merge, tag
  main with v1.1.0 rather than the PR branch.

## Test plan

- [x] `make ci` passes locally
- [x] Deployed to dev VM (fastly-log-analysis in us-central1-a) — all
      three containers healthy, GET /api/health returns 200
- [x] Falco verified in production image: v2.3.0
- [x] Exclude-regex endpoint reachable + returns expected shape
- [x] Off-network attack probes: Host-spoof, SQL injection
      (read_csv_auto, information_schema, getenv, duckdb_secrets),
      unauthenticated teardown — all rejected with expected status codes
- [ ] Reviewer: open /admin/session-scoring, scroll to the URL
      exclusion regex card, paste a custom regex (e.g. \.(healthz)$),
      click Save → publish flow completes

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dmichael-fastly added a commit that referenced this pull request Jun 3, 2026
…ecv exclusion override

This branch ships v1.1.0, three workstreams squashed from 70+ original
commits. 170+ files changed; `make ci` green (lint + format + mypy +
3,087 backend pytest + 9 vcl + 65 Rust scorer + 265 frontend vitest +
OSV no-vulns). Deployed and verified on the dev VM.

## 1 — Session scoring (Phases A / B / C)

End-to-end edge anomaly-detection pipeline for Fastly Compute. Layer 1
behavioural (cookie compliance, impossibly-fast browsing, robotic
dwell) + Layer 2 transition-matrix scoring + combined 0–100 quantized
score. Dual-implemented in Python (backend/scoring) and Rust
(compute/scorer); cross-language wire-format tests pin the AES-GCM
cookie codec byte-for-byte.

- Edge: Compute scorer, 6-snippet VCL preflight (recv / pass / fetch /
  deliver / miss / enforce), AES-GCM cookie carrying rotating sid +
  transition state, `fastly.ddos_detected` bypass.
- Backend: training pipeline, FOS-published matrix versioning,
  labelled-session retrain loop, /scoring/evaluation + /scoring/health
  + composite /scoring/dashboard, matrix version history + rollback,
  AES key rotation with grace window, sliding cookie lifetime,
  scoring audit log, threshold enforcement that 429s flagged requests
  at the edge within seconds of commit.
- Admin UI at /admin/session-scoring: StatusPanel with live ROC-AUC
  against accumulated labels, ScoringHealthCard, ThresholdSlider with
  counterfactual flag/pass preview + precision/recall, RocPrCurves,
  TopFlaggedTable, LabelsTab with click-to-view-events, RetrainButton,
  RotateKeyButton, MatrixVersionsCard, per-reason AUC breakdown,
  session-events viewer, ExcludeRegexCard (see §3), help popups.

See docs/session_scoring_runbook.md + docs/features.md for the runbook
and feature reference.

## 2 — Security remediation (Phases 0–4)

40-finding security audit closed in five phases. All fixes deployed
and verified. Full breakdown in the `### Security` block of
CHANGELOG.md 1.1.0; one-line summary per phase:

- Phase 0 — uvicorn `--proxy-headers` + Host-spoof bypass /
  leftmost-XFF / teardown-auth (#012, #013, #029, #017, #034). Three
  extras spotted during the IR sweep:
  - E1 Caddyfile peer-IP gate on `Fastly-Client-IP → XFF` rewrite
    (port 80 was open to `0.0.0.0/0`).
  - E2 docker json-file log rotation (50 MB × 10 compressed).
  - E3 `generate_analyst_invite` fail-fast on missing token +
    defensive Fastly-response shape check.
- Phase 1 — 14-finding trivial sweep: tenant scoping (#7 / #008 /
  #019), TOCTOU (#2), quarantine narrowing (#3), email-enum
  timing equalisation (#4), `isoparse` validation (#1),
  `service_id` path-traversal regex (#6), session re-sync (#010),
  rate-limiter bounds (#014), VCL UA/referer cap (#016), GET→POST CSRF
  (#020), read_only query-param removal (#026), stack-trace strip
  (#027 / #028) + sweep fixture.
- Phase 2 — backend/utils/sql_validator.py implements Decision B:
  statement-type whitelist + recursive parse-tree walker (catalog +
  function blocklists) + fail-closed parse + audit log + perf budget
  (#031 / #033 / #035). escape_sql_literal helper + characterisation
  tests at four ingest sites (#009). VCL preamble unsetting
  client-spoofable internal headers (#021). Origin-metric VCL
  log-injection gates (#015). Path-traversal cages in /api/download
  (#5) + cache cleanup (#022). SSH host-key pinning via
  configs/ssh_known_hosts with fail-safe _ensure_known_hosts (#011).
- Phase 3 — Fastly vcl_hash keys on full req.url not just path
  (#024). Next.js /admin middleware gates on Caddy-injected
  X-Proxied-By-Caddy marker not Host header (#032). Scorer
  Python+Rust parity: L1_SCORE_COOKIE_TAMPERED=100,
  L1_ROBOTIC_DWELL_LOW_S 0.5 → 0.20 (#036 / #037). #038 sliding-
  window mean documented as tracked follow-up.
- Phase 4 — cross-tenant scope enforcement on /api/alerts/* and
  /api/views/* with pre-flight get_alert_by_id / get_view_by_id
  helpers so unauthorised mutations never land (#039 / #040). NGWAF
  workspace listing token-gated (#018). #025 covered by Phase 0 #017.
  Cache-layer audit confirmed every per-tenant cache includes
  service_id in the key.

## 3 — Scoring-recv URL exclusion regex (new operator control)

The "which requests get sent to Compute" condition was previously a
hard-coded _ASSET_EXT_REGEX in code. Operators can now override it
per-service from the Session Scoring page; the default static-asset
extension list still ships as the fallback.

- Backend — recv_snippet + generate_scoring_vcl accept an
  exclude_url_regex parameter; persisted in
  cfg.scoring.exclude_url_regex (None / "" = use default).
  update_recv_exclusion_regex orchestrator clones only the active
  version, swaps the recv snippet, validates, activates — ~5–15s vs.
  the full enable_scoring flow.
- New endpoints — GET /api/services/{id}/scoring/exclude-regex
  (returns current + default + effective) and PUT
  /api/services/{id}/scoring/exclude-regex?confirm=true (token-gated;
  audit-logged as scoring_exclude_regex_changed).
- Three-layer validation before any VCL ships:
  1. Input policy — length cap (2 KB), no double-quote / control
     chars, must compile under Python re.
  2. falco static analysis (github.com/ysugimoto/falco) on the
     assembled recv snippet (catches composition errors that slip past
     Python's compiler).
  3. Fastly's own VCL compiler at activate time.
- Frontend — ExcludeRegexCard on the overview tab: textarea
  pre-populated with current value, "Show default" toggle, "Reset to
  default" button, inline lint-error display, confirm-dialog before
  publish.
- Infra — falco v2.3.0 baked into the backend Docker image; production
  sets SCORING_REQUIRE_FALCO=1 so a missing binary fails closed
  instead of degrading to input-policy-only.

## Infrastructure

- Backend + frontend Docker base: python:3.12-slim-bullseye →
  python:3.12-slim-bookworm (cuts CVE-laden Debian 11 base; remaining
  13 high CVEs are deep-dependency / OpenSSL CVEs every major Python
  base inherits).
- Falco v2.3.0 in the backend image — required by the scoring-recv-
  snippet validator.
- Dependency freshness sweep on all four ecosystems:
  - Python: aiohttp 3.13.5 → 3.14.0, cfn-lint 1.51.2 → 1.51.4,
    distlib, filelock, idna 3.17 → 3.18, joserfc 1.6.8 → 1.7.0.
  - Frontend: @tanstack/react-query 5.100.14 → 5.101.0 (+ devtools),
    @types/react 19.2.15 → 19.2.16, eslint-config-next 16.2.6 →
    16.2.7, next 16.2.6 → 16.2.7, react / react-dom 19.2.6 → 19.2.7.
  - Rust: bitflags 2.11.1 → 2.12.1.
  - Deferred (major bumps reserved for 1.2): TypeScript 5.9 → 6.0
    (compiler-API breaking changes); Fastly Rust SDK 0.11 → 0.12
    (Compute@Edge API churn); jsdom / eslint / vitest where we're
    already ahead of the npm "latest" tag.

## Versioning

Bumped to 1.1.0 in pyproject.toml, frontend/package.json, and the
FastAPI app.version. CHANGELOG updated under [1.1.0] - 2026-06-03
with Security + Infrastructure sections.

## Test coverage

  backend pytest    3,087   (+321 vs v1.0.0)
  Rust scorer          65   (+8)
  frontend vitest     265   (+13)
  VCL tests             9   (same)

New test files for this release:
  tests/utils/test_sql_validator.py            (60)
  tests/utils/test_vcl_validator.py            (18)
  tests/test_proxy_headers_regression.py       (10)
  tests/test_no_trace_leakage_sweep.py          (4)
  tests/routers/test_provision_teardown_auth.py (9)
  tests/routers/test_cross_tenant_scope.py      (9)
  tests/routers/test_scoring_exclude_regex.py   (9)

## Notes for reviewers

- Branch was squashed from 70+ commits; full per-commit history is in
  git reflog locally. The squash makes this reviewable as one
  semantic unit (v1.1.0 release) instead of paging through unrelated
  intermediate work.
- Every security fix has acceptance tests. OpenAPI snapshot
  regenerated.
- Audit-finding working docs (docs/security_remediation_final_*.md,
  audit-findings/) were intentionally .gitignored and cleaned up at
  the end of the cycle — a fresh audit will produce fresh artifacts.
- Stale v1.1.0 tag was deleted before the squash. After merge, tag
  main with v1.1.0 rather than the PR branch.

## Test plan

- [x] `make ci` passes locally
- [x] Deployed to dev VM (fastly-log-analysis in us-central1-a) — all
      three containers healthy, GET /api/health returns 200
- [x] Falco verified in production image: v2.3.0
- [x] Exclude-regex endpoint reachable + returns expected shape
- [x] Off-network attack probes: Host-spoof, SQL injection
      (read_csv_auto, information_schema, getenv, duckdb_secrets),
      unauthenticated teardown — all rejected with expected status codes
- [ ] Reviewer: open /admin/session-scoring, scroll to the URL
      exclusion regex card, paste a custom regex (e.g. \.(healthz)$),
      click Save → publish flow completes

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dmichael-fastly added a commit that referenced this pull request Jun 3, 2026
…ecv exclusion override

This branch ships v1.1.0, three workstreams squashed from 70+ original
commits. 170+ files changed; `make ci` green (lint + format + mypy +
3,087 backend pytest + 9 vcl + 65 Rust scorer + 265 frontend vitest +
OSV no-vulns). Deployed and verified on the dev VM.

## 1 — Session scoring (Phases A / B / C)

End-to-end edge anomaly-detection pipeline for Fastly Compute. Layer 1
behavioural (cookie compliance, impossibly-fast browsing, robotic
dwell) + Layer 2 transition-matrix scoring + combined 0–100 quantized
score. Dual-implemented in Python (backend/scoring) and Rust
(compute/scorer); cross-language wire-format tests pin the AES-GCM
cookie codec byte-for-byte.

- Edge: Compute scorer, 6-snippet VCL preflight (recv / pass / fetch /
  deliver / miss / enforce), AES-GCM cookie carrying rotating sid +
  transition state, `fastly.ddos_detected` bypass.
- Backend: training pipeline, FOS-published matrix versioning,
  labelled-session retrain loop, /scoring/evaluation + /scoring/health
  + composite /scoring/dashboard, matrix version history + rollback,
  AES key rotation with grace window, sliding cookie lifetime,
  scoring audit log, threshold enforcement that 429s flagged requests
  at the edge within seconds of commit.
- Admin UI at /admin/session-scoring: StatusPanel with live ROC-AUC
  against accumulated labels, ScoringHealthCard, ThresholdSlider with
  counterfactual flag/pass preview + precision/recall, RocPrCurves,
  TopFlaggedTable, LabelsTab with click-to-view-events, RetrainButton,
  RotateKeyButton, MatrixVersionsCard, per-reason AUC breakdown,
  session-events viewer, ExcludeRegexCard (see §3), help popups.

See docs/session_scoring_runbook.md + docs/features.md for the runbook
and feature reference.

## 2 — Security remediation (Phases 0–4)

40-finding security audit closed in five phases. All fixes deployed
and verified. Full breakdown in the `### Security` block of
CHANGELOG.md 1.1.0; one-line summary per phase:

- Phase 0 — uvicorn `--proxy-headers` + Host-spoof bypass /
  leftmost-XFF / teardown-auth (#012, #013, #029, #017, #034). Three
  extras spotted during the IR sweep:
  - E1 Caddyfile peer-IP gate on `Fastly-Client-IP → XFF` rewrite
    (port 80 was open to `0.0.0.0/0`).
  - E2 docker json-file log rotation (50 MB × 10 compressed).
  - E3 `generate_analyst_invite` fail-fast on missing token +
    defensive Fastly-response shape check.
- Phase 1 — 14-finding trivial sweep: tenant scoping (#7 / #008 /
  #019), TOCTOU (#2), quarantine narrowing (#3), email-enum
  timing equalisation (#4), `isoparse` validation (#1),
  `service_id` path-traversal regex (#6), session re-sync (#010),
  rate-limiter bounds (#014), VCL UA/referer cap (#016), GET→POST CSRF
  (#020), read_only query-param removal (#026), stack-trace strip
  (#027 / #028) + sweep fixture.
- Phase 2 — backend/utils/sql_validator.py implements Decision B:
  statement-type whitelist + recursive parse-tree walker (catalog +
  function blocklists) + fail-closed parse + audit log + perf budget
  (#031 / #033 / #035). escape_sql_literal helper + characterisation
  tests at four ingest sites (#009). VCL preamble unsetting
  client-spoofable internal headers (#021). Origin-metric VCL
  log-injection gates (#015). Path-traversal cages in /api/download
  (#5) + cache cleanup (#022). SSH host-key pinning via
  configs/ssh_known_hosts with fail-safe _ensure_known_hosts (#011).
- Phase 3 — Fastly vcl_hash keys on full req.url not just path
  (#024). Next.js /admin middleware gates on Caddy-injected
  X-Proxied-By-Caddy marker not Host header (#032). Scorer
  Python+Rust parity: L1_SCORE_COOKIE_TAMPERED=100,
  L1_ROBOTIC_DWELL_LOW_S 0.5 → 0.20 (#036 / #037). #038 sliding-
  window mean documented as tracked follow-up.
- Phase 4 — cross-tenant scope enforcement on /api/alerts/* and
  /api/views/* with pre-flight get_alert_by_id / get_view_by_id
  helpers so unauthorised mutations never land (#039 / #040). NGWAF
  workspace listing token-gated (#018). #025 covered by Phase 0 #017.
  Cache-layer audit confirmed every per-tenant cache includes
  service_id in the key.

## 3 — Scoring-recv URL exclusion regex (new operator control)

The "which requests get sent to Compute" condition was previously a
hard-coded _ASSET_EXT_REGEX in code. Operators can now override it
per-service from the Session Scoring page; the default static-asset
extension list still ships as the fallback.

- Backend — recv_snippet + generate_scoring_vcl accept an
  exclude_url_regex parameter; persisted in
  cfg.scoring.exclude_url_regex (None / "" = use default).
  update_recv_exclusion_regex orchestrator clones only the active
  version, swaps the recv snippet, validates, activates — ~5–15s vs.
  the full enable_scoring flow.
- New endpoints — GET /api/services/{id}/scoring/exclude-regex
  (returns current + default + effective) and PUT
  /api/services/{id}/scoring/exclude-regex?confirm=true (token-gated;
  audit-logged as scoring_exclude_regex_changed).
- Three-layer validation before any VCL ships:
  1. Input policy — length cap (2 KB), no double-quote / control
     chars, must compile under Python re.
  2. falco static analysis (github.com/ysugimoto/falco) on the
     assembled recv snippet (catches composition errors that slip past
     Python's compiler).
  3. Fastly's own VCL compiler at activate time.
- Frontend — ExcludeRegexCard on the overview tab: textarea
  pre-populated with current value, "Show default" toggle, "Reset to
  default" button, inline lint-error display, confirm-dialog before
  publish.
- Infra — falco v2.3.0 baked into the backend Docker image; production
  sets SCORING_REQUIRE_FALCO=1 so a missing binary fails closed
  instead of degrading to input-policy-only.

## Infrastructure

- Backend + frontend Docker base: python:3.12-slim-bullseye →
  python:3.12-slim-bookworm (cuts CVE-laden Debian 11 base; remaining
  13 high CVEs are deep-dependency / OpenSSL CVEs every major Python
  base inherits).
- Falco v2.3.0 in the backend image — required by the scoring-recv-
  snippet validator.
- Dependency freshness sweep on all four ecosystems:
  - Python: aiohttp 3.13.5 → 3.14.0, cfn-lint 1.51.2 → 1.51.4,
    distlib, filelock, idna 3.17 → 3.18, joserfc 1.6.8 → 1.7.0.
  - Frontend: @tanstack/react-query 5.100.14 → 5.101.0 (+ devtools),
    @types/react 19.2.15 → 19.2.16, eslint-config-next 16.2.6 →
    16.2.7, next 16.2.6 → 16.2.7, react / react-dom 19.2.6 → 19.2.7.
  - Rust: bitflags 2.11.1 → 2.12.1.
  - Deferred (major bumps reserved for 1.2): TypeScript 5.9 → 6.0
    (compiler-API breaking changes); Fastly Rust SDK 0.11 → 0.12
    (Compute@Edge API churn); jsdom / eslint / vitest where we're
    already ahead of the npm "latest" tag.

## Versioning

Bumped to 1.1.0 in pyproject.toml, frontend/package.json, and the
FastAPI app.version. CHANGELOG updated under [1.1.0] - 2026-06-03
with Security + Infrastructure sections.

## Test coverage

  backend pytest    3,087   (+321 vs v1.0.0)
  Rust scorer          65   (+8)
  frontend vitest     265   (+13)
  VCL tests             9   (same)

New test files for this release:
  tests/utils/test_sql_validator.py            (60)
  tests/utils/test_vcl_validator.py            (18)
  tests/test_proxy_headers_regression.py       (10)
  tests/test_no_trace_leakage_sweep.py          (4)
  tests/routers/test_provision_teardown_auth.py (9)
  tests/routers/test_cross_tenant_scope.py      (9)
  tests/routers/test_scoring_exclude_regex.py   (9)

## Notes for reviewers

- Branch was squashed from 70+ commits; full per-commit history is in
  git reflog locally. The squash makes this reviewable as one
  semantic unit (v1.1.0 release) instead of paging through unrelated
  intermediate work.
- Every security fix has acceptance tests. OpenAPI snapshot
  regenerated.
- Audit-finding working docs (docs/security_remediation_final_*.md,
  audit-findings/) were intentionally .gitignored and cleaned up at
  the end of the cycle — a fresh audit will produce fresh artifacts.
- Stale v1.1.0 tag was deleted before the squash. After merge, tag
  main with v1.1.0 rather than the PR branch.

## Test plan

- [x] `make ci` passes locally
- [x] Deployed to dev VM (fastly-log-analysis in us-central1-a) — all
      three containers healthy, GET /api/health returns 200
- [x] Falco verified in production image: v2.3.0
- [x] Exclude-regex endpoint reachable + returns expected shape
- [x] Off-network attack probes: Host-spoof, SQL injection
      (read_csv_auto, information_schema, getenv, duckdb_secrets),
      unauthenticated teardown — all rejected with expected status codes
- [ ] Reviewer: open /admin/session-scoring, scroll to the URL
      exclusion regex card, paste a custom regex (e.g. \.(healthz)$),
      click Save → publish flow completes

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dmichael-fastly added a commit that referenced this pull request Jun 3, 2026
…ecv exclusion override

This branch ships v1.1.0, three workstreams squashed from 70+ original
commits. 170+ files changed; `make ci` green (lint + format + mypy +
3,087 backend pytest + 9 vcl + 65 Rust scorer + 265 frontend vitest +
OSV no-vulns). Deployed and verified on the dev VM.

## 1 — Session scoring (Phases A / B / C)

End-to-end edge anomaly-detection pipeline for Fastly Compute. Layer 1
behavioural (cookie compliance, impossibly-fast browsing, robotic
dwell) + Layer 2 transition-matrix scoring + combined 0–100 quantized
score. Dual-implemented in Python (backend/scoring) and Rust
(compute/scorer); cross-language wire-format tests pin the AES-GCM
cookie codec byte-for-byte.

- Edge: Compute scorer, 6-snippet VCL preflight (recv / pass / fetch /
  deliver / miss / enforce), AES-GCM cookie carrying rotating sid +
  transition state, `fastly.ddos_detected` bypass.
- Backend: training pipeline, FOS-published matrix versioning,
  labelled-session retrain loop, /scoring/evaluation + /scoring/health
  + composite /scoring/dashboard, matrix version history + rollback,
  AES key rotation with grace window, sliding cookie lifetime,
  scoring audit log, threshold enforcement that 429s flagged requests
  at the edge within seconds of commit.
- Admin UI at /admin/session-scoring: StatusPanel with live ROC-AUC
  against accumulated labels, ScoringHealthCard, ThresholdSlider with
  counterfactual flag/pass preview + precision/recall, RocPrCurves,
  TopFlaggedTable, LabelsTab with click-to-view-events, RetrainButton,
  RotateKeyButton, MatrixVersionsCard, per-reason AUC breakdown,
  session-events viewer, ExcludeRegexCard (see §3), help popups.

See docs/session_scoring_runbook.md + docs/features.md for the runbook
and feature reference.

## 2 — Security remediation (Phases 0–4)

40-finding security audit closed in five phases. All fixes deployed
and verified. Full breakdown in the `### Security` block of
CHANGELOG.md 1.1.0; one-line summary per phase:

- Phase 0 — uvicorn `--proxy-headers` + Host-spoof bypass /
  leftmost-XFF / teardown-auth (#012, #013, #029, #017, #034). Three
  extras spotted during the IR sweep:
  - E1 Caddyfile peer-IP gate on `Fastly-Client-IP → XFF` rewrite
    (port 80 was open to `0.0.0.0/0`).
  - E2 docker json-file log rotation (50 MB × 10 compressed).
  - E3 `generate_analyst_invite` fail-fast on missing token +
    defensive Fastly-response shape check.
- Phase 1 — 14-finding trivial sweep: tenant scoping (#7 / #008 /
  #019), TOCTOU (#2), quarantine narrowing (#3), email-enum
  timing equalisation (#4), `isoparse` validation (#1),
  `service_id` path-traversal regex (#6), session re-sync (#010),
  rate-limiter bounds (#014), VCL UA/referer cap (#016), GET→POST CSRF
  (#020), read_only query-param removal (#026), stack-trace strip
  (#027 / #028) + sweep fixture.
- Phase 2 — backend/utils/sql_validator.py implements Decision B:
  statement-type whitelist + recursive parse-tree walker (catalog +
  function blocklists) + fail-closed parse + audit log + perf budget
  (#031 / #033 / #035). escape_sql_literal helper + characterisation
  tests at four ingest sites (#009). VCL preamble unsetting
  client-spoofable internal headers (#021). Origin-metric VCL
  log-injection gates (#015). Path-traversal cages in /api/download
  (#5) + cache cleanup (#022). SSH host-key pinning via
  configs/ssh_known_hosts with fail-safe _ensure_known_hosts (#011).
- Phase 3 — Fastly vcl_hash keys on full req.url not just path
  (#024). Next.js /admin middleware gates on Caddy-injected
  X-Proxied-By-Caddy marker not Host header (#032). Scorer
  Python+Rust parity: L1_SCORE_COOKIE_TAMPERED=100,
  L1_ROBOTIC_DWELL_LOW_S 0.5 → 0.20 (#036 / #037). #038 sliding-
  window mean documented as tracked follow-up.
- Phase 4 — cross-tenant scope enforcement on /api/alerts/* and
  /api/views/* with pre-flight get_alert_by_id / get_view_by_id
  helpers so unauthorised mutations never land (#039 / #040). NGWAF
  workspace listing token-gated (#018). #025 covered by Phase 0 #017.
  Cache-layer audit confirmed every per-tenant cache includes
  service_id in the key.

## 3 — Scoring-recv URL exclusion regex (new operator control)

The "which requests get sent to Compute" condition was previously a
hard-coded _ASSET_EXT_REGEX in code. Operators can now override it
per-service from the Session Scoring page; the default static-asset
extension list still ships as the fallback.

- Backend — recv_snippet + generate_scoring_vcl accept an
  exclude_url_regex parameter; persisted in
  cfg.scoring.exclude_url_regex (None / "" = use default).
  update_recv_exclusion_regex orchestrator clones only the active
  version, swaps the recv snippet, validates, activates — ~5–15s vs.
  the full enable_scoring flow.
- New endpoints — GET /api/services/{id}/scoring/exclude-regex
  (returns current + default + effective) and PUT
  /api/services/{id}/scoring/exclude-regex?confirm=true (token-gated;
  audit-logged as scoring_exclude_regex_changed).
- Three-layer validation before any VCL ships:
  1. Input policy — length cap (2 KB), no double-quote / control
     chars, must compile under Python re.
  2. falco static analysis (github.com/ysugimoto/falco) on the
     assembled recv snippet (catches composition errors that slip past
     Python's compiler).
  3. Fastly's own VCL compiler at activate time.
- Frontend — ExcludeRegexCard on the overview tab: textarea
  pre-populated with current value, "Show default" toggle, "Reset to
  default" button, inline lint-error display, confirm-dialog before
  publish.
- Infra — falco v2.3.0 baked into the backend Docker image; production
  sets SCORING_REQUIRE_FALCO=1 so a missing binary fails closed
  instead of degrading to input-policy-only.

## Infrastructure

- Backend + frontend Docker base: python:3.12-slim-bullseye →
  python:3.12-slim-bookworm (cuts CVE-laden Debian 11 base; remaining
  13 high CVEs are deep-dependency / OpenSSL CVEs every major Python
  base inherits).
- Falco v2.3.0 in the backend image — required by the scoring-recv-
  snippet validator.
- Dependency freshness sweep on all four ecosystems:
  - Python: aiohttp 3.13.5 → 3.14.0, cfn-lint 1.51.2 → 1.51.4,
    distlib, filelock, idna 3.17 → 3.18, joserfc 1.6.8 → 1.7.0.
  - Frontend: @tanstack/react-query 5.100.14 → 5.101.0 (+ devtools),
    @types/react 19.2.15 → 19.2.16, eslint-config-next 16.2.6 →
    16.2.7, next 16.2.6 → 16.2.7, react / react-dom 19.2.6 → 19.2.7.
  - Rust: bitflags 2.11.1 → 2.12.1.
  - Deferred (major bumps reserved for 1.2): TypeScript 5.9 → 6.0
    (compiler-API breaking changes); Fastly Rust SDK 0.11 → 0.12
    (Compute@Edge API churn); jsdom / eslint / vitest where we're
    already ahead of the npm "latest" tag.

## Versioning

Bumped to 1.1.0 in pyproject.toml, frontend/package.json, and the
FastAPI app.version. CHANGELOG updated under [1.1.0] - 2026-06-03
with Security + Infrastructure sections.

## Test coverage

  backend pytest    3,087   (+321 vs v1.0.0)
  Rust scorer          65   (+8)
  frontend vitest     265   (+13)
  VCL tests             9   (same)

New test files for this release:
  tests/utils/test_sql_validator.py            (60)
  tests/utils/test_vcl_validator.py            (18)
  tests/test_proxy_headers_regression.py       (10)
  tests/test_no_trace_leakage_sweep.py          (4)
  tests/routers/test_provision_teardown_auth.py (9)
  tests/routers/test_cross_tenant_scope.py      (9)
  tests/routers/test_scoring_exclude_regex.py   (9)

## Notes for reviewers

- Branch was squashed from 70+ commits; full per-commit history is in
  git reflog locally. The squash makes this reviewable as one
  semantic unit (v1.1.0 release) instead of paging through unrelated
  intermediate work.
- Every security fix has acceptance tests. OpenAPI snapshot
  regenerated.
- Audit-finding working docs (docs/security_remediation_final_*.md,
  audit-findings/) were intentionally .gitignored and cleaned up at
  the end of the cycle — a fresh audit will produce fresh artifacts.
- Stale v1.1.0 tag was deleted before the squash. After merge, tag
  main with v1.1.0 rather than the PR branch.

## Test plan

- [x] `make ci` passes locally
- [x] Deployed to dev VM (fastly-log-analysis in us-central1-a) — all
      three containers healthy, GET /api/health returns 200
- [x] Falco verified in production image: v2.3.0
- [x] Exclude-regex endpoint reachable + returns expected shape
- [x] Off-network attack probes: Host-spoof, SQL injection
      (read_csv_auto, information_schema, getenv, duckdb_secrets),
      unauthenticated teardown — all rejected with expected status codes
- [ ] Reviewer: open /admin/session-scoring, scroll to the URL
      exclusion regex card, paste a custom regex (e.g. \.(healthz)$),
      click Save → publish flow completes

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dmichael-fastly added a commit that referenced this pull request Jun 3, 2026
…ecv exclusion override

This branch ships v1.1.0, three workstreams squashed from 70+ original
commits. 170+ files changed; `make ci` green (lint + format + mypy +
3,087 backend pytest + 9 vcl + 65 Rust scorer + 265 frontend vitest +
OSV no-vulns). Deployed and verified on the dev VM.

## 1 — Session scoring (Phases A / B / C)

End-to-end edge anomaly-detection pipeline for Fastly Compute. Layer 1
behavioural (cookie compliance, impossibly-fast browsing, robotic
dwell) + Layer 2 transition-matrix scoring + combined 0–100 quantized
score. Dual-implemented in Python (backend/scoring) and Rust
(compute/scorer); cross-language wire-format tests pin the AES-GCM
cookie codec byte-for-byte.

- Edge: Compute scorer, 6-snippet VCL preflight (recv / pass / fetch /
  deliver / miss / enforce), AES-GCM cookie carrying rotating sid +
  transition state, `fastly.ddos_detected` bypass.
- Backend: training pipeline, FOS-published matrix versioning,
  labelled-session retrain loop, /scoring/evaluation + /scoring/health
  + composite /scoring/dashboard, matrix version history + rollback,
  AES key rotation with grace window, sliding cookie lifetime,
  scoring audit log, threshold enforcement that 429s flagged requests
  at the edge within seconds of commit.
- Admin UI at /admin/session-scoring: StatusPanel with live ROC-AUC
  against accumulated labels, ScoringHealthCard, ThresholdSlider with
  counterfactual flag/pass preview + precision/recall, RocPrCurves,
  TopFlaggedTable, LabelsTab with click-to-view-events, RetrainButton,
  RotateKeyButton, MatrixVersionsCard, per-reason AUC breakdown,
  session-events viewer, ExcludeRegexCard (see §3), help popups.

See docs/session_scoring_runbook.md + docs/features.md for the runbook
and feature reference.

## 2 — Security remediation (Phases 0–4)

40-finding security audit closed in five phases. All fixes deployed
and verified. Full breakdown in the `### Security` block of
CHANGELOG.md 1.1.0; one-line summary per phase:

- Phase 0 — uvicorn `--proxy-headers` + Host-spoof bypass /
  leftmost-XFF / teardown-auth (#012, #013, #029, #017, #034). Three
  extras spotted during the IR sweep:
  - E1 Caddyfile peer-IP gate on `Fastly-Client-IP → XFF` rewrite
    (port 80 was open to `0.0.0.0/0`).
  - E2 docker json-file log rotation (50 MB × 10 compressed).
  - E3 `generate_analyst_invite` fail-fast on missing token +
    defensive Fastly-response shape check.
- Phase 1 — 14-finding trivial sweep: tenant scoping (#7 / #008 /
  #019), TOCTOU (#2), quarantine narrowing (#3), email-enum
  timing equalisation (#4), `isoparse` validation (#1),
  `service_id` path-traversal regex (#6), session re-sync (#010),
  rate-limiter bounds (#014), VCL UA/referer cap (#016), GET→POST CSRF
  (#020), read_only query-param removal (#026), stack-trace strip
  (#027 / #028) + sweep fixture.
- Phase 2 — backend/utils/sql_validator.py implements Decision B:
  statement-type whitelist + recursive parse-tree walker (catalog +
  function blocklists) + fail-closed parse + audit log + perf budget
  (#031 / #033 / #035). escape_sql_literal helper + characterisation
  tests at four ingest sites (#009). VCL preamble unsetting
  client-spoofable internal headers (#021). Origin-metric VCL
  log-injection gates (#015). Path-traversal cages in /api/download
  (#5) + cache cleanup (#022). SSH host-key pinning via
  configs/ssh_known_hosts with fail-safe _ensure_known_hosts (#011).
- Phase 3 — Fastly vcl_hash keys on full req.url not just path
  (#024). Next.js /admin middleware gates on Caddy-injected
  X-Proxied-By-Caddy marker not Host header (#032). Scorer
  Python+Rust parity: L1_SCORE_COOKIE_TAMPERED=100,
  L1_ROBOTIC_DWELL_LOW_S 0.5 → 0.20 (#036 / #037). #038 sliding-
  window mean documented as tracked follow-up.
- Phase 4 — cross-tenant scope enforcement on /api/alerts/* and
  /api/views/* with pre-flight get_alert_by_id / get_view_by_id
  helpers so unauthorised mutations never land (#039 / #040). NGWAF
  workspace listing token-gated (#018). #025 covered by Phase 0 #017.
  Cache-layer audit confirmed every per-tenant cache includes
  service_id in the key.

## 3 — Scoring-recv URL exclusion regex (new operator control)

The "which requests get sent to Compute" condition was previously a
hard-coded _ASSET_EXT_REGEX in code. Operators can now override it
per-service from the Session Scoring page; the default static-asset
extension list still ships as the fallback.

- Backend — recv_snippet + generate_scoring_vcl accept an
  exclude_url_regex parameter; persisted in
  cfg.scoring.exclude_url_regex (None / "" = use default).
  update_recv_exclusion_regex orchestrator clones only the active
  version, swaps the recv snippet, validates, activates — ~5–15s vs.
  the full enable_scoring flow.
- New endpoints — GET /api/services/{id}/scoring/exclude-regex
  (returns current + default + effective) and PUT
  /api/services/{id}/scoring/exclude-regex?confirm=true (token-gated;
  audit-logged as scoring_exclude_regex_changed).
- Three-layer validation before any VCL ships:
  1. Input policy — length cap (2 KB), no double-quote / control
     chars, must compile under Python re.
  2. falco static analysis (github.com/ysugimoto/falco) on the
     assembled recv snippet (catches composition errors that slip past
     Python's compiler).
  3. Fastly's own VCL compiler at activate time.
- Frontend — ExcludeRegexCard on the overview tab: textarea
  pre-populated with current value, "Show default" toggle, "Reset to
  default" button, inline lint-error display, confirm-dialog before
  publish.
- Infra — falco v2.3.0 baked into the backend Docker image; production
  sets SCORING_REQUIRE_FALCO=1 so a missing binary fails closed
  instead of degrading to input-policy-only.

## Infrastructure

- Backend + frontend Docker base: python:3.12-slim-bullseye →
  python:3.12-slim-bookworm (cuts CVE-laden Debian 11 base; remaining
  13 high CVEs are deep-dependency / OpenSSL CVEs every major Python
  base inherits).
- Falco v2.3.0 in the backend image — required by the scoring-recv-
  snippet validator.
- Dependency freshness sweep on all four ecosystems:
  - Python: aiohttp 3.13.5 → 3.14.0, cfn-lint 1.51.2 → 1.51.4,
    distlib, filelock, idna 3.17 → 3.18, joserfc 1.6.8 → 1.7.0.
  - Frontend: @tanstack/react-query 5.100.14 → 5.101.0 (+ devtools),
    @types/react 19.2.15 → 19.2.16, eslint-config-next 16.2.6 →
    16.2.7, next 16.2.6 → 16.2.7, react / react-dom 19.2.6 → 19.2.7.
  - Rust: bitflags 2.11.1 → 2.12.1.
  - Deferred (major bumps reserved for 1.2): TypeScript 5.9 → 6.0
    (compiler-API breaking changes); Fastly Rust SDK 0.11 → 0.12
    (Compute@Edge API churn); jsdom / eslint / vitest where we're
    already ahead of the npm "latest" tag.

## Versioning

Bumped to 1.1.0 in pyproject.toml, frontend/package.json, and the
FastAPI app.version. CHANGELOG updated under [1.1.0] - 2026-06-03
with Security + Infrastructure sections.

## Test coverage

  backend pytest    3,087   (+321 vs v1.0.0)
  Rust scorer          65   (+8)
  frontend vitest     265   (+13)
  VCL tests             9   (same)

New test files for this release:
  tests/utils/test_sql_validator.py            (60)
  tests/utils/test_vcl_validator.py            (18)
  tests/test_proxy_headers_regression.py       (10)
  tests/test_no_trace_leakage_sweep.py          (4)
  tests/routers/test_provision_teardown_auth.py (9)
  tests/routers/test_cross_tenant_scope.py      (9)
  tests/routers/test_scoring_exclude_regex.py   (9)

## Notes for reviewers

- Branch was squashed from 70+ commits; full per-commit history is in
  git reflog locally. The squash makes this reviewable as one
  semantic unit (v1.1.0 release) instead of paging through unrelated
  intermediate work.
- Every security fix has acceptance tests. OpenAPI snapshot
  regenerated.
- Audit-finding working docs (docs/security_remediation_final_*.md,
  audit-findings/) were intentionally .gitignored and cleaned up at
  the end of the cycle — a fresh audit will produce fresh artifacts.
- Stale v1.1.0 tag was deleted before the squash. After merge, tag
  main with v1.1.0 rather than the PR branch.

## Test plan

- [x] `make ci` passes locally
- [x] Deployed to dev VM (fastly-log-analysis in us-central1-a) — all
      three containers healthy, GET /api/health returns 200
- [x] Falco verified in production image: v2.3.0
- [x] Exclude-regex endpoint reachable + returns expected shape
- [x] Off-network attack probes: Host-spoof, SQL injection
      (read_csv_auto, information_schema, getenv, duckdb_secrets),
      unauthenticated teardown — all rejected with expected status codes
- [ ] Reviewer: open /admin/session-scoring, scroll to the URL
      exclusion regex card, paste a custom regex (e.g. \.(healthz)$),
      click Save → publish flow completes

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants