mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-09 03:20:56 +00:00
parent
42e340af42
commit
902befac2b
7 changed files with 1382 additions and 8 deletions
477
run.json
477
run.json
File diff suppressed because one or more lines are too long
1
stages/004-preflight_lint@1/output.log
Normal file
1
stages/004-preflight_lint@1/output.log
Normal file
|
|
@ -0,0 +1 @@
|
|||
blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126
|
||||
8
stages/004-preflight_lint@1/script_timing.json
Normal file
8
stages/004-preflight_lint@1/script_timing.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126",
|
||||
"exit_code": 0,
|
||||
"duration_ms": 79088,
|
||||
"termination": "exited",
|
||||
"output_bytes": 0,
|
||||
"live_streaming": false
|
||||
}
|
||||
6
stages/004-preflight_lint@1/status.json
Normal file
6
stages/004-preflight_lint@1/status.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"outcome": "succeeded",
|
||||
"notes": "Script completed: cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1",
|
||||
"failure_reason": null,
|
||||
"timestamp": "2026-05-25T16:17:18.050610Z"
|
||||
}
|
||||
846
stages/005-implement@1/prompt.md
Normal file
846
stages/005-implement@1/prompt.md
Normal file
|
|
@ -0,0 +1,846 @@
|
|||
Goal: # Secrets Rationalization Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Make Fabro server secret resolution simple and predictable: bootstrap secrets come only from process env or `server.env`; optional integration secrets come only from the vault.
|
||||
|
||||
**Architecture:** Introduce a small shared registry that classifies well-known secret names as bootstrap or optional. Keep `ServerSecrets` focused on bootstrap startup/runtime requirements, add vault-only helpers for optional integrations, and wire install mode so it can write optional secrets before enabling features such as GitHub auth.
|
||||
|
||||
**Tech Stack:** Rust workspace crates (`fabro-static`, `fabro-auth`, `fabro-agent`, `fabro-server`, `fabro-cli`), existing vault storage, existing install persistence, existing docs under `docs/internal` and `docs/public`.
|
||||
|
||||
---
|
||||
|
||||
## Decisions Locked In
|
||||
|
||||
- `server.env` is only for bootstrap/runtime secrets that the server may need before the vault can be used.
|
||||
- Process env is also bootstrap-only for server runtime. Optional server integrations do not read process env.
|
||||
- Vault is the only server/runtime source for optional integration secrets.
|
||||
- Optional secrets may be workflow-visible for now.
|
||||
- No compatibility shims or fallback aliases are required. Remove server-runtime `GH_TOKEN` fallback and optional `server.env` fallbacks.
|
||||
- Non-secret configuration, such as base URLs, org IDs, or catalog settings, remains configuration. This plan only governs secrets.
|
||||
- CLI/library code may still use env-backed credential sources where explicitly selected outside server runtime. The server runtime must not use that path for optional integrations.
|
||||
|
||||
## Secret Model
|
||||
|
||||
Bootstrap secrets:
|
||||
|
||||
- `SESSION_SECRET`
|
||||
- `FABRO_DEV_TOKEN`
|
||||
- object-store/storage credentials resolved by current server object-store builders, including manual AWS credentials such as `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN`
|
||||
|
||||
Optional vault secrets:
|
||||
|
||||
- LLM provider API keys and OAuth credential records
|
||||
- `FABRO_SLACK_BOT_TOKEN`
|
||||
- `FABRO_SLACK_APP_TOKEN`
|
||||
- `DAYTONA_API_KEY`
|
||||
- `BRAVE_SEARCH_API_KEY`
|
||||
- `GITHUB_TOKEN`
|
||||
- `GITHUB_APP_PRIVATE_KEY`
|
||||
- `GITHUB_APP_CLIENT_SECRET`
|
||||
- `GITHUB_APP_WEBHOOK_SECRET`
|
||||
|
||||
## Change Map
|
||||
|
||||
- Create `lib/crates/fabro-static/src/secret_registry.rs` for shared classification.
|
||||
- Modify `lib/crates/fabro-static/src/lib.rs` to export the registry.
|
||||
- Modify `lib/crates/fabro-server/src/server_secrets.rs` to make the bootstrap boundary explicit in tests and naming.
|
||||
- Modify `lib/crates/fabro-server/src/server.rs`, `diagnostics.rs`, `run_files.rs`, `run_manifest.rs`, `web_auth.rs`, `github_webhooks.rs`, and server handlers under `server/handler/` to use vault-only optional lookup.
|
||||
- Modify `lib/crates/fabro-server/src/startup.rs`, `serve.rs`, and `jwt_auth.rs` so GitHub auth validates `GITHUB_APP_CLIENT_SECRET` from vault while session/dev-token auth still validates bootstrap secrets.
|
||||
- Modify `lib/crates/fabro-auth/src/vault_source.rs` to expose an explicit vault-only credential source constructor for server runtime.
|
||||
- Modify `lib/crates/fabro-agent/src/config.rs` and `tools.rs` so `web_search` receives `BRAVE_SEARCH_API_KEY` explicitly instead of reading process env inside the tool.
|
||||
- Modify install flows in `lib/crates/fabro-server/src/install.rs` and `lib/crates/fabro-cli/src/commands/install.rs` so GitHub App secrets are written to vault, not `server.env`.
|
||||
- Modify `lib/crates/fabro-server/src/server/handler/secrets.rs` so bootstrap secrets cannot be written through the vault API.
|
||||
- Update public and internal docs after behavior is implemented.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Add The Shared Secret Registry
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `lib/crates/fabro-static/src/secret_registry.rs`
|
||||
- Modify: `lib/crates/fabro-static/src/lib.rs`
|
||||
- Test: unit tests in `secret_registry.rs`
|
||||
|
||||
- [ ] **Step 1: Define the registry types and classified names**
|
||||
|
||||
Create `secret_registry.rs` with this shape:
|
||||
|
||||
```rust
|
||||
use crate::EnvVars;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum SecretScope {
|
||||
Bootstrap,
|
||||
OptionalVault,
|
||||
}
|
||||
|
||||
const BOOTSTRAP_SECRETS: &[&str] = &[
|
||||
EnvVars::SESSION_SECRET,
|
||||
EnvVars::FABRO_DEV_TOKEN,
|
||||
EnvVars::AWS_ACCESS_KEY_ID,
|
||||
EnvVars::AWS_SECRET_ACCESS_KEY,
|
||||
EnvVars::AWS_SESSION_TOKEN,
|
||||
];
|
||||
|
||||
const OPTIONAL_VAULT_SECRETS: &[&str] = &[
|
||||
EnvVars::ANTHROPIC_API_KEY,
|
||||
EnvVars::BRAVE_SEARCH_API_KEY,
|
||||
EnvVars::FABRO_SLACK_APP_TOKEN,
|
||||
EnvVars::FABRO_SLACK_BOT_TOKEN,
|
||||
EnvVars::GEMINI_API_KEY,
|
||||
EnvVars::GITHUB_APP_CLIENT_SECRET,
|
||||
EnvVars::GITHUB_APP_PRIVATE_KEY,
|
||||
EnvVars::GITHUB_APP_WEBHOOK_SECRET,
|
||||
EnvVars::GITHUB_TOKEN,
|
||||
EnvVars::INCEPTION_API_KEY,
|
||||
EnvVars::KIMI_API_KEY,
|
||||
EnvVars::MINIMAX_API_KEY,
|
||||
EnvVars::OPENAI_API_KEY,
|
||||
EnvVars::ZAI_API_KEY,
|
||||
EnvVars::DAYTONA_API_KEY,
|
||||
];
|
||||
|
||||
pub fn secret_scope(name: &str) -> Option<SecretScope> {
|
||||
if BOOTSTRAP_SECRETS.contains(&name) {
|
||||
Some(SecretScope::Bootstrap)
|
||||
} else if OPTIONAL_VAULT_SECRETS.contains(&name) {
|
||||
Some(SecretScope::OptionalVault)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_bootstrap_secret(name: &str) -> bool {
|
||||
secret_scope(name) == Some(SecretScope::Bootstrap)
|
||||
}
|
||||
|
||||
pub fn is_optional_vault_secret(name: &str) -> bool {
|
||||
secret_scope(name) == Some(SecretScope::OptionalVault)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Export the registry**
|
||||
|
||||
Modify `lib.rs`:
|
||||
|
||||
```rust
|
||||
mod env_vars;
|
||||
mod secret_registry;
|
||||
|
||||
pub use env_vars::EnvVars;
|
||||
pub use secret_registry::{
|
||||
SecretScope, is_bootstrap_secret, is_optional_vault_secret, secret_scope,
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Add registry tests**
|
||||
|
||||
Add tests proving:
|
||||
|
||||
- `SESSION_SECRET` and `FABRO_DEV_TOKEN` are bootstrap.
|
||||
- `GITHUB_APP_CLIENT_SECRET`, `GITHUB_APP_PRIVATE_KEY`, `GITHUB_TOKEN`, Slack tokens, Daytona, Brave, and common LLM API keys are optional vault secrets.
|
||||
- `GH_TOKEN` is not classified.
|
||||
- non-secret config names such as `GITHUB_BASE_URL`, `SLACK_BASE_URL`, and `DAYTONA_API_URL` are not classified.
|
||||
|
||||
- [ ] **Step 4: Run the focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo nextest run -p fabro-static
|
||||
```
|
||||
|
||||
Expected: all `fabro-static` tests pass.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/crates/fabro-static/src/lib.rs lib/crates/fabro-static/src/secret_registry.rs
|
||||
git commit -m "refactor: classify server secret scopes"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Enforce Bootstrap Boundaries In The Secret API
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `lib/crates/fabro-server/src/server/handler/secrets.rs`
|
||||
- Test: existing server handler tests or new focused tests near secret handler coverage
|
||||
|
||||
- [ ] **Step 1: Reject bootstrap secrets in `create_secret`**
|
||||
|
||||
Before OAuth parsing or Daytona validation, reject `SecretScope::Bootstrap`:
|
||||
|
||||
```rust
|
||||
if fabro_static::is_bootstrap_secret(&name) {
|
||||
return ApiError::bad_request(format!(
|
||||
"{name} is a bootstrap secret; configure it with process env or server.env"
|
||||
))
|
||||
.into_response();
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Keep optional and unknown secret writes allowed**
|
||||
|
||||
Do not reject unknown names. Workflows may define arbitrary vault-visible secrets.
|
||||
|
||||
- [ ] **Step 3: Add API tests**
|
||||
|
||||
Add tests proving:
|
||||
|
||||
- `POST /secrets` with `SESSION_SECRET` returns `400`.
|
||||
- `POST /secrets` with `FABRO_DEV_TOKEN` returns `400`.
|
||||
- `POST /secrets` with `GITHUB_APP_CLIENT_SECRET` succeeds.
|
||||
- `POST /secrets` with a custom name such as `CUSTOM_WORKFLOW_TOKEN` succeeds.
|
||||
|
||||
- [ ] **Step 4: Run focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo nextest run -p fabro-server secret
|
||||
```
|
||||
|
||||
Expected: secret handler tests pass.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/crates/fabro-server/src/server/handler/secrets.rs lib/crates/fabro-server
|
||||
git commit -m "fix: reject bootstrap secrets in vault API"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Replace Server Optional Lookups With Vault-Only Lookups
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `lib/crates/fabro-server/src/server.rs`
|
||||
- Modify: `lib/crates/fabro-server/src/diagnostics.rs`
|
||||
- Modify: `lib/crates/fabro-server/src/run_files.rs`
|
||||
- Modify: `lib/crates/fabro-server/src/run_manifest.rs`
|
||||
- Modify: `lib/crates/fabro-server/src/server/handler/sandbox.rs`
|
||||
- Modify: `lib/crates/fabro-server/src/server/handler/sessions.rs`
|
||||
- Test: `lib/crates/fabro-server/src/server/tests.rs` and existing handler tests
|
||||
|
||||
- [ ] **Step 1: Add explicit helpers on `AppState`**
|
||||
|
||||
Replace `vault_or_env` and `vault_or_env_pub` with:
|
||||
|
||||
```rust
|
||||
pub(crate) fn vault_secret(&self, name: &str) -> Option<String> {
|
||||
self.vault
|
||||
.try_read()
|
||||
.ok()
|
||||
.and_then(|vault| vault.get(name).map(str::to_string))
|
||||
}
|
||||
|
||||
fn config_env_lookup(&self, name: &str) -> Option<String> {
|
||||
(self.env_lookup)(name)
|
||||
}
|
||||
```
|
||||
|
||||
Keep `server_secret` for bootstrap secrets only.
|
||||
|
||||
- [ ] **Step 2: Change Daytona secret reads**
|
||||
|
||||
Use `state.vault_secret(EnvVars::DAYTONA_API_KEY)` wherever a Daytona API key is required. Keep non-secret Daytona URL and organization settings on `env_lookup` or settings-derived configuration, not vault lookup.
|
||||
|
||||
- [ ] **Step 3: Change GitHub token reads**
|
||||
|
||||
In `github_credentials`, resolve token strategy only through:
|
||||
|
||||
```rust
|
||||
let token = self
|
||||
.vault_secret(EnvVars::GITHUB_TOKEN)
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|token| !token.is_empty())
|
||||
.map(str::to_string);
|
||||
```
|
||||
|
||||
Remove the `GH_TOKEN` fallback and update the error message to say:
|
||||
|
||||
```text
|
||||
GITHUB_TOKEN not configured -- run fabro install or run fabro secret set GITHUB_TOKEN
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Change diagnostics and handlers**
|
||||
|
||||
Replace `state.vault_or_env(...)` and `state.vault_or_env_pub(...)` with `state.vault_secret(...)` for optional secret checks. Update diagnostics remediation text to use `fabro secret set`.
|
||||
|
||||
- [ ] **Step 5: Add regression tests**
|
||||
|
||||
Add tests proving:
|
||||
|
||||
- `DAYTONA_API_KEY` in process env but absent from vault is treated as missing by server diagnostics.
|
||||
- `GITHUB_TOKEN` in process env but absent from vault is treated as missing by token strategy.
|
||||
- `GH_TOKEN` in vault or process env is ignored by server runtime.
|
||||
- `DAYTONA_API_KEY` in vault is accepted.
|
||||
|
||||
- [ ] **Step 6: Run focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo nextest run -p fabro-server daytona
|
||||
cargo nextest run -p fabro-server github
|
||||
```
|
||||
|
||||
Expected: relevant server tests pass.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/crates/fabro-server
|
||||
git commit -m "refactor: resolve optional server secrets from vault only"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Make Server LLM Credentials Vault-Only
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `lib/crates/fabro-auth/src/vault_source.rs`
|
||||
- Modify: `lib/crates/fabro-server/src/server.rs`
|
||||
- Test: `lib/crates/fabro-auth/src/vault_source.rs`, `lib/crates/fabro-server/src/server/tests.rs`
|
||||
|
||||
- [ ] **Step 1: Add an explicit vault-only constructor**
|
||||
|
||||
Add this constructor to `VaultCredentialSource`:
|
||||
|
||||
```rust
|
||||
#[must_use]
|
||||
pub fn vault_only(vault: Arc<AsyncRwLock<Vault>>) -> Self {
|
||||
Self::with_env_lookup(vault, |_| None)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Use vault-only source in server app state**
|
||||
|
||||
In `build_app_state`, change the LLM source construction from `with_env_lookup(... env_lookup ...)` to:
|
||||
|
||||
```rust
|
||||
let llm_source: Arc<dyn CredentialSource> =
|
||||
Arc::new(VaultCredentialSource::vault_only(Arc::clone(&vault)));
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Keep CLI/library env sources explicit**
|
||||
|
||||
Do not remove `EnvCredentialSource`. Do not change direct CLI/library flows that intentionally choose env-backed credentials outside server runtime.
|
||||
|
||||
- [ ] **Step 4: Add tests**
|
||||
|
||||
Add tests proving:
|
||||
|
||||
- `VaultCredentialSource::vault_only` does not resolve process env values.
|
||||
- Server readiness sees a provider configured only when the matching vault secret exists.
|
||||
|
||||
- [ ] **Step 5: Run focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo nextest run -p fabro-auth
|
||||
cargo nextest run -p fabro-server llm
|
||||
```
|
||||
|
||||
Expected: auth and server LLM tests pass.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/crates/fabro-auth/src/vault_source.rs lib/crates/fabro-server/src/server.rs lib/crates/fabro-server/src/server/tests.rs
|
||||
git commit -m "fix: use vault-only llm credentials in server runtime"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Move GitHub App Runtime Secrets To Vault
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `lib/crates/fabro-server/src/server.rs`
|
||||
- Modify: `lib/crates/fabro-server/src/web_auth.rs`
|
||||
- Modify: `lib/crates/fabro-server/src/github_webhooks.rs`
|
||||
- Modify: `lib/crates/fabro-server/src/diagnostics.rs`
|
||||
- Modify: `lib/crates/fabro-server/src/startup.rs`
|
||||
- Modify: `lib/crates/fabro-server/src/serve.rs`
|
||||
- Modify: `lib/crates/fabro-server/src/jwt_auth.rs`
|
||||
- Test: server startup, auth, webhook, diagnostics, worker command tests
|
||||
|
||||
- [ ] **Step 1: Read GitHub App private key from vault**
|
||||
|
||||
In `AppState::github_credentials`, replace:
|
||||
|
||||
```rust
|
||||
let raw = self.server_secret(EnvVars::GITHUB_APP_PRIVATE_KEY);
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```rust
|
||||
let raw = self.vault_secret(EnvVars::GITHUB_APP_PRIVATE_KEY);
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Read OAuth client secret from vault**
|
||||
|
||||
In the GitHub OAuth callback handler, replace `state.server_secret(EnvVars::GITHUB_APP_CLIENT_SECRET)` with `state.vault_secret(EnvVars::GITHUB_APP_CLIENT_SECRET)`.
|
||||
|
||||
- [ ] **Step 3: Read webhook secret from vault**
|
||||
|
||||
Replace webhook secret resolution from `server_secret(WEBHOOK_SECRET_ENV)` to `vault_secret(WEBHOOK_SECRET_ENV)`. Startup logging should report webhook presence based on the vault value after the vault is loaded.
|
||||
|
||||
- [ ] **Step 4: Validate GitHub auth after vault load**
|
||||
|
||||
Change startup validation so `SESSION_SECRET` and `FABRO_DEV_TOKEN` are read from `ServerSecrets`, while `GITHUB_APP_CLIENT_SECRET` is read from the vault.
|
||||
|
||||
Use a composite lookup for auth validation:
|
||||
|
||||
```rust
|
||||
let auth_secret_lookup = |name: &str| match name {
|
||||
EnvVars::GITHUB_APP_CLIENT_SECRET => vault.get(name).map(str::to_string),
|
||||
_ => server_secrets.get(name),
|
||||
};
|
||||
```
|
||||
|
||||
Load the vault before calling GitHub-auth validation. Pass the loaded vault into `build_app_state` or factor vault loading so the server does not perform inconsistent duplicate loads.
|
||||
|
||||
- [ ] **Step 5: Update worker GitHub App key injection**
|
||||
|
||||
Where the worker command currently forwards `GITHUB_APP_PRIVATE_KEY` from `server_secret`, forward it from `vault_secret`. Keep the explicit worker injection because worker environments remain scrubbed by default.
|
||||
|
||||
- [ ] **Step 6: Update tests**
|
||||
|
||||
Add or change tests proving:
|
||||
|
||||
- GitHub auth enabled with `GITHUB_APP_CLIENT_SECRET` only in `server.env` fails startup.
|
||||
- GitHub auth enabled with `GITHUB_APP_CLIENT_SECRET` in vault passes startup.
|
||||
- OAuth callback reads the vault client secret.
|
||||
- Webhook verification reads the vault webhook secret.
|
||||
- Worker command forwards the GitHub App private key from vault.
|
||||
- Diagnostics reports missing GitHub App secrets based on vault, not `server.env`.
|
||||
|
||||
- [ ] **Step 7: Run focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo nextest run -p fabro-server github
|
||||
cargo nextest run -p fabro-server worker_auth
|
||||
cargo nextest run -p fabro-cli server_start
|
||||
```
|
||||
|
||||
Expected: GitHub auth, webhook, worker, and startup tests pass.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/crates/fabro-server lib/crates/fabro-cli/tests
|
||||
git commit -m "fix: store github app runtime secrets in vault"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Move Slack To Vault-Only Credentials
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `lib/crates/fabro-server/src/server.rs`
|
||||
- Modify: `lib/crates/fabro-slack/src/config.rs` only if names or status messages need adjustment
|
||||
- Test: Slack credential resolution and server app-state tests
|
||||
|
||||
- [ ] **Step 1: Resolve Slack tokens from vault**
|
||||
|
||||
In `build_app_state`, replace:
|
||||
|
||||
```rust
|
||||
resolve_slack_credentials_status_with_lookup(|name| server_secrets.get(name))
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```rust
|
||||
resolve_slack_credentials_status_with_lookup(|name| {
|
||||
vault.try_read().ok().and_then(|vault| vault.get(name).map(str::to_string))
|
||||
})
|
||||
```
|
||||
|
||||
If the implementation already holds a synchronous vault read guard in this block, reuse that guard rather than calling `try_read` repeatedly.
|
||||
|
||||
- [ ] **Step 2: Keep Slack base URL out of the secret model**
|
||||
|
||||
Leave `SLACK_BASE_URL` as a non-secret test/development override. Do not add it to the secret registry.
|
||||
|
||||
- [ ] **Step 3: Update Slack tests**
|
||||
|
||||
Add tests proving:
|
||||
|
||||
- Slack tokens in vault enable Slack service.
|
||||
- Slack tokens in `server.env` but absent from vault do not enable Slack service.
|
||||
- Missing vault tokens produce the existing disabled status with missing token names.
|
||||
|
||||
- [ ] **Step 4: Run focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo nextest run -p fabro-slack
|
||||
cargo nextest run -p fabro-server slack
|
||||
```
|
||||
|
||||
Expected: Slack tests pass.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/crates/fabro-server/src/server.rs lib/crates/fabro-slack/src/config.rs
|
||||
git commit -m "fix: resolve slack credentials from vault"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Make Brave Search Tool Use Server-Provided Secrets
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `lib/crates/fabro-agent/src/config.rs`
|
||||
- Modify: `lib/crates/fabro-agent/src/tools.rs`
|
||||
- Modify: server session/profile construction where `SessionOptions` are built
|
||||
- Test: `lib/crates/fabro-agent/src/tools.rs`, server diagnostics/tool integration tests
|
||||
|
||||
- [ ] **Step 1: Add tool secret configuration**
|
||||
|
||||
Add to `config.rs`:
|
||||
|
||||
```rust
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct ToolSecrets {
|
||||
pub brave_search_api_key: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
Add this field to `SessionOptions`:
|
||||
|
||||
```rust
|
||||
pub tool_secrets: ToolSecrets,
|
||||
```
|
||||
|
||||
Default it to `ToolSecrets::default()` and include it in the debug impl without printing secret values. Use a boolean:
|
||||
|
||||
```rust
|
||||
.field(
|
||||
"brave_search_configured",
|
||||
&self.tool_secrets.brave_search_api_key.is_some(),
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Pass the Brave key into tool registration**
|
||||
|
||||
Change `register_core_tools`:
|
||||
|
||||
```rust
|
||||
registry.register(make_web_search_tool_with_api_key(
|
||||
config.tool_secrets.brave_search_api_key.clone(),
|
||||
));
|
||||
```
|
||||
|
||||
Remove the process-env-reading `make_web_search_tool` wrapper.
|
||||
|
||||
- [ ] **Step 3: Improve the missing-key message**
|
||||
|
||||
Change the error text from “environment variable is not set” to:
|
||||
|
||||
```text
|
||||
BRAVE_SEARCH_API_KEY is not configured
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Populate `SessionOptions.tool_secrets` in server runtime**
|
||||
|
||||
Where the server constructs agent sessions, set:
|
||||
|
||||
```rust
|
||||
tool_secrets: ToolSecrets {
|
||||
brave_search_api_key: state.vault_secret(EnvVars::BRAVE_SEARCH_API_KEY),
|
||||
},
|
||||
```
|
||||
|
||||
Keep direct CLI agent behavior explicit: if the CLI should support local env-backed Brave Search, set this field at the CLI boundary from `std::env::var(EnvVars::BRAVE_SEARCH_API_KEY).ok()`. Do not let the tool read process env internally.
|
||||
|
||||
- [ ] **Step 5: Update tests**
|
||||
|
||||
Add tests proving:
|
||||
|
||||
- `make_web_search_tool_with_api_key(None)` returns `BRAVE_SEARCH_API_KEY is not configured`.
|
||||
- `register_core_tools` wires `SessionOptions.tool_secrets.brave_search_api_key` into the web search tool.
|
||||
- Server diagnostics and server-created sessions both use the same vault-backed key.
|
||||
|
||||
- [ ] **Step 6: Run focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo nextest run -p fabro-agent web_search
|
||||
cargo nextest run -p fabro-server brave
|
||||
```
|
||||
|
||||
Expected: Brave tests pass.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/crates/fabro-agent lib/crates/fabro-server
|
||||
git commit -m "fix: pass brave search credentials through server configuration"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Update Install Mode Persistence
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `lib/crates/fabro-server/src/install.rs`
|
||||
- Modify: `lib/crates/fabro-cli/src/commands/install.rs`
|
||||
- Modify: install tests in `lib/crates/fabro-server/tests/it/api/install.rs` and `lib/crates/fabro-cli/tests/it/cmd/install.rs`
|
||||
|
||||
- [ ] **Step 1: Server install writes GitHub App secrets to vault**
|
||||
|
||||
In `post_install_finish`, replace GitHub App `server_env_writes.push(...)` calls with `vault_secrets.push(...)` calls:
|
||||
|
||||
```rust
|
||||
vault_secrets.push(VaultSecretWrite {
|
||||
name: EnvVars::GITHUB_APP_PRIVATE_KEY.to_string(),
|
||||
value: BASE64_STANDARD.encode(github.pem.as_bytes()),
|
||||
secret_type: VaultSecretType::File,
|
||||
description: None,
|
||||
});
|
||||
|
||||
vault_secrets.push(VaultSecretWrite {
|
||||
name: EnvVars::GITHUB_APP_CLIENT_SECRET.to_string(),
|
||||
value: github.client_secret,
|
||||
secret_type: VaultSecretType::Token,
|
||||
description: None,
|
||||
});
|
||||
|
||||
if let Some(secret) = github.webhook_secret {
|
||||
vault_secrets.push(VaultSecretWrite {
|
||||
name: EnvVars::GITHUB_APP_WEBHOOK_SECRET.to_string(),
|
||||
value: secret,
|
||||
secret_type: VaultSecretType::Token,
|
||||
description: None,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Remove stale GitHub App keys from `server.env` during install persistence**
|
||||
|
||||
Add removals for:
|
||||
|
||||
- `GITHUB_APP_PRIVATE_KEY`
|
||||
- `GITHUB_APP_CLIENT_SECRET`
|
||||
- `GITHUB_APP_WEBHOOK_SECRET`
|
||||
|
||||
This keeps greenfield installs clean and removes stale optional secrets if a developer has run earlier local install attempts.
|
||||
|
||||
- [ ] **Step 3: Align CLI GitHub install helper**
|
||||
|
||||
Update CLI install persistence so switching to GitHub App writes app secrets to vault and removes the app keys from `server.env`. Switching to token continues to write `GITHUB_TOKEN` to vault and remove app vault secrets.
|
||||
|
||||
- [ ] **Step 4: Update install tests**
|
||||
|
||||
Add or change tests proving:
|
||||
|
||||
- Browser install with GitHub App writes private key, client secret, and webhook secret to vault.
|
||||
- Browser install with GitHub App does not write those keys to `server.env`.
|
||||
- CLI GitHub App install writes app secrets to vault.
|
||||
- Switching strategies removes stale secrets from the other strategy.
|
||||
- `SESSION_SECRET`, `FABRO_DEV_TOKEN`, and object-store credentials remain in `server.env`.
|
||||
|
||||
- [ ] **Step 5: Run focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo nextest run -p fabro-server install
|
||||
cargo nextest run -p fabro-cli install
|
||||
```
|
||||
|
||||
Expected: install tests pass.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add lib/crates/fabro-server/src/install.rs lib/crates/fabro-server/tests/it/api/install.rs lib/crates/fabro-cli/src/commands/install.rs lib/crates/fabro-cli/tests/it/cmd/install.rs
|
||||
git commit -m "fix: persist optional install secrets in vault"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: Update Strategy And Public Docs
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `docs/internal/server-secrets-strategy.md`
|
||||
- Modify: `docs/public/administration/server-configuration.mdx`
|
||||
- Modify: `docs/public/administration/self-host-docker.mdx`
|
||||
- Modify: `docs/public/administration/deploy-railway.mdx`
|
||||
- Modify: `docs/public/integrations/github.mdx`
|
||||
- Modify: `docs/public/integrations/slack.mdx`
|
||||
- Modify: `docs/public/integrations/brave-search.mdx`
|
||||
- Modify: `docs/public/integrations/daytona.mdx`
|
||||
- Modify: LLM provider docs that still say server runtime reads provider keys from process env
|
||||
|
||||
- [ ] **Step 1: Rewrite internal strategy**
|
||||
|
||||
Update `server-secrets-strategy.md` to state:
|
||||
|
||||
- `ServerSecrets` means bootstrap secrets only.
|
||||
- Bootstrap source precedence is process env then `server.env`.
|
||||
- Optional integration secrets are vault-only.
|
||||
- Requiredness is independent from source: GitHub auth may require a vault secret at startup.
|
||||
- Server worker env remains scrubbed and receives only explicit injected values.
|
||||
|
||||
- [ ] **Step 2: Update administration docs**
|
||||
|
||||
State that `server.env` is for:
|
||||
|
||||
- `SESSION_SECRET`
|
||||
- `FABRO_DEV_TOKEN`
|
||||
- storage/object-store bootstrap credentials
|
||||
|
||||
State that `server.env` is not used for:
|
||||
|
||||
- Slack
|
||||
- Daytona
|
||||
- Brave Search
|
||||
- LLM provider keys
|
||||
- GitHub token
|
||||
- GitHub App private key/client secret/webhook secret
|
||||
|
||||
- [ ] **Step 3: Update integration docs**
|
||||
|
||||
For each optional integration, show `fabro secret set` as the server-runtime configuration path:
|
||||
|
||||
```bash
|
||||
fabro secret set BRAVE_SEARCH_API_KEY <key>
|
||||
fabro secret set DAYTONA_API_KEY <key>
|
||||
fabro secret set FABRO_SLACK_BOT_TOKEN <xoxb-token>
|
||||
fabro secret set FABRO_SLACK_APP_TOKEN <xapp-token>
|
||||
fabro secret set GITHUB_TOKEN <token>
|
||||
```
|
||||
|
||||
For GitHub App, document that install mode stores app secrets in vault.
|
||||
|
||||
- [ ] **Step 4: Remove stale `server.env` guidance**
|
||||
|
||||
Search:
|
||||
|
||||
```bash
|
||||
rg -n "server.env|process env -> server.env|GITHUB_APP_CLIENT_SECRET|FABRO_SLACK" docs/public docs/internal
|
||||
```
|
||||
|
||||
Each result should either describe bootstrap secrets or explicitly say the secret is vault-only.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/internal docs/public
|
||||
git commit -m "docs: document vault-only optional secrets"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 10: Final Verification And Cleanup
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify any touched tests or docs needed by final verification.
|
||||
|
||||
- [ ] **Step 1: Search for removed server-runtime patterns**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
rg -n "vault_or_env|vault_or_env_pub|GH_TOKEN|GITHUB_APP_CLIENT_SECRET.*server_secret|FABRO_SLACK_.*server_secret|BRAVE_SEARCH_API_KEY.*std::env|DAYTONA_API_KEY.*process_env" lib/crates docs
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- no server-runtime `vault_or_env` helper remains;
|
||||
- no server-runtime `GH_TOKEN` fallback remains;
|
||||
- no optional secret docs claim `server.env` is a valid server-runtime source;
|
||||
- live-test annotations and explicit CLI/library env usage may remain.
|
||||
|
||||
- [ ] **Step 2: Run focused crate tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo nextest run -p fabro-static
|
||||
cargo nextest run -p fabro-auth
|
||||
cargo nextest run -p fabro-agent web_search
|
||||
cargo nextest run -p fabro-slack
|
||||
cargo nextest run -p fabro-server
|
||||
cargo nextest run -p fabro-cli install
|
||||
```
|
||||
|
||||
Expected: all listed tests pass.
|
||||
|
||||
- [ ] **Step 3: Run workspace quality checks**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cargo +nightly-2026-04-14 fmt --check --all
|
||||
cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings
|
||||
```
|
||||
|
||||
Expected: formatting and clippy pass.
|
||||
|
||||
- [ ] **Step 4: Run one end-to-end install smoke if credentials are available**
|
||||
|
||||
Run a non-live smoke that exercises install persistence and restart validation through existing install tests. If local live credentials are available, run the relevant ignored/live tests for GitHub App, Daytona, and Brave Search.
|
||||
|
||||
- [ ] **Step 5: Final commit**
|
||||
|
||||
If Task 10 required cleanup changes, commit them:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "test: verify rationalized server secrets"
|
||||
```
|
||||
|
||||
If Task 10 produced no file changes, do not create an empty commit.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- `server.env` is no longer a provider for Slack, Daytona, Brave Search, LLM provider credentials, GitHub token, or GitHub App secrets.
|
||||
- Process env is no longer a server-runtime provider for optional integration secrets.
|
||||
- Install mode can enable GitHub auth in one pass by writing GitHub App secrets to vault before normal startup.
|
||||
- Startup fails clearly when GitHub auth is enabled and `GITHUB_APP_CLIENT_SECRET` is missing from vault.
|
||||
- `fabro secret set` rejects bootstrap secrets and accepts optional integration secrets.
|
||||
- Brave Search diagnostics and the actual `web_search` tool agree on whether the key is configured.
|
||||
- Worker subprocess env remains scrubbed, with only explicit internal injections preserved.
|
||||
|
||||
|
||||
## Completed stages
|
||||
- **toolchain**: succeeded
|
||||
- Script: `command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1`
|
||||
- Output:
|
||||
```
|
||||
cargo 1.95.0 (f2d3ce0bd 2026-03-21)
|
||||
```
|
||||
- **preflight_compile**: succeeded
|
||||
- Script: `cargo check -q --workspace 2>&1`
|
||||
- Output: (empty)
|
||||
- **preflight_lint**: succeeded
|
||||
- Script: `cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1`
|
||||
- Output: (empty)
|
||||
|
||||
|
||||
Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD.
|
||||
6
stages/005-implement@1/provider_used.json
Normal file
6
stages/005-implement@1/provider_used.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"mode": "agent",
|
||||
"provider": "openai",
|
||||
"model": "gpt-5.5",
|
||||
"reasoning_effort": "xhigh"
|
||||
}
|
||||
46
stages/005-implement@1/response.md
Normal file
46
stages/005-implement@1/response.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
Implemented the full Secrets Rationalization plan and committed the remaining work.
|
||||
|
||||
Summary of changes:
|
||||
- Added/kept shared secret scope registry and bootstrap-secret rejection in `/secrets`.
|
||||
- Replaced server optional secret resolution with vault-only lookups for:
|
||||
- LLM provider credentials
|
||||
- GitHub token/App private key/client secret/webhook secret
|
||||
- Slack tokens
|
||||
- Daytona API key
|
||||
- Brave Search API key
|
||||
- Kept bootstrap secrets on `process env -> server.env` via `ServerSecrets`.
|
||||
- Added startup vault loading so GitHub auth validates `GITHUB_APP_CLIENT_SECRET` from the vault.
|
||||
- Changed Brave Search so the tool no longer reads process env internally; runtime/CLI pass `ToolSecrets` explicitly.
|
||||
- Updated install persistence so GitHub App secrets are written to vault, stale optional `server.env` keys are removed, and strategy switches clean up stale vault secrets.
|
||||
- Updated docs/public/internal guidance to document the new bootstrap-vs-vault model.
|
||||
- Working tree is clean.
|
||||
|
||||
Commits created:
|
||||
- `75d77fb3a refactor: resolve optional server secrets from vault only`
|
||||
- `1502c8d97 fix: persist optional install secrets in vault`
|
||||
- `c14d44fb5 docs: document vault-only optional secrets`
|
||||
|
||||
Existing commits from earlier task progress:
|
||||
- `097fb172c refactor: classify server secret scopes`
|
||||
- `2e612af86 fix: reject bootstrap secrets in vault API`
|
||||
|
||||
Verification run:
|
||||
- `cargo check -q --workspace`
|
||||
- `cargo nextest run -p fabro-static`
|
||||
- `cargo nextest run -p fabro-auth`
|
||||
- `cargo nextest run -p fabro-vault`
|
||||
- `cargo nextest run -p fabro-agent web_search`
|
||||
- `cargo nextest run -p fabro-agent brave_search`
|
||||
- `cargo nextest run -p fabro-slack`
|
||||
- `cargo nextest run -p fabro-server`
|
||||
- `cargo nextest run -p fabro-cli install --no-fail-fast`
|
||||
- `cargo nextest run -p fabro-cli server_start --no-fail-fast`
|
||||
- `cargo nextest run -p fabro-server github --no-fail-fast`
|
||||
- `cargo nextest run -p fabro-workflow build_registry_accepts_vault_only_llm_provider`
|
||||
- `cargo +nightly-2026-04-14 fmt --check --all`
|
||||
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings`
|
||||
|
||||
Pattern checks:
|
||||
- No `vault_or_env` / `vault_or_env_pub` server-runtime helper remains.
|
||||
- No server-runtime `GH_TOKEN` fallback remains.
|
||||
- Remaining `GH_TOKEN` hits are non-server-runtime/test/static/CLI legacy lookup references.
|
||||
Loading…
Add table
Reference in a new issue