Skip to content

fix(hermes_cli): stop hermes update from leaking default API keys into distribution profiles - #71435

Open
fangliquanflq wants to merge 10 commits into
NousResearch:mainfrom
fangliquanflq:fix/profile-dist-seed-env
Open

fix(hermes_cli): stop hermes update from leaking default API keys into distribution profiles#71435
fangliquanflq wants to merge 10 commits into
NousResearch:mainfrom
fangliquanflq:fix/profile-dist-seed-env

Conversation

@fangliquanflq

@fangliquanflq fangliquanflq commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fresh hermes profile install profiles never got a per-profile .env sentinel (unlike hermes profile create). The next hermes update then treated them as legacy and copied the default profile's .env (API keys / tokens) into the installed profile, breaking credential isolation.

Bug Cause

install_distribution bootstrapped dirs and copied the dist payload but skipped seeding .env. Credentials are intentionally excluded from the payload (USER_OWNED_EXCLUDE), so the new profile had no secrets file. backfill_profile_envs (invoked from hermes update) copies the default home .env into any named profile missing .env - correct for pre-#44792 regular profiles, wrong for brand-new distribution installs that never shared those credentials.

Reproduction Steps

  1. Put real secrets in the default profile .env (e.g. OPENAI_API_KEY=...).
  2. hermes profile install <local-or-git-dist> --name telem -y.
  3. Confirm profiles/telem/ has no .env (only .env.EXAMPLE if the manifest lists env requirements).
  4. Run hermes update.
  5. Observe profiles/telem/.env is a byte-copy of the default secrets.

Expected: installed dist profile keeps its own empty/placeholder .env (or none that is later filled with foreign secrets).
Before fix: hermes update copies default API keys into the dist profile.

Fix

  1. Seed the same comment-only owner-only .env sentinel on install_distribution that create_profile already writes; fail closed on fresh installs if the write fails (remove the half-created tree).
  2. Harden backfill_profile_envs so profiles with distribution.yaml get a placeholder instead of a copy of the default .env (covers pre-fix installs already on disk).
  3. Regression tests for seed content, backfill isolation, --force preserve, write-failure cleanup, and legacy dist without .env.

Related Issue

No issue

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • 🔒 Security fix

Changes Made

  • hermes_cli/profile_distribution.py - seed empty .env after dist install; fail closed on fresh targets if seed write fails
  • hermes_cli/profiles.py - backfill_profile_envs writes a placeholder for distribution profiles instead of copying default secrets
  • tests/hermes_cli/test_profile_distribution.py - regression coverage for seed, backfill isolation, force preserve, and seed write failure

How to Test

  1. Manual: follow Reproduction Steps; after fix, step 2 creates a placeholder .env, and step 4 must not copy default secrets (including when .env is deleted to simulate a pre-fix install).
  2. Automated (already run locally, passed):
scripts/run_tests.sh \
  tests/hermes_cli/test_profile_distribution.py \
  tests/hermes_cli/test_profiles.py::TestBackfillProfileEnvs \
  -q

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature
  • I've run scripts/run_tests.sh on relevant tests and they pass
  • I've added tests for my changes
  • I've tested on my platform: Windows 11

Documentation & Housekeeping

  • I've updated relevant documentation - N/A
  • I've updated cli-config.yaml.example if I added/changed config keys - N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows - N/A
  • I've considered cross-platform impact - N/A (pathlib + chmod best-effort)
  • I've updated tool descriptions/schemas if I changed tool behavior - N/A

Without a per-profile .env sentinel, hermes update's backfill copies the
default profile's API keys into a freshly installed distribution profile.
If the sentinel write fails after the profile tree is created, refuse the
install and remove a fresh target so hermes update cannot backfill default
credentials into an env-less profile.
…ofiles

Pre-fix dist installs lack .env; hermes update must seed a placeholder
instead of copying the default profile's API keys into them.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard area/profiles Multi-profile isolation, HERMES_HOME scoping area/auth Authentication, OAuth, credential pools sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 25, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for tracing this through both distribution installation and the update-time migration. Current main confirms the premise: hermes_cli/profile_distribution.py:101-103 excludes .env, install_distribution() does not replace it at :635-642, and backfill_profile_envs() copies the default .env at hermes_cli/profiles.py:1263-1264.

Problems

  • hermes_cli/update_cmd.py:3880-3881 always says seeded profiles were “copied from default.” With this PR, distribution profiles instead receive a placeholder, so that status message is inaccurate.

Suggested changes

  • Make the update summary distinguish copied versus placeholder results, or use neutral wording, and cover that output path.

This is an automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/install-update Installer, updater, packaging, wheels, doctor labels Jul 30, 2026
…fill summary

Distribution profiles now get a placeholder .env; stop claiming every
seeded profile was copied from default.
@fangliquanflq

Copy link
Copy Markdown
Contributor Author

Addressed in 7afd76a.

backfill_profile_envs now returns BackfillEnvResult with separate copied / placeholder lists, and hermes update prints via format_backfill_env_summary so the status line distinguishes the two (e.g. copied from default: ... vs placeholder: ..., or both when mixed). Added unit coverage for that summary path.

…-env

# Conflicts:
#	hermes_cli/main.py
#	tests/hermes_cli/test_profiles.py
Distribution-aware backfill no longer copies default secrets; stop
claiming that in the install fail-closed message.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools area/install-update Installer, updater, packaging, wheels, doctor area/profiles Multi-profile isolation, HERMES_HOME scoping comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants