Skip to content

feat: auth vault phases 3+4 - failover, isolation, encryption (#23) - #34

Merged
getappz merged 3 commits into
masterfrom
worktree-auth-phase3-v2
Jul 7, 2026
Merged

feat: auth vault phases 3+4 - failover, isolation, encryption (#23)#34
getappz merged 3 commits into
masterfrom
worktree-auth-phase3-v2

Conversation

@getappz

@getappz getappz commented Jul 7, 2026

Copy link
Copy Markdown
Owner

auth run: wrap CLI with auto-failover on rate limit (5 retries).
auth isolate add/ls/delete: isolated HOME profiles for parallel sessions.
auth exec: run command with isolated profile HOME.
auth login: login flow with auto-backup on success.
auth_crypt: AES-256-GCM vault encryption with PBKDF2 key derivation.
Copilot added to auth catalog.
Daemon detection helpers for codex app-server.

Summary by CodeRabbit

  • New Features
    • Added passphrase-based encryption for stored vault auth data.
    • Introduced isolated profiles to run auth flows in separate home directories (add/list/delete, plus exec/login helpers).
    • Added an auth command runner that can detect rate limiting and automatically retry with profile rotation.
    • Updated and expanded Winget distribution assets and added an automated Winget manifest updater.
  • Bug Fixes
    • Improved auth restoration to safely skip files that can’t be decrypted, instead of failing the entire restore process.

auth run: wrap CLI with auto-failover on rate limit (5 retries).
auth isolate add/ls/delete: isolated HOME profiles for parallel sessions.
auth exec: run command with isolated profile HOME.
auth login: login flow with auto-backup on success.
auth_crypt: AES-256-GCM vault encryption with PBKDF2 key derivation.
Copilot added to auth catalog.
Daemon detection helpers for codex app-server.
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9075e794-12bd-4ac8-aef9-80fb80427df9

📥 Commits

Reviewing files that changed from the base of the PR and between 5c257ef and ec9b9cd.

📒 Files selected for processing (1)
  • .github/workflows/winget.yml
 _______________________________________________________________________________________________________________
< Make quality a requirements issue. Involve your users in determining the project's real quality requirements. >
 ---------------------------------------------------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
📝 Walkthrough

Walkthrough

Adds AES-256-GCM passphrase-based encryption for vault auth files, an isolate subsystem for running agents under redirected HOME directories, a retry-capable auth runner with daemon detection/reload, new CLI subcommands wiring these features, and new Winget package manifest files for v1.1.0.

Changes

Encrypted vault and isolated auth execution

Layer / File(s) Summary
AES-GCM encryption module
Cargo.toml, src/auth_crypt.rs
Adds aes-gcm and pbkdf2 dependencies and a new module deriving keys via PBKDF2-HMAC-SHA256 with encrypt()/decrypt() functions and get_passphrase(), plus unit tests.
Encrypted vault backup/restore integration
src/auth.rs
backup() and activate() now use auth_crypt to encrypt/decrypt auth files with an optional passphrase, skipping files on decryption failure; a "copilot" catalog entry is added.
Isolated auth environment subsystem
src/auth.rs
Adds isolate_add, isolate_ls, isolate_delete, auth_exec, and auth_login to create/manage isolate directories, symlink/copy shared host files, populate agent auth via activate_into(), and back up login results to the vault.
Auth runner with retry and daemon management
src/auth_runner.rs
Adds run() with rate-limit detection, cooldown/profile rotation retry logic, stderr capture, and daemon_running()/reload_daemon() helpers, with unit tests for exit categorization.
CLI command wiring
src/main.rs
Declares auth_crypt and auth_runner modules, adds AuthAction::Run/Isolate/Exec/Login and IsolateAction variants, and dispatches them to the new handler functions.

Estimated code review effort: 4 (Complex) | ~60 minutes

Winget Package Manifests

Layer / File(s) Summary
Winget manifests for v1.1.0
winget/getappz.agentflare.yaml, winget/getappz.agentflare.installer.yaml, winget/getappz.agentflare.locale.en-US.yaml
Adds version, installer (portable x64 with SHA-256), and en-US locale manifests for the package at version 1.1.0.

Estimated code review effort: 1 (Trivial) | ~3 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant auth as auth.rs
  participant auth_crypt
  participant Vault as Vault filesystem

  User->>auth: backup()
  auth->>auth_crypt: get_passphrase()
  auth_crypt-->>auth: passphrase or None
  alt passphrase present
    auth->>auth_crypt: encrypt(file bytes, passphrase)
    auth_crypt-->>auth: nonce||ciphertext
    auth->>Vault: write encrypted blob
  else no passphrase
    auth->>Vault: write plaintext
  end

  User->>auth: activate()
  auth->>Vault: read stored file
  alt passphrase present
    auth->>auth_crypt: decrypt(blob, passphrase)
    auth_crypt-->>auth: plaintext or None
    alt decryption fails
      auth-->>User: warn and skip file
    else success
      auth->>Vault: write decrypted file to HOME
    end
  else no passphrase
    auth->>Vault: write plaintext to HOME
  end
Loading
sequenceDiagram
  participant CLI
  participant auth_runner
  participant Agent as Agent process
  participant auth_db
  participant auth as auth.rs

  CLI->>auth_runner: run(agent, args, json)
  loop up to MAX_RETRIES
    auth_runner->>Agent: spawn_and_capture()
    Agent-->>auth_runner: exit code, stderr
    auth_runner->>auth_runner: categorize_exit()
    alt RateLimited
      auth_runner->>auth_db: record cooldown for profile
      auth_runner->>auth: rotate to next profile
      auth_runner->>CLI: log retry (unless json)
    else Success
      auth_runner-->>CLI: return
    else Failure
      auth_runner-->>CLI: exit(code)
    end
  end
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is mostly a bullet summary and omits the required Summary, Test plan, and Notes for reviewers sections. Rewrite it to match the template with a Summary, a filled test plan checklist, and Notes for reviewers covering risk areas and backward compatibility.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: auth failover, isolation, and encryption.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-auth-phase3-v2

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (1)
src/auth_runner.rs (1)

16-44: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider a short backoff between retries.

On a rate-limit, the loop rotates and immediately re-spawns. If rotation yields the same (or another already-limited) profile, it can burn all 5 retries near-instantly without giving quotas time to recover. A brief sleep (optionally increasing) before retrying would make the failover more effective.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/auth_runner.rs` around lines 16 - 44, The retry loop in
auth_runner::spawn_and_capture handling ExitKind::RateLimited rotates profiles
and immediately respawns, which can exhaust retries too quickly. Add a short
backoff before the next iteration in this branch, ideally after
auth_db::set_cooldown and crate::auth::rotate, and consider making it small but
increasing per retry so repeated rate limits have time to recover.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/auth_crypt.rs`:
- Around line 20-27: The interactive passphrase prompt in prompt_passphrase
currently uses stdin::read_line, which echoes the secret to the terminal.
Replace the input path with a hidden-entry reader such as rpassword so typed
characters are not displayed, while keeping the existing prompt and empty-input
handling in prompt_passphrase unchanged.
- Line 8: The PBKDF2 setup in auth_crypt uses a fixed compile-time SALT and a
low iteration count, so update the key derivation in
auth_crypt::encrypt/auth_crypt::decrypt to generate a unique random salt per
vault or per ciphertext and persist it alongside the encrypted payload, then
read it back during decryption. Also raise the PBKDF2 iteration count from the
current hardcoded value to a substantially higher one aligned with current
guidance, and keep the salt/iteration handling centralized so both encryption
and decryption stay in sync.

In `@src/auth_runner.rs`:
- Around line 65-72: The spawn-and-capture path currently panics on launch
failure via the spawn_agent expectation, which gives a bad CLI experience when
the binary is missing or not executable. Update spawn_and_capture to handle the
Command::new(...).spawn() failure gracefully, mirroring auth_exec: report a
user-friendly error, include the agent/binary context, and exit with a non-zero
status instead of panicking.
- Around line 129-149: `reload_daemon` only handles the `codex` agent, so
`claude-code` and other agents end up with empty process names and a bad
`pkill`/no-op path. Update the agent-to-process mapping in `reload_daemon` (and
keep it consistent with `daemon_running`) so `claude-code` is handled
explicitly, or add an early return for unsupported agents before invoking
`pkill`/`taskkill`. Use the existing `agent` match and `names` selection to
locate the fix.

In `@src/auth.rs`:
- Around line 881-894: The activate_into helper is copying vault files directly
with fs::copy instead of using the decrypt-aware restore flow. Update
activate_into to reuse the same passphrase-aware restoration logic as activate,
using the existing vault/profile handling in auth::activate and
profile_dir/catalog_for so files are decrypted before being written into
target_dir. Ensure the restore path handles both encrypted and unencrypted
backups consistently, rather than manually copying rel-targeted files.
- Around line 862-872: The post-login backup path in auth_login is writing vault
files with plain fs::copy instead of the encrypted backup flow, so update this
loop to use the same auth_crypt::encrypt-backed path as backup() and preserve
the shared format marker. Locate the logic that iterates CATALOG files after a
successful login, and replace direct copying of src to dest with the same
encryption/serialization step used by backup() so the new auth state is stored
encrypted at rest.
- Around line 803-835: Set the isolated home environment variables in auth_exec
and auth_login, not just HOME, because Windows programs may ignore HOME and read
USERPROFILE (and sometimes HOMEDRIVE/HOMEPATH) instead. Update the command
environment setup around std::process::Command in auth_exec and the login flow
in auth_login so the isolated profile directory is exposed through the
platform-appropriate home variables alongside HOME, using the existing
agent/profile directory logic as the source of truth.
- Around line 94-105: The backup logic in `backup()` currently writes auth file
bytes without any explicit format marker, so later readers like `activate()`,
`activate_into()`, and `auth_login()` can misinterpret plaintext vs encrypted
vault entries based only on the current passphrase state. Update the vault write
path in `backup()` to prefix each stored entry with a small header/version that
records whether the payload is plaintext or ciphertext, and then change the
corresponding read/restore paths to branch on that marker instead of inferring
from `get_passphrase()`. Use the existing `auth_crypt` and `vault` handling in
`src/auth.rs` as the main place to locate and apply the fix.

---

Nitpick comments:
In `@src/auth_runner.rs`:
- Around line 16-44: The retry loop in auth_runner::spawn_and_capture handling
ExitKind::RateLimited rotates profiles and immediately respawns, which can
exhaust retries too quickly. Add a short backoff before the next iteration in
this branch, ideally after auth_db::set_cooldown and crate::auth::rotate, and
consider making it small but increasing per retry so repeated rate limits have
time to recover.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6fe3a3a6-5493-41be-a919-5ef8a8c9ea01

📥 Commits

Reviewing files that changed from the base of the PR and between 58dccb6 and 5c257ef.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • Cargo.toml
  • src/auth.rs
  • src/auth_crypt.rs
  • src/auth_runner.rs
  • src/main.rs
  • winget/getappz.agentflare.installer.yaml
  • winget/getappz.agentflare.locale.en-US.yaml
  • winget/getappz.agentflare.yaml

Comment thread src/auth_crypt.rs
use sha2::Sha256;
use std::io::Write;

const SALT: &[u8] = b"agentflare-vault-salt-v1";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Hardcoded salt undermines PBKDF2; iteration count is low.

SALT is a fixed compile-time constant, so every installation derives the key solely from the passphrase. This removes the salt's purpose (per-vault uniqueness / precomputation resistance) and means a single precomputed dictionary attacks all vaults. Prefer a random per-vault (or per-file) salt persisted alongside the ciphertext, similar to how the nonce is already prepended in encrypt.

Additionally, 100,000 iterations is below current guidance for PBKDF2-HMAC-SHA256 (OWASP suggests ~600,000). Consider raising it.

Also applies to: 29-31

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/auth_crypt.rs` at line 8, The PBKDF2 setup in auth_crypt uses a fixed
compile-time SALT and a low iteration count, so update the key derivation in
auth_crypt::encrypt/auth_crypt::decrypt to generate a unique random salt per
vault or per ciphertext and persist it alongside the encrypted payload, then
read it back during decryption. Also raise the PBKDF2 iteration count from the
current hardcoded value to a substantially higher one aligned with current
guidance, and keep the salt/iteration handling centralized so both encryption
and decryption stay in sync.

Comment thread src/auth_crypt.rs
Comment on lines +20 to +27
fn prompt_passphrase() -> Option<String> {
print!("vault passphrase: ");
std::io::stdout().flush().ok();
let mut input = String::new();
std::io::stdin().read_line(&mut input).ok()?;
let pw = input.trim().to_string();
if pw.is_empty() { None } else { Some(pw) }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Passphrase is echoed to the terminal.

std::io::stdin().read_line echoes typed characters, so the vault passphrase is visible on screen (and any terminal capture). Use a hidden-input reader (e.g. the rpassword crate) for the interactive prompt.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/auth_crypt.rs` around lines 20 - 27, The interactive passphrase prompt in
prompt_passphrase currently uses stdin::read_line, which echoes the secret to
the terminal. Replace the input path with a hidden-entry reader such as
rpassword so typed characters are not displayed, while keeping the existing
prompt and empty-input handling in prompt_passphrase unchanged.

Comment thread src/auth_runner.rs
Comment on lines +65 to +72
fn spawn_and_capture(agent: &str, args: &[String]) -> (i32, String) {
let binary = find_binary(agent);
let mut child = Command::new(&binary)
.args(args)
.stdout(Stdio::inherit())
.stderr(Stdio::piped())
.spawn()
.expect("spawn agent");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Panic on spawn failure yields a poor CLI experience.

.expect("spawn agent") aborts with a Rust panic/backtrace if the binary is missing or not executable. Since find_binary falls back to the bare agent name, this is a reachable path. Prefer graceful error reporting + exit, consistent with auth_exec.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/auth_runner.rs` around lines 65 - 72, The spawn-and-capture path
currently panics on launch failure via the spawn_agent expectation, which gives
a bad CLI experience when the binary is missing or not executable. Update
spawn_and_capture to handle the Command::new(...).spawn() failure gracefully,
mirroring auth_exec: report a user-friendly error, include the agent/binary
context, and exit with a non-zero status instead of panicking.

Comment thread src/auth_runner.rs
Comment on lines +129 to +149
let names = match agent {
"codex" => &["codex"][..],
_ => &[],
};
// SIGTERM on Unix, taskkill on Windows
#[cfg(windows)]
{
for name in names {
std::process::Command::new("taskkill")
.args(["/IM", &format!("{name}.exe")])
.output()
.map_err(|e| format!("taskkill: {e}"))?;
}
}
#[cfg(not(windows))]
{
std::process::Command::new("pkill")
.args(names)
.output()
.map_err(|e| format!("pkill: {e}"))?;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

reload_daemon silently no-ops (or misfires) for non-codex agents.

daemon_running recognizes claude-code, but here names is &[] for anything but codex. On Unix this invokes pkill with no pattern, which returns a usage error rather than reloading anything; on Windows the loop body never runs. Either handle claude-code explicitly or return early for unsupported agents to avoid the empty-args call.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/auth_runner.rs` around lines 129 - 149, `reload_daemon` only handles the
`codex` agent, so `claude-code` and other agents end up with empty process names
and a bad `pkill`/no-op path. Update the agent-to-process mapping in
`reload_daemon` (and keep it consistent with `daemon_running`) so `claude-code`
is handled explicitly, or add an early return for unsupported agents before
invoking `pkill`/`taskkill`. Use the existing `agent` match and `names`
selection to locate the fix.

Comment thread src/auth.rs
Comment on lines +94 to +105
let passphrase = auth_crypt::get_passphrase();
for &rel in cat.files {
let src = home().join(rel);
let dest = vault.join(rel.rsplit('/').next().unwrap_or(rel));
if src.exists() {
fs::copy(&src, &dest).expect("copy");
let data = fs::read(&src).expect("read");
if let Some(ref pw) = passphrase {
let encrypted = auth_crypt::encrypt(&data, pw).expect("encrypt");
fs::write(&dest, encrypted).expect("write");
} else {
fs::write(&dest, data).expect("write");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant functions and show compact structure first.
ast-grep outline src/auth.rs --view expanded >/tmp/auth_outline.txt || true
sed -n '1,220p' /tmp/auth_outline.txt

# Show the relevant line ranges around the reported locations and related helpers.
nl -ba src/auth.rs | sed -n '70,130p'
echo '---'
nl -ba src/auth.rs | sed -n '170,220p'
echo '---'
nl -ba src/auth.rs | sed -n '850,910p'
echo '---'
nl -ba src/auth.rs | sed -n '1,70p'

Repository: getappz/agentflare

Length of output: 3521


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for any magic header / version marker / encryption marker logic.
rg -n "magic|header|version|marker|encrypted|decrypt|encrypt|passphrase|get_passphrase|fs::copy|activate_into|auth_login|backup\(" src/auth.rs src -g '!target' || true

# Inspect any types or helper functions that might tag encrypted payloads.
python3 - <<'PY'
from pathlib import Path
p = Path('src/auth.rs')
text = p.read_text()
for needle in ['get_passphrase', 'encrypt(', 'decrypt(', 'activate_into', 'auth_login', 'backup(']:
    idx = text.find(needle)
    print(f'[{needle}] first_occurrence={idx}')
PY

Repository: getappz/agentflare

Length of output: 20872


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the relevant sections with line numbers using tools available in the sandbox.
sed -n '81,210p' src/auth.rs | cat -n
echo '---'
sed -n '837,905p' src/auth.rs | cat -n
echo '---'
sed -n '907,980p' src/auth.rs | cat -n

# Also show the auth_crypt helpers.
echo '---'
sed -n '1,90p' src/auth_crypt.rs | cat -n

Repository: getappz/agentflare

Length of output: 14208


Add an explicit vault-format marker before copying auth files

backup() writes either ciphertext or plaintext, but activate(), activate_into(), and auth_login() infer how to handle the bytes from the current passphrase state. That can write encrypted vault data back into live auth files as raw ciphertext, or treat plaintext vault files as encrypted and skip them. Prefix vault entries with a small header/version and branch on that instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/auth.rs` around lines 94 - 105, The backup logic in `backup()` currently
writes auth file bytes without any explicit format marker, so later readers like
`activate()`, `activate_into()`, and `auth_login()` can misinterpret plaintext
vs encrypted vault entries based only on the current passphrase state. Update
the vault write path in `backup()` to prefix each stored entry with a small
header/version that records whether the payload is plaintext or ciphertext, and
then change the corresponding read/restore paths to branch on that marker
instead of inferring from `get_passphrase()`. Use the existing `auth_crypt` and
`vault` handling in `src/auth.rs` as the main place to locate and apply the fix.

Comment thread src/auth.rs
Comment on lines +803 to +835
pub fn auth_exec(agent: &str, profile: &str, args: &[String], json: bool) {
let dir = isolates_dir().join(agent).join(profile);
if !dir.exists() {
if json {
println!("{}", serde_json::json!({"error": "isolated profile not found"}));
} else {
eprintln!("error: isolated profile '{agent}/{profile}' not found — run 'auth isolate add' first");
}
return;
}

if args.is_empty() {
eprintln!("error: no command specified after --");
return;
}

let binary = &args[0];
let rest = &args[1..];

let status = std::process::Command::new(binary)
.args(rest)
.env("HOME", &dir)
.spawn()
.and_then(|mut c| c.wait())
.unwrap_or_else(|e| {
eprintln!("error: {e}");
std::process::exit(1);
});

if !status.success() {
std::process::exit(status.code().unwrap_or(1));
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '760,880p' src/auth.rs

Repository: getappz/agentflare

Length of output: 4055


🏁 Script executed:

rg -n --hidden --glob '!target' --glob '!node_modules' 'USERPROFILE|HOMEDRIVE|HOMEPATH|HOME' src README.md Cargo.toml

Repository: getappz/agentflare

Length of output: 1164


🏁 Script executed:

sed -n '1,120p' src/paths.rs && printf '\n---\n' && sed -n '150,180p' README.md && printf '\n---\n' && sed -n '288,340p' src/main.rs

Repository: getappz/agentflare

Length of output: 4765


🏁 Script executed:

rg -n --hidden --glob '!target' --glob '!node_modules' 'install\.ps1|cross-compile|Windows|USERPROFILE|HOMEDRIVE|HOMEPATH' README.md src .github

Repository: getappz/agentflare

Length of output: 1776


Set the Windows home variables too

auth_exec and auth_login only override HOME. On Windows, many programs read USERPROFILE (and sometimes HOMEDRIVE/HOMEPATH) for the home directory, so the isolated profile can be ignored there. Set the platform-appropriate variable(s) alongside HOME.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/auth.rs` around lines 803 - 835, Set the isolated home environment
variables in auth_exec and auth_login, not just HOME, because Windows programs
may ignore HOME and read USERPROFILE (and sometimes HOMEDRIVE/HOMEPATH) instead.
Update the command environment setup around std::process::Command in auth_exec
and the login flow in auth_login so the isolated profile directory is exposed
through the platform-appropriate home variables alongside HOME, using the
existing agent/profile directory logic as the source of truth.

Comment thread src/auth.rs
Comment on lines +862 to +872
if status.success() {
// After login, backup the new auth state
let dir = isolates_dir().join(agent).join(profile);
for &rel in CATALOG.iter().find(|c| c.agent_key == agent).map(|c| c.files).unwrap_or(&[]) {
let dest = profile_dir(agent, profile).join(rel.rsplit('/').next().unwrap_or(rel));
let src = dir.join(rel);
if src.exists() {
fs::create_dir_all(dest.parent().unwrap()).ok();
fs::copy(&src, &dest).ok();
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

auth_login writes post-login auth into the vault unencrypted.

The backup loop here uses fs::copy directly, bypassing auth_crypt::encrypt. Credentials captured via the login flow are therefore stored in plaintext in the vault, inconsistent with backup() and defeating encryption-at-rest for exactly the secrets this flow produces. Route these through the same encryption path (and shared format marker) as backup().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/auth.rs` around lines 862 - 872, The post-login backup path in auth_login
is writing vault files with plain fs::copy instead of the encrypted backup flow,
so update this loop to use the same auth_crypt::encrypt-backed path as backup()
and preserve the shared format marker. Locate the logic that iterates CATALOG
files after a successful login, and replace direct copying of src to dest with
the same encryption/serialization step used by backup() so the new auth state is
stored encrypted at rest.

Comment thread src/auth.rs
Comment on lines +881 to +894
fn activate_into(agent: &str, profile: &str, target_dir: &std::path::Path) {
let cat = match catalog_for(agent) { Some(c) => c, None => { return; } };
let vault = profile_dir(agent, profile);
for &rel in cat.files {
let src = vault.join(rel.rsplit('/').next().unwrap_or(rel));
if src.exists() {
let dest = target_dir.join(rel);
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent).ok();
}
fs::copy(&src, &dest).ok();
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

activate_into copies vault files without decrypting.

Unlike activate(), this helper does a raw fs::copy (Line 891) with no passphrase handling. When the vault was backed up with a passphrase, the isolate HOME receives encrypted bytes, so the agent reads corrupted credentials. It should reuse the same decrypt-aware restore path as activate().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/auth.rs` around lines 881 - 894, The activate_into helper is copying
vault files directly with fs::copy instead of using the decrypt-aware restore
flow. Update activate_into to reuse the same passphrase-aware restoration logic
as activate, using the existing vault/profile handling in auth::activate and
profile_dir/catalog_for so files are decrypted before being written into
target_dir. Ensure the restore path handles both encrypted and unencrypted
backups consistently, rather than manually copying rel-targeted files.

Triggers on release published. Uses komac to update winget manifests automatically and create PR against microsoft/winget-pkgs. Keeps last 5 versions via komac cleanup.
Comment on lines +10 to +24
runs-on: windows-latest
steps:
- name: Install komac
run: winget install --id russellbanks.Komac --accept-source-agreements --accept-package-agreements

- name: Update winget manifest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
KOMAC_FORK_OWNER: getappz
run: |
$id = "getappz.agentflare"
$urls = gh release view --json assets --jq '.assets[] | select(.name | test("\\.(exe|msi)$")) | .url'
if (-not $urls) { throw "No winget installer assets found" }
komac update $id --version "${{ github.event.release.tag_name }}" --urls $urls
komac cleanup --only-merged
@getappz
getappz merged commit 4d6ab4d into master Jul 7, 2026
10 of 11 checks passed
@getappz
getappz deleted the worktree-auth-phase3-v2 branch July 7, 2026 08:07
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 7, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants