diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e22f093d2..075e70bbd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,4 @@ /.github/CODEOWNERS @AnthonyRonning /.github/workflows/ @AnthonyRonning /updates/ @AnthonyRonning +/proxy/ @AnthonyRonning diff --git a/.github/workflows/proxy-container.yml b/.github/workflows/proxy-container.yml new file mode 100644 index 000000000..0153c694c --- /dev/null +++ b/.github/workflows/proxy-container.yml @@ -0,0 +1,53 @@ +name: Proxy container CI + +permissions: + contents: read + +on: + push: + branches: [master] + paths: + - ".github/workflows/proxy-container.yml" + - "proxy/.dockerignore" + - "proxy/Dockerfile" + - "proxy/Cargo.toml" + - "proxy/Cargo.lock" + - "proxy/src/**" + pull_request: + paths: + - ".github/workflows/proxy-container.yml" + - "proxy/.dockerignore" + - "proxy/Dockerfile" + - "proxy/Cargo.toml" + - "proxy/Cargo.lock" + - "proxy/src/**" + +jobs: + proxy-container: + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + - platform: linux/arm64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + timeout-minutes: 30 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # was v4 + with: + persist-credentials: false + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # was v3 + + - name: Build proxy container without publishing + uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # was v5 + with: + context: proxy + file: proxy/Dockerfile + platforms: ${{ matrix.platform }} + cache-from: type=gha,scope=proxy-${{ matrix.platform }} + cache-to: type=gha,mode=max,scope=proxy-${{ matrix.platform }} + push: false diff --git a/.github/workflows/proxy-rust.yml b/.github/workflows/proxy-rust.yml new file mode 100644 index 000000000..76e1c1952 --- /dev/null +++ b/.github/workflows/proxy-rust.yml @@ -0,0 +1,69 @@ +name: Proxy Rust CI + +permissions: + contents: read + +on: + push: + branches: [master] + paths: + - ".github/workflows/proxy-rust.yml" + - "proxy/Cargo.toml" + - "proxy/Cargo.lock" + - "proxy/clippy.toml" + - "proxy/flake.nix" + - "proxy/flake.lock" + - "proxy/rust-toolchain.toml" + - "proxy/rustfmt.toml" + - "proxy/src/**" + - "proxy/tests/**" + - "proxy/examples/**" + pull_request: + paths: + - ".github/workflows/proxy-rust.yml" + - "proxy/Cargo.toml" + - "proxy/Cargo.lock" + - "proxy/clippy.toml" + - "proxy/flake.nix" + - "proxy/flake.lock" + - "proxy/rust-toolchain.toml" + - "proxy/rustfmt.toml" + - "proxy/src/**" + - "proxy/tests/**" + - "proxy/examples/**" + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: "-D warnings" + +jobs: + proxy-rust: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # was v4 + with: + persist-credentials: false + + - name: Install Nix + uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # was v22 + with: + github-token: "" + + - name: Cache Rust dependencies + uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # was v2 + with: + workspaces: proxy -> target + add-rust-environment-hash-key: "false" + key: ${{ hashFiles('proxy/Cargo.lock', 'proxy/rust-toolchain.toml', 'proxy/flake.lock') }} + + - name: Run credential-free proxy checks + run: | + nix develop --no-update-lock-file ./proxy -c bash -lc ' + cd proxy + cargo fmt --all -- --check + cargo clippy --locked --all-targets --all-features -- -D warnings + cargo test --locked --all-features + RUSTDOCFLAGS="-D warnings" cargo doc --locked --no-deps --all-features + cargo machete + ' diff --git a/.github/workflows/proxy-supply-chain.yml b/.github/workflows/proxy-supply-chain.yml new file mode 100644 index 000000000..5faa645dd --- /dev/null +++ b/.github/workflows/proxy-supply-chain.yml @@ -0,0 +1,40 @@ +name: Proxy Rust supply-chain checks + +on: + pull_request: + paths: + - ".github/workflows/proxy-supply-chain.yml" + - "proxy/deny.toml" + - "proxy/Cargo.toml" + - "proxy/Cargo.lock" + push: + branches: [master] + paths: + - ".github/workflows/proxy-supply-chain.yml" + - "proxy/deny.toml" + - "proxy/Cargo.toml" + - "proxy/Cargo.lock" + schedule: + - cron: "29 7 * * *" + workflow_dispatch: + +permissions: + contents: read + +jobs: + proxy-cargo-deny: + name: Proxy RustSec advisories and malicious crates + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # was v4 + with: + persist-credentials: false + + - name: Check proxy RustSec advisories and incident denylist + uses: EmbarkStudios/cargo-deny-action@3c6349835b2b7b196a839186cb8b78e02f7b5f25 # was v2 + with: + manifest-path: proxy/Cargo.toml + command: check advisories bans + arguments: --config proxy/deny.toml --all-features --locked diff --git a/AGENTS.md b/AGENTS.md index 46378c8b4..ad5376729 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -52,6 +52,14 @@ and Rust consumers switch together. Do not assume the TypeScript and Rust SDKs have identical transports, retries, or API coverage. A backend contract change that Maple consumes needs compatibility checks for every affected client path. +The standalone proxy source lives under `proxy/`. From the repository root, +run its Rust commands through +`nix develop --no-update-lock-file ./proxy -c bash -lc 'cd proxy && ...'`; +root path-scoped workflows own proxy CI. Until the coordinated Rust dependency +switch lands, the Tauri app and proxy continue to consume their published +crate dependencies, so a proxy-only source change is not yet an application +build input. + ## Code ownership and placement - `frontend/src/routes`, `components`, `contexts`, and `state` own routing, diff --git a/README.md b/README.md index 1f029f790..790a60555 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,11 @@ uses the TypeScript OpenSecret SDK with the Responses and Conversations APIs; Agent Mode embeds Goose and uses the Rust OpenSecret SDK through Tauri. The local OpenAI-compatible proxy is a separate user-facing service. -The OpenSecret SDK source and its upstream Git history are imported under -[`sdk/`](sdk/README.md). Maple still consumes the published TypeScript and Rust -SDK packages until follow-up changes explicitly switch those dependencies to -the in-repository source. +The OpenSecret SDK source and its upstream Git history live under +[`sdk/`](sdk/README.md), and Maple's TypeScript client consumes that in-tree +package. The proxy source and its upstream history live under +[`proxy/`](proxy/README.md). Native Maple and the proxy still consume published +Rust crates until their local references switch together. ## Quick start diff --git a/proxy/.githooks/pre-commit b/proxy/.githooks/pre-commit deleted file mode 100755 index 049654020..000000000 --- a/proxy/.githooks/pre-commit +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env bash -# Pre-commit hook for Maple Proxy -# Ensures code quality before commits - -set -e - -echo "๐Ÿ” Running pre-commit checks..." - -# Check if we're in a git repository -if ! git rev-parse --git-dir >/dev/null 2>&1; then - echo "โŒ Not in a git repository" - exit 1 -fi - -# Function to print step status -print_step() { - echo "๐Ÿ“‹ $1..." -} - -print_success() { - echo "โœ… $1" -} - -print_error() { - echo "โŒ $1" -} - -# Rust formatting check -print_step "Checking Rust formatting" -if cargo fmt --check; then - print_success "Code formatting is correct" -else - print_error "Code formatting issues found" - echo "๐Ÿ’ก Run 'cargo fmt' to fix formatting" - exit 1 -fi - -# Clippy linting -print_step "Running Clippy lints" -if cargo clippy --locked --all-targets --all-features -- -D warnings; then - print_success "Clippy checks passed" -else - print_error "Clippy lints failed" - echo "๐Ÿ’ก Fix the issues above before committing" - exit 1 -fi - -# Cargo check (compilation) -print_step "Checking compilation" -if cargo check --locked --all-targets --all-features; then - print_success "Code compiles successfully" -else - print_error "Compilation failed" - exit 1 -fi - -# Run tests -print_step "Running tests" -if cargo test --locked --all-features; then - print_success "All tests passed" -else - print_error "Tests failed" - exit 1 -fi - -echo "" -echo "๐ŸŽ‰ All pre-commit checks passed!" -echo " Ready to commit" diff --git a/proxy/CLAUDE.md b/proxy/CLAUDE.md deleted file mode 100644 index 7b08c5ca1..000000000 --- a/proxy/CLAUDE.md +++ /dev/null @@ -1,101 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -Maple Proxy is a lightweight OpenAI-compatible proxy server that forwards requests to Maple/OpenSecret's TEE (Trusted Execution Environment) infrastructure. It acts as a translation layer between OpenAI client libraries and the OpenSecret backend, enabling secure AI processing in trusted enclaves. - -## Common Development Commands - -### Build and Run -- `just run` - Start the development server (loads config from .env) -- `just run-local` - Run pointing to local backend (http://localhost:3000) -- `just run-prod` - Run pointing to production backend (https://enclave.trymaple.ai) -- `just build` - Build debug binary -- `just release` - Build optimized release binary -- `cargo run --locked` - Run directly with cargo - -### Testing and Quality -- `just test` - Run all tests -- `just fmt` or `just format` - Format code with rustfmt -- `just lint` or `just clippy` - Run clippy lints with strict warnings -- `just check` - Run format, lint, and test in sequence - -### Docker Operations -- `just docker-build` - Build Docker image locally -- `just docker-run` - Run container interactively -- `just docker-run-detached` - Run container in background -- `just compose-up` - Start with docker-compose -- `just compose-down` - Stop docker-compose services - -## Architecture - -### Core Components - -1. **main.rs** - Entry point that initializes the server with configuration and starts the Axum web server on the configured host/port. - -2. **lib.rs** - Library root that exports the main `create_app` function, which builds the Axum router with: - - Health check endpoints (/, /health) - - OpenAI-compatible endpoints (/v1/models, /v1/chat/completions) - - Optional CORS support - - Request tracing - -3. **config.rs** - Configuration management using clap for CLI args and environment variables: - - Server settings (host, port) - - Backend URL configuration - - API key management - - Debug and CORS flags - - OpenAI-compatible error types - -4. **proxy.rs** - Core proxy logic that: - - Extracts API keys from Authorization headers or falls back to default - - Creates OpenSecret client and performs attestation handshake - - Forwards requests to the TEE backend - - Handles streaming responses for chat completions - - Transforms responses to OpenAI format - -### Request Flow - -1. Client sends OpenAI-compatible request to proxy -2. Proxy extracts API key (from header or default config) -3. Creates OpenSecret client and performs TEE attestation -4. Forwards request to Maple backend (enclave.trymaple.ai or configured URL) -5. Streams response back to client in OpenAI format - -### Authentication - -The proxy supports two authentication modes: -- **Default API Key**: Set via `MAPLE_API_KEY` environment variable -- **Per-Request**: Clients provide `Authorization: Bearer ` header - -For public deployments, avoid setting default API key to require per-request authentication. - -## Configuration - -Environment variables (can be set in .env file): -- `MAPLE_HOST` - Server bind address (default: 127.0.0.1) -- `MAPLE_PORT` - Server port (default: 8080) -- `MAPLE_BACKEND_URL` - OpenSecret backend URL (default: https://enclave.trymaple.ai) -- `MAPLE_API_KEY` - Default API key (optional) -- `MAPLE_DEBUG` - Enable debug logging -- `MAPLE_ENABLE_CORS` - Enable CORS for web clients -- `MAPLE_REQUEST_TIMEOUT_SECS` - Backend request timeout in seconds (default: 300) -- `MAPLE_STREAM_IDLE_TIMEOUT_SECS` - Streaming idle timeout in seconds (default: 300) - -## Testing - -Tests are located in `tests/` directory. Currently includes: -- `health_test.rs` - Tests for health check endpoints - -Run tests with `just test` or `cargo test --locked`. - -## Dependencies - -Key dependencies: -- **opensecret** - Official OpenSecret SDK for TEE communication -- **axum** - Web framework for the HTTP server -- **tokio** - Async runtime -- **tower/tower-http** - Middleware for CORS and tracing -- **clap** - CLI argument parsing -- **dotenvy** - .env file support diff --git a/proxy/Cargo.toml b/proxy/Cargo.toml index b63fb94d2..94556df0a 100644 --- a/proxy/Cargo.toml +++ b/proxy/Cargo.toml @@ -5,8 +5,8 @@ edition = "2021" authors = ["OpenSecret"] description = "Lightweight OpenAI-compatible proxy server for Maple/OpenSecret TEE infrastructure" license = "MIT" -repository = "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/OpenSecretCloud/maple-proxy" -homepage = "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/OpenSecretCloud/maple-proxy" +repository = "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/OpenSecretCloud/Maple" +homepage = "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/OpenSecretCloud/Maple/tree/master/proxy" keywords = ["openai", "proxy", "tee", "opensecret", "maple"] categories = ["web-programming::http-server", "api-bindings"] include = [ diff --git a/proxy/README.md b/proxy/README.md index 1327115fd..c642b92e9 100644 --- a/proxy/README.md +++ b/proxy/README.md @@ -20,8 +20,8 @@ Environment (TEE) processing. ### As a Binary ```bash -git clone -cd maple-proxy +git clone https://github.com/OpenSecretCloud/Maple.git +cd Maple/proxy cargo build --locked --release ``` @@ -31,9 +31,7 @@ Add to your `Cargo.toml`: ```toml [dependencies] -maple-proxy = { git = "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/opensecretcloud/maple-proxy" } -# Or if published to crates.io: -# maple-proxy = "0.3.2" +maple-proxy = "0.3.2" ``` ## โš™๏ธ Configuration @@ -385,22 +383,19 @@ environment: ### Docker Images & CI/CD -**Automated Builds (GitHub Actions)** -- Every push to `master` automatically builds and publishes to `ghcr.io/opensecretcloud/maple-proxy:latest` -- Git tags (e.g., `v1.0.0`) trigger versioned releases -- Multi-platform images (linux/amd64, linux/arm64) built automatically -- No manual intervention needed - just push your code! +The source lives under [`proxy/`](https://github.com/OpenSecretCloud/Maple/tree/master/proxy) +in the Maple repository. Root, path-scoped workflows run locked Rust checks, +supply-chain policy, and non-publishing AMD64/ARM64 container builds for proxy +changes. Production publishing is intentionally handled separately from these +CI checks. **Local Development (Justfile)** ```bash # For local testing and debugging just docker-build # Build locally just docker-run # Test locally -just ghcr-push v1.2.3 # Manual push (requires login) ``` -Use GitHub Actions for production releases, Justfile for local development. - ### Build from Source ```bash cargo build --locked diff --git a/proxy/flake.nix b/proxy/flake.nix index f9fec1a7c..1ba6243cc 100644 --- a/proxy/flake.nix +++ b/proxy/flake.nix @@ -34,6 +34,7 @@ # Rust tooling rust rust-analyzer + cargo-machete pkg-config openssl zlib @@ -92,26 +93,6 @@ alias docker='podman' echo "Using 'podman' as an alias for 'docker'" echo "You can now use 'docker' commands, which will be executed by podman" - - # Podman configuration - export CONTAINERS_CONF=$HOME/.config/containers/containers.conf - export CONTAINERS_POLICY=$HOME/.config/containers/policy.json - mkdir -p $HOME/.config/containers - echo '{"default":[{"type":"insecureAcceptAnything"}]}' > $CONTAINERS_POLICY - - # Create a basic containers.conf if it doesn't exist - if [ ! -f $CONTAINERS_CONF ]; then - echo "[engine] - cgroup_manager = \"cgroupfs\" - events_logger = \"file\" - runtime = \"crun\" - - [storage] - driver = \"vfs\"" > $CONTAINERS_CONF - fi - - # Ensure correct permissions - chmod 600 $CONTAINERS_POLICY $CONTAINERS_CONF 2>/dev/null || true ''} ''; }; diff --git a/proxy/justfile b/proxy/justfile index 409fb0bf4..a3b03e130 100644 --- a/proxy/justfile +++ b/proxy/justfile @@ -14,7 +14,6 @@ default: # Set up development environment setup: @echo "๐Ÿ”ง Setting up development environment..." - @bash setup-hooks.sh @cargo check --locked --all-features @echo "โœ… Development environment ready" @@ -27,6 +26,10 @@ format: # Alias for format fmt: format +# Verify formatting without modifying files +fmt-check: + @cargo fmt --all -- --check + # Run clippy lints lint: @echo "๐Ÿ” Running clippy lints..." @@ -43,7 +46,7 @@ test: @echo "โœ… Tests passed" # Run all checks (format, lint, test) -check: format lint test +check: fmt-check lint test @echo "โœ… All checks passed" # Run the development server @@ -88,7 +91,7 @@ build-all: # Clean build artifacts clean: @echo "๐Ÿงน Cleaning build artifacts..." - @cargo clean + @CARGO_BUILD_BUILD_DIR=target cargo clean @echo "โœ… Build artifacts cleaned" # Update dependencies @@ -138,12 +141,6 @@ watch: @echo "๐Ÿ‘๏ธ Watching for changes..." @cargo watch -x 'test --locked' -# Create a new git commit with conventional commit message -commit message: - @git add -A - @git commit -m "{{message}}" - @echo "โœ… Changes committed" - # Quick test with curl test-curl: @echo "๐Ÿงช Testing with curl..." @@ -165,7 +162,7 @@ env: @echo "MAPLE_PORT: ${MAPLE_PORT:-8080}" @echo "MAPLE_BACKEND_URL: ${MAPLE_BACKEND_URL:-https://enclave.trymaple.ai}" @echo "MAPLE_PCR0_ENVIRONMENT: ${MAPLE_PCR0_ENVIRONMENT:-production}" - @echo "MAPLE_API_KEY: ${MAPLE_API_KEY:-[not set]}" + @echo "MAPLE_API_KEY: $(if [ -n \"${MAPLE_API_KEY:-}\" ]; then printf '[set]'; else printf '[not set]'; fi)" @echo "MAPLE_DEBUG: ${MAPLE_DEBUG:-false}" @echo "MAPLE_ENABLE_CORS: ${MAPLE_ENABLE_CORS:-false}" @echo "MAPLE_REQUEST_TIMEOUT_SECS: ${MAPLE_REQUEST_TIMEOUT_SECS:-300}" diff --git a/proxy/setup-hooks.sh b/proxy/setup-hooks.sh deleted file mode 100755 index 480a24570..000000000 --- a/proxy/setup-hooks.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# Setup git hooks for Maple Proxy - -echo "๐Ÿ”— Setting up git hooks..." - -# Get the git directory -GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) - -if [ -z "$GIT_DIR" ]; then - echo "โŒ Not in a git repository" - exit 1 -fi - -# Set git hooks path to use our custom hooks -git config core.hooksPath .githooks - -# Make sure hooks are executable -chmod +x .githooks/* - -echo "โœ… Git hooks installed successfully!" -echo "๐Ÿ“ Pre-commit hook will run:" -echo " - cargo fmt --check" -echo " - cargo clippy --locked" -echo " - cargo check --locked" -echo " - cargo test --locked" -echo "" -echo "To bypass hooks (not recommended), use: git commit --no-verify" diff --git a/scripts/ci/change_detection.py b/scripts/ci/change_detection.py index 728018476..240e95eb8 100644 --- a/scripts/ci/change_detection.py +++ b/scripts/ci/change_detection.py @@ -157,6 +157,10 @@ def classify_path(path: str) -> frozenset[str]: return frozenset({"frontend"}) if path.startswith("sdk/"): return frozenset() + if path.startswith("proxy/"): + # Proxy has its own path-scoped checks until the native app starts + # consuming the in-tree crate in the follow-up dependency switch. + return frozenset() if path in PURE_FRONTEND_FILES or path.startswith(PURE_FRONTEND_PREFIXES): return frozenset({"frontend"}) if path.startswith("frontend/src-tauri/"): diff --git a/scripts/ci/test_change_detection.py b/scripts/ci/test_change_detection.py index aa3407e24..6b6f58797 100644 --- a/scripts/ci/test_change_detection.py +++ b/scripts/ci/test_change_detection.py @@ -22,6 +22,8 @@ def test_documentation_and_independent_components_skip_maple_app_builds(self) -> self.assert_routes(["sdk/src/lib/test/models.test.ts"]) self.assert_routes(["sdk/src/lib/test/integration/web.test.ts"]) self.assert_routes(["updates/src/index.ts"]) + self.assert_routes(["proxy/src/proxy.rs"]) + self.assert_routes(["proxy/Cargo.toml", "proxy/Dockerfile"]) self.assert_routes([".githooks/pre-commit", "justfile", "zapstore.yaml"]) def test_typescript_sdk_inputs_mark_only_the_frontend_lane(self) -> None: