fix(linux,wsl,mcp,envfile): window controls, WSL path bridge, MCP param naming, and .env write gating - #19
Merged
Merged
Conversation
Phase A diagnosis from docs/plans/issue-6-oversized-cursor-linux.md could not reach a verdict: this environment is WSL, and CLAUDE.local.md explicitly documents WSLg as unsupported for reproducing/verifying this bug (needs a real X11/Wayland desktop session). Static analysis ruled out the app-side candidates (no cursor: CSS rules, no zoom/transform/ image-rendering hacks, no set_zoom call, no custom cursor assets, no GTK/XCURSOR env vars set in lib.rs), but D1/D2/D3/D4/D6 all require a live compositor and remain unrun. No Phase B code change is warranted without a confirmed H3/H4/H5 verdict. Posted findings + needs-linux-verifier label on issue #6 instead of guessing a cause. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extend the custom titlebar controls to Linux (issue #5), matching the Windows gate. Replaces the isWindows boolean + useEffect with a platform-derived Chrome config computed via a lazy useState initializer (platform() is synchronous in Tauri v2, so the effect + one-frame no-controls flash is unnecessary). macOS behaviour is unchanged by design (see plan §4.3) and maximize stays out of scope (§4.4). Also adds focus-visible outline styling to the minimize/close buttons so keyboard focus is visible (§4.5) — previously only hover: classes existed. No Rust changes: existing capabilities (allow-minimize, allow-close, allow-start-dragging) are not platform-scoped and already apply on Linux. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…10) crypt_env_inject_environment and crypt_env_generate_example_env were the only two MCP tools naming the environment identifier `id`, diverging from every other environment-scoped tool's `environment_id`. An LLM caller that inferred the parameter name from the majority would pass `environment_id` to these two, have it silently ignored, and — when project/environment were also present — inject a different environment's full decrypted variable set with no error. Both schemas now advertise `environment_id` with the canonical wording used everywhere else. `resolve_environment_id` accepts `id` as an unadvertised, deprecated alias for the 1.0.x line only (removed in 1.1.0, marked DEPRECATED in-source); using it still succeeds but appends a fixed deprecation notice to the tool response. The ambiguity error in crypt_env_inject_env_by_name now names environment_id too. Adds an in-crate #[cfg(test)] mod tests (cargo test --bin crypt-env-mcp) asserting every environment-scoped tool declares environment_id and not a bare id, that the five item/category/workspace tools keep bare id, and that the resolver prefers environment_id over the alias. Updates docs/reference.md and adds a CHANGELOG 1.0.2 entry. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ect names Issue #12: adds idx_environments_name_nocase (project_id, name COLLATE NOCASE) and retrofits the same pre-check to idx_projects_name_nocase, so an install that already holds a case-colliding pair (e.g. MyApp/myapp, production/Production) is deterministically deduped (lowest id keeps its name, losers get -2/-3 suffixes, audit persisted under settings key env_name_dedup_v1) instead of bricking VaultDb::open. Unique-constraint violations are now detected via sqlx's is_unique_violation() and mapped to a stable "conflict:" sentinel string instead of substring-matching sqlx's error text, closing a raw-SQL leak in the 500 fallback path. project::resolve_environment now rejects ambiguous case-insensitive project/environment matches instead of silently taking the first one (SQLite NOCASE folds ASCII only, so non-ASCII collisions like PRODUCCIÓN/producción survive the index and need this Unicode-aware check) — the HTTP layer maps that to 409 AMBIGUOUS_SCOPE. New src-tauri/tests/environment_naming.rs covers T1-T8/T11 from the plan plus a projects-side dedup case; sqlx added to [dev-dependencies] to seed pre-collision state that's otherwise unreachable through the public API once the index exists. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…r (issue #3, Phase 1) Adds a new `wsl` module (`wsl_list_distros`, `wsl_distro_home`) so Windows users with projects living inside a WSL2 distro can attach a project's .env by picking a distro instead of typing `\\wsl.localhost\...` UNC paths by hand. `project_pick_env_path` gains an optional `start_dir` to seed the native dialog there; the environment editor gets a "WSL" button beside the existing browse control, gated to Windows and non-empty distro lists. Also lands the data-loss gate this feature's own plan flagged as non-negotiable: `inject_environment`'s .env read now distinguishes `ErrorKind::NotFound` (start from empty) from every other error kind (abort with an error) instead of treating any read failure as "file is empty" and silently overwriting it — the most likely real failure mode once WSL/UNC paths are reachable from the picker. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…bering (#8) Adds a shared envfile module (std-only leaf: inspect/commit/marker policy) that every filesystem sink writing an env file now goes through before any secret is decrypted. A resolved path that already exists and doesn't carry the crypt-env marker is refused (409 TARGET_EXISTS) unless the caller opts into `overwrite: true`, which first preserves the prior bytes in `<path>.bak` (refusing again with 409 BACKUP_EXISTS if one is already there). - POST /fill, POST /environments/:id/example, POST /environments/:id/inject all gain `overwrite` and surface `backup`/`unmanagedPaths` in responses. - inject_environment restructures into resolve -> gate -> decrypt -> write, with the gate strictly before decryption; owner-configured environment.paths[] are grandfathered (reported, not blocked) per the upgrade-safety trade-off, while output_path/output_dir-derived paths are hard-gated. - TempEnvFile now delegates to envfile::commit (fixes the write-then-chmod window) and, on the error path, truncates rather than removes a pre-existing file it didn't create. - CLI `fill --force`, MCP `overwrite` param + 409 handling on the three affected tools, and a GUI confirm-before-overwrite dialog backed by a new environment_inject_preview command. - Replaces the `read_to_string(..).unwrap_or_default()` at the old project/mod.rs:328 (which turned any read error, not just NotFound, into "treat as empty") with envfile::inspect's explicit NotFound/other-error split — this also happens to be the fix for the silent-overwrite failure mode on a stopped-WSL-distro UNC path that issue #3 flags as blocking. Explicitly out of scope: path traversal/containment validation on output_dir (issue #7's territory — see envfile/mod.rs's doc comment on the boundary). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…overage baseline; drop stale Postman collection Implements docs/plans/issue-11-test-coverage-and-postman.md: a crate::test_support harness (TestVault fixture + router()/req() oneshot helpers) so every module's private/pub(crate) logic is unit-testable without a Tauri runtime or live socket, plus 119 new test cases across api/, project/, vault/, db/, share/, the CLI's scope resolver, and the MCP binary's environment-picking logic (17 -> 136 total). Two pure refactors enable the harness, both behavior-preserving: - api::build_router / ApiState::new extracted from start_server (pub(crate), not pub — zero external API widening). - vault::set_item_global's fork logic extracted the same way create_project_item already was, so the Tauri command becomes a thin delegating wrapper. - crypt-env-mcp's resolve_environment_id split into network-fetching + pure pick_environment_id, mirroring the plan's resolve_environment_id split. Deletes the stale Postman collection (asserted a removed /health field, every request 422'd without the mandatory scope params) and replaces it with curl examples + the executed api::tests::* suite in docs/reference.md. Adds .github/workflows/test.yml running cargo test on push/PR with an informational cargo-llvm-cov summary step. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lity, sentinel visibility
Addresses adversarial review of the issue-12 case-insensitive-uniqueness
work:
- Critical: next_free_{project,environment}_name folded the rename
candidate with Rust's to_lowercase() (full Unicode) but compared it
against SQLite's own LOWER(name) (ASCII-only). For a non-ASCII base
this made the two folds disagree, so a real collision against a
pre-existing suffixed sibling (e.g. producciÓn-2) was missed, the
"free" candidate was accepted, and the following UPDATE then hit the
table's exact-match UNIQUE constraint -- init_schema returned Err and
the vault could not be opened at all. Fixed by comparing via
LOWER(name) = LOWER(?) so both sides are folded by SQLite consistently.
Added a regression test reproducing the exact crash shape.
- env_name_dedup_v1 (the sole reversal path for an otherwise
irreversible rename) is now persisted per-rename, inside the dedup
loops themselves, instead of batched and written only after both
CREATE UNIQUE INDEX statements succeed -- a renamed row can no longer
land in the DB without also being recorded, even if a later migration
step fails or the process is killed mid-run.
- PROJECT_NAME_CONFLICT bumped from private to pub (matching
ENVIRONMENT_NAME_CONFLICT, also bumped from pub(crate) to pub): each
src/bin/* target is its own crate separate from crypt_env_lib, so
pub(crate) was invisible cross-crate. Exported
project::AMBIGUOUS_MATCH_PREFIX so the ambiguity sentinel is a named
constant instead of a bare string literal at every match site.
- persist_rename_report no longer silently discards prior rename
history on a JSON parse failure -- logs to stderr and preserves the
raw value verbatim under env_name_dedup_v1_corrupt before starting a
fresh list.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
# Conflicts: # src-tauri/Cargo.toml
# Conflicts: # src-tauri/src/bin/crypt-env-mcp.rs
# Conflicts: # docs/reference.md # src-tauri/src/bin/crypt-env-mcp.rs # src-tauri/src/lib.rs # src-tauri/src/project/mod.rs
The five branches in this batch add four integration targets under src-tauri/tests/ (environment_naming, path_containment, project_relay, vault_integration). `cargo test --lib --bins` builds none of them, so they would have been invisible to CI while appearing to be covered. Refs #11
…nt-fixes # Conflicts: # docs/reference.md # src-tauri/src/bin/crypt-env-mcp.rs # src-tauri/src/project/mod.rs
A regex-based bulk merge-conflict resolution (main -> tier2) swallowed content between adjacent conflict hunks in two spots: the duplicate `mod test_support` declaration in lib.rs, and the doc-comment + pick_environment_id signature plus the entire append_scope_params/ is_safe_env_key/pick_environment_id test block in crypt-env-mcp.rs. Restored both so the merge commit actually compiles and tests pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Five independent fixes with no cross-dependencies. Stacked on #11/#12 (PR #18) only because they share the test harness.
Closes #5
Closes #8
Closes #10
Closes #3
Refs #6
Type of Change
What Changed
#5 — window controls now render on Linux as well as Windows; platform decision moved into a pure
chromeFor(os)and out of auseEffectthat left the first paint control-less. Adds focus-visible outlines.#3 (Phase 1) — new
wslmodule (wsl_list_distros,wsl_distro_home) and a "WSL" button beside the browse control, so Windows users with projects inside WSL2 pick a distro instead of typing\\wsl.localhost\.... Also lands the data-loss gate its own plan made non-negotiable:inject_environment's read now distinguishesNotFound(start empty) from every other error kind (abort), instead of treating any read failure as "file is empty" and overwriting. Phase 2 (running the CLI inside WSL) is explicitly not committed — three blocking design questions, one with no design at all. Worth a separate issue if you want it tracked.#10 —
crypt_env_inject_environmentandcrypt_env_generate_example_envwere the only two MCP tools naming the environment identifierid. An LLM inferring the name from the other 13 tools would passenvironment_id, have it silently ignored, and — whenproject+environmentwere also present — inject a different environment's decrypted variables with no error. Both now advertiseenvironment_id. Bareidstays as an unadvertised deprecated alias for 1.0.x (removed in 1.1.0); calls using it succeed but the response appends a deprecation notice. Tests assert no environment-scoped tool declares a bareid.#8 — new
envfilemodule. Writes to a caller-suppliedoutput_path/output_dirrefuse to clobber a file crypt-env didn't create (no# crypt-env:marker) unlessoverwrite: true, in which case the prior contents are preserved in<path>.bak. The gate runs strictly before any decryption. Owner-configuredenvironment.paths[]are grandfathered — reported inunmanagedPaths, never hard-gated — so existing installs don't break on upgrade. Addsenvironment_inject_previewfor a pre-inject confirm dialog.#6 — README "known issue" note only. Root cause is unconfirmed and the plan's Phase A diagnosis requires a native X11/Wayland session (WSLg is not a valid repro environment), so this does not close #6.
Behavior change worth calling out
Under #8, injecting into a pre-existing configured
.envthat lacks the marker no longer merges into it — the file is backed up to.bakand rewritten from the environment's keys, then self-heals (the marker makes every subsequent inject a normal merge). Unrelated keys survive in the backup, not in the live file. This supersedes the old merge contract and its test.Testing
cargo checkpassescargo test --lib --bins --tests— 221 passed, 0 failedtsc --noEmitcleanpnpm tauri dev(not run — no Windows toolchain this session). Linux: window has no close/minimize buttons (native controls missing) #5 and WSL bridge: reach project .env files across the Windows/WSL boundary #3's UI need a real Linux/Windows GUI pass before merge.Security Implications
#8 closes a silent-truncation path:
/fill,/inject, and/examplepreviously wrote tooutput_pathwith a plainstd::fs::writeand no existence check, so pointing one at an unrelated file truncated it. Decryption now happens only past the gate, so a refused write never decrypts..bakfiles are created0600on Unix. The marker contains project and environment names but never secret material.