Skip to content

security: path traversal fix, Claude Code credential gate, DANGEROUS_PATTERNS gaps - #7156

Merged
teknium1 merged 6 commits into
mainfrom
security/bucket-j
Apr 10, 2026
Merged

teknium1 merged 6 commits into
mainfrom
security/bucket-j

Conversation

@teknium1

Copy link
Copy Markdown
Collaborator

Summary

Three security fixes salvaged from PRs #7065, #7009, and #6961. All contributor authorship preserved.

1. Skill manager path traversal (PR #7065, @Dusk1e)

Symlinks inside skill directories could escape to arbitrary files for write/patch/remove. Adds _resolve_skill_target() with resolved-path containment check. 3 tests.

2. Claude Code credential gate (PR #7009, @wanpengxie)

When a user's primary provider fails, the auxiliary fallback chain silently discovered and used Claude Code OAuth tokens from ~/.claude/.credentials.json without consent. Adds is_provider_explicitly_configured() gate — credentials are only used when the user explicitly configured Anthropic. Defense in depth: gate in credential pool + gate in aux client + suppression on hermes auth remove. 9 tests.

3. DANGEROUS_PATTERNS gaps (PR #6961, @win4r)

Closes 4 bypass categories: heredoc script execution (python3 <<), pgrep kill expansion, git destructive ops (reset --hard, push --force, clean -f, branch -D), and chmod+exec combo. 11 new patterns, 23 tests.

E2E verification

All three fixes verified with real file I/O, real symlink creation, real env var manipulation:

  • Symlink escapes blocked for write/patch/remove; legitimate writes pass
  • CLAUDE_CODE_OAUTH_TOKEN excluded from explicit config detection; ANTHROPIC_API_KEY included
  • All 8 dangerous commands flagged, all 7 safe commands pass (zero false positives)

Test results

301 targeted tests passing across all affected files.

Junass1 and others added 6 commits April 10, 2026 05:05
Gate function for checking whether a user has explicitly selected a
provider via hermes model/setup, auth.json active_provider, or env
vars.  Used in subsequent commits to prevent unauthorized credential
auto-discovery.  Follows the pattern from PR #4210.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…er config

_seed_from_singletons('anthropic') now checks
is_provider_explicitly_configured('anthropic') before reading
~/.claude/.credentials.json.  Without this, the auxiliary client
fallback chain silently discovers and uses Claude Code tokens when
the user's primary provider key is invalid — consuming their Claude
Max subscription quota without consent.

Follows the same gating pattern as PR #4210 (setup wizard gate)
but applied to the credential pool seeding path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously, removing a claude_code credential from the anthropic pool
only printed a note — the next load_pool() re-seeded it from
~/.claude/.credentials.json.  Now writes a 'suppressed_sources' flag
to auth.json that _seed_from_singletons checks before seeding.

Follows the pattern of env: source removal (clears .env var) and
device_code removal (clears auth store state).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…configured

_resolve_api_key_provider() now checks is_provider_explicitly_configured
before calling _try_anthropic().  Previously, any auxiliary fallback
(e.g. when kimi-coding key was invalid) would silently discover and use
Claude Code OAuth tokens — consuming the user's Claude Max subscription
without their knowledge.

This is the auxiliary-client counterpart of the setup-wizard gate in
PR #4210.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Four gaps in DANGEROUS_PATTERNS found by running 10 targeted tests that
each mapped to a specific pattern in approval.py and checked whether the
documented defense actually held.

1. **Heredoc script injection** — `python3 << 'EOF'` bypasses the
   existing `-e`/`-c` flag pattern. Adds pattern for interpreter + `<<`
   covering python{2,3}, perl, ruby, node.

2. **PID expansion self-termination** — `kill -9 $(pgrep hermes)` is
   opaque to the existing `pkill|killall` + name pattern because command
   substitution is not expanded at detection time. Adds structural
   patterns matching `kill` + `$(pgrep` and backtick variants.

3. **Git destructive operations** — `git reset --hard`, `push --force`,
   `push -f`, `clean -f*`, and `branch -D` were entirely absent.
   Note: `branch -d` also triggers because IGNORECASE is global —
   acceptable since -d is still a delete, just a safe one, and the
   prompt is only a confirmation, not a hard block.

4. **chmod +x then execute** — two-step social engineering where a
   script containing dangerous commands is first written to disk (not
   checked by write_file), then made executable and run as `./script`.
   Pattern catches `chmod +x ... [;&|]+ ./` combos. Does not solve the
   deeper architectural issue (write_file not checking content) — that
   is called out in the PR description as a known limitation.

Tests: 23 new cases across 4 test classes, all in test_approval.py:
  - TestHeredocScriptExecution (7 cases, incl. regressions for -c)
  - TestPgrepKillExpansion (5 cases, incl. safe kill PID negative)
  - TestGitDestructiveOps (8 cases, incl. safe git status/push negatives)
  - TestChmodExecuteCombo (3 cases, incl. safe chmod-only negative)

Full suite: 146 passed, 0 failed.
@teknium1
teknium1 merged commit aedf6c7 into main Apr 10, 2026
5 of 6 checks passed
CybercodeXx pushed a commit to CybercodeXx/hermes-agent that referenced this pull request Apr 17, 2026
Scenario: user configures kimi-coding with a malformed API key.
When the main agent fails and auxiliary fallback runs, the fix must
prevent _try_anthropic() from silently reading ~/.claude/.credentials.json.

9 tests covering all three vulnerable paths from PRs NousResearch#7009/NousResearch#7156:
  A. _seed_from_singletons() — credential pool gate
  B. _resolve_api_key_provider() — auxiliary client fallback chain
  C. suppress_credential_source() — re-seed prevention after auth remove

Also verifies CLAUDE_CODE_OAUTH_TOKEN (implicit) vs ANTHROPIC_API_KEY
(explicit) are distinguished correctly by is_provider_explicitly_configured().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fcavalcantirj added a commit to fcavalcantirj/hermes-agent that referenced this pull request Aug 13, 2026
…credentials don't pick providers

The auto-detect loop in resolve_provider('auto') treated
CLAUDE_CODE_OAUTH_TOKEN like a user-exported API key and auto-selected
the Anthropic HTTP lane from its mere presence. That token is written by
Claude Code's own login, not by a user configuring Hermes — the exact
distinction is_provider_explicitly_configured() already draws (NousResearch#7156:
'credentials are only used when the user explicitly configured
Anthropic'), and the same loop already skips copilot/lmstudio for the
same implicit-credential reason.

Ambient auto-selection routes those users into documented pain: 429s on
the OAuth-over-HTTP path (NousResearch#70039) or silent metered 'extra usage'
overage billing (NousResearch#58546). Hermes' own explicit flows are untouched —
they persist ANTHROPIC_TOKEN (still matched), and model.provider:
anthropic with only the Claude Code token still resolves and uses it.

When the token is present and nothing else is configured, the
no-provider AuthError now says the token was seen and deliberately not
auto-selected, with the one-line remedy.

Tests: token-only auto now raises (was: silent anthropic);
ANTHROPIC_API_KEY still auto-selects (no over-exclusion); explicit
provider with the token unaffected. _clear_provider_env also learns the
ANTHROPIC_*/CLAUDE_CODE_OAUTH_TOKEN vars so the suite stops depending on
the developer machine's login state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants