Skip to content

Security/harden shell cicd#211

Open
vlad-activeloop wants to merge 7 commits into
mainfrom
security/harden-shell-cicd
Open

Security/harden shell cicd#211
vlad-activeloop wants to merge 7 commits into
mainfrom
security/harden-shell-cicd

Conversation

@vlad-activeloop
Copy link
Copy Markdown
Contributor

@vlad-activeloop vlad-activeloop commented May 27, 2026

Summary

Version Bump

To trigger a release, bump "version" in package.json before merging.

Change type Version bump Example
Bug fix patch (1.2.0 → 1.2.1) "version": "1.2.1"
New feature minor (1.2.0 → 1.3.0) "version": "1.3.0"
Breaking change major (1.2.0 → 2.0.0) "version": "2.0.0"

If you don't bump the version, no release will be created.

Test plan

  • Tests pass locally (npm test)
  • Relevant new tests added
  • Version bumped in package.json, or no release needed for this change

Summary by CodeRabbit

  • Security Updates

    • Enhanced protection against arbitrary code execution by removing unsafe shell commands and improving quote handling.
    • Backend notifications now properly isolated to prevent injection risks.
  • Improvements

    • Update process now pins to exact package versions for more predictable and reliable installations.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

📝 Walkthrough

Walkthrough

This PR applies security hardening (shell fallback removal, unsafe command filtering, backend notification flagging), optimizes CI workflows, tightens release secret handling, and pins npm global updates to exact fetched versions instead of resolving @latest at install time.

Changes

Infrastructure, Security Hardening, and Update Flow

Layer / File(s) Summary
CI and Release workflow optimization
.github/workflows/ci.yaml, .github/workflows/release.yaml
CI jobs switch from npm install to npm ci for deterministic installs. Release workflow loads 1Password secrets with export-env: false and explicitly passes CLAWHUB_TOKEN through step outputs.
Pre-tool-use shell fallback removal and memory path safety
src/hooks/memory-path-utils.ts, src/hooks/pre-tool-use.ts, tests/claude-code/pre-tool-use-branches.test.ts
Memory-path isSafe rejects ANSI-C quoting and removes sed/awk/tar/env from allowlist. Pre-tool-use removes SHELL_BUNDLE computation and buildFallbackDecision helper, returning null when config is absent or direct read fails. Grep pattern is single-quote escaped. Tests updated to assert null interception instead of shell-bundle fallback.
NPM global update version pinning
src/cli/update.ts, tests/cli/cli-update.test.ts
runUpdate pins npm install to exact fetched ${latest} version instead of @latest. Dry-run and manual-instruction messages display pinned version. Test assertions updated for version-pinned commands.
Backend notification safety marking
src/notifications/sources/backend.ts
toClient() adds userVisibleOnly: true to backend notifications to prevent server-controlled content in model prompt context.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • activeloopai/hivemind#187: Modifies npm-global upgrade path in src/cli/update.ts with pidfile-based concurrency serialization for the same npm install invocation.
  • activeloopai/hivemind#91: Also modifies runUpdate global-upgrade logic to pin npm install to fetched latest version instead of @latest resolution.

Suggested reviewers

  • efenocchi
  • kaghni

Poem

🐰 Workflows flow smoother with ci instead of install,
Secrets hidden safely, no longer leaked to all,
Shell fallbacks vanish where unsafe paths dwell,
Versions pinned precisely—no @latest to sell!
Notifications now whisper "user eyes only, please"—
Security's hardened, the model at ease!

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is entirely template-boilerplate with no actual content filled in; the 'Summary' section is blank, leaving the rationale and details of security hardening unexplained. Fill in the Summary section explaining the security hardening changes, version bump decision, and confirm test plan checkboxes reflect actual completion.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Security/harden shell cicd' is vague and generic, using abbreviations and not clearly conveying what security hardening changes are included. Clarify the title with specific changes (e.g., 'Remove unsafe shell utilities and strengthen hook validation' or 'Harden VFS shell safety checks and CI dependencies').
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch security/harden-shell-cicd

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot requested review from efenocchi and kaghni May 27, 2026 16:33
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/memory-path-utils.ts`:
- Around line 8-25: The isSafe() check relying on SAFE_BUILTINS is bypassable
because it only checks the first token and allows shell wrappers/control syntax
to dispatch child commands; update the validation in isSafe() (and any related
code that iterates SAFE_BUILTINS) to perform strict token-level checks: parse
the entire command line into tokens and ensure the first token exactly matches
an allowed binary name and that no control/operator tokens (e.g., ; && || | `$(
)` backticks, $(), `exec`, `if`, `then`, `else`, `for`, `while`, `find -exec`,
`timeout`, meta-words like `env`) or redirections are present anywhere in the
token list, or alternatively remove wrapper/keyword names from SAFE_BUILTINS so
only literal safe binaries remain; in short, replace the current first-token
substring check with exact-token matching plus a reject-list of shell control
keywords to prevent wrappers from invoking child commands (update SAFE_BUILTINS
usage and the isSafe() implementation accordingly).

In `@src/hooks/pre-tool-use.ts`:
- Around line 284-286: The early return of null when shellCmd and config are
present lets dangerous real-shell commands run; instead, inside the pre-tool-use
flow (the branch after getShellCommand() returns a shellCmd and config exists)
detect memory-touching/virtual-memory paths and do not return null — either
reject the tool invocation or return the existing retry guidance response used
elsewhere; update the logic around getShellCommand(), shellCmd and config to
call the same denial/retry handler used for other memory-touching cases (reuse
the retry guidance path) so commands like sort /etc/passwd ~/.deeplake/... are
blocked rather than forwarded to the real Bash tool.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cb3c19f8-f016-465b-a950-65a8f41ec869

📥 Commits

Reviewing files that changed from the base of the PR and between e046a23 and 91e4469.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • .github/workflows/ci.yaml
  • .github/workflows/release.yaml
  • src/cli/update.ts
  • src/hooks/memory-path-utils.ts
  • src/hooks/pre-tool-use.ts
  • src/notifications/sources/backend.ts
  • tests/claude-code/pre-tool-use-branches.test.ts
  • tests/cli/cli-update.test.ts

Comment on lines 8 to 25
export const SAFE_BUILTINS = new Set([
"cat", "ls", "cp", "mv", "rm", "rmdir", "mkdir", "touch", "ln", "chmod",
"stat", "readlink", "du", "tree", "file",
"grep", "egrep", "fgrep", "rg", "sed", "awk", "cut", "tr", "sort", "uniq",
// sed and awk removed: sed supports `-e '1e <cmd>'` (execute shell command)
// and awk supports `system()` / `|` pipelines — both enable arbitrary code
// execution through the just-bash fallback.
"grep", "egrep", "fgrep", "rg", "cut", "tr", "sort", "uniq",
"wc", "head", "tail", "tac", "rev", "nl", "fold", "expand", "unexpand",
"paste", "join", "comm", "column", "diff", "strings", "split",
"find", "xargs", "which",
"jq", "yq", "xan", "base64", "od",
"tar", "gzip", "gunzip", "zcat",
// tar removed: --to-command=<cmd> executes an arbitrary program per entry.
// env removed: `env <cmd>` runs an arbitrary program.
"gzip", "gunzip", "zcat",
"md5sum", "sha1sum", "sha256sum",
"echo", "printf", "tee",
"pwd", "cd", "basename", "dirname", "env", "printenv", "hostname", "whoami",
"pwd", "cd", "basename", "dirname", "printenv", "hostname", "whoami",
"date", "seq", "expr", "sleep", "timeout", "time", "true", "false", "test",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

isSafe() is still bypassable via shell wrappers/control syntax.

This still only validates the first token of each stage, but the allowlist includes tokens that can hide a second command. if true; then curl ~/.deeplake/memory/x; fi, timeout 1 curl ..., and find / -exec curl ... \; all pass this check today because the dangerous command is never the first token. Please switch this to explicit validation of the exact Bash shapes you later intercept, or remove every keyword/wrapper that can dispatch a child command.

Also applies to: 30-38

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/memory-path-utils.ts` around lines 8 - 25, The isSafe() check
relying on SAFE_BUILTINS is bypassable because it only checks the first token
and allows shell wrappers/control syntax to dispatch child commands; update the
validation in isSafe() (and any related code that iterates SAFE_BUILTINS) to
perform strict token-level checks: parse the entire command line into tokens and
ensure the first token exactly matches an allowed binary name and that no
control/operator tokens (e.g., ; && || | `$( )` backticks, $(), `exec`, `if`,
`then`, `else`, `for`, `while`, `find -exec`, `timeout`, meta-words like `env`)
or redirections are present anywhere in the token list, or alternatively remove
wrapper/keyword names from SAFE_BUILTINS so only literal safe binaries remain;
in short, replace the current first-token substring check with exact-token
matching plus a reject-list of shell control keywords to prevent wrappers from
invoking child commands (update SAFE_BUILTINS usage and the isSafe()
implementation accordingly).

Comment thread src/hooks/pre-tool-use.ts
Comment on lines 284 to 286
if (!shellCmd) return null;
if (!config) return buildFallbackDecision(shellCmd, shellBundle);
if (!config) return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Do not fall through to the real Bash tool for accepted memory commands.

Once a memory-touching Bash command has made it past getShellCommand(), returning null here hands the original command back to Claude Code's host shell. That is not harmless: sort /etc/passwd ~/.deeplake/memory/index.md > /tmp/out will still read/write real files if no virtual handler matches. These paths should deny the tool or emit the existing retry guidance instead of no-oping.

Also applies to: 512-518

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/pre-tool-use.ts` around lines 284 - 286, The early return of null
when shellCmd and config are present lets dangerous real-shell commands run;
instead, inside the pre-tool-use flow (the branch after getShellCommand()
returns a shellCmd and config exists) detect memory-touching/virtual-memory
paths and do not return null — either reject the tool invocation or return the
existing retry guidance response used elsewhere; update the logic around
getShellCommand(), shellCmd and config to call the same denial/retry handler
used for other memory-touching cases (reuse the retry guidance path) so commands
like sort /etc/passwd ~/.deeplake/... are blocked rather than forwarded to the
real Bash tool.

expect(code).toBe(0);
expect(spawn).toHaveBeenCalledTimes(2);
expect(spawn.mock.calls[0]).toEqual(["npm", ["install", "-g", "@deeplake/hivemind@latest"]]);
expect(spawn.mock.calls[0]).toEqual(["npm", ["install", "-g", "@deeplake/hivemind@1.3.0"]]);
Copy link
Copy Markdown
Collaborator

@efenocchi efenocchi May 27, 2026

Choose a reason for hiding this comment

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

Is setting 1.3.0 correct?

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.

2 participants