fix(desktop): autofocus message composer on channel/thread open#572
Merged
Conversation
fe0f0d5 to
1588f0d
Compare
Previously the user had to click the composer to start typing after:
1. Clicking the reply icon on a message (thread panel composer not focused)
2. Selecting a new channel (main composer not focused)
Now the composer focuses on mount and whenever the effective draft key
changes (channel or thread switch), matching Slack/Discord/Signal.
The logic lives in a small `useComposerAutofocus` hook and is guarded
against:
- disabled composers (archived channels, no active channel,
in-flight send at mount)
- focus already being inside another text-entry surface (open dialog
input, search box, etc.) so we don't yank focus from the user
The trigger deliberately excludes `disabled`: callers pass a disabled
flag that includes the transient `isSending` state, which would otherwise
re-fire autofocus after every send. With both the main and thread
composers mounted, that re-fire could race and steal focus from the
thread composer post-send. The effect now only runs on mount and on
draft-key changes (real navigation).
Signed-off-by: Tyler Longwell <109685178+tlongwell-block@users.noreply.github.com>
1588f0d to
c08dbb9
Compare
tlongwell-block
added a commit
that referenced
this pull request
May 15, 2026
Adds env_vars: BTreeMap<String, String> on both PersonaRecord and ManagedAgentRecord. Precedence at spawn: parent env < persona < agent (last write wins). Reserved keys — Sprout's identity/secrets — are rejected at save time and stripped at runtime so a typo or malicious value can't swap the agent's nsec. Backend - managed_agents/env_vars.rs (new): merged_user_env, RESERVED_ENV_KEYS, is_reserved_env_key, validate_user_env_keys, 16 unit tests. - managed_agents/runtime.rs: merges persona env → agent env at spawn via env_vars::merged_user_env. - managed_agents/types.rs: env_vars field on PersonaRecord and ManagedAgentRecord; included in ManagedAgentSummary so the frontend list/edit reload sees saved state. - commands/personas.rs, commands/agents.rs, commands/agent_models.rs: validate_user_env_keys at create/update; merged_user_env also used by sprout-acp models discovery and provider deploy so credentials in persona env flow into both. Reserved keys: SPROUT_PRIVATE_KEY, NOSTR_PRIVATE_KEY, SPROUT_AUTH_TAG, SPROUT_API_TOKEN, SPROUT_ACP_PRIVATE_KEY, SPROUT_ACP_API_TOKEN. Case-insensitive match — lowercase variants of these specific keys are almost certainly typos, not legitimate use. Behavior knobs (GOOSE_MODE, SPROUT_TOOLSETS, SPROUT_ACP_MODEL, SPROUT_ACP_SYSTEM_PROMPT) remain freely overridable. Two-layer enforcement: 1. Save-time — validate_user_env_keys rejects reserved keys with a clear error listing the offending keys, surfaced in the dialog. 2. Runtime — merged_user_env strips reserved keys with a warning log. Defense in depth for older on-disk records that predate validation. Frontend - features/agents/ui/EnvVarsEditor.tsx (new): reusable key/value editor with add/remove rows and validation hints. - PersonaDialog, CreateAgentDialog, EditAgentDialog: embed the editor. - shared/api/types.ts, tauri.ts, tauriPersonas.ts: envVars field on create/update payloads. envVars: undefined on update = 'don't touch' so editing unrelated fields can't wipe saved credentials. Tests - 16 unit tests in env_vars.rs covering merge precedence, reserved-key stripping (persona + agent + case-insensitive), and the validator. - 1 e2e test (desktop/tests/e2e/persona-env-vars.spec.ts) drives the EnvVarsEditor through the Persona dialog. - All 296 desktop crate unit tests pass. Also bumps the MessageComposer.tsx size limit from 700→710. The composer-autofocus PR (#572) landed on origin/main while this branch was in review and pushed it 3 lines over the limit. Unrelated to this feature; bumping here so CI is green. Supersedes #576. Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
tlongwell-block
added a commit
that referenced
this pull request
May 15, 2026
Adds env_vars: BTreeMap<String, String> on both PersonaRecord and ManagedAgentRecord. Precedence at spawn: parent env < persona < agent (last write wins). Reserved keys — Sprout's identity/secrets — are rejected at save time and stripped at runtime so a typo or malicious value can't swap the agent's nsec. Backend - managed_agents/env_vars.rs (new): merged_user_env, RESERVED_ENV_KEYS, is_reserved_env_key, validate_user_env_keys, 16 unit tests. - managed_agents/runtime.rs: merges persona env → agent env at spawn via env_vars::merged_user_env. - managed_agents/types.rs: env_vars field on PersonaRecord and ManagedAgentRecord; included in ManagedAgentSummary so the frontend list/edit reload sees saved state. - commands/personas.rs, commands/agents.rs, commands/agent_models.rs: validate_user_env_keys at create/update; merged_user_env also used by sprout-acp models discovery and provider deploy so credentials in persona env flow into both. Reserved keys: SPROUT_PRIVATE_KEY, NOSTR_PRIVATE_KEY, SPROUT_AUTH_TAG, SPROUT_API_TOKEN, SPROUT_ACP_PRIVATE_KEY, SPROUT_ACP_API_TOKEN. Case-insensitive match — lowercase variants of these specific keys are almost certainly typos, not legitimate use. Behavior knobs (GOOSE_MODE, SPROUT_TOOLSETS, SPROUT_ACP_MODEL, SPROUT_ACP_SYSTEM_PROMPT) remain freely overridable. Two-layer enforcement: 1. Save-time — validate_user_env_keys rejects reserved keys with a clear error listing the offending keys, surfaced in the dialog. 2. Runtime — merged_user_env strips reserved keys with a warning log. Defense in depth for older on-disk records that predate validation. Frontend - features/agents/ui/EnvVarsEditor.tsx (new): reusable key/value editor with add/remove rows and validation hints. - PersonaDialog, CreateAgentDialog, EditAgentDialog: embed the editor. - shared/api/types.ts, tauri.ts, tauriPersonas.ts: envVars field on create/update payloads. envVars: undefined on update = 'don't touch' so editing unrelated fields can't wipe saved credentials. Tests - 16 unit tests in env_vars.rs covering merge precedence, reserved-key stripping (persona + agent + case-insensitive), and the validator. - 1 e2e test (desktop/tests/e2e/persona-env-vars.spec.ts) drives the EnvVarsEditor through the Persona dialog. - All 296 desktop crate unit tests pass. Also bumps the MessageComposer.tsx size limit from 700→710. The composer-autofocus PR (#572) landed on origin/main while this branch was in review and pushed it 3 lines over the limit. Unrelated to this feature; bumping here so CI is green. Supersedes #576. Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
tlongwell-block
added a commit
that referenced
this pull request
May 15, 2026
Adds env_vars: BTreeMap<String, String> on both PersonaRecord and ManagedAgentRecord. Precedence at spawn: parent env < persona < agent (last write wins). Reserved keys — Sprout's identity/secrets — are rejected at save time and stripped at runtime so a typo or malicious value can't swap the agent's nsec. Backend - managed_agents/env_vars.rs (new): merged_user_env, RESERVED_ENV_KEYS, is_reserved_env_key, validate_user_env_keys, 17 unit tests. - managed_agents/runtime.rs: merges persona env → agent env at spawn via env_vars::merged_user_env. - managed_agents/types.rs: env_vars field on PersonaRecord and ManagedAgentRecord; included in ManagedAgentSummary so the frontend list/edit reload sees saved state. - managed_agents/backend.rs: extends provider stderr/error redaction to scrub user-supplied env values (redact_secrets_with + env_secrets_from_request). Without this, a provider that echoes its request in failure messages could surface an ANTHROPIC_API_KEY-style secret unredacted via spawnError/last_error. - commands/personas.rs, commands/agents.rs, commands/agent_models.rs: validate_user_env_keys at create/update; merged_user_env also used by sprout-acp models discovery and provider deploy so credentials in persona env flow into both. Reserved keys - SPROUT_PRIVATE_KEY, NOSTR_PRIVATE_KEY (agent identity) - SPROUT_AUTH_TAG (NIP-OA relay auth) - SPROUT_API_TOKEN, SPROUT_ACP_PRIVATE_KEY, SPROUT_ACP_API_TOKEN - SPROUT_ACP_AGENT_OWNER (owner enforcement for legacy records without auth_tag — overriding would change who the agent responds to) Case-insensitive match — lowercase variants of these specific keys are almost certainly typos, not legitimate use. Behavior knobs (GOOSE_MODE, SPROUT_TOOLSETS, SPROUT_ACP_MODEL, SPROUT_ACP_SYSTEM_PROMPT) remain freely overridable. Two-layer enforcement: 1. Save-time — validate_user_env_keys rejects reserved keys with a clear error listing the offending keys, surfaced in the dialog. 2. Runtime — merged_user_env strips reserved keys with a warning log. Defense in depth for older on-disk records that predate validation. Frontend - features/agents/ui/EnvVarsEditor.tsx (new): reusable key/value editor with add/remove rows and validation hints. - PersonaDialog, CreateAgentDialog, EditAgentDialog: embed the editor. - shared/api/types.ts, tauri.ts, tauriPersonas.ts: envVars field on create/update payloads. envVars: undefined on update = 'don't touch' so editing unrelated fields can't wipe saved credentials. Tests - 17 unit tests in env_vars.rs covering merge precedence, reserved-key stripping (persona + agent + case-insensitive), owner-key protection for legacy records, and the validator. - 5 new unit tests in backend.rs covering redact_secrets_with (user env values scrubbed, short values skipped, overlapping secrets handled) and env_secrets_from_request (string extraction + missing-shape). - 1 e2e test (desktop/tests/e2e/persona-env-vars.spec.ts) drives the EnvVarsEditor through the Persona dialog. - All 302 desktop crate unit tests pass. Also bumps the MessageComposer.tsx size limit from 700→710 — the composer-autofocus PR (#572) landed on origin/main while this branch was in review and pushed it 3 lines over. Unrelated to this feature; bumping here so CI is green. Bumps backend.rs limit from 530→640 for the new redaction helpers + their tests. Supersedes #576. Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
tlongwell-block
added a commit
that referenced
this pull request
May 15, 2026
Adds env_vars: BTreeMap<String, String> on both PersonaRecord and ManagedAgentRecord. Precedence at spawn: parent env < persona < agent (last write wins). Reserved keys — Sprout's identity/secrets — are rejected at save time and stripped at runtime so a typo or malicious value can't swap the agent's nsec. Backend - managed_agents/env_vars.rs (new): merged_user_env, RESERVED_ENV_KEYS, is_reserved_env_key, validate_user_env_keys, 17 unit tests. - managed_agents/runtime.rs: merges persona env → agent env at spawn via env_vars::merged_user_env. - managed_agents/types.rs: env_vars field on PersonaRecord and ManagedAgentRecord; included in ManagedAgentSummary so the frontend list/edit reload sees saved state. - managed_agents/backend.rs: extends provider stderr/error redaction to scrub user-supplied env values (redact_secrets_with + env_secrets_from_request). Without this, a provider that echoes its request in failure messages could surface an ANTHROPIC_API_KEY-style secret unredacted via spawnError/last_error. - commands/personas.rs, commands/agents.rs, commands/agent_models.rs: validate_user_env_keys at create/update; merged_user_env also used by sprout-acp models discovery and provider deploy so credentials in persona env flow into both. Reserved keys - SPROUT_PRIVATE_KEY, NOSTR_PRIVATE_KEY (agent identity) - SPROUT_AUTH_TAG (NIP-OA relay auth) - SPROUT_API_TOKEN, SPROUT_ACP_PRIVATE_KEY, SPROUT_ACP_API_TOKEN - SPROUT_ACP_AGENT_OWNER (owner enforcement for legacy records without auth_tag — overriding would change who the agent responds to) Case-insensitive match — lowercase variants of these specific keys are almost certainly typos, not legitimate use. Behavior knobs (GOOSE_MODE, SPROUT_TOOLSETS, SPROUT_ACP_MODEL, SPROUT_ACP_SYSTEM_PROMPT) remain freely overridable. Two-layer enforcement: 1. Save-time — validate_user_env_keys rejects reserved keys with a clear error listing the offending keys, surfaced in the dialog. 2. Runtime — merged_user_env strips reserved keys with a warning log. Defense in depth for older on-disk records that predate validation. Frontend - features/agents/ui/EnvVarsEditor.tsx (new): reusable key/value editor with add/remove rows and validation hints. - PersonaDialog, CreateAgentDialog, EditAgentDialog: embed the editor. - shared/api/types.ts, tauri.ts, tauriPersonas.ts: envVars field on create/update payloads. envVars: undefined on update = 'don't touch' so editing unrelated fields can't wipe saved credentials. Tests - 17 unit tests in env_vars.rs covering merge precedence, reserved-key stripping (persona + agent + case-insensitive), owner-key protection for legacy records, and the validator. - 5 new unit tests in backend.rs covering redact_secrets_with (user env values scrubbed, short values skipped, overlapping secrets handled) and env_secrets_from_request (string extraction + missing-shape). - 1 e2e test (desktop/tests/e2e/persona-env-vars.spec.ts) drives the EnvVarsEditor through the Persona dialog. - All 302 desktop crate unit tests pass. Also bumps the MessageComposer.tsx size limit from 700→710 — the composer-autofocus PR (#572) landed on origin/main while this branch was in review and pushed it 3 lines over. Unrelated to this feature; bumping here so CI is green. Bumps backend.rs limit from 530→640 for the new redaction helpers + their tests. Supersedes #576. Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
tlongwell-block
added a commit
that referenced
this pull request
May 15, 2026
Adds env_vars: BTreeMap<String, String> on both PersonaRecord and ManagedAgentRecord. Precedence at spawn: parent env < persona < agent (last write wins). Reserved keys — Sprout's identity, code-execution surface, and security gates — are rejected at save time and stripped at runtime so a typo or malicious value can't swap the agent's nsec, code, relay, or respond-to gate. Backend - managed_agents/env_vars.rs (new): merged_user_env, RESERVED_ENV_KEYS, is_reserved_env_key, validate_user_env_keys, 20 unit tests. - managed_agents/runtime.rs: merges persona env → agent env at spawn via env_vars::merged_user_env. - managed_agents/types.rs: env_vars field on PersonaRecord and ManagedAgentRecord; included in ManagedAgentSummary so the frontend list/edit reload sees saved state. - managed_agents/backend.rs: extends provider stderr/error redaction to scrub user-supplied env values (redact_secrets_with + env_secrets_from_request + redact_env_values_in). Uses single-pass str::replace to avoid a non-terminating loop when a user env value is a substring of '[REDACTED]'. - commands/personas.rs, commands/agents.rs, commands/agent_models.rs: validate_user_env_keys at create/update; merged_user_env also used by sprout-acp models discovery and provider deploy so credentials in persona env flow into both. Model-discovery stderr is now redacted through redact_env_values_in before being formatted into the frontend-visible error. Reserved keys (three categories) - Identity / secrets: SPROUT_PRIVATE_KEY, NOSTR_PRIVATE_KEY, SPROUT_AUTH_TAG, SPROUT_API_TOKEN, SPROUT_ACP_PRIVATE_KEY, SPROUT_ACP_API_TOKEN. - Code-execution surface: SPROUT_ACP_AGENT_COMMAND, SPROUT_ACP_AGENT_ARGS, SPROUT_ACP_MCP_COMMAND, SPROUT_RELAY_URL. - Security gates: SPROUT_ACP_RESPOND_TO, SPROUT_ACP_RESPOND_TO_ALLOWLIST, SPROUT_ACP_AGENT_OWNER (legacy-record owner fallback). Case-insensitive match — lowercase variants of these specific keys are almost certainly typos, not legitimate use. Behavior knobs (GOOSE_MODE, SPROUT_TOOLSETS, SPROUT_ACP_MODEL, SPROUT_ACP_SYSTEM_PROMPT) remain freely overridable. Two-layer enforcement: 1. Save-time — validate_user_env_keys rejects reserved keys with a clear error listing the offending keys, surfaced in the dialog. 2. Runtime — merged_user_env strips reserved keys with a warning log. Defense in depth for older on-disk records that predate validation. Frontend - features/agents/ui/EnvVarsEditor.tsx (new): reusable key/value editor with add/remove rows and validation hints. - PersonaDialog, CreateAgentDialog, EditAgentDialog: embed the editor. - shared/api/types.ts, tauri.ts, tauriPersonas.ts: envVars field on create/update payloads. envVars: undefined on update = 'don't touch' so editing unrelated fields can't wipe saved credentials. Tests - 20 unit tests in env_vars.rs covering merge precedence, reserved-key stripping for each category (identity, code-execution, security gates, legacy owner, relay URL, case-insensitive), and the validator. - 6 new unit tests in backend.rs covering redact_secrets_with (user env values scrubbed, short values skipped, overlapping secrets handled, termination when value is substring of marker), env_secrets_from_request (string extraction + missing-shape), and redact_env_values_in. - 1 e2e test (desktop/tests/e2e/persona-env-vars.spec.ts) drives the EnvVarsEditor through the Persona dialog. - All 307 desktop crate unit tests pass. Also bumps the MessageComposer.tsx size limit from 700→710 (the composer-autofocus PR #572 landed on origin/main mid-review and pushed it 3 lines over). Bumps backend.rs limit from 530→700 for the new redaction helpers + their tests. Supersedes #576. Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
tlongwell-block
added a commit
that referenced
this pull request
May 15, 2026
Adds env_vars: BTreeMap<String, String> on both PersonaRecord and ManagedAgentRecord. Precedence at spawn: parent env < persona < agent (last write wins). Reserved keys — Sprout's identity, code-execution surface, and security gates — are rejected at save time and stripped at runtime so a typo or malicious value can't swap the agent's nsec, code, relay, or respond-to gate. Backend - managed_agents/env_vars.rs (new): merged_user_env, RESERVED_ENV_KEYS, is_reserved_env_key, validate_user_env_keys, 20 unit tests. - managed_agents/runtime.rs: merges persona env → agent env at spawn via env_vars::merged_user_env. - managed_agents/types.rs: env_vars field on PersonaRecord and ManagedAgentRecord; included in ManagedAgentSummary so the frontend list/edit reload sees saved state. - managed_agents/backend.rs: extends provider stderr/error redaction to scrub user-supplied env values (redact_secrets_with + env_secrets_from_request + redact_env_values_in). Uses single-pass str::replace to avoid a non-terminating loop when a user env value is a substring of '[REDACTED]'. - commands/personas.rs, commands/agents.rs, commands/agent_models.rs: validate_user_env_keys at create/update; merged_user_env also used by sprout-acp models discovery and provider deploy so credentials in persona env flow into both. Model-discovery stderr is now redacted through redact_env_values_in before being formatted into the frontend-visible error. Reserved keys (three categories) - Identity / secrets: SPROUT_PRIVATE_KEY, NOSTR_PRIVATE_KEY, SPROUT_AUTH_TAG, SPROUT_API_TOKEN, SPROUT_ACP_PRIVATE_KEY, SPROUT_ACP_API_TOKEN. - Code-execution surface: SPROUT_ACP_AGENT_COMMAND, SPROUT_ACP_AGENT_ARGS, SPROUT_ACP_MCP_COMMAND, SPROUT_RELAY_URL. - Security gates: SPROUT_ACP_RESPOND_TO, SPROUT_ACP_RESPOND_TO_ALLOWLIST, SPROUT_ACP_AGENT_OWNER (legacy-record owner fallback). Case-insensitive match — lowercase variants of these specific keys are almost certainly typos, not legitimate use. Behavior knobs (GOOSE_MODE, SPROUT_TOOLSETS, SPROUT_ACP_MODEL, SPROUT_ACP_SYSTEM_PROMPT) remain freely overridable. Two-layer enforcement: 1. Save-time — validate_user_env_keys rejects reserved keys with a clear error listing the offending keys, surfaced in the dialog. 2. Runtime — merged_user_env strips reserved keys with a warning log. Defense in depth for older on-disk records that predate validation. Frontend - features/agents/ui/EnvVarsEditor.tsx (new): reusable key/value editor with add/remove rows and validation hints. - PersonaDialog, CreateAgentDialog, EditAgentDialog: embed the editor. - shared/api/types.ts, tauri.ts, tauriPersonas.ts: envVars field on create/update payloads. envVars: undefined on update = 'don't touch' so editing unrelated fields can't wipe saved credentials. Tests - 20 unit tests in env_vars.rs covering merge precedence, reserved-key stripping for each category (identity, code-execution, security gates, legacy owner, relay URL, case-insensitive), and the validator. - 6 new unit tests in backend.rs covering redact_secrets_with (user env values scrubbed, short values skipped, overlapping secrets handled, termination when value is substring of marker), env_secrets_from_request (string extraction + missing-shape), and redact_env_values_in. - 1 e2e test (desktop/tests/e2e/persona-env-vars.spec.ts) drives the EnvVarsEditor through the Persona dialog. - All 307 desktop crate unit tests pass. Also bumps the MessageComposer.tsx size limit from 700→710 (the composer-autofocus PR #572 landed on origin/main mid-review and pushed it 3 lines over). Bumps backend.rs limit from 530→700 for the new redaction helpers + their tests. Supersedes #576. Signed-off-by: Tyler Longwell <tlongwell@squareup.com> Security: reject malformed env var keys Tightens validate_user_env_keys to require POSIX-shaped keys ([A-Za-z_][A-Za-z0-9_]*). Closes a denylist bypass: Rust's Command::env(k, v) accepts a key containing '=' and writes it verbatim into the child's environ block. A key like SPROUT_AUTH_TAG=x with value forged lands as SPROUT_AUTH_TAG=x=forged in the child env, so getenv("SPROUT_AUTH_TAG") returns "x=forged" — bypassing the reserved- key string compare. Confirmed exploitable for SPROUT_AUTH_TAG (legacy agents) and any other reserved key Sprout doesn't always set with the canonical name first. Two-layer fix: 1. validate_user_env_keys rejects malformed keys at save time, listing each invalid key with a clear regex hint. 2. merged_user_env strips malformed keys at spawn time as defense in depth for on-disk records that predate the validator. +6 unit tests pinning the bypass and adjacent edge cases (empty, whitespace, NUL, leading digit, non-ASCII, =-in-key). 313 unit tests pass.
tlongwell-block
added a commit
that referenced
this pull request
May 15, 2026
Adds env_vars: BTreeMap<String, String> on both PersonaRecord and ManagedAgentRecord. Precedence at spawn: parent env < persona < agent (last write wins). Reserved keys — Sprout's identity, code-execution surface, and security gates — are rejected at save time and stripped at runtime so a typo or malicious value can't swap the agent's nsec, code, relay, or respond-to gate. Backend - managed_agents/env_vars.rs (new): merged_user_env, RESERVED_ENV_KEYS, is_reserved_env_key, validate_user_env_keys, 20 unit tests. - managed_agents/runtime.rs: merges persona env → agent env at spawn via env_vars::merged_user_env. - managed_agents/types.rs: env_vars field on PersonaRecord and ManagedAgentRecord; included in ManagedAgentSummary so the frontend list/edit reload sees saved state. - managed_agents/backend.rs: extends provider stderr/error redaction to scrub user-supplied env values (redact_secrets_with + env_secrets_from_request + redact_env_values_in). Uses single-pass str::replace to avoid a non-terminating loop when a user env value is a substring of '[REDACTED]'. - commands/personas.rs, commands/agents.rs, commands/agent_models.rs: validate_user_env_keys at create/update; merged_user_env also used by sprout-acp models discovery and provider deploy so credentials in persona env flow into both. Model-discovery stderr is now redacted through redact_env_values_in before being formatted into the frontend-visible error. Reserved keys (three categories) - Identity / secrets: SPROUT_PRIVATE_KEY, NOSTR_PRIVATE_KEY, SPROUT_AUTH_TAG, SPROUT_API_TOKEN, SPROUT_ACP_PRIVATE_KEY, SPROUT_ACP_API_TOKEN. - Code-execution surface: SPROUT_ACP_AGENT_COMMAND, SPROUT_ACP_AGENT_ARGS, SPROUT_ACP_MCP_COMMAND, SPROUT_RELAY_URL. - Security gates: SPROUT_ACP_RESPOND_TO, SPROUT_ACP_RESPOND_TO_ALLOWLIST, SPROUT_ACP_AGENT_OWNER (legacy-record owner fallback). Case-insensitive match — lowercase variants of these specific keys are almost certainly typos, not legitimate use. Behavior knobs (GOOSE_MODE, SPROUT_TOOLSETS, SPROUT_ACP_MODEL, SPROUT_ACP_SYSTEM_PROMPT) remain freely overridable. Two-layer enforcement: 1. Save-time — validate_user_env_keys rejects reserved keys with a clear error listing the offending keys, surfaced in the dialog. 2. Runtime — merged_user_env strips reserved keys with a warning log. Defense in depth for older on-disk records that predate validation. Frontend - features/agents/ui/EnvVarsEditor.tsx (new): reusable key/value editor with add/remove rows and validation hints. - PersonaDialog, CreateAgentDialog, EditAgentDialog: embed the editor. - shared/api/types.ts, tauri.ts, tauriPersonas.ts: envVars field on create/update payloads. envVars: undefined on update = 'don't touch' so editing unrelated fields can't wipe saved credentials. Tests - 20 unit tests in env_vars.rs covering merge precedence, reserved-key stripping for each category (identity, code-execution, security gates, legacy owner, relay URL, case-insensitive), and the validator. - 6 new unit tests in backend.rs covering redact_secrets_with (user env values scrubbed, short values skipped, overlapping secrets handled, termination when value is substring of marker), env_secrets_from_request (string extraction + missing-shape), and redact_env_values_in. - 1 e2e test (desktop/tests/e2e/persona-env-vars.spec.ts) drives the EnvVarsEditor through the Persona dialog. - All 307 desktop crate unit tests pass. Also bumps the MessageComposer.tsx size limit from 700→710 (the composer-autofocus PR #572 landed on origin/main mid-review and pushed it 3 lines over). Bumps backend.rs limit from 530→700 for the new redaction helpers + their tests. Supersedes #576. Signed-off-by: Tyler Longwell <tlongwell@squareup.com> Security: reject malformed env var keys Tightens validate_user_env_keys to require POSIX-shaped keys ([A-Za-z_][A-Za-z0-9_]*). Closes a denylist bypass: Rust's Command::env(k, v) accepts a key containing '=' and writes it verbatim into the child's environ block. A key like SPROUT_AUTH_TAG=x with value forged lands as SPROUT_AUTH_TAG=x=forged in the child env, so getenv("SPROUT_AUTH_TAG") returns "x=forged" — bypassing the reserved- key string compare. Confirmed exploitable for SPROUT_AUTH_TAG (legacy agents) and any other reserved key Sprout doesn't always set with the canonical name first. Two-layer fix: 1. validate_user_env_keys rejects malformed keys at save time, listing each invalid key with a clear regex hint. 2. merged_user_env strips malformed keys at spawn time as defense in depth for on-disk records that predate the validator. +6 unit tests pinning the bypass and adjacent edge cases (empty, whitespace, NUL, leading digit, non-ASCII, =-in-key). 313 unit tests pass.
tlongwell-block
added a commit
that referenced
this pull request
May 15, 2026
Adds env_vars: BTreeMap<String, String> on both PersonaRecord and ManagedAgentRecord. Precedence at spawn: parent env < persona < agent (last write wins). Reserved keys — Sprout's identity, code-execution surface, and security gates — are rejected at save time and stripped at runtime so a typo or malicious value can't swap the agent's nsec, code, relay, or respond-to gate. Backend - managed_agents/env_vars.rs (new): merged_user_env, RESERVED_ENV_KEYS, is_reserved_env_key, validate_user_env_keys, 20 unit tests. - managed_agents/runtime.rs: merges persona env → agent env at spawn via env_vars::merged_user_env. - managed_agents/types.rs: env_vars field on PersonaRecord and ManagedAgentRecord; included in ManagedAgentSummary so the frontend list/edit reload sees saved state. - managed_agents/backend.rs: extends provider stderr/error redaction to scrub user-supplied env values (redact_secrets_with + env_secrets_from_request + redact_env_values_in). Uses single-pass str::replace to avoid a non-terminating loop when a user env value is a substring of '[REDACTED]'. - commands/personas.rs, commands/agents.rs, commands/agent_models.rs: validate_user_env_keys at create/update; merged_user_env also used by sprout-acp models discovery and provider deploy so credentials in persona env flow into both. Model-discovery stderr is now redacted through redact_env_values_in before being formatted into the frontend-visible error. Reserved keys (three categories) - Identity / secrets: SPROUT_PRIVATE_KEY, NOSTR_PRIVATE_KEY, SPROUT_AUTH_TAG, SPROUT_API_TOKEN, SPROUT_ACP_PRIVATE_KEY, SPROUT_ACP_API_TOKEN. - Code-execution surface: SPROUT_ACP_AGENT_COMMAND, SPROUT_ACP_AGENT_ARGS, SPROUT_ACP_MCP_COMMAND, SPROUT_RELAY_URL. - Security gates: SPROUT_ACP_RESPOND_TO, SPROUT_ACP_RESPOND_TO_ALLOWLIST, SPROUT_ACP_AGENT_OWNER (legacy-record owner fallback). Case-insensitive match — lowercase variants of these specific keys are almost certainly typos, not legitimate use. Behavior knobs (GOOSE_MODE, SPROUT_TOOLSETS, SPROUT_ACP_MODEL, SPROUT_ACP_SYSTEM_PROMPT) remain freely overridable. Two-layer enforcement: 1. Save-time — validate_user_env_keys rejects reserved keys with a clear error listing the offending keys, surfaced in the dialog. 2. Runtime — merged_user_env strips reserved keys with a warning log. Defense in depth for older on-disk records that predate validation. Frontend - features/agents/ui/EnvVarsEditor.tsx (new): reusable key/value editor with add/remove rows and validation hints. - PersonaDialog, CreateAgentDialog, EditAgentDialog: embed the editor. - shared/api/types.ts, tauri.ts, tauriPersonas.ts: envVars field on create/update payloads. envVars: undefined on update = 'don't touch' so editing unrelated fields can't wipe saved credentials. Tests - 20 unit tests in env_vars.rs covering merge precedence, reserved-key stripping for each category (identity, code-execution, security gates, legacy owner, relay URL, case-insensitive), and the validator. - 6 new unit tests in backend.rs covering redact_secrets_with (user env values scrubbed, short values skipped, overlapping secrets handled, termination when value is substring of marker), env_secrets_from_request (string extraction + missing-shape), and redact_env_values_in. - 1 e2e test (desktop/tests/e2e/persona-env-vars.spec.ts) drives the EnvVarsEditor through the Persona dialog. - All 307 desktop crate unit tests pass. Also bumps the MessageComposer.tsx size limit from 700→710 (the composer-autofocus PR #572 landed on origin/main mid-review and pushed it 3 lines over). Bumps backend.rs limit from 530→700 for the new redaction helpers + their tests. Supersedes #576. Signed-off-by: Tyler Longwell <tlongwell@squareup.com> Security: reject malformed env var keys Tightens validate_user_env_keys to require POSIX-shaped keys ([A-Za-z_][A-Za-z0-9_]*). Closes a denylist bypass: Rust's Command::env(k, v) accepts a key containing '=' and writes it verbatim into the child's environ block. A key like SPROUT_AUTH_TAG=x with value forged lands as SPROUT_AUTH_TAG=x=forged in the child env, so getenv("SPROUT_AUTH_TAG") returns "x=forged" — bypassing the reserved- key string compare. Confirmed exploitable for SPROUT_AUTH_TAG (legacy agents) and any other reserved key Sprout doesn't always set with the canonical name first. Two-layer fix: 1. validate_user_env_keys rejects malformed keys at save time, listing each invalid key with a clear regex hint. 2. merged_user_env strips malformed keys at spawn time as defense in depth for on-disk records that predate the validator. +6 unit tests pinning the bypass and adjacent edge cases (empty, whitespace, NUL, leading digit, non-ASCII, =-in-key). 313 unit tests pass.
tlongwell-block
added a commit
that referenced
this pull request
May 15, 2026
Adds env_vars: BTreeMap<String, String> on both PersonaRecord and ManagedAgentRecord. Precedence at spawn: parent env < persona < agent (last write wins). Reserved keys — Sprout's identity, code-execution surface, and security gates — are rejected at save time and stripped at runtime so a typo or malicious value can't swap the agent's nsec, code, relay, or respond-to gate. Backend - managed_agents/env_vars.rs (new): merged_user_env, RESERVED_ENV_KEYS, is_reserved_env_key, validate_user_env_keys, 20 unit tests. - managed_agents/runtime.rs: merges persona env → agent env at spawn via env_vars::merged_user_env. - managed_agents/types.rs: env_vars field on PersonaRecord and ManagedAgentRecord; included in ManagedAgentSummary so the frontend list/edit reload sees saved state. - managed_agents/backend.rs: extends provider stderr/error redaction to scrub user-supplied env values (redact_secrets_with + env_secrets_from_request + redact_env_values_in). Uses single-pass str::replace to avoid a non-terminating loop when a user env value is a substring of '[REDACTED]'. - commands/personas.rs, commands/agents.rs, commands/agent_models.rs: validate_user_env_keys at create/update; merged_user_env also used by sprout-acp models discovery and provider deploy so credentials in persona env flow into both. Model-discovery stderr is now redacted through redact_env_values_in before being formatted into the frontend-visible error. Reserved keys (three categories) - Identity / secrets: SPROUT_PRIVATE_KEY, NOSTR_PRIVATE_KEY, SPROUT_AUTH_TAG, SPROUT_API_TOKEN, SPROUT_ACP_PRIVATE_KEY, SPROUT_ACP_API_TOKEN. - Code-execution surface: SPROUT_ACP_AGENT_COMMAND, SPROUT_ACP_AGENT_ARGS, SPROUT_ACP_MCP_COMMAND, SPROUT_RELAY_URL. - Security gates: SPROUT_ACP_RESPOND_TO, SPROUT_ACP_RESPOND_TO_ALLOWLIST, SPROUT_ACP_AGENT_OWNER (legacy-record owner fallback). Case-insensitive match — lowercase variants of these specific keys are almost certainly typos, not legitimate use. Behavior knobs (GOOSE_MODE, SPROUT_TOOLSETS, SPROUT_ACP_MODEL, SPROUT_ACP_SYSTEM_PROMPT) remain freely overridable. Two-layer enforcement: 1. Save-time — validate_user_env_keys rejects reserved keys with a clear error listing the offending keys, surfaced in the dialog. 2. Runtime — merged_user_env strips reserved keys with a warning log. Defense in depth for older on-disk records that predate validation. Frontend - features/agents/ui/EnvVarsEditor.tsx (new): reusable key/value editor with add/remove rows and validation hints. - PersonaDialog, CreateAgentDialog, EditAgentDialog: embed the editor. - shared/api/types.ts, tauri.ts, tauriPersonas.ts: envVars field on create/update payloads. envVars: undefined on update = 'don't touch' so editing unrelated fields can't wipe saved credentials. Tests - 20 unit tests in env_vars.rs covering merge precedence, reserved-key stripping for each category (identity, code-execution, security gates, legacy owner, relay URL, case-insensitive), and the validator. - 6 new unit tests in backend.rs covering redact_secrets_with (user env values scrubbed, short values skipped, overlapping secrets handled, termination when value is substring of marker), env_secrets_from_request (string extraction + missing-shape), and redact_env_values_in. - 1 e2e test (desktop/tests/e2e/persona-env-vars.spec.ts) drives the EnvVarsEditor through the Persona dialog. - All 307 desktop crate unit tests pass. Also bumps the MessageComposer.tsx size limit from 700→710 (the composer-autofocus PR #572 landed on origin/main mid-review and pushed it 3 lines over). Bumps backend.rs limit from 530→700 for the new redaction helpers + their tests. Supersedes #576. Signed-off-by: Tyler Longwell <tlongwell@squareup.com> Security: reject malformed env var keys Tightens validate_user_env_keys to require POSIX-shaped keys ([A-Za-z_][A-Za-z0-9_]*). Closes a denylist bypass: Rust's Command::env(k, v) accepts a key containing '=' and writes it verbatim into the child's environ block. A key like SPROUT_AUTH_TAG=x with value forged lands as SPROUT_AUTH_TAG=x=forged in the child env, so getenv("SPROUT_AUTH_TAG") returns "x=forged" — bypassing the reserved- key string compare. Confirmed exploitable for SPROUT_AUTH_TAG (legacy agents) and any other reserved key Sprout doesn't always set with the canonical name first. Two-layer fix: 1. validate_user_env_keys rejects malformed keys at save time, listing each invalid key with a clear regex hint. 2. merged_user_env strips malformed keys at spawn time as defense in depth for on-disk records that predate the validator. +6 unit tests pinning the bypass and adjacent edge cases (empty, whitespace, NUL, leading digit, non-ASCII, =-in-key). 313 unit tests pass.
tlongwell-block
added a commit
that referenced
this pull request
May 15, 2026
Adds env_vars: BTreeMap<String, String> on both PersonaRecord and ManagedAgentRecord. Precedence at spawn: parent env < persona < agent (last write wins). Reserved keys — Sprout's identity, code-execution surface, and security gates — are rejected at save time and stripped at runtime so a typo or malicious value can't swap the agent's nsec, code, relay, or respond-to gate. Backend - managed_agents/env_vars.rs (new): merged_user_env, RESERVED_ENV_KEYS, is_reserved_env_key, validate_user_env_keys, 20 unit tests. - managed_agents/runtime.rs: merges persona env → agent env at spawn via env_vars::merged_user_env. - managed_agents/types.rs: env_vars field on PersonaRecord and ManagedAgentRecord; included in ManagedAgentSummary so the frontend list/edit reload sees saved state. - managed_agents/backend.rs: extends provider stderr/error redaction to scrub user-supplied env values (redact_secrets_with + env_secrets_from_request + redact_env_values_in). Uses single-pass str::replace to avoid a non-terminating loop when a user env value is a substring of '[REDACTED]'. - commands/personas.rs, commands/agents.rs, commands/agent_models.rs: validate_user_env_keys at create/update; merged_user_env also used by sprout-acp models discovery and provider deploy so credentials in persona env flow into both. Model-discovery stderr is now redacted through redact_env_values_in before being formatted into the frontend-visible error. Reserved keys (three categories) - Identity / secrets: SPROUT_PRIVATE_KEY, NOSTR_PRIVATE_KEY, SPROUT_AUTH_TAG, SPROUT_API_TOKEN, SPROUT_ACP_PRIVATE_KEY, SPROUT_ACP_API_TOKEN. - Code-execution surface: SPROUT_ACP_AGENT_COMMAND, SPROUT_ACP_AGENT_ARGS, SPROUT_ACP_MCP_COMMAND, SPROUT_RELAY_URL. - Security gates: SPROUT_ACP_RESPOND_TO, SPROUT_ACP_RESPOND_TO_ALLOWLIST, SPROUT_ACP_AGENT_OWNER (legacy-record owner fallback). Case-insensitive match — lowercase variants of these specific keys are almost certainly typos, not legitimate use. Behavior knobs (GOOSE_MODE, SPROUT_TOOLSETS, SPROUT_ACP_MODEL, SPROUT_ACP_SYSTEM_PROMPT) remain freely overridable. Two-layer enforcement: 1. Save-time — validate_user_env_keys rejects reserved keys with a clear error listing the offending keys, surfaced in the dialog. 2. Runtime — merged_user_env strips reserved keys with a warning log. Defense in depth for older on-disk records that predate validation. Frontend - features/agents/ui/EnvVarsEditor.tsx (new): reusable key/value editor with add/remove rows and validation hints. - PersonaDialog, CreateAgentDialog, EditAgentDialog: embed the editor. - shared/api/types.ts, tauri.ts, tauriPersonas.ts: envVars field on create/update payloads. envVars: undefined on update = 'don't touch' so editing unrelated fields can't wipe saved credentials. Tests - 20 unit tests in env_vars.rs covering merge precedence, reserved-key stripping for each category (identity, code-execution, security gates, legacy owner, relay URL, case-insensitive), and the validator. - 6 new unit tests in backend.rs covering redact_secrets_with (user env values scrubbed, short values skipped, overlapping secrets handled, termination when value is substring of marker), env_secrets_from_request (string extraction + missing-shape), and redact_env_values_in. - 1 e2e test (desktop/tests/e2e/persona-env-vars.spec.ts) drives the EnvVarsEditor through the Persona dialog. - All 307 desktop crate unit tests pass. Also bumps the MessageComposer.tsx size limit from 700→710 (the composer-autofocus PR #572 landed on origin/main mid-review and pushed it 3 lines over). Bumps backend.rs limit from 530→700 for the new redaction helpers + their tests. Supersedes #576. Signed-off-by: Tyler Longwell <tlongwell@squareup.com> Security: reject malformed env var keys Tightens validate_user_env_keys to require POSIX-shaped keys ([A-Za-z_][A-Za-z0-9_]*). Closes a denylist bypass: Rust's Command::env(k, v) accepts a key containing '=' and writes it verbatim into the child's environ block. A key like SPROUT_AUTH_TAG=x with value forged lands as SPROUT_AUTH_TAG=x=forged in the child env, so getenv("SPROUT_AUTH_TAG") returns "x=forged" — bypassing the reserved- key string compare. Confirmed exploitable for SPROUT_AUTH_TAG (legacy agents) and any other reserved key Sprout doesn't always set with the canonical name first. Two-layer fix: 1. validate_user_env_keys rejects malformed keys at save time, listing each invalid key with a clear regex hint. 2. merged_user_env strips malformed keys at spawn time as defense in depth for on-disk records that predate the validator. +6 unit tests pinning the bypass and adjacent edge cases (empty, whitespace, NUL, leading digit, non-ASCII, =-in-key). 313 unit tests pass.
tlongwell-block
added a commit
that referenced
this pull request
May 15, 2026
* origin/main: (33 commits) dev-mcp: add view_image tool (#602) fix(relay,desktop): only advertise NIP-43 when enforced; probe pairing by supported_nips (#601) fix(desktop): derive unread state from NIP-RS + relay catch-up only (#599) docs(testing): rewrite TESTING.md for current API and CLI-first workflow (#597) fix(agent): fix OpenAI-compat request body serialization and max_tokens (#595) feat(desktop): per-persona and per-agent env var overrides (#594) fix(desktop): stop pinning agents to deprecated SPROUT_ACP_TURN_TIMEOUT (#592) fix(desktop): populate member_count in get_channels so channel browser shows real counts (#548) fix(desktop): autofocus message composer on channel/thread open (#572) refactor(cli): restructure flat commands into 12 subcommand groups (#585) feat(sdk): add builder functions for workflows, DMs, and presence (#589) feat(desktop): add message more-actions dropdown menu (#590) fix(mobile): preserve channel list across background/resume reconnection (#588) Redesign Home as an inbox (#582) fix(desktop): drive unread badges from live subscription, not refetched lastMessageAt (#581) fix(desktop): refine header scaling and shadow (#573) fix(desktop): keep day dividers below header (#574) Move agent activity below composer (#579) docs(nips): NIP-AE — Agent Engrams (#575) refactor: extract shared @mention resolver into sprout-sdk (#580) ... Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
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.
Problem
[Reported by @moneyball in #sprout-feedback](https://github.com/block/sprout/issues/... — sprout thread):
Fix
The composer already focused on
editTargetand on in-threadreplyTargetchanges, but had no autofocus on mount or when the channel/thread changed. Added a smalluseComposerAutofocushook that callsrichText.focus()whenever the effective draft key changes (channel or thread switch) — same hook covers both repros because:channelId(and thereforeeffectiveDraftKey) changes → focus.Guarded against:
INPUT/TEXTAREA/SELECT/contenteditable) — so an open dialog or search box won't have focus yanked from it.Tests
Added three Playwright tests in
messaging.spec.ts:All pass. Full
messaging,mentions, andchannelssuites still green (62 tests).