Skip to content

feat: mount skills directory into remote backends (Modal, Docker)#3890

Merged
teknium1 merged 1 commit intomainfrom
hermes/hermes-deb3d2ef
Mar 30, 2026
Merged

feat: mount skills directory into remote backends (Modal, Docker)#3890
teknium1 merged 1 commit intomainfrom
hermes/hermes-deb3d2ef

Conversation

@teknium1
Copy link
Copy Markdown
Contributor

Summary

Skills with scripts/, templates/, and references/ subdirectories need those files available inside sandboxed execution environments. Previously only individual credential files were mounted — the skills directory itself was completely absent from Modal/Docker sandboxes, meaning skill scripts couldn't be executed.

Reported by ilovescience (Tanishq) who uses Modal as a terminal backend — ~/.hermes/skills/ didn't exist at all in the sandbox.

Changes

File Change
tools/credential_files.py Add get_skills_directory_mount() — returns $HERMES_HOME/skills/ mount info
tools/credential_files.py Fix name/path key fallback — skills using name in required_credential_files were silently skipped
tools/environments/modal.py Mount skills dir via Mount.from_local_dir() at sandbox creation
tools/environments/docker.py Mount skills dir as read-only bind mount
tests/tools/test_credential_files.py 8 new tests

How it works

The skills tree is mounted read-only at /root/.hermes/skills/ inside the container. This means:

  • Skill scripts are executable in the remote env (python /root/.hermes/skills/.../scripts/setup.py)
  • The agent's context references to skill paths resolve correctly
  • No config, .env, auth.json, or other sensitive files leak — only the skills tree

Tests

  • 8 new tests: name/path fallback, skills dir mount presence/absence, custom container base, missing file reporting
  • 38 existing docker/modal tests pass
  • All tools tests pass
@teknium1 teknium1 force-pushed the hermes/hermes-deb3d2ef branch from 3a18eca to 0219cbf Compare March 30, 2026 06:34
@github-actions
Copy link
Copy Markdown

⚠️ Supply Chain Risk Detected

This PR contains patterns commonly associated with supply chain attacks. This does not mean the PR is malicious — but these patterns require careful human review before merging.

⚠️ WARNING: exec() or eval() usage

Dynamic code execution can hide malicious behavior, especially when combined with base64 or network fetches.

Matches (first 20):

326:+                    self._sandbox.process.exec(f"mkdir -p {parent}")
343:+                self._sandbox.process.exec(f"mkdir -p {container_root}")
351:+                        self._sandbox.process.exec(f"mkdir -p {remote_parent}")

Automated scan triggered by supply-chain-audit. If this is a false positive, a maintainer can approve after manual review.

@teknium1 teknium1 force-pushed the hermes/hermes-deb3d2ef branch from 0219cbf to bce4fa6 Compare March 30, 2026 06:54
@github-actions
Copy link
Copy Markdown

⚠️ Supply Chain Risk Detected

This PR contains patterns commonly associated with supply chain attacks. This does not mean the PR is malicious — but these patterns require careful human review before merging.

⚠️ WARNING: exec() or eval() usage

Dynamic code execution can hide malicious behavior, especially when combined with base64 or network fetches.

Matches (first 20):

465:+                    self._sandbox.process.exec(f"mkdir -p {parent}")
486:+                            self._sandbox.process.exec(f"mkdir -p {remote_parent}")

Automated scan triggered by supply-chain-audit. If this is a false positive, a maintainer can approve after manual review.

@teknium1 teknium1 force-pushed the hermes/hermes-deb3d2ef branch from bce4fa6 to 8c9f28c Compare March 30, 2026 07:12
@github-actions
Copy link
Copy Markdown

⚠️ Supply Chain Risk Detected

This PR contains patterns commonly associated with supply chain attacks. This does not mean the PR is malicious — but these patterns require careful human review before merging.

⚠️ WARNING: exec() or eval() usage

Dynamic code execution can hide malicious behavior, especially when combined with base64 or network fetches.

Matches (first 20):

1249:+                    self._sandbox.process.exec(f"mkdir -p {parent}")
1270:+                            self._sandbox.process.exec(f"mkdir -p {remote_parent}")

Automated scan triggered by supply-chain-audit. If this is a false positive, a maintainer can approve after manual review.

@teknium1 teknium1 force-pushed the hermes/hermes-deb3d2ef branch from 8c9f28c to 2dc1cfc Compare March 30, 2026 07:19
@github-actions
Copy link
Copy Markdown

⚠️ Supply Chain Risk Detected

This PR contains patterns commonly associated with supply chain attacks. This does not mean the PR is malicious — but these patterns require careful human review before merging.

⚠️ WARNING: exec() or eval() usage

Dynamic code execution can hide malicious behavior, especially when combined with base64 or network fetches.

Matches (first 20):

1236:+            home = self._sandbox.process.exec("echo $HOME").result.strip()
1262:+                    self._sandbox.process.exec(f"mkdir -p {parent}")
1281:+                            self._sandbox.process.exec(f"mkdir -p {remote_parent}")

Automated scan triggered by supply-chain-audit. If this is a false positive, a maintainer can approve after manual review.

Skills with scripts/, templates/, and references/ subdirectories need
those files available inside sandboxed execution environments. Previously
the skills directory was missing entirely from remote backends.

Live sync — files stay current as credentials refresh and skills update:
- Docker/Singularity: bind mounts are inherently live (host changes
  visible immediately)
- Modal: _sync_files() runs before each command with mtime+size caching,
  pushing only changed credential and skill files (~13μs no-op overhead)
- SSH: rsync --safe-links before each command (naturally incremental)
- Daytona: _upload_if_changed() with mtime+size caching before each command

Security — symlink filtering:
- Docker/Singularity: sanitized temp copy when symlinks detected
- Modal/Daytona: iter_skills_files() skips symlinks
- SSH: rsync --safe-links skips symlinks pointing outside source tree
- Temp dir cleanup via atexit + reuse across calls

Non-root user support:
- SSH: detects remote home via echo $HOME, syncs to $HOME/.hermes/
- Daytona: detects sandbox home before sync, uploads to $HOME/.hermes/
- Docker/Modal/Singularity: run as root, /root/.hermes/ is correct

Also:
- credential_files.py: fix name/path key fallback in required_credential_files
- Singularity, SSH, Daytona: gained credential file support
- 14 tests covering symlink filtering, name/path fallback, iter_skills_files
@teknium1 teknium1 force-pushed the hermes/hermes-deb3d2ef branch from 2dc1cfc to 534ec87 Compare March 30, 2026 07:24
@github-actions
Copy link
Copy Markdown

⚠️ Supply Chain Risk Detected

This PR contains patterns commonly associated with supply chain attacks. This does not mean the PR is malicious — but these patterns require careful human review before merging.

⚠️ WARNING: base64 encoding/decoding detected

Base64 has legitimate uses (images, JWT, etc.) but is also commonly used to obfuscate malicious payloads. Verify the usage is appropriate.

Matches (first 20):

1420:+        b64 = base64.b64encode(content).decode("ascii")

⚠️ WARNING: exec() or eval() usage

Dynamic code execution can hide malicious behavior, especially when combined with base64 or network fetches.

Matches (first 20):

1236:+            home = self._sandbox.process.exec("echo $HOME").result.strip()
1267:+            self._sandbox.process.exec(f"mkdir -p {parent}")

Automated scan triggered by supply-chain-audit. If this is a false positive, a maintainer can approve after manual review.

@teknium1 teknium1 merged commit 5148682 into main Mar 30, 2026
2 of 3 checks passed
itsXactlY pushed a commit to itsXactlY/hermes-agent that referenced this pull request Mar 30, 2026
…ousResearch#3890)

Skills with scripts/, templates/, and references/ subdirectories need
those files available inside sandboxed execution environments. Previously
the skills directory was missing entirely from remote backends.

Live sync — files stay current as credentials refresh and skills update:
- Docker/Singularity: bind mounts are inherently live (host changes
  visible immediately)
- Modal: _sync_files() runs before each command with mtime+size caching,
  pushing only changed credential and skill files (~13μs no-op overhead)
- SSH: rsync --safe-links before each command (naturally incremental)
- Daytona: _upload_if_changed() with mtime+size caching before each command

Security — symlink filtering:
- Docker/Singularity: sanitized temp copy when symlinks detected
- Modal/Daytona: iter_skills_files() skips symlinks
- SSH: rsync --safe-links skips symlinks pointing outside source tree
- Temp dir cleanup via atexit + reuse across calls

Non-root user support:
- SSH: detects remote home via echo $HOME, syncs to $HOME/.hermes/
- Daytona: detects sandbox home before sync, uploads to $HOME/.hermes/
- Docker/Modal/Singularity: run as root, /root/.hermes/ is correct

Also:
- credential_files.py: fix name/path key fallback in required_credential_files
- Singularity, SSH, Daytona: gained credential file support
- 14 tests covering symlink filtering, name/path fallback, iter_skills_files
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant