From 3303371afac6c5eb15e2c0229470f056c679ae53 Mon Sep 17 00:00:00 2001 From: Joseph Yaksich <294273268+gitcommit90@users.noreply.github.com> Date: Mon, 13 Jul 2026 22:03:28 +0000 Subject: [PATCH] Harden ReRouted for 0.4.2 launch --- .github/ISSUE_TEMPLATE/bug_report.yml | 91 +++++ .github/ISSUE_TEMPLATE/config.yml | 8 + .github/ISSUE_TEMPLATE/feature_request.yml | 40 ++ .github/ISSUE_TEMPLATE/question.yml | 47 +++ .github/pull_request_template.md | 18 + .github/workflows/test.yml | 27 ++ CONTRIBUTING.md | 51 +++ PRIVACY.md | 62 +++ README.md | 29 +- SECURITY.md | 32 ++ docs/architecture.md | 20 +- docs/signing.md | 8 +- package-lock.json | 6 +- package.json | 6 +- scripts/serve-site.js | 7 +- site/index.html | 27 +- site/social-card.html | 2 +- site/social-card.png | Bin 105290 -> 105655 bytes src/lib/gateway.js | 6 +- src/lib/ipc-security.js | 67 ++++ src/lib/logger.js | 86 +++- src/lib/model-test.js | 26 +- src/lib/oauth.js | 220 ++++++---- src/lib/single-instance.js | 9 + src/lib/store.js | 73 +++- src/lib/usage.js | 445 +++++++++++++++------ src/main.js | 185 +++++---- src/preload.js | 61 ++- src/renderer/app.js | 109 +++-- src/renderer/index.html | 1 + src/renderer/lock-state.js | 35 ++ tests/gateway-key-disable.test.js | 40 ++ tests/ipc-security.test.js | 82 ++++ tests/logger-redaction.test.js | 117 ++++++ tests/model-error.test.js | 35 +- tests/oauth-security.test.js | 143 +++++++ tests/quota.test.js | 42 ++ tests/renderer-lock.test.js | 36 ++ tests/single-instance.test.js | 31 ++ tests/store-recovery.test.js | 76 ++++ tests/usage.test.js | 122 ++++++ 41 files changed, 2154 insertions(+), 374 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/ISSUE_TEMPLATE/question.yml create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/test.yml create mode 100644 CONTRIBUTING.md create mode 100644 PRIVACY.md create mode 100644 SECURITY.md create mode 100644 src/lib/ipc-security.js create mode 100644 src/lib/single-instance.js create mode 100644 src/renderer/lock-state.js create mode 100644 tests/gateway-key-disable.test.js create mode 100644 tests/ipc-security.test.js create mode 100644 tests/logger-redaction.test.js create mode 100644 tests/oauth-security.test.js create mode 100644 tests/renderer-lock.test.js create mode 100644 tests/single-instance.test.js create mode 100644 tests/store-recovery.test.js create mode 100644 tests/usage.test.js diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..206fb4f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,91 @@ +name: Bug report +description: Report a reproducible ReRouted problem without exposing credentials or private data. +title: "[Bug]: " +labels: + - bug +body: + - type: markdown + attributes: + value: | + Thanks for helping improve ReRouted. + + **Do not paste API keys, gateway keys, tokens, OAuth callback URLs or codes, cookies, raw `config.json`, email addresses, account IDs, private prompts, or full unreviewed logs.** Use placeholders and include only sanitized diagnostic excerpts. + + For a suspected vulnerability, follow [SECURITY.md](https://github.com/gitcommit90/rerouted/blob/main/SECURITY.md) instead of filing a public report. + - type: input + id: version + attributes: + label: ReRouted version + placeholder: "For example: 0.4.2" + validations: + required: true + - type: input + id: macos + attributes: + label: macOS version and Mac + placeholder: "macOS 15.5, M2 MacBook Air" + validations: + required: true + - type: input + id: client + attributes: + label: Client and request mode + description: Name the editor, agent, or script and whether the request was streaming. + placeholder: "Client name/version, streaming" + validations: + required: true + - type: input + id: provider + attributes: + label: Provider, authentication method, and model + description: Use generic account labels; do not include an email address or account ID. + placeholder: "Claude, OAuth, claude-sonnet model" + validations: + required: true + - type: dropdown + id: request_target + attributes: + label: Request target + options: + - Direct provider/model + - Named fallback route + - Named round-robin route + - Onboarding or account connection + - Application UI or settings + - Other + validations: + required: true + - type: textarea + id: problem + attributes: + label: What happened? + description: Describe the expected and actual behavior, including sanitized status codes or error text. + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Minimal reproduction + description: List the smallest safe sequence that reproduces the problem. Replace credentials and private request content with placeholders. + placeholder: | + 1. Connect one provider using OAuth. + 2. Create a route with ... + 3. Send a sanitized request with ... + 4. Observe ... + validations: + required: true + - type: textarea + id: diagnostics + attributes: + label: Sanitized diagnostics + description: Optional. Paste only the few relevant lines after reviewing and redacting them. + render: shell + - type: checkboxes + id: safety + attributes: + label: Safety check + options: + - label: I tested the latest stable release and searched existing issues. + required: true + - label: I removed credentials, OAuth data, account identifiers, email addresses, private prompts, and other sensitive information. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..767b5e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Security report + url: https://github.com/gitcommit90/rerouted/blob/main/SECURITY.md + about: Read the private-reporting instructions before disclosing a vulnerability. + - name: Privacy information + url: https://github.com/gitcommit90/rerouted/blob/main/PRIVACY.md + about: Learn what ReRouted stores locally and how to remove its data. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..024c939 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,40 @@ +name: Feature request +description: Suggest a focused improvement to ReRouted. +title: "[Feature]: " +labels: + - enhancement +body: + - type: markdown + attributes: + value: | + Describe the routing or application problem first, then the change you would like. + + **Do not include credentials, tokens, OAuth callback data, account identifiers, email addresses, private prompts, or unreviewed logs.** + - type: textarea + id: problem + attributes: + label: Problem to solve + description: What workflow is difficult or unavailable today? + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed behavior + description: Explain the smallest useful outcome rather than prescribing an implementation. + validations: + required: true + - type: textarea + id: context + attributes: + label: Relevant context + description: Include sanitized provider, client, or route details only when they clarify the request. + - type: checkboxes + id: safety + attributes: + label: Safety check + options: + - label: I searched existing issues for the same request. + required: true + - label: This request contains no credentials, OAuth data, account identifiers, email addresses, private prompts, or other sensitive information. + required: true diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml new file mode 100644 index 0000000..4fdeb44 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -0,0 +1,47 @@ +name: Question or support +description: Ask a focused usage or compatibility question without exposing private data. +title: "[Question]: " +labels: + - question +body: + - type: markdown + attributes: + value: | + Include enough sanitized context to understand the workflow. + + **Do not include credentials, tokens, OAuth callback data, account identifiers, email addresses, private prompts, or unreviewed logs.** + - type: input + id: version + attributes: + label: ReRouted version + placeholder: "For example: 0.4.2" + validations: + required: true + - type: input + id: environment + attributes: + label: macOS version and client + placeholder: "macOS 15.5, client name/version" + validations: + required: true + - type: textarea + id: question + attributes: + label: Question + description: Describe what you are trying to accomplish and where you are unsure. + validations: + required: true + - type: textarea + id: context + attributes: + label: Sanitized context + description: Optional provider, authentication method, route, or model details that clarify the question. + - type: checkboxes + id: safety + attributes: + label: Safety check + options: + - label: I searched existing issues and documentation first. + required: true + - label: This question contains no credentials, OAuth data, account identifiers, email addresses, private prompts, or other sensitive information. + required: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..0580536 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ +## External contributions are not yet accepted + +ReRouted is source-visible while its licensing approach is finalized. Please read [CONTRIBUTING.md](../CONTRIBUTING.md) before opening a pull request. External code and documentation contributions may be closed without review until a license and contribution process are published. + +Never include real API keys, gateway keys, OAuth tokens or codes, callback URLs, cookies, account identifiers, email addresses, private prompts, provider responses, or unreviewed logs. + +## Maintainer change + +Describe the user-visible problem and the resulting behavior. + +## Verification + +- [ ] Focused validation passes. +- [ ] `npm test` passes. +- [ ] `git diff --check` passes. +- [ ] UI changes include current captures where applicable. +- [ ] No credentials, private user data, or generated release artifacts are included. +- [ ] Release/version work is left to the maintainer release process. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..836f194 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +name: Tests + +on: + push: + branches: + - main + pull_request: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Use Node.js 22.13 + uses: actions/setup-node@v4 + with: + node-version: 22.13.0 + cache: npm + - name: Install dependencies + run: npm ci + - name: Run tests + run: npm test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..640764b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# Contributing + +Thank you for helping improve ReRouted. + +## Current contribution status + +ReRouted is source-visible while its licensing approach is finalized. No software license has been selected, and external code contributions are not currently accepted. + +Please do not open a pull request with code or documentation changes yet. It may be closed without review. Focused issues, feature requests, compatibility reports, and sanitized reproduction cases are welcome. + +## Before opening an issue + +1. Install the latest stable release and check existing issues. +2. Confirm the behavior with the smallest route and request that reproduce it. +3. Record the ReRouted version, macOS version, provider type, authentication method, model, client, and whether the request streamed. +4. Replace account names, model identifiers, or request content when they are not essential to the report. + +Never attach `config.json` or paste full, unreviewed diagnostics. Remove API keys, gateway keys, tokens, OAuth callback URLs or codes, cookies, account IDs, email addresses, prompts, and private provider responses. + +Use the repository's bug or feature request form so maintainers receive the context needed to reproduce the report. + +## Reproduction cases + +Good reports distinguish between: + +- A response produced by the upstream provider and a response produced by ReRouted. +- A direct provider/model request and a named-route request. +- Streaming and non-streaming behavior. +- One connected account and an OAuth account pool. + +Include exact status codes and sanitized error text when available. Do not include credentials to make a reproduction executable. + +## Local verification + +These commands document the baseline used by maintainers and may help when investigating an issue locally: + +```bash +npm ci +npm test +git diff --check +``` + +Node.js 22.13 or newer is required. The packaged application currently targets Apple Silicon and macOS 12 Monterey or newer. + +Maintainers handle package version changes, signing, notarization, release publication, and installation verification. Reproduction branches should not include generated release artifacts or real provider credentials. + +## Security reports + +Do not use a public issue for a vulnerability or credential exposure. Follow [Security](./SECURITY.md) instead. + +This policy will be updated when a project license and an external pull-request process are in place. diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..a2c2a5a --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,62 @@ +# Privacy + +ReRouted is a local macOS application. It has no ReRouted account, hosted control plane, or third-party product analytics service. + +This document describes the application as shipped. The upstream providers and clients you connect have their own privacy policies and data practices. + +## Website + +The public `rerouted.dev` website is a static site delivered through Cloudflare and loads its display fonts from Google Fonts. Those services may receive ordinary web-request metadata such as your IP address, browser headers, and requested asset URLs under their own privacy policies. The website does not include ReRouted product analytics or an account system. + +## Data stored on your Mac + +ReRouted stores application data in its macOS Application Support directory, normally `~/Library/Application Support/ReRouted`. Existing installations may use `~/Library/Application Support/rerouted`. + +Local data includes: + +- Provider settings, OAuth credentials, API keys, gateway keys, routes, and application preferences in `config.json`. +- Request metadata, provider and route selections, statuses, timestamps, and token counts in the uncapped `usage.sqlite` database. +- Gateway, OAuth, update, and routing diagnostics in `rerouted.log`. + +Prompt bodies are not intentionally persisted. Provider credentials and gateway keys are not encrypted at rest. ReRouted restricts local file permissions where macOS supports doing so, but anyone who can access your macOS user account or its files may be able to read them. + +Diagnostics can contain provider error text, model and route names, account identifiers, and OAuth metadata. Treat logs as sensitive and review every line before sharing an excerpt. + +## Local credential discovery + +Credential discovery happens when you choose to scan or import accounts. Depending on the providers installed on the Mac, ReRouted may inspect supported entries in the Codex configuration, the Claude Code macOS Keychain or supported local auth files, ReRouted auth-profile folders, and Antigravity-named JSON files in supported folders including `~/Downloads`. ReRouted summarizes discoveries before import; selected credentials are copied into its own configuration. + +At startup, ReRouted may also read the local `~/.grok/auth.json` file to attach a human-readable identity to an xAI account that is already connected. This startup lookup only updates local account labeling; it does not import a new account by itself. + +## Network activity + +ReRouted makes network requests only as needed to operate features you choose: + +- Completion requests, credentials, and supported image inputs are sent to the selected upstream provider. +- OAuth authorization and token refresh requests are sent to the relevant provider. +- Quota checks are sent to supported providers when you open the Quota page, every 60 seconds while that page remains open, or when you manually refresh it. +- Automatic update checks contact `update.electronjs.org` shortly after launch and about every six hours; signed update downloads come from GitHub Releases. + +The gateway binds to `127.0.0.1` by default. If you enable network access, it binds to `0.0.0.0`; devices that can reach the Mac can attempt to access it, and the gateway bearer key becomes the primary access boundary. + +## OAuth and subscription notice + +ReRouted is independent and is not affiliated with or endorsed by any upstream provider. This provider's subscription or OAuth session is not officially licensed for proxy or router use. Using it this way may result in account restrictions or bans. Proceed at your own risk, review the provider's current terms, and prefer a documented API-key integration when you need a stable production path. + +## Retention and deletion + +Usage history is not automatically pruned and remains on the Mac until the Application Support data is removed. Existing installations may also retain the former `usage.json` as a one-time migration backup. Logs can be cleared from the Activity diagnostics view. + +Uninstalling the application bundle does not automatically remove Application Support data. To remove ReRouted and its stored credentials completely: + +1. Quit ReRouted. +2. Delete `/Applications/ReRouted.app`. +3. Delete the ReRouted Application Support directory listed above. + +Deleting the Application Support directory permanently removes connected accounts, keys, routes, settings, usage history, and logs. Back up only the data you intentionally want to retain. + +## Sharing diagnostics + +Never post `config.json` or an unreviewed log file. Remove API keys, gateway keys, tokens, OAuth codes and callback URLs, cookies, email addresses, account identifiers, private provider responses, and prompt content before sharing a reproduction or log excerpt. + +See [Security](./SECURITY.md) for reporting a suspected vulnerability and [Contributing](./CONTRIBUTING.md) for safe bug-report guidance. diff --git a/README.md b/README.md index 484cf92..baa4bac 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,9 @@ Website | Download | Quick start | - Architecture + Architecture | + Security | + Privacy
@@ -91,11 +93,15 @@ OAuth accounts and keyed providers can live in the same route. ReRouted handles
ReRouted is an independent project and is not affiliated with or endorsed by any upstream provider.
+> **OAuth notice:** This provider's subscription or OAuth session is not officially licensed for proxy or router use. Using it this way may result in account restrictions or bans. Proceed at your own risk. Provider behavior and policies can change without notice; API-key integrations are the more stable choice where available.
+
## Quick start
### 1. Install
-[Download ReRouted 0.4.1 for Apple Silicon](https://github.com/gitcommit90/rerouted/releases/download/v0.4.1/ReRouted-0.4.1-arm64.dmg), open the DMG, and drag ReRouted to Applications.
+[Download ReRouted 0.4.2 for Apple Silicon](https://github.com/gitcommit90/rerouted/releases/download/v0.4.2/ReRouted-0.4.2-arm64.dmg), open the DMG, and drag ReRouted to Applications.
+
+ReRouted requires Apple Silicon and macOS 12 Monterey or newer.
The macOS release is Developer ID signed, notarized by Apple, and stapled for a normal Gatekeeper launch.
@@ -155,15 +161,26 @@ Requests require a generated bearer key except for `/` and `/health`. OpenAI-sty
- The gateway binds to `127.0.0.1` by default.
- Configuration, credentials, request metadata, usage, and logs are stored locally.
+- Usage history is stored in an uncapped local SQLite database so all-time statistics do not silently discard older requests.
- Prompt bodies are not intentionally persisted.
- Local config and usage files are written with restrictive permissions where supported.
- Provider credentials are not encrypted at rest.
- Requests and the credentials needed to authorize them are sent to the upstream services you choose.
- Enabling network access binds the gateway to `0.0.0.0`; only do that on a network you trust.
+See [Privacy](./PRIVACY.md) for the local files ReRouted keeps, the network services it contacts, and how to remove its data.
+
+## Support, security, and project status
+
+- For questions, reproducible bugs, and feature requests, use [GitHub Issues](https://github.com/gitcommit90/rerouted/issues).
+- For a suspected vulnerability, follow [the security policy](./SECURITY.md) and do not post credentials or sensitive details in a public issue.
+- Before sharing diagnostics, remove API keys, gateway keys, OAuth callback URLs or codes, account identifiers, email addresses, and any provider response that may contain private data.
+
+ReRouted's source is public for review and local builds, but no software license has been selected yet. The repository is therefore source-visible, not offered as open source. External code contributions are not currently accepted while licensing is finalized; focused issues and sanitized reproduction reports are welcome. See [Contributing](./CONTRIBUTING.md) for the current policy.
+
## Build from source
-Requires Node.js 22.12 or newer. Packaging requires macOS and produces an Apple Silicon DMG.
+Requires Node.js 22.13 or newer. Packaging requires macOS and produces an Apple Silicon DMG.
```bash
git clone https://github.com/gitcommit90/rerouted.git
@@ -181,13 +198,11 @@ npm run package:dmg
The implementation is intentionally small: Electron, Node's built-in HTTP server, and a vanilla HTML/CSS/JavaScript renderer. See [the architecture document](./docs/architecture.md) for the runtime, routing, persistence, and packaging details.
-Questions and bug reports are welcome in [GitHub Issues](https://github.com/gitcommit90/rerouted/issues).
-
## Current release
-ReRouted `0.4.1` ships for Apple Silicon macOS with masked OAuth account identities, image inputs in chat completions, a Developer ID signature, stapled Apple notarization tickets, and in-app updates backed by stable GitHub Releases. The public API is intentionally limited to health, model discovery, and chat completions; a published third-party client compatibility matrix is still forthcoming.
+ReRouted `0.4.2` ships for Apple Silicon macOS with hardened OAuth callbacks and renderer lock boundaries, uncapped SQLite usage history, 60-second quota refreshes while the Quota page is open, masked account identities, and image inputs in chat completions. Public builds are Developer ID signed, notarized, stapled, and distributed through stable GitHub Releases with in-app updates. The public API is intentionally limited to health, model discovery, and chat completions; a published third-party client compatibility matrix is still forthcoming.
-ReRouted is released by [Public Bytes](https://publicbytes.org), a nonprofit building practical technology for public good.
+ReRouted is an independent personal project.
## Thanks
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..b00ce9c
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,32 @@
+# Security Policy
+
+ReRouted handles OAuth sessions, API keys, gateway keys, and local routing data. Treat security reports and diagnostic material accordingly.
+
+## Supported versions
+
+Security fixes are provided for the latest stable release. Update through **Settings -> Software updates** or install the newest signed release before reporting a problem that may already be fixed.
+
+## Reporting a vulnerability
+
+Do not disclose a vulnerability, credential, or sensitive reproduction in a public issue.
+
+Use GitHub's private [Report a vulnerability](https://github.com/gitcommit90/rerouted/security/advisories/new) form. Private vulnerability reporting is enabled for this repository. Do not open a public placeholder issue or disclose report details in Discussions, pull requests, or logs.
+
+Include only the information needed to investigate:
+
+- ReRouted version and macOS version.
+- The affected feature and realistic impact.
+- Reproduction steps that use placeholder credentials and sanitized data.
+- Whether the gateway was bound only to localhost or exposed to a network.
+
+Never include API keys, gateway keys, access or refresh tokens, OAuth authorization codes, callback URLs, cookies, raw `config.json`, full unreviewed logs, account identifiers, or private prompts.
+
+## Appropriate reports
+
+Examples include authentication bypasses, credential disclosure, unsafe network exposure, cross-account data leakage, update verification failures, or a way for untrusted local content to execute code in the application.
+
+Provider outages, expired subscriptions, quota behavior, unsupported models, and ordinary routing failures belong in a sanitized bug report unless they expose a security boundary.
+
+## Response expectations
+
+ReRouted is an independently maintained personal project. Reports are handled on a best-effort basis. Please allow time to confirm the issue and coordinate a fix before public disclosure.
diff --git a/docs/architecture.md b/docs/architecture.md
index d5ae859..92e921d 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -13,7 +13,7 @@ ReRouted is one Electron process with three jobs:
There is no separate daemon. Closing or hiding the panel does not stop the gateway; quitting ReRouted does.
```text
-OpenAI-compatible client
+OpenAI-style chat-completions client
|
| Bearer rr-... + /v1/chat/completions
v
@@ -29,14 +29,14 @@ src/lib/providers/* ---> upstream provider API
|
| normalize response/SSE
v
-OpenAI-compatible response
+OpenAI-style chat-completions response
```
## Main process and panel
`src/main.js` owns the single-instance lock, tray, frameless panel window, login-item preference, IPC handlers, store, usage store, router, and gateway lifecycle.
-The panel is a local file loaded from `src/renderer/index.html`. `src/preload.js` exposes an IPC bridge to `src/renderer/app.js`; context isolation is enabled and renderer Node integration is disabled. The BrowserWindow sandbox is currently disabled.
+The panel is a local file loaded from `src/renderer/index.html`. `src/preload.js` exposes an allowlisted IPC bridge to `src/renderer/app.js`; context isolation and the renderer sandbox are enabled, and renderer Node integration is disabled.
The renderer is vanilla HTML, CSS, and JavaScript. It renders onboarding and the Status, Accounts, Routes, Activity, and Settings pages from state returned by the main process.
@@ -53,6 +53,8 @@ The renderer is vanilla HTML, CSS, and JavaScript. It renders onboarding and the
The default bind is `127.0.0.1:4949`. Settings can switch the host to `0.0.0.0` for LAN or Tailscale access. CORS is currently `*`, so the bearer key is the gateway's access boundary when network binding is enabled.
+JSON request bodies are limited to 32 MiB. Oversized requests receive a JSON `413` response before routing begins.
+
## Model IDs and routes
Provider model IDs are generated by `src/lib/providers/index.js`. A direct model resolves to one enabled provider/model pair.
@@ -102,12 +104,14 @@ Electron's `userData` directory contains:
| File | Contents |
| --- | --- |
| `config.json` | Providers, credentials, models, routes, gateway keys, bind settings, onboarding state, admin password hash |
-| `usage.json` | Up to 20,000 recent request metadata rows and token counts |
+| `usage.sqlite` | Uncapped local request metadata and token counts, indexed by timestamp for period and all-time statistics |
| `rerouted.log` | Gateway, OAuth, and operational diagnostics |
The Quota page probes subscription windows directly for ChatGPT/Codex, Claude, and Antigravity. Probe failures remain isolated per account and do not disable chat routing.
-Config and usage writes use a temporary file followed by rename. The files are written with mode `0600`; parent directories are created with mode `0700` where supported.
+Config writes use a temporary file followed by rename. Usage inserts use SQLite WAL mode with prepared statements. The primary files are written with mode `0600`; parent directories are created with mode `0700` where supported.
+
+On the first `0.4.2` launch, every row still present in the legacy `usage.json` file is imported transactionally into `usage.sqlite`. The legacy file remains as a migration backup, and a database marker prevents duplicate imports. New history is not automatically pruned.
The admin password is scrypt-hashed. Provider credentials and gateway keys are not encrypted at rest.
@@ -136,13 +140,13 @@ Packaged builds use Electron's native macOS updater and the public stable GitHub
## Tests and current gaps
-`tests/gateway.test.js` covers password hashing, config persistence, bearer auth, model listing, streaming and non-streaming completion paths, fallback, round-robin ordering, timeouts, OAuth request behavior, token refresh, format translation, SSE decoding, multiple gateway keys, disabled models, and usage aggregation.
+`tests/gateway.test.js` covers password hashing, config persistence, bearer auth, request-size enforcement, model listing, streaming and non-streaming completion paths, fallback, round-robin ordering, timeouts, OAuth request behavior, token refresh, format translation, SSE decoding, multiple gateway keys, disabled models, and usage aggregation.
Important gaps to keep visible:
- No automated renderer or end-to-end Electron tests.
-- No macOS packaging test in CI.
-- No request-body size limit or gateway rate limit.
+- The Node test suite runs in GitHub Actions, but there is no macOS packaging test in CI.
+- No gateway request-rate limit.
- No automated release publication or CI-hosted signing/notarization.
- No commit SHA embedded in the app bundle.
- No compatibility matrix for third-party OpenAI clients.
diff --git a/docs/signing.md b/docs/signing.md
index c97e0b7..184d380 100644
--- a/docs/signing.md
+++ b/docs/signing.md
@@ -64,6 +64,8 @@ That command uses Developer ID signing when an identity is available. Without on
The release script verifies each stage. These commands provide an independent check:
```bash
+VERSION="$(node -p 'require("./package.json").version')"
+
codesign --verify --deep --strict --verbose=2 \
dist/ReRouted-darwin-arm64/ReRouted.app
@@ -71,12 +73,12 @@ xcrun stapler validate dist/ReRouted-darwin-arm64/ReRouted.app
spctl --assess --type execute --verbose=4 \
dist/ReRouted-darwin-arm64/ReRouted.app
-xcrun stapler validate dist/ReRouted-0.3.1-arm64.dmg
+xcrun stapler validate "dist/ReRouted-${VERSION}-arm64.dmg"
spctl --assess --type open --context context:primary-signature --verbose=4 \
- dist/ReRouted-0.3.1-arm64.dmg
+ "dist/ReRouted-${VERSION}-arm64.dmg"
UPDATE_DIR="$(mktemp -d)"
-ditto -x -k dist/ReRouted-0.3.1-mac-arm64.zip "$UPDATE_DIR"
+ditto -x -k "dist/ReRouted-${VERSION}-mac-arm64.zip" "$UPDATE_DIR"
codesign --verify --deep --strict --verbose=2 "$UPDATE_DIR/ReRouted.app"
xcrun stapler validate "$UPDATE_DIR/ReRouted.app"
spctl --assess --type execute --verbose=4 "$UPDATE_DIR/ReRouted.app"
diff --git a/package-lock.json b/package-lock.json
index ed379e9..6b83c59 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,19 +1,19 @@
{
"name": "rerouted",
- "version": "0.4.1",
+ "version": "0.4.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rerouted",
- "version": "0.4.1",
+ "version": "0.4.2",
"devDependencies": {
"@electron/osx-sign": "1.3.3",
"@electron/packager": "18.4.4",
"electron": "43.1.0"
},
"engines": {
- "node": ">=22.12.0"
+ "node": ">=22.13.0"
}
},
"node_modules/@electron-internal/extract-zip": {
diff --git a/package.json b/package.json
index 546db19..85bb5ac 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
{
"name": "rerouted",
- "version": "0.4.1",
+ "version": "0.4.2",
"description": "A macOS menu-bar router for accounts, models, and automatic fallback.",
- "author": "Public Bytes",
+ "author": "gitcommit90",
"homepage": "https://rerouted.dev",
"repository": {
"type": "git",
@@ -31,6 +31,6 @@
"electron": "43.1.0"
},
"engines": {
- "node": ">=22.12.0"
+ "node": ">=22.13.0"
}
}
diff --git a/scripts/serve-site.js b/scripts/serve-site.js
index 2c89e8b..1d60edc 100644
--- a/scripts/serve-site.js
+++ b/scripts/serve-site.js
@@ -6,7 +6,7 @@ const http = require("node:http");
const fs = require("node:fs");
const path = require("node:path");
-const ROOT = path.resolve(__dirname, "..", "site");
+const ROOT = path.resolve(process.env.SITE_ROOT || path.join(__dirname, "..", "site"));
const PORT = Number(process.env.PORT || 8099);
const HOST = process.env.HOST || "127.0.0.1";
@@ -40,8 +40,9 @@ const server = http.createServer((req, res) => {
const urlPath = decodeURIComponent((req.url || "/").split("?")[0]);
let rel = urlPath === "/" ? "/index.html" : urlPath;
// resolve inside ROOT only — block traversal
- const abs = path.normalize(path.join(ROOT, rel));
- if (!abs.startsWith(ROOT)) {
+ const abs = path.resolve(ROOT, `.${rel}`);
+ const relative = path.relative(ROOT, abs);
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
res.writeHead(403).end("forbidden");
return;
}
diff --git a/site/index.html b/site/index.html
index 27ec496..cef9184 100644
--- a/site/index.html
+++ b/site/index.html
@@ -16,7 +16,7 @@
-
+
@@ -24,7 +24,7 @@
-
+
@@ -690,7 +690,7 @@
.foot-inner { display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
.foot-copy { font-size: 13.5px; color: var(--muted); }
.foot-copy b { color: var(--ink-soft); font-weight: 600; }
- .foot-links { display: flex; gap: 24px; font-size: 13.5px; color: var(--muted); }
+ .foot-links { display: flex; gap: 24px; flex-wrap: wrap; font-size: 13.5px; color: var(--muted); }
.foot-links a:hover { color: var(--route-dark); }
.foot-mono { font-family: var(--mono); font-size: 12px; color: var(--faint); }
@@ -741,7 +741,7 @@
Local gateway · macOS menu bar
- Point any configurable chat-completions client at one local URL. + Point a compatible configurable chat-completions client at one local URL. When a provider rate-limits, times out, or fails before output begins, ReRouted moves to the next route member — your endpoint and route stay the same.
@@ -907,7 +907,10 @@- …plus any custom OpenAI-compatible service — just a base URL and a key. + …plus custom upstreams that expose the supported OpenAI-style chat-completions shape. +
++ Notice: This provider's subscription or OAuth session is not officially licensed for proxy or router use. Using it this way may result in account restrictions or bans. Proceed at your own risk. ReRouted is independent and is not affiliated with or endorsed by upstream providers.
@@ -966,15 +969,15 @@