Commit graph

2698 commits

Author SHA1 Message Date
Marcel Hild
8a6f83bb08
fix(cli): use working_directory for manifest git detection
build_manifest_git() was called with the CLI's cwd, which detects the
wrong repo/branch when fabro is invoked from a workspace directory that
differs from the target repo (e.g. via `[run] working_dir = "repos/foo"`
in .fabro/project.toml). Now resolve working_directory once in
build_run_manifest, share it with resolve_manifest_goal (dropping the
duplicate resolution), and pass it to build_manifest_git.

Also rename the build_manifest_git parameter from `cwd` to `repo_path`
to reflect that it now receives the resolved working directory.

Add a regression test that spins up a workspace git repo and a
separate target git repo beneath it, points `[run] working_dir` at the
target, and asserts the manifest's git branch and origin come from the
target repo.

Ports https://github.com/durandom/fabro/pull/2 to the post-v2-schema
code (Settings -> SettingsLayer).

Closes #159

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 16:53:55 -04:00
Bryan Helmkamp
3f807f42a6
Merge pull request #169 from fabro-sh/fix/dependabot-openssl-astro
fix(deps): patch rust-openssl and astro security advisories
2026-04-23 15:35:41 -04:00
Bryan Helmkamp
fce31708de
Merge remote-tracking branch 'origin/main' into fix/dependabot-openssl-astro 2026-04-23 15:29:00 -04:00
Bryan Helmkamp
dda6f44d1e
ci: drop check-env-mutation.sh, rely on clippy disallowed_methods
clippy.toml already bans std::env::{set_var,remove_var} via
disallowed_methods, and every existing call site carries a scoped
#[expect(clippy::disallowed_methods, reason = "...")]. The shell grep
is redundant and forced a second, less granular allowlist.

Also update server-secrets-strategy.md to describe clippy as the
enforcement mechanism.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:24:33 -04:00
Bryan Helmkamp
b5b67226f1
ci: restore install.rs server-symbol allowlist and worker-token scrub exemption
Both Boundary checks have been red on main for multiple commits:

- check-boundary.sh: install.rs reintroduced direct use of
  fabro_config::ServerSettings::from_layer in 93b6577cd but was dropped
  from server_symbol_allowlist in bb0d05be2. Re-add it.
- check-env-mutation.sh: the worker FABRO_WORKER_TOKEN scrub added in
  077469d0c is documented as the approved pattern in
  docs-internal/server-secrets-strategy.md but was missing from the
  allowlist. Add the exact line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:20:58 -04:00
Bryan Helmkamp
077469d0c6
refactor(auth): scrub FABRO_WORKER_TOKEN from worker env at startup
The worker subprocess is spawned with env_clear+allowlist by the server, so
the only sensitive value in its env is FABRO_WORKER_TOKEN itself. Read the
token and remove_var it from the process env in main() before Tokio starts
worker threads, then thread it explicitly through runner::execute(&str).

Every descendant (hooks, local sandbox, devcontainer initializeCommand,
MCP stdio, etc.) now inherits a worker env with no bearer in it, so an
unscrubbed spawn site cannot leak the token. This makes the prior denylist
scrub in fabro-hooks and fabro-sandbox redundant — delete it and the shared
WORKER_SECRET_ENV_DENYLIST constant. The sandbox keeps its _api_key/_secret/
_token/_password/_credential suffix heuristic for user-supplied env_vars
hygiene.

Extend the server-dispatched-worker env-leak integration test to also
assert a Bash stage running in the worker does not observe FABRO_WORKER_TOKEN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:10:58 -04:00
Bryan Helmkamp
7456cb3252
fix(deps): bump astro from 5.9.3 to 6.1.6
Patches GHSA-j687-52p2-xcff (CVE-2026-41067): XSS in define:vars via
incomplete </script> tag sanitization. Requires Astro >= 6.1.6.

Also bumps @astrojs/react to ^5.0.4 for Astro 6 compatibility.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:08:28 -04:00
Bryan Helmkamp
9ed21bcf38
fix(deps): bump rust-openssl from 0.10.75 to 0.10.78
Patches multiple security advisories in rust-openssl (<0.10.78):
- GHSA: Deriver::derive and PkeyCtxRef::derive buffer overflow
- GHSA: Incorrect bounds assertion in AES key wrap
- GHSA: MdCtxRef::digest_final writes past caller buffer
- GHSA: Unchecked callback length in PSK/cookie trampolines
- GHSA: Out-of-bounds read in PEM password callback

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:07:10 -04:00
Bryan Helmkamp
fbb924b9c4
chore: fix nightly clippy lints on worker jwt branch
- fabro-client: collapse identical match arms for DevToken/Worker bearer
- fabro-hooks: rewrite filter_map(bool::then) as filter().map() chain
- fabro-sandbox: import WORKER_SECRET_ENV_DENYLIST rather than absolute path
- fabro-server: box large execute_run_in_process future; take path: &str in
  test-only bearer_request; use let-else in session-secret test; replace unit
  pattern _ with () in worker_token request_parts helper; import StatusCode
- fabro-cli run/mod.rs: box large runner::execute future
- fabro-cli worker_auth.rs: drop unused async on shutdown, allow
  clippy::unwrap_used at file level for subprocess test harness setup

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 12:08:55 -04:00
Bryan Helmkamp
a75e820064
refactor(auth): simplify worker jwt helpers and reuse existing utilities
- drop _pub wrapper parse helpers; make originals pub(crate)
- drop AppState::issue_worker_token thin wrapper
- delete 9 narrating comments the extractor types already encode
- add ActorRef::system_worker() alongside ::user()/::agent()
- share WORKER_SECRET_ENV_DENYLIST from fabro-util between hooks/sandbox
- reuse jwt_auth::bearer_token and session_secret_key_error helpers

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 12:01:22 -04:00
Bryan Helmkamp
0b36ed985b
test(auth): share cli test jwt helpers 2026-04-23 11:42:50 -04:00
Bryan Helmkamp
891b7f90ae
test(auth): backfill worker jwt regression coverage 2026-04-23 11:35:08 -04:00
Bryan Helmkamp
519f46038d
refactor(auth): use per-run worker JWTs for subprocesses 2026-04-23 11:04:45 -04:00
Bryan Helmkamp
e4b6d626a2
plan 2026-04-23 10:39:39 -04:00
Bryan Helmkamp
e210fe9fed
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-cli/src/command_context.rs
#	lib/crates/fabro-cli/src/main.rs
2026-04-23 08:56:40 -04:00
Bryan Helmkamp
a770eb8e12
Merge remote-tracking branch 'origin/main' 2026-04-23 08:52:44 -04:00
Bryan Helmkamp
fc4e0381ac
simplify: dedupe splice helpers, trim dead map wrapper surface
- Collapse splice_model_fallbacks and splice_events into a single
  generic splice_combine guarded by a new SpliceMarker trait; the
  Combine impl for Vec<T: SpliceMarker> replaces the two per-enum impls.
- Drop inherent iter/iter_mut (redundant with Deref/DerefMut),
  AsRef/AsMut, and IntoIterator for &_/&mut _ on ReplaceMap/StickyMap/
  MergeMap — they had zero external callers. DerefMut and IntoIterator
  for Self stay because labels.extend(...) relies on both.
- Drop unused _api parameter on resolve_web.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:52:41 -04:00
Bryan Helmkamp
da9453cbd1
refactor(config): move settings combine into types 2026-04-23 08:42:19 -04:00
Bryan Helmkamp
4ad4d8fd36
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-cli/src/commands/dump.rs
#	lib/crates/fabro-cli/src/commands/store/mod.rs
#	lib/crates/fabro-cli/src/main.rs
2026-04-23 08:42:16 -04:00
Bryan Helmkamp
3bfd93aa60
simplify: add CommandContext::verbose() accessor
The check `ctx.user_settings().cli.output.verbosity == OutputVerbosity::Verbose`
repeated 6 times across doctor, preflight, run command/resume/mod. Add a
`verbose()` method and replace every call site. `cargo fix` handles the
now-unused `OutputVerbosity` imports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:36:52 -04:00
Bryan Helmkamp
7f96069345
docs 2026-04-23 08:35:43 -04:00
Bryan Helmkamp
37cd8ff45f
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-server/src/lib.rs
#	lib/crates/fabro-server/src/serve.rs
#	lib/crates/fabro-server/src/server.rs
2026-04-23 08:34:44 -04:00
Bryan Helmkamp
829a02f1f8
fix: reorder test imports to satisfy rustfmt
Auto-applied by `cargo +nightly-2026-04-14 fmt --all`. `cargo fix`
earlier inserted the import in a position that violated the grouped
ordering.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:23:39 -04:00
Bryan Helmkamp
04d43785e6
refactor(server): drop EnvSource trait for direct HashMap passing
After StubEnv was removed, the EnvSource trait had only two impls
(ProcessEnv unit struct and a blanket HashMap impl) and tests already
passed HashMaps. Replace the trait with a free `process_env_snapshot()`
function and take `HashMap<String, String>` by value in
`ServerSecrets::load` and the startup validators.

Also flatten `StartupResolution` to a `(AuthMode, ServerSecrets)` tuple
and drop the `StartupValidationError` wrapper in favor of
`anyhow::Result`, and inline the `*_with_lookup` test-only wrappers in
`spawn_env.rs` so tests call `apply_allowlist` directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:22:35 -04:00
Bryan Helmkamp
4318e07ad7
simplify: drop with_server_mode_from_loaded_settings test seam
The cfg(test) helper only re-implemented the production `with_server_mode`
struct literal so tests could inject settings without disk I/O. Three
tests used it, but each one was self-referential — asserting what the
helper itself does rather than exercising production code. Remove the
helper and those three tests.

`synthetic_context_with_settings` is no longer needed either (its
flexibility only mattered for the removed tests); collapse it into
`synthetic_context`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:21:48 -04:00
Bryan Helmkamp
c52e89438b
simplify: fold ResolvedBaseContext into CommandContext
`ResolvedBaseContext` was a staging struct that held pre-context
settings so main.rs could read `user_settings.cli` before committing
to a full `CommandContext`. Now that install no longer needs the
indirection, the staging step doesn't earn its weight.

Give `CommandContext` a direct `from_disk(cli_layer, process_local_json)`
constructor that does the load + printer derivation + struct build in
one shot. main.rs builds one `base_ctx` before the dispatch match and
every arm borrows it — the `build_base_ctx` closure and ~20 duplicate
`let base_ctx = build_base_ctx()?;` lines disappear.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:17:27 -04:00
Bryan Helmkamp
967b1efe76
simplify: migrate install to CommandContext and delete cli_settings local
Public entry points (`execute`, `run_install`, `run_install_github_command`,
`run_install_inner`, `run_install_github_inner`) now take `&CommandContext`
instead of a 4-tuple of `(cli, cli_layer, process_local_json, printer)`.
Extract cli/printer/json from the context once at the top of each.

The nested doctor invocation inside run_install_inner previously built a
fresh `ResolvedBaseContext::from_disk(...).to_context()` with
`process_local_json = false`. Doctor only reads the resolved output
format (`base_ctx.json_output()`), not the invocation flag, so passing
the parent ctx through is behaviorally equivalent and avoids a second
disk load.

main.rs drops the `cli_settings` local entirely — no remaining command
needs it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:12:21 -04:00
Bryan Helmkamp
d51c6dfb76
simplify: migrate exec/parse/workflow/upgrade/uninstall to CommandContext
These top-level commands still took `&CliNamespace` + `Printer`
separately. Thread `&CommandContext` through the public entry points
and pull what's needed (`user_settings().cli`, `printer()`,
`json_output()`) from the context:

- parse: both args were unused — drop entirely.
- workflow list/create: use `ctx.json_output()` / `ctx.printer()`.
- exec: bind `cli = &ctx.user_settings().cli` at the top; drop unused
  printer param.
- upgrade: extract cli/printer inside run_upgrade; leave the private
  run_upgrade_brew helper with its existing signature (unit tests use
  `CliNamespace::default()` directly).
- uninstall: use `ctx.json_output()` / `ctx.printer()`.

Install remains on the old signature — its nested callback structure
makes a larger refactor than this simplification pass warrants.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:06:46 -04:00
Bryan Helmkamp
e7099adf3d
refactor(server): drop StubEnv newtype for HashMap EnvSource impl
StubEnv was a thin newtype only used by tests but compiled into every
build. Implementing EnvSource directly on HashMap<String, String> lets
test sites pass a HashMap and removes the type entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:03:00 -04:00
Bryan Helmkamp
f5758d99d0
refactor(config): collapse settings resolver 2026-04-23 08:00:56 -04:00
Bryan Helmkamp
4a696e0706
simplify: inline rendered_config into execute
The helper was a 2-line indirection with a single caller. Inline its
body so the whole command fits in one function.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:00:43 -04:00
Bryan Helmkamp
b98513875a
simplify: return derived context from load_pr_record
`load_pr_record` built a `with_target`-derived context internally and
threw it away, forcing callers to re-derive or fall back to `base_ctx`.
Return the context alongside the record and let close/merge/view use
it directly for printer/json access and github-credentials lookup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:00:02 -04:00
Bryan Helmkamp
a8623fb996
simplify: flatten commands/store/ after dump rename
The `fabro store dump` -> `fabro dump` rename left `commands/store/` as a
vestigial directory with a stale one-line `StoreRunExport` alias. Move
`dump.rs` and `rebuild.rs` up to `commands/`, import `RunDump` directly,
rename `dump::dump_command` -> `dump::run`, and clean up stale docs and
a noise test that only asserted clap's default error output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:58:26 -04:00
Bryan Helmkamp
72894b3c46
simplify: drop CommandContext::base in favor of ResolvedBaseContext
`CommandContext::base` had a single caller (install.rs) and duplicated
the disk-load path that `ResolvedBaseContext::from_disk` already
provides. Route install through `ResolvedBaseContext::from_disk(...).to_context()`
and drop the standalone constructor. `base_with_settings` stays as the
private shared helper behind both `to_context` entry points.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:54:53 -04:00
Bryan Helmkamp
2e07cf10a1
simplify: borrow cli_settings from ResolvedBaseContext instead of cloning
The `cli_settings` local was a standalone clone of `user_settings.cli`.
Drop the clone and rebind it as `&resolved_base.user_settings().cli`
inside the async block — all callers already borrowed it anyway.
Pre-async uses inline `resolved_base.user_settings().cli.<field>`
directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:52:06 -04:00
Bryan Helmkamp
56667c17a8
fix(server): cover secret boundary enforcement 2026-04-23 07:51:28 -04:00
Bryan Helmkamp
b08cb7631b
simplify: pass CommandContext to secret subcommands
The secret dispatcher pre-computed json/printer and threaded them into
every subcommand. Pass the context directly so each subcommand pulls
what it needs, dropping the fabro_util:🖨️:Printer import from
three files along the way.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:50:41 -04:00
Bryan Helmkamp
80a36d1fca
simplify: pass CommandContext to runs archive/rm helpers
`run_bulk` and `remove_from` took separate `json: bool` + `printer`
parameters. Thread the context through instead and pull json/printer
out of it inside the helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:49:07 -04:00
Bryan Helmkamp
c87e282f8c
simplify: gate with_server_mode_from_loaded_settings behind cfg(test)
The helper only exists to let tests inject pre-loaded settings. Inline
the struct-literal into the sole production caller and mark the helper
`#[cfg(test)]` so the test-seam intent is explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:48:04 -04:00
Bryan Helmkamp
cb9b762119
feat(cli): rename store dump to dump 2026-04-23 07:48:03 -04:00
Bryan Helmkamp
5041669d6d
plans 2026-04-23 07:45:26 -04:00
Bryan Helmkamp
eee89aa8e1
Merge remote-tracking branch 'origin/main' 2026-04-23 07:36:48 -04:00
Bryan Helmkamp
897b818a9e
simplify: introduce CommandContext::json_output() accessor
The check `ctx.user_settings().cli.output.format == OutputFormat::Json`
(and its `!=` variant) was repeated 51 times across 36 files. Add a
`json_output()` method on CommandContext and replace every call site.
`cargo fix` handles the now-unused `OutputFormat` imports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:34:11 -04:00
Bryan Helmkamp
4fa5896d78
refactor(cli): narrow base context construction 2026-04-23 07:33:21 -04:00
Bryan Helmkamp
b794ec5343
fix(cli): suppress preview open for JSON output 2026-04-23 07:29:37 -04:00
Bryan Helmkamp
07d4aee67c
simplify: drop redundant CommandContext derivations and guards
- ssh/graph: remove `explicit_json_requested() &&` guard before
  `require_no_json_override()` (the call already no-ops without --json).
- pr close/merge/view: drop the outer `with_target` derivation that was
  used only for `printer()` and the output format — both match base_ctx,
  so the derivation was an unused disk-read + settings re-merge.
- command_context tests: collapse `synthetic_context` to delegate to
  `synthetic_context_with_settings`, removing duplicated struct literals.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 07:28:54 -04:00
Bryan Helmkamp
e6e091fe8e
refactor(server): lock down server secrets 2026-04-23 07:15:27 -04:00
Bryan Helmkamp
2b933597b2
refactor(cli): finish command context cleanup 2026-04-23 07:14:32 -04:00
fabro-releases[bot]
7fd8f5a57b Bump version to 0.212.0-nightly.0 2026-04-23 09:45:03 +00:00
Bryan Helmkamp
fbe0bdfbc1
refactor: align cli command boundaries on CommandContext 2026-04-23 01:08:14 -04:00