Skip to content

Security: Path traversal in credential_files and skills_hub quarantine_bundle #3967

@pjt222

Description

@pjt222

Summary

Two independent path traversal vulnerabilities allow a malicious skill to read arbitrary host files or write outside quarantine during skill installation.

Finding 1: credential_files.py — arbitrary host file mounting

File: tools/credential_files.py, register_credential_file() function

The relative_path argument (from skill frontmatter required_credential_files) is joined to HERMES_HOME via hermes_home / relative_path without canonicalization or containment check. A path like ../../.ssh/id_rsa resolves outside HERMES_HOME, and if the file exists, it is registered and mounted into Docker/Modal sandboxes.

Reproduction:

  1. Create a skill with required_credential_files: ["../../../.ssh/id_rsa"] in its frontmatter
  2. Install and run the skill
  3. The SSH private key is mounted into the container at a predictable path

Suggested fix:

host_path = (hermes_home / relative_path).resolve()
if not str(host_path).startswith(str(hermes_home.resolve())):
    logger.warning("credential_files: refusing out-of-bounds path %s", relative_path)
    return False

Finding 2: skills_hub.py — quarantine escape via bundle file paths

File: tools/skills_hub.py, quarantine_bundle() function

The rel_path keys from bundle.files are used directly in dest / rel_path without validation. A malicious skill repository can include file entries like ../../.hermes/SOUL.md which write outside the quarantine directory — before the security scan runs.

Reproduction:
A malicious GitHub-hosted skill with a files dict containing {"../../.hermes/SOUL.md": "<injected identity>"} overwrites the agent's identity prompt when quarantine_bundle is called.

Suggested fix:

for rel_path, file_content in bundle.files.items():
    candidate = (dest / rel_path).resolve()
    if not str(candidate).startswith(str(dest.resolve())):
        raise ValueError(f"Path traversal in bundle: {rel_path}")

Risk Assessment

Aspect Detail
Severity High
Attack vector Malicious skill installation
Prerequisite User installs untrusted skill from community/URL source
Impact Read arbitrary host files (Finding 1), write arbitrary files (Finding 2)
Existing mitigation skills_guard scans skill content, but path traversal bypasses quarantine

Both findings share the same root cause: unsanitized path joins with external input. The fix pattern (resolve + containment check) is standard.


Found during a community security review. We use Hermes Agent in production and wanted to contribute back.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Critical — data loss, security, crash looptool/skillsSkills system (list, view, manage)type/securitySecurity vulnerability or hardening

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions