Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/audit-cron.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: step-security/harden-runner@v2
- uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2
with:
egress-policy: audit
- uses: actions/checkout@v6
- uses: rustsec/audit-check@v2.0.0
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: rustsec/audit-check@69366f33c96575abad1ee0dba8212993eecbe998 # v2.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
142 changes: 71 additions & 71 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .github/workflows/cla.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
cla-check:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const ALLOWLIST = [
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/cleanup-branches.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Delete merged branch
uses: actions/github-script@v7
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const pr = context.payload.pull_request;
Expand Down Expand Up @@ -51,7 +51,7 @@ jobs:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
- uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const owner = context.repo.owner;
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,19 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable

- name: Rust cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
with:
shared-key: codeql
save-if: ${{ github.ref == 'refs/heads/main' }}

- name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4
with:
languages: rust

Expand All @@ -66,7 +66,7 @@ jobs:

- name: Perform CodeQL Analysis
id: analyze
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4
with:
category: "/language:rust"
output: codeql-results
Expand All @@ -75,7 +75,7 @@ jobs:

- name: Store CodeQL results
if: always()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: codeql-results-${{ github.sha }}
path: ${{ steps.analyze.outputs.sarif-output }}
Expand Down
18 changes: 9 additions & 9 deletions .github/workflows/docs-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: step-security/harden-runner@v2
- uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2
with:
egress-policy: audit
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Run markdownlint-cli2
uses: DavidAnson/markdownlint-cli2-action@v20
uses: DavidAnson/markdownlint-cli2-action@992badcdf24e3b8eb7e87ff9287fe931bcb00c6e # v20
with:
globs: |
docs/**/*.md
Expand All @@ -58,10 +58,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- uses: step-security/harden-runner@v2
- uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2
with:
egress-policy: audit
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Scan for hardcoded versions
run: |
set -euo pipefail
Expand All @@ -86,18 +86,18 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: step-security/harden-runner@v2
- uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2
with:
egress-policy: audit
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Restore lychee cache
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: .lycheecache
key: cache-lychee-${{ github.sha }}
restore-keys: cache-lychee-
- name: Run lychee
uses: lycheeverse/lychee-action@v2
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2
with:
# --offline would skip network checks; we keep live checking
# but cache results to avoid hammering external hosts.
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,17 @@ jobs:
toolchain: [beta, nightly]
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: step-security/harden-runner@v2
- uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2
with:
egress-policy: audit
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
ref: main
- uses: dtolnay/rust-toolchain@master
- uses: dtolnay/rust-toolchain@d1031067263f94b142dd6c0ce24c5eb9d02d52a0 # master
with:
toolchain: ${{ matrix.toolchain }}
components: clippy
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
with:
shared-key: nightly-${{ matrix.toolchain }}-${{ matrix.os }}
save-if: true
Expand Down Expand Up @@ -88,11 +88,11 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: step-security/harden-runner@v2
- uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2
with:
egress-policy: audit
- name: Open or update tracking issue
uses: actions/github-script@v7
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const label = 'nightly-toolchain';
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/release-plz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ jobs:
group: release-plz-pr
cancel-in-progress: false
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
token: ${{ secrets.RELEASE_PLZ_TOKEN || github.token }}

- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable

- uses: release-plz/action@v0.5
- uses: release-plz/action@b5543c19b03be9bd48852d20ca89f478b7723260 # v0.5
with:
command: release-pr
env:
Expand All @@ -57,15 +57,15 @@ jobs:
group: release-plz-tag
cancel-in-progress: false
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
ref: main
token: ${{ secrets.RELEASE_PLZ_TOKEN || github.token }}

- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable

- uses: release-plz/action@v0.5
- uses: release-plz/action@b5543c19b03be9bd48852d20ca89f478b7723260 # v0.5
with:
command: release
env:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/semgrep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
image: semgrep/semgrep:1.144.0

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6

- name: Run Semgrep
run: >-
Expand All @@ -43,7 +43,7 @@ jobs:

- name: Store Semgrep results
if: always()
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: semgrep-results-${{ github.sha }}
path: semgrep-results.sarif
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/shellcheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: step-security/harden-runner@v2
- uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2
with:
egress-policy: audit
- uses: actions/checkout@v6
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Run shellcheck
run: |
set -euo pipefail
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ after the child exits.

## Configuration

Read the [gateway review and adoption guide](docs/product-review.md) for provider
redirect behavior, validation exit codes, offline regression checks and the
prioritized roadmap. Configure final provider URLs: Gate does not automatically
follow provider redirects.

Start with the interactive wizard:

```bash
Expand Down
85 changes: 85 additions & 0 deletions docs/product-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Gateway review and adoption guide

Reviewed on 2026-09-05. Gate's useful position is a local Rust gateway for AI
tools with configurable provider routing, DLP and operational evidence.
Feature presence does not establish a certification, DLP detection rate or
performance advantage.

## Market references

| Reference | Observed practice | Direction for Gate |
| --- | --- | --- |
| [LiteLLM fallback documentation](https://docs.litellm.ai/docs/proxy/reliability) | Explicit fallback groups and provider failure testing | Keep provider failover in the configured routing chain and test with synthetic upstream failures. |
| [LiteLLM documentation](https://docs.litellm.ai/) | Gateway budgets, guardrails and observability | Describe defaults and limits for each control. Avoid treating feature switches as evidence that a policy was enforced. |
| [Reqwest redirect policy](https://docs.rs/reqwest/latest/reqwest/redirect/index.html) | Redirect behavior is an explicit HTTP-client policy | Disable automatic redirects for provider calls so configured routing remains the egress boundary. |

## Changes and operator impact

### Provider redirects

Provider HTTP clients do not automatically follow 301, 302, 303, 307 or 308
responses. Prompts and provider-specific headers must not be replayed at a
destination selected by a redirect. Configure the final provider endpoint
directly. This applies to both streaming and non-streaming calls using the
shared provider client.

If HTTP-client initialization fails, Gate stops instead of silently replacing
the requested TLS and redirect policy with a default client. Investigate the
startup error and certificate configuration before restarting. This does not
add an outbound firewall or validate every possible DNS resolution.

### Scriptable validation

`gate validate` now exits unsuccessfully when provider initialization fails or
a router model has no healthy provider. A working fallback still counts as a
usable model, matching the printed health summary. Partial provider failures
remain visible in that summary. This command makes real provider calls and can
consume tokens; it is not an offline configuration-only check.

```bash
# Explicit live validation before adopting a configuration:
gate validate && echo "Every checked router model has a healthy provider"
```

### Dependency security

The HTTP/2 dependency is updated to address RUSTSEC-2026-0258 (unbounded empty
DATA-frame queuing). The change is limited to that package in the lockfile.
The existing dependency-advisory CI gate remains enabled.

### Supply chain

Third-party GitHub Actions are pinned to verified commit IDs while preserving
their existing selected versions. Dependabot can propose updates for review.
Commit pinning does not certify the contents or provenance of an action.

## Offline regression checks

```bash
cargo fmt --all -- --check
cargo test --locked --lib
cargo clippy --locked --all-targets -- -D warnings
```

Redirect tests use two local synthetic HTTP servers and check that no redirected
prompt reaches the second server. Direct requests still succeed. Validation
tests cover empty results, broken models, mixed model health and a working
fallback. No production provider credentials are required by these tests.

## Prioritized next milestones

| Priority | Work | Acceptance criterion |
| --- | --- | --- |
| P1 | Budget enforcement under concurrency | Parallel requests cannot overrun a configured reservation ceiling; restart and failed-upstream accounting are tested. |
| P1 | Streaming privacy corpus | Secrets split across chunks and tool-call fields are tested with documented precision/recall limits. |
| P1 | Audit continuity | Detect tampering, missing records and key rotation using synthetic fixtures and an independently verified export. |
| P2 | Provider compatibility matrix | Versioned fixtures exercise errors, streaming cancellation, tool calls and fallback for each supported protocol. |
| P2 | Reproducible performance evidence | Publish p50/p95/p99, throughput, memory and hardware with direct-provider baselines and identical payloads. |

## Rollback

There is no configuration or spend-journal migration. Revert the change commit
to restore prior behavior. Endpoint URLs that depended on redirects should be
corrected rather than relying on rollback, which restores the egress risk.
Automation that previously ignored validation failures must handle nonzero
exit status after this change.
Loading
Loading