Skip to content

fix(config): strip control chars from saved env credentials - #55336

Open
ooiuuii wants to merge 1 commit into
NousResearch:mainfrom
ooiuuii:fix/secret-input-strip-controls
Open

fix(config): strip control chars from saved env credentials#55336
ooiuuii wants to merge 1 commit into
NousResearch:mainfrom
ooiuuii:fix/secret-input-strip-controls

Conversation

@ooiuuii

@ooiuuii ooiuuii commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #55335.

This strips ASCII control bytes (C0 plus DEL) from values passed through save_env_value() before Hermes writes .env and before it updates os.environ.

Hermes already stripped \n and \r, and already warns/strips non-ASCII credential artifacts. The missing gap was other ASCII controls such as NUL, TAB, ESC, and DEL:

  • NUL can make os.environ[key] = value raise ValueError: embedded null character on Windows after the dirty value has already been written to .env.
  • TAB/DEL survive silently in .env and process env, causing confusing provider auth/header failures later.

Validation

  • Before the fix, the new regression test failed on Windows with ValueError: embedded null character.
  • Direct repro after the fix stores and loads OPENAI_API_KEY=sk-livekeynext with no control bytes.
  • python -m pytest tests/hermes_cli/test_config.py::TestSaveEnvValueSecure -q --basetemp .pytest-tmp-envsave → 9 passed
  • python -m pytest tests/hermes_cli/test_non_ascii_credential.py -q --basetemp .pytest-tmp-nonascii → 12 passed
  • ruff check hermes_cli/config.py tests/hermes_cli/test_config.py → passed
  • git diff --check → passed

Local note: the full tests/hermes_cli/test_config.py file is not a clean Windows/GBK gate in this checkout; it has unrelated existing failures around the Windows default home expectation and locale-decoding UTF-8 config files. The touched save-env class passes.

Agent transcript

  • Compared this against OpenClaw's recent secret-input control-character hardening and mapped it to Hermes' save_env_value() path.
  • Searched open/closed Hermes issues and PRs for duplicate credential/control-byte reports. Adjacent work exists for FAL runtime validation and non-ASCII warnings, but not this save-time .env path.
  • Reproduced the failure before changing code: NUL writes to .env then crashes on os.environ; TAB/DEL persist without crashing.
  • Added a focused regression test and kept the implementation scoped to pre-existing env credential normalization.

@alt-glitch alt-glitch added type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard area/auth Authentication, OAuth, credential pools P2 Medium — degraded but workaround exists labels Jun 30, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.
Competing with #40887 (earlier, still open) for the same save_env_value() save-time control-char gap. Scope differs: #40887 strips C0-except-TAB + C1 and adds http(s) validation for *_URL/_HOST/_ENDPOINT; this PR strips C0+DEL (including TAB) and explicitly handles the Windows os.environ NUL crash. Cross-linking so a maintainer can pick (or merge the superset). Also related: #25133 (getpass control chars on Windows).

@tonydwb tonydwb 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.

LGTM. Strips ASCII control characters from saved credentials. Clean security fix with test.

@tonydwb

tonydwb commented Jun 30, 2026

Copy link
Copy Markdown

Code Review Summary

Verdict: Approved

Strips ASCII control characters (below space, DEL) from saved environment credentials. This prevents invisible characters from being stored in .env files which could cause authentication failures.

✅ Looks Good

  • Clean function that filters by ordinal value
  • Good test coverage: verifies control chars are stripped, visible chars preserved
  • Replaces the previous narrow newline-only stripping
  • Works correctly with the existing non-ASCII check

Reviewed by Hermes Agent

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 15, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for identifying the save-time control-byte gap. The central premise is still present on current main: hermes_cli/config.py:7554 only removes LF/CR before the helper writes the value and assigns it to os.environ at hermes_cli/config.py:7618.

Problems

  • The TUI save flow calls save_env_value() at tui_gateway/server.py:12958 but then replaces the sanitized environment value with raw api_key at tui_gateway/server.py:12962. The same raw post-save overwrite exists in hermes_cli/secrets_cli.py:176-177 and hermes_cli/onepassword_secrets_cli.py:145-146; a NUL can still fail there after the clean .env write.
  • The new helper applies to all save_env_value() inputs, while hermes_cli/mcp_catalog.py:438 documents that this writer stores non-secrets too. The unconditional TAB removal needs an explicit compatibility decision or narrower credential-only scope.

Suggested changes

  • Eliminate or sanitize the post-save raw assignments and cover a TUI provider-key save regression.
  • Specify and test the non-secret/TAB policy.

Automated hermes-sweeper review.

Comment thread hermes_cli/config.py
@@ -6826,7 +6831,7 @@ def save_env_value(key: str, value: str):
if not _ENV_VAR_NAME_RE.match(key):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

save_env_value() itself updates os.environ, but tui_gateway/server.py:12958-12962, hermes_cli/secrets_cli.py:176-177, and hermes_cli/onepassword_secrets_cli.py:145-146 overwrite that entry with the original raw credential afterward. Please remove or sanitize those redundant assignments and add a path-level regression; otherwise a NUL still raises after the clean .env write.

Comment thread hermes_cli/config.py Outdated
@@ -6789,6 +6789,11 @@ def _check_non_ascii_credential(key: str, value: str) -> str:
return sanitized


def _strip_ascii_control_credential_chars(value: str) -> str:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This helper is invoked for every save_env_value() call, including non-secret MCP catalog variables (hermes_cli/mcp_catalog.py:438). Please establish whether stripping TAB is intended for that broader API, or constrain the normalization to credential values.

@ooiuuii
ooiuuii force-pushed the fix/secret-input-strip-controls branch from 1566874 to 0d59186 Compare August 12, 2026 12:21
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 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-platform-windows Sweeper risk: may break or behave differently on native Windows 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.

save_env_value can persist ASCII control characters in API keys

4 participants