Commit Graph

9 Commits

Author SHA1 Message Date
teknium1 602801baa1 chore(secrets): cover the spawn ledger and meet-node token in the private-writer invariant; drop unused import
Both writers already go through atomic_json_write(mode=0o600) but were
missing from tests/test_private_credential_writers.py::_writers, so a
"write then chmod" regression in either would not be caught. Also removes
the `import os` left unused in agent/secret_sources/_cache.py (ruff F401).
2026-09-13 05:07:11 -07:00
teknium1 2be8e6147a refactor(secrets): every private-credential file is written by utils.atomic_json_write(mode=0o600)
Ten hand-rolled "write a token file safely" routines each carried a
different subset of {0600-on-create, fsync, atomic_replace, parent-0700
guard, BaseException cleanup}. Two of them (iron_proxy state files,
the exchanged-JWT store) still opened the temp file at process umask
and chmod'ed afterwards - the exact TOCTOU window the others document
as fixed. None of the bare-os.replace copies got atomic_replace's
Windows-contention retry or EXDEV fallback.

utils gains fsync_dir= (absorbs auth.py's dir fsync), atomic_write_bytes
(vault blob) and mode= on atomic_write_text; the ten sites become 1-3
line callers. mkstemp creates the temp file O_EXCL at 0600 regardless of
umask, so the payload is never umask-readable.

Behavior change: iron_proxy proxy.yaml/mappings.json and the exchanged-JWT
store are now 0600 from creation and fsync'd; every credential write goes
through atomic_replace (symlink-preserving, Windows retry, EXDEV copy).
auth_nous shared store now uses atomic_replace too (it forced os.replace
with no recorded reason). secret_sources cache parent-0700 goes through
the guarded secure_parent_dir instead of an unguarded chmod.
2026-09-13 05:07:11 -07:00
Teknium d63e380324 compat(plugins): warn once per name when a plugin resolves an old import path; lint step restored in CI
Every PLUGIN-COMPAT __getattr__ now calls hermes_cli.plugin_compat.warn_once(facade, name, target) before
resolving, emitting a HermesPluginCompatWarning (FutureWarning) once per process per name: old path, new
path, removal target. Importing a facade for its live API stays silent; only resolving a moved name warns.
COMPAT_MANIFEST.md documents the warning and how to silence it during migration.

Verified the runtime never routes through a pointer: every entry point (run_agent, cli, hermes_cli.main,
gateway.run, tui_gateway.server, web_server, model_tools + tool discovery, hermes_state, cron.scheduler,
browser_tool, mcp_tool, kanban, auth) imports clean and `hermes doctor` runs end to end with the warning
promoted to an error.

Also restores the check_compat_pointers CI step to .github/workflows/lint.yml, which a0be177aac dropped
when the compat layer was regenerated (the lint script itself was present; the workflow step was not).

hermes_cli/plugin_compat.py, tests/test_plugin_compat_warning.py and the two-line insert per facade are
part of the compat layer and go away with it.
2026-09-04 00:15:16 -07:00
Teknium 2776813df3 compat(plugins): temporary import-path shims for external plugins — ONE commit, revert on schedule
The Sep 2026 decomposition (PR #102117) makes internal import paths a non-API: names now live in
the focused modules that define them. This commit is the ONLY thing keeping the old paths alive,
so external plugins have time to update. It is deliberately a single, unsquashed commit:

    git revert <this sha>

removes every shim, stub and manifest at once on the announced date. Nothing in-tree may depend on
these pointers: scripts/check_compat_pointers.py (wired into lint.yml) fails CI if it does.

What it adds (see COMPAT_MANIFEST.md, compat_manifest.json):
- 332 facade modules get one delimited `PLUGIN-COMPAT` block appended at the end of the file
- 1,172 moved names resolved lazily via a module `__getattr__` (PEP 562) — never a top-level import,
  so no import cycles; facades that already had `__getattr__` get a chained one
- 592 third-party/stdlib names the old modules used to expose, with their original import statements
- 266 public definitions that had been deleted as unused, restored byte-for-byte from the pre-decomposition
  tree (+40 private helpers and 16 imports pulled in only because a restored definition needs them)
- 3 deleted modules recreated as re-export stubs (gateway/startup_watchdog, hermes_cli/observability/
  relay_runtime, tools/environments/modal_utils)
- private names (`_x`) get no pointer: they were never API (3,792 skipped)

Verified: all 335 touched modules import under a fresh HERMES_HOME and every manifest name resolves;
the lint reports zero in-tree uses; ruff clean; targeted suites unchanged.
2026-09-03 17:13:22 -07:00
Teknium 2ac0a6a73d refactor(agent/secret_sources): compact signatures/schemas/error strings, next()-based scheme owner lookup, drop _b64d 2026-09-02 19:18:03 -07:00
Teknium 5117249d39 refactor(agent/secret_sources): shared fingerprint + remediation_hints table, _validate_source, drop unused _cache re-exports, compact docstrings 2026-09-02 18:53:00 -07:00
Teknium d4f2933262 refactor(agent/creds): unify secret-source CLI/cache/error plumbing in base and _cache
- base: run_cli (shared subprocess wrapper), classify_cli_error (rule tables),
  coerce_float, source_child_env, FetchResult.fail, SecretSource.token_env /
  token_env_key / default_token_env / override_existing_default so per-backend
  override_existing/protected_env_vars overrides collapse into the ABC;
  generic remediation is a kind->template table.
- _cache: atomic_write_json (mkstemp->0600->replace) and entry_from_payload
  shared by DiskCache and the bws encrypted cache; SecretCache = L1 dict + L2
  DiskCache with lookup/store/clear.
2026-09-02 13:29:46 -07:00
teknium1 19055492aa fix: route stray HERMES_HOME hardcodes through get_hermes_home() (profile + native-Windows safety) 2026-07-29 09:33:48 -07:00
Taylor H. Perkins db495b0fba refactor(secrets): extract shared cache/result substrate for secret sources
Pull the disk-cache + FetchResult substrate out of bitwarden.py into a new
agent/secret_sources/_cache.py: FetchResult, CachedFetch, is_valid_env_name,
and a generic DiskCache (atomic mkstemp -> chmod 0600 -> os.replace write,
0700 cache dir, TTL-gated read AND write). Bitwarden now consumes it via a
module-level DiskCache instance and thin wrappers, so the security-sensitive
atomic-write/0600/TTL logic lives in exactly one place instead of being
copy-pasted per backend (and drifting). Behavior is unchanged — the full
Bitwarden suite passes untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 04:58:07 -07:00