docs(workflow-executor): document FOREST_EXECUTOR_ENCRYPTION_KEY#1725
Conversation
19a12d3 to
e3db719
Compare
|
Coverage Impact This PR will not change total coverage. 🚦 See full report on Qlty Cloud »🛟 Help
|
Scra3
left a comment
There was a problem hiding this comment.
Docs review (PRD-367 PR4). The section is accurate against the implementation; one small suggestion.
|
|
||
| | Variable | Description | | ||
| | --- | --- | | ||
| | `FOREST_EXECUTOR_ENCRYPTION_KEY` | At-rest encryption key (AES-256-GCM) for the OAuth credentials the executor stores. Generate with `openssl rand -hex 32`. Use a **separate** secret from `FOREST_AUTH_SECRET`. | |
There was a problem hiding this comment.
suggestion: unlike FOREST_ENV_SECRET, the key's length/format is not validated by the code — worth noting operators must use the exact openssl rand -hex 32 output, since a weak value is accepted silently.
There was a problem hiding this comment.
Good catch — confirmed against the code: deriveKey() only throws on an empty value and otherwise feeds the secret straight into hkdfSync, so length/format aren't validated and a weak value is accepted silently.
Added a note to the entry: "The value isn't validated, so use a genuinely random secret."
I framed it around entropy rather than the exact openssl rand -hex 32 output, since the code accepts any non-empty secret (HKDF), not only hex — so the real requirement is a high-entropy value. Thanks for the review!
0783047 to
d3dbde6
Compare
| return links; | ||
| } | ||
|
|
||
| export function isMcpAuthError(error: unknown): boolean { |
There was a problem hiding this comment.
🟡 Medium src/mcp-auth-error.ts:42
isMcpAuthError returns true for an error whose message matches /unauthorized/i even when a later link in the cause chain carries an explicit 403 status. For example, new Error('Unauthorized scope', { cause: Object.assign(new Error('forbidden'), { status: 403 }) }) is classified as refreshable auth, because .some() returns at the outer message match before the inner 403 is inspected. The comment states an explicit status is authoritative, but the implementation lets a message match short-circuit before any later status is checked. Consider scanning the full chain for an explicit non-refreshable status before falling back to message matching.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/ai-proxy/src/mcp-auth-error.ts around line 42:
`isMcpAuthError` returns `true` for an error whose message matches `/unauthorized/i` even when a later link in the cause chain carries an explicit `403` status. For example, `new Error('Unauthorized scope', { cause: Object.assign(new Error('forbidden'), { status: 403 }) })` is classified as refreshable auth, because `.some()` returns at the outer message match before the inner `403` is inspected. The comment states an explicit status is authoritative, but the implementation lets a message match short-circuit before any later status is checked. Consider scanning the full chain for an explicit non-refreshable status before falling back to message matching.
There was a problem hiding this comment.
🟡 Medium
createWorkflowExecutor.start() calls runner.start() before server.start(), so the polling loop can invoke DatabaseMcpOAuthCredentialsStore.get() — which issues SELECT against ai_mcp_oauth_credentials — before ExecutorHttpServer.start() runs mcpOAuthCredentialsStore.init() to create the table. On startup with a pending OAuth-backed step, the first poll cycle fails with a missing-table error instead of executing the run. Consider awaiting the store migration before runner.start(), or calling init() explicitly in buildDatabaseExecutor prior to starting the runner.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/workflow-executor/src/build-workflow-executor.ts around line 198:
`createWorkflowExecutor.start()` calls `runner.start()` before `server.start()`, so the polling loop can invoke `DatabaseMcpOAuthCredentialsStore.get()` — which issues `SELECT` against `ai_mcp_oauth_credentials` — before `ExecutorHttpServer.start()` runs `mcpOAuthCredentialsStore.init()` to create the table. On startup with a pending OAuth-backed step, the first poll cycle fails with a missing-table error instead of executing the run. Consider awaiting the store migration before `runner.start()`, or calling `init()` explicitly in `buildDatabaseExecutor` prior to starting the runner.
4305503 to
1dc94e2
Compare
Document the FOREST_EXECUTOR_ENCRYPTION_KEY environment variable in the workflow-executor README: what it encrypts, how to generate it, using the same value across instances sharing a database, its lazy behaviour, and that it has no managed rotation. Complements the .env.example entry. Refs: PRD-626 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1dc94e2 to
1087b4a
Compare

What
Documents
FOREST_EXECUTOR_ENCRYPTION_KEYin the workflow-executor README — a new "OAuth-protected MCP connectors" section covering:openssl rand -hex 32) and to keep it separate fromFOREST_AUTH_SECRET503and those connectors stay unavailable; the executor still boots, with a startup warning)It complements the one-line entry already in
.env.example.Why
PRD-626 (the docs half of PRD-367): operators deploying the executor with OAuth-protected MCP connectors need a README reference for this variable.
Base / merge order
Based on the PRD-692 branch (
feature/prd-692-harden-executor-oauth-runtime-clear-idempotency-phase-on, #1724) — the top of the OAuth-MCP executor stack, whereFOREST_EXECUTOR_ENCRYPTION_KEYand the code that reads it already live. Targeting that branch keeps this diff to just the README change. Intended to merge last, once PR1 (#1619), PR2 (#1665) and PRD-692 (#1724) are in.Replaces docs-site PR ForestAdmin/docs#3 (now closed) — the executor's own README, next to the code and
.env.example, is the right home for an operator-facing deployment variable (and "executor" isn't a concept on the public docs site).Refs: PRD-626
🤖 Generated with Claude Code
Note
Document
FOREST_EXECUTOR_ENCRYPTION_KEYin workflow-executor READMEAdds an 'OAuth-protected MCP connectors' section to README.md explaining how the executor encrypts per-user OAuth credentials at rest using
FOREST_EXECUTOR_ENCRYPTION_KEY. Covers key generation, the requirement to use a separate secret fromFOREST_AUTH_SECRET, that the key is only needed when OAuth-protected connectors are in use, and that all instances sharing a database must use the same key.Macroscope summarized 1087b4a.