Bump typescript from 5.9.3 to 6.0.3 in /frontend#2
Open
dependabot[bot] wants to merge 1 commit into
Open
Conversation
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>
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>
7 tasks
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps typescript from 5.9.3 to 6.0.3.
Release notes
Sourced from typescript's releases.
Commits
050880cBump version to 6.0.3 and LKGeeae9dd🤖 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 (#...607a22aBump version to 6.0.2 and LKG9e72ab7🤖 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 (#...e175b69Bump version to 6.0.1-rc and LKGaf4caacUpdate LKG8efd7e8Merge remote-tracking branch 'origin/main' into release-6.0Dependabot 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 rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill 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 versionwill 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 dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)