cf60ebbdfd264c3fc067b9e3230df8752c50d2ec
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1152d4d3ce |
fix(cli): recognize whitespace around '=' in .env save/remove
_env_line_defines_key() decides which .env lines the writers may rewrite or
drop. It matched on the `KEY=` prefix, but load_env() splits on the first
`=` and strips the name:
key, _, value = line.partition('=')
env_vars[key.strip()] = _parse_env_value(value)
so `OPENAI_API_KEY = sk-...` is a live assignment — the key resolves, the
provider works, and every UI shows it as set. The writers did not see it.
This is the same resurrection hole #40041 fixed for `export KEY=`, still
open for the whitespace form:
- DELETE /api/env 404s ("not found in .env") while the credential stays
active — a key the user revoked through the UI is never actually revoked
- PUT /api/env appends a SECOND line instead of replacing; a later delete
removes the appended line and the original value silently comes back
Rotate-then-delete on a spaced line therefore restores exactly the key the
user rotated away from.
Match load_env()'s parse instead of prefix-matching, so the writers accept
precisely what the reader accepts: skip blank/comment/no-'=' lines, strip an
`export ` prefix, then compare the stripped name. Commented-out lines stay
untouched and `KEY_EXTRA=`/`MY_KEY=` still do not match `KEY`.
Verified against the real dashboard endpoints on a temp HERMES_HOME: the
spaced line is now removed, rotation replaces it in place with no duplicate,
and a parity check asserts the writer matches a line iff load_env() does.
|
||
|
|
39975613b1 |
test: prune wave 2 + speed fixes — 28,106 → 19,757 test functions, suite wall 315s → 294s
Second, deeper pass over tools/gateway/hermes_cli plus first pass over the trees wave 1 missed (acp, acp_adapter, skills, computer_use, docker, dashboard, conformance, monitoring, secret_sources, hermes_state, providers). Same rubric as wave 1 (AGENTS.md test policy); security, alternation/caching invariants, issue-number regressions, and E2E kept. Real test-quality fixes found and rooted out along the way: - tests/tools/test_command_guards.py made real auxiliary-LLM HTTPS calls (DEFAULT_CONFIG smart-approval leaked in) — pinned approval mode=manual via autouse fixture: 17.4s → 0.4s. - test_model_switch_custom_providers.py / test_user_providers_model_switch.py silently probed live provider catalogs (~2s/test) — stubbed cached_provider_model_ids/provider_model_ids/fetch_api_models. - test_telegram_noise_filter.py: 15-platform copy-paste matrix over shared gateway.run logic → 3 representative platforms (55s → 3.9s). - test_gateway_shutdown.py: stop()'s 5s interrupt-deadline loop spun on MagicMock agents — interrupt.side_effect now clears _running_agents (22s → 1.0s). - test_gateway_inactivity_timeout.py poll-harness timings shrunk 3-5x (24s → 1.1s); test_mcp_stability.py backoff/SIGTERM-grace sleeps patched (15.4s → 2.5s); test_async_delegation.py negative-drain wait 5s → 0.5s. - test_telegram_init_deadline.py: loop-block margin restored to 1.0s with rationale comment — the watchdog-dump assertion needs the loop blocked well past deadline+grace under parallel load (flaked once in the 40-worker verification run at a 0.2s margin). Verification: full hermetic suite via scripts/run_tests.sh — 2,438 files, 21,718 tests passed, 0 failed, 293.9s wall. Suite totals vs original baseline: 46,820 → 19,757 test functions (−57.8%), wall 583.5s → 293.9s (−50%), subprocess CPU 13,564s → 11,623s. |
||
|
|
9a987f142d |
fix(credentials): unified provider key delete/update across .env, auth.json, config.yaml (#67213)
* fix(env): recognize export-prefixed .env lines in save/remove (#40041) load_env() parses bash-compatible 'export KEY=value' lines (#6659), so a hand-added 'export GITHUB_TOKEN=ghp_...' shows as set (green light) in the desktop Tools & Keys page. But save_env_value/remove_env_value only matched plain 'KEY=' lines: - DELETE /api/env 404'd ('not found in .env') — the token could not be removed through the UI - PUT /api/env appended a SECOND line; a later delete removed the new line while the export line silently resurrected the old value Both writers now match assignments through a shared _env_line_defines_key() helper that understands the export prefix. Commented-out lines are still ignored. Regression tests drive the real dashboard endpoint handlers against a temp HERMES_HOME with runtime-constructed classic-PAT-shaped fixtures, covering save-does-not-500, export-line remove, export-line replace-without-duplicate, and the plain-line path staying intact. Fixes #40041 * fix(credentials): unify provider key delete/update across .env, auth.json, config.yaml (#51071 #59761 #62269) A provider API key can live in three stores at once: ~/.hermes/.env, auth.json credential_pool (env-seeded 'env:<VAR>' entries persisted by the pool loader), and config.yaml mirrors (model.api_key, auxiliary.*.api_key, custom_providers[*].api_key). The desktop/dashboard endpoints and the TUI gateway RPCs only ever mutated .env, so the stores diverged: - #51071/#59761: DELETE /api/env removed the key from .env but left the credential_pool entry (the loader is additive-only and never prunes), so the provider kept appearing in the model picker — surviving restart via the stale pool entry + provider_models_cache.json row. - #62269: PUT /api/env rewrote .env but left the OLD key in config.yaml (model.api_key wins over env at client construction), producing 401s with a key the UI no longer showed. New hermes_cli/credential_lifecycle.py is the single choke point: - remove_provider_env_credential(): clears the .env entry, prunes env:<VAR> pool entries across ALL providers (a shared var like GITHUB_TOKEN can seed several), suppresses the env source so a lingering shell export can't re-seed it (matching 'hermes auth remove' semantics), drops the affected providers' model-cache rows, and scrubs value-matched config.yaml api_key mirrors. Returns 'found' spanning every store so a stale pool-only entry is cleanable through the same delete button. - save_provider_env_credential(): writes .env, rotates any config.yaml mirror that held the PREVIOUS value (value-matched — an unrelated inline key is untouched), and lifts a prior env-source suppression so re-adding behaves like 'hermes auth add'. OAuth preservation: only entries with source == 'env:<VAR>' are pruned. OAuth/device-code/manual/borrowed pool entries and providers.<id> OAuth token blocks are never touched by a key-only delete. (model.disconnect in the TUI gateway still clears OAuth via clear_provider_auth — that surface is a full provider disconnect, which is the documented intent there.) Rerouted call sites: PUT/DELETE /api/env (dashboard + desktop), tui_gateway model.save_key / model.disconnect, save_env_value_secure (TUI/gateway secret capture), and hermes config set/unset for env-shaped keys. E2E tests drive the real endpoint handlers against temp-HERMES_HOME fixtures (.env + auth.json + config.yaml with runtime-constructed fake keys) and assert cross-store consistency after delete/update, pool-reload survival ('restart'), OAuth preservation, models-cache invalidation, and the suppress/unsuppress round-trip. Fixes #51071 Fixes #59761 Fixes #62269 |