Skip to content

Focused security, save and process verification

Candidate branch: audit/mellow-20261011. Starting/source HEAD at this review: 9e42e17e8307d0171525bf325efbaf8d85178383. Results below describe the working candidate; HEAD alone does not contain these uncommitted changes. The parent tracker records the eventual commit and archive identities.

Host: native macOS 26.6.2 (25G83), ARM64. Final focused commands use Rust 1.90.0 (1159e78c4 2025-09-14) through a command-scoped PATH. The initial module suites are native macOS source unit/protocol tests, not signed distribution or paid-provider qualification. The Linux VM addendum below is separate evidence.

Executed checks

export PATH=/Users/rukmini/.rustup/toolchains/1.90.0-aarch64-apple-darwin/bin:$PATH
cargo test --locked --bin mellow buffer::tests -- --nocapture
cargo test --locked --bin mellow format::tests -- --nocapture
cargo test --locked --bin mellow ai:: -- --nocapture
cargo test --locked --bin mellow claude:: -- --nocapture
cargo test --locked --bin mellow file_ops:: -- --nocapture
cargo test --locked --bin mellow execution_trust -- --nocapture
cargo test --locked --bin mellow format_on_save -- --nocapture
cargo check --locked --all-targets
cargo clippy --locked --all-targets --all-features -- -D warnings
cargo fmt --all -- --check
Focused suite Native result
Buffer/save 42 passed, zero failed/ignored
Formatter/process 13 passed, zero failed/ignored; inherited-pipe test reaches the ten-second deadline
AI/privacy/streaming 28 passed, zero failed/ignored
Claude protocol 4 passed, zero failed/ignored
Recoverable trash 9 passed, zero failed/ignored
Outside-root execution boundary 3 passed, zero failed/ignored
Existing format-on-save workflows 5 passed, zero failed/ignored (including one settings test)

The module suites total 96 tests; the final outside-root pass ran an additional 8 tests. cargo check --locked --all-targets passed after the cancellable APIs and clipboard wiring compiled. Pinned all-target Clippy and full formatting checks subsequently passed too, as did git diff --check. These checks must be rerun after the final candidate changes. Release build and packaging were not executed in this focused review; their results belong to the candidate's overall verification record. Intermediate failures during concurrent edits are not substituted for passing final runs.

A direct Tokio dependency exposes the runtime already present transitively via Reqwest. cargo check --offline generated the lock update: one addition to Mellow's dependency list, no package-version changes. Cargo.lock was not edited by hand.

Regression fixtures and the failures they guard

These test names identify the exact regression scenarios. Original-release red reproductions of F01–F04 are in the supplied audit. This focused session ran the current regression suite; it did not re-execute every test against the original release or claim an independently recorded red run for every new test.

Defect/boundary Exact regression Original or adverse behavior exercised
F01 shell_commands_reject_every_terminal_control_before_cleaning All C0, DEL and C1 characters, including controls that trimming previously hid, are refused before a suggestion can be reviewed/copied.
F02 redirects_never_receive_the_claude_key, ai_requests_do_not_follow_redirects_to_another_host, debug_output_never_shows_the_api_key Two loopback endpoints and dummy keys; redirects receive zero connections; Debug omits stored keys and URL credentials/query secrets.
F03 atomic_save_preserves_extended_attributes_owner_group_and_mode, atomic_save_preserves_native_acl_and_nondefault_group Native Darwin xattr, ACL and supplementary-group preservation on an atomic replacement. The alternate group branch ran without the unavailable-group message.
F03 saving_a_hard_link_refuses_without_mutating_either_name Hard-link divergence is prevented by refusal with Save As guidance; both names, original inode and dirty buffer remain intact.
F03 race save_baseline_cannot_adopt_a_writer_after_the_conflict_check A writer changes the file between the conflict check and save setup; the accepted baseline is retained and the later version is not overwritten.
F03 race saved_snapshot_is_bound_to_the_written_inode_not_a_later_path A replacement path cannot be adopted as if its bytes were the editor's saved version.
F03 fallback in_place_fallback_refuses_a_replaced_file_or_new_symlink_before_truncation Replaced content and a newly introduced symlink are refused before truncation.
F03 Save As publishing_a_new_file_never_replaces_one_that_appeared_meanwhile A destination appearing after initial inspection is retained. Unsupported exclusive atomic publication fails instead of falling back to check-plus-rename.
F04 exited_formatter_with_inherited_output_pipe_obeys_deadline A direct child exits while a descendant retains stdout/stderr; partial output is rejected and the operation ends at its single deadline.
F04 input_backpressure_obeys_one_deadline, output_is_bounded_before_acceptance, cancellation_kills_the_formatter_and_its_descendant Blocked stdin, endless output and cancellation; a delayed descendant cannot create its marker.
Process output bounded_tool_runner_preserves_failed_task_output_and_empty_clipboards, formatter_error_never_injects_terminal_control_characters Failed tasks retain stdout/stderr for diagnostics; an empty successful clipboard is valid; formatter status text strips terminal controls.
M31 cancellation_aborts_waiting_for_response_headers, cancellation_aborts_the_inflight_transport_and_closes_its_socket Cancellation during send/header wait and a silent body returns within one second and the local server observes socket EOF.
M31 streaming_openai_reports_provider_usage_and_preserves_unicode_deltas, streaming_claude_merges_usage_and_refuses_cut_off_text UTF-8 split across tiny writes, LF/CRLF SSE frames, provider usage and refusal of length-limited text.
M31/M30 streaming_missing_completion_marker_never_accepts_partial_proposals A partial edit proposal is never accepted without a completion marker; already reported usage survives the error.
M31 stream_errors_and_limits_withhold_credentials_and_reject_hostile_shell_controls Credential-echoing error events, response caps and a streamed CR in a shell command are rejected.
M30 json_usage_and_inline_output_follow_the_same_bounded_transport Non-streaming JSON fallback reports actual supplied usage; Claude cached input tokens are included; absent usage remains unknown.
M29 additive_deny_globs_match_paths_and_defaults_cannot_be_removed, user_local_policy_is_bounded_and_resolves_file_aliases_fail_closed User globs, aliases, malformed patterns, excessive size, nonprivate ownership/write permissions and a symlink policy file. Defaults cannot be removed.
M29 staged_alias_privacy_uses_the_repository_root_not_process_cwd Git diff paths resolve against the actual repository before canonical privacy checks, even when the editor's process cwd differs.
Trash trash_survives_restart_and_restore_never_overwrites_new_work, exclusive_rename_never_replaces_a_directory_or_dangling_symlink New destination work, directories and dangling symlinks survive attempted restore/rename.
Trash restore_refuses_changed_manifest_or_item_after_review, restore_refuses_a_replaced_original_parent A reviewed entry or original parent cannot silently change identity before restore.
Trash trash_manifest_never_follows_symlinks_or_reads_unbounded_data, trash_refuses_symlink_root_and_nonprivate_entries, trash_rejects_acl_access_even_when_mode_bits_are_private Manifests are bounded/non-symlink/regular/private; directories must be private and user owned. Darwin ACL grants are refused even when POSIX mode is 0700.

User-owned additive privacy policy

Optional file: settings::config_root()/ai-deny.conf (normally ~/.config/mellow/ai-deny.conf; XDG overrides apply). Missing file means the built-in policy remains active. It is never loaded from the project.

Example:

# Additional exclusions; defaults remain mandatory
*.private
private/**
**/customer?.json

One case-sensitive glob per line. Blank lines and lines starting with # are ignored. * matches within one path component, ? matches one character in a component, ** crosses directories, and **/ can match zero directories. Patterns match complete suffixes starting at a path-component boundary, against both the displayed path and canonical existing target. Leading / is ignored; there is no allow/negation rule. Use / separators. Bracket classes, brace expansion, backslash escapes, control characters and leading ! are unsupported and cause refusal, rather than silently weakening the policy.

Limits: UTF-8 regular file, at most 64 KiB, 256 patterns, 1024 bytes per pattern. On Unix it must be owned by the current user, not writable by group/others and not a symlink. Read/parse/ownership failures refuse context before network use. Git drafting checks staged paths relative to the repository root and checks the full diff before truncating its displayed payload. Defaults and credential-like text checks remain mandatory. This is a conservative deny policy, not proof that all possible secrets or hard-link aliases can be identified.

Transport, save and trust limits

HTTP redirects remain disabled for both transport families. Each request caps its output-token request; total HTTP envelopes are limited to 512 KiB and parsed text to 64,000 characters. SSE preview deltas are data only: a completed, valid proposal still requires ordinary review/acceptance before a buffer edit. Local transport cancellation drops pending HTTP futures with a 25 ms polling interval. A system DNS worker may finish separately; runtime shutdown does not block on it. Provider-side processing or billing may already have occurred.

Usage is exactly what the provider reports, including partial usage available before errors. Missing input/output fields remain unknown. These observations cannot establish a monetary ceiling, refund or provider compliance with a token request. Parent-owned session request caps and post-response token budgets must be qualified by their own app tests.

Safe save preserves supported metadata or refuses publication. Hard links are refused with an independent Save As route. Save As requires exclusive publication; filesystems without that support are refused. A bind-mount in-place fallback retains a recovery copy but is not crash-atomic. The final conflict-check to rename window cannot provide compare-and-swap serialization against an uncooperative external writer. Process groups terminate cooperating descendants; a tool that intentionally detaches into another session is not sandboxed.

Trash performs an exclusive same-filesystem move; cross-device moves fail with the source retained, with no copy/delete fallback. Restore validates the reviewed manifest/item and original parent identities and refuses existing destinations. Directory/manifest trust checks are not a sandbox against an attacker already running as the same OS user. Manifest corruption blocks listing with an explicit error; the stored item is retained for manual repair.

The focused app review identified stale clipboard-focus/progress boundaries; these were sent to the parent for implementation and regression coverage. Their final disposition belongs in the overall tracker.

The outside-root execution fix is implemented in the formatter and language service entry points. Workspace trust applies only when the existing target and displayed parent both resolve within the canonical workspace. New Save As paths require an existing canonical parent within it; unresolved/dangling aliases and component-prefix siblings are refused. Outside files remain editable and save through the core disk-save path, with formatting restricted. Active outside documents stop the current language service and are excluded from multi-tab synchronization. There is no persistent trust expansion or OS sandbox claim.

execution_trust_does_not_follow_outside_files_or_symlink_aliases covers a direct outside Rust document and an in-root symlink to it: a dummy formatter's marker is never created, no service document is opened, the edit is saved and the status explains the restriction. The service assertions prove refusal before open_document; they do not claim a packaged dummy-server process run. execution_trust_allows_new_save_as_inside_the_canonical_root proves that a new in-root Save As still invokes the dummy formatter and clears dirty state. execution_trust_refuses_unresolved_and_component_prefix_paths checks sibling prefixes, missing parents, dangling aliases, outside symlink parents and an outside-parent symlink back into a trusted file. The existing rustfmt fixture now declares its trusted workspace explicitly. These three tests and the five existing format-on-save tests passed on the native host with pinned Rust 1.90. Path checks cannot prevent a hostile same-user process from replacing paths between validation and a tool launch, and trusted tools can still access dependencies, global configuration and services beyond the workspace.

Optional actual Linux file-mount gate

saving_a_bind_mounted_file_rewrites_it_in_place is now explicitly ignored in ordinary Linux unit runs. The previous version returned success when mounting was unavailable; the optional gate instead requires MELLOW_BIND_MOUNT_FILE=/fixture/mounted.txt and an actual single-file Docker bind mount with exactly mellow-bind-mount-fixture: original\n as its contents. Use only a disposable synthetic host file: this test changes its bytes and mode. Run the exact test with --ignored --exact --nocapture inside the Linux image.

The test directly observes replacement rename returning EBUSY, then checks Mellow's SaveMode::InPlace, saved bytes, cleared dirty state and settled conflict snapshot. Device, inode, owner, group and mode remain unchanged. User xattrs are checked if the mounted filesystem supports them; unsupported xattrs produce an explicit unexercised-branch message. Temporary save files must be absent. This buffer-level gate does not exercise the app recovery journal.

Executed result: 1 passed, zero failed/ignored on Linux 7.0.12-linuxkit aarch64, in Docker's Linux VM on the macOS ARM64 host. Rust was 1.90.0 (1159e78c4 2025-09-14), host aarch64-unknown-linux-gnu. Image mellow-audit-20261011-mellow-dev:latest had ID sha256:9fa16bd142e0ce7d51d0345e260a3cc00cdc4cbdbd315a5b291e910c22463fcb. The mounted VirtioFS host file did not support user xattrs; the test printed the exact unexercised-branch message. The preserved inode/mode checks and actual EBUSY fallback ran, and a host-side read confirmed updated mellow-bind-mount-fixture: original\n and mode 0640.

A separate Linux VM cargo test --locked --bin mellow buffer::tests -- --nocapture run completed 41 passed, zero failed, 1 ignored. The ignored test was the explicit mount gate above, which passed in its separate invocation. atomic_save_preserves_linux_extended_attributes passed on the container's ordinary temporary filesystem; this does not fill the unsupported mounted VirtioFS xattr branch.

The successful invocation used a disposable mktemp host file with the marker above, mounted individually at /fixture/mounted.txt, and these command arguments:

docker run --rm \
  -e CARGO_HOME=/cargo -e CARGO_TARGET_DIR=/target \
  -e MELLOW_BIND_MOUNT_FILE=/fixture/mounted.txt \
  --mount type=bind,source="$PWD",target=/workspace \
  --mount type=bind,source="$mellow_mount_fixture",target=/fixture/mounted.txt \
  -v mellow-audit-20261011_mellow-cargo-registry:/cargo/registry \
  -v mellow-audit-20261011_mellow-cargo-git:/cargo/git \
  -v mellow-audit-20261011_mellow-target:/target \
  -w /workspace mellow-audit-20261011-mellow-dev:latest \
  cargo test --locked --bin mellow \
  buffer::tests::saving_a_bind_mounted_file_rewrites_it_in_place \
  -- --ignored --exact --nocapture

This verifies source behavior in an actual Linux-kernel file mount, not Linux hardware, an extracted distribution executable or native Linux ACL behavior.

Remaining qualification gates

  • Exact packaged executable PTY/security regressions and executable/archive digests.
  • Mounted VirtioFS user xattrs remain unsupported; normal Linux filesystem xattrs, actual POSIX ACL preservation and cross-owner refusal have separate final packaged VM evidence in DELIVERY.md.
  • Native macOS Intel and Linux x64/ARM64 candidate gates.
  • Cross-owner preservation where the current user cannot apply ownership; current tests exercised same owner plus supplementary group.
  • Live AI accounts/provider compatibility and provider-side cancellation/billing behavior.
  • Native signing/notarization and distribution install paths, governed by release authorization.
  • Real terminal/SSH/tmux, glyph shaping and assistive-technology evaluation; source/PTY text tests cannot establish those claims.

Final packaged Linux metadata evaluation

The accepted static musl ARM64 archive from clean 917238f preserved a real system.posix_acl_access ACL with named UID65534, owner/group/mode0640 and content across atomic inode replacement in an 80×24 PTY. A child UID65534 then tried to save a root-owned0666 synthetic file in a writable directory: Mellow refused ownership loss, retaining original bytes/inode/device/uid/gid/mode and no temporary remnants. Explicit error wording was captured at240×24. This executes Linux in Docker's ARM64 VM, not macOS or physical Linux hardware. Raw log and reproducible probe retain exact archive/binary digests, platform, observer corrections and final zero exit.