Skip to content

fix: prevent zombie processes, redact cron stderr, skip symlinks in skill enumeration#6952

Closed
aaronlab wants to merge 1 commit into
NousResearch:mainfrom
aaronlab:fix/zombie-process-stderr-redact-symlink-enum
Closed

fix: prevent zombie processes, redact cron stderr, skip symlinks in skill enumeration#6952
aaronlab wants to merge 1 commit into
NousResearch:mainfrom
aaronlab:fix/zombie-process-stderr-redact-symlink-enum

Conversation

@aaronlab

Copy link
Copy Markdown
Contributor

Summary

  • process_registry.py — zombie process leak: _reader_loop() places process.wait() after the try-except block (line 380). If the reader thread exits via an unexpected exception path (e.g., MemoryError), wait() is skipped and the child process becomes a zombie. Moved wait() and cleanup into a finally block so the child is always reaped regardless of how the reader exits.

  • cron/scheduler.py — stderr not redacted on failure: _run_job_script() only redacts secrets in stdout on the success path (lines 417-421). When a script fails (non-zero exit), both stdout and stderr are returned without redaction (lines 407-413). A cron script that accidentally prints an API key to stderr during failure would leak it into the LLM prompt context. Moved redaction before the success/failure branch.

  • skill_commands.py — symlink enumeration bypass: _build_skill_message() enumerates supporting files with rglob("*") and is_file() (line 171) but doesn't filter symlinks. PR fix: skill symlink guard, delegate batch prefix, TTS path traversal and cleanup #6693 added symlink protection to scan_skill_commands() but missed this function. A malicious skill with symlinks in references/ can expose arbitrary file paths to the LLM. Added not f.is_symlink() check.

Test plan

  • Kill a reader thread mid-execution and verify the child process is reaped (no zombie in ps aux)
  • Run a cron script that prints sk-test1234567890 to stderr and exits with code 1 → verify output is redacted
  • Place a symlink in a skill's references/ directory → verify it's excluded from the file listing

🤖 Generated with Claude Code

…kill enumeration

process_registry.py: _reader_loop() has process.wait() after the try-except
block (line 380).  If the reader thread crashes with an unexpected exception
(e.g. MemoryError, KeyboardInterrupt), control exits the except handler but
skips wait() — leaving the child as a zombie process.  Move wait() and the
cleanup into a finally block so the child is always reaped.

cron/scheduler.py: _run_job_script() only redacts secrets in stdout on the
SUCCESS path (line 417-421).  When a cron script fails (non-zero exit), both
stdout and stderr are returned WITHOUT redaction (lines 407-413).  A script
that accidentally prints an API key to stderr during a failure would leak it
into the LLM context.  Move redaction before the success/failure branch so
both paths benefit.

skill_commands.py: _build_skill_message() enumerates supporting files using
rglob("*") but only checks is_file() (line 171) without filtering symlinks.
PR NousResearch#6693 added symlink protection to scan_skill_commands() but missed this
function.  A malicious skill can create symlinks in references/ pointing to
arbitrary files, exposing their paths (and potentially content via skill_view)
to the LLM.  Add is_symlink() check to match the guard in scan_skill_commands.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@teknium1

Copy link
Copy Markdown
Contributor

Merged via #7654 with authorship preserved. Thanks for the contribution!

@teknium1 teknium1 closed this Apr 11, 2026
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