Skip to content
Closed
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
14 changes: 12 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
OPENAI_API_KEY=sk-YOUR-API-KEY-HERE
# Instance auth (optional): set INSTANCE_AUTH=true to gate /services/* on the
# api_key callers send, looked up in lightning_clients via POSTGRES_URL. See
# services/_instance_auth/README.md.
# INSTANCE_AUTH=true

# Optional at-rest encryption for stored client Anthropic keys. Base64 of 32 bytes
# (openssl rand -base64 32). See services/_instance_auth/README.md.
# APOLLO_ENC_KEY=

ANTHROPIC_API_KEY=sk-YOUR-API-KEY-HERE

OPENAI_API_KEY=sk-YOUR-API-KEY-HERE
PINECONE_KEY=YOUR-API-KEY-HERE
POSTGRES_URL=POSTGRES_URL=postgresql://localhost:5432/apollo_dev
POSTGRES_URL=postgresql://localhost:5432/apollo_dev
SENTRY_DSN=YOUR-API-KEY-HERE
GITHUB_TOKEN=KEY

Expand Down
26 changes: 26 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,32 @@ TypeScript) service modules.
- **Service discovery**: `platform/src/util/describe-modules.ts` - Auto-mounts
any `services/<name>/` directory not starting with `_`. Detects service type
by checking for `<name>.py` (Python) or `<name>.ts` (TypeScript) index file.
- **Instance auth** (`platform/src/middleware/auth.ts`): `/services/*` is gated
when the `INSTANCE_AUTH` env var is set (opt-in; otherwise open). The
credential is the `api_key` the caller (Lightning) already sends in the request
body — there is no bearer token and no Lightning-side change. Its SHA-256 is
looked up in the `lightning_clients` table via `POSTGRES_URL`; a missing or
unknown key is rejected with `401`. The inbound `api_key` is treated purely as
a credential and is **never** forwarded to the LLM: on a match it is replaced
with the matched client's stored `anthropic_api_key`, or stripped (falling back
to the global `ANTHROPIC_API_KEY`) when that column is `NULL`. The stored
`anthropic_api_key` may be plaintext or AES-256-GCM-encrypted (`enc:v1:`
values, decrypted with `APOLLO_ENC_KEY`; see
`platform/src/util/instance-key-crypto.ts` and
`services/_instance_auth/encrypt_key.ts`). Lookups are
cached in memory (~60s TTL), so the DB is hit at most once per minute per
process, not per request. The refresh is single-flight with
stale-while-revalidate, so a burst of requests at the TTL boundary shares one
DB read (cold start awaits it; a warm-but-stale cache is served while one
background refresh runs) rather than stampeding the DB. If auth is enabled but
the table can't be reached, the gate fails closed (rejects all external
callers). The gate is scoped to
`/services/*`, so health/root endpoints outside that group are unaffected.
Internal Apollo-to-Apollo `apollo()` calls are exempt via a per-process
internal token (`APOLLO_INTERNAL_TOKEN`, minted at startup, injected into the
env and echoed back by `services/util.py`); this replaces the old loopback
exemption so a co-located Lightning is still required to authenticate.
Provisioning lives in `services/_instance_auth/`.

### Services Architecture

Expand Down
48 changes: 46 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,56 @@ in your json, keep it inside a tmp dir and it'll remain safe and secret.

The Apollo server uses bunjs with the Elysia framework.

It is a very lightweight server, with at the time of writing no authentication
or governance included.
It is a very lightweight server. By default it includes no authentication, but
instance auth can be enabled (see below).

Python services are hosted at `/services/<name>`. Each service expects a POST
request with a JSON body, and will return JSON.

## Database

First, make sure you've configured your desired `POSTGRES_URL` in your `.env`
file.

### Create the DB

Create a Postgres DB matching your POSTGRES_URL from the `.env` file

### To reset the DB

`set -a; . ./.env; set +a; psql "$POSTGRES_URL" -c "DROP TABLE IF EXISTS lightning_clients, adaptor_function_docs CASCADE;"`

### Run the "migrations" (apply both schemas):

`set -a; . ./.env; set +a; psql "$POSTGRES_URL" -f services/_instance_auth/schema.sql && psql "$POSTGRES_URL" -f services/load_adaptor_docs/schema.sql`

### Instance authentication (optional)

`/services/*` can be gated so that only known clients (e.g. specific Lightning
instances) may call it, with Apollo using **each client's own Anthropic API
key** for that client's requests.

- It is **opt-in and backward compatible**: auth is active only when the
`INSTANCE_AUTH` environment variable is set (e.g. `INSTANCE_AUTH=true`).
Otherwise the server stays fully open as before. Tokens are looked up in the
`lightning_clients` table via `POSTGRES_URL`; if auth is enabled but that
table can't be reached, the gate **fails closed** (rejects all external
callers) rather than silently opening up.
- The credential is the **`api_key` the caller already sends in the request
body** — there is no bearer token, no `Authorization` header, and no
Lightning-side change. Apollo stores only a SHA-256 hash of it; an
unknown/missing key gets `401 { "code": 401, "type": "UNAUTHORIZED" }`.
- On a match, the inbound `api_key` is treated purely as a credential and is
**never** forwarded to the LLM: it is replaced with the client's stored
Anthropic key (so LLM usage bills to that client), or stripped — falling back
to the global `ANTHROPIC_API_KEY` — if the client has no stored key.
- Health/root endpoints (`/livez`, `/status`, `/`) are outside `/services/*` and
never gated. Internal Apollo-to-Apollo `apollo()` calls are exempt via a
per-process internal token (`APOLLO_INTERNAL_TOKEN`), not by network position.

To enable it and provision clients, see
[`services/_instance_auth/`](services/_instance_auth/README.md).

There is very little standard for formality in the JSON structures to date. The
server may soon establish some conventions for better interopability with the
CLI.
Expand Down
Loading