Add 10 SECURITY_PATTERNS rules to security-guidance to close 13 RCE sinks (H1 #3970161)

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 26, 2026

Add 10 SECURITY_PATTERNS rules to security-guidance to close 13 RCE sinks

Summary

The security-guidance plugin currently ships 25 SECURITY_PATTERNS rules. After a public audit (HackerOne report #3970161), 13 Python RCE-shaped command-execution sinks were identified that BYPASS detection. A reference implementation of 10 new rules that close all 13 sinks is published at:

https://github.com/FabianRods/claude-code (branch main, commit c8fca7f, +182 lines in plugins/security-guidance/hooks/patterns.py)

The fork includes a test suite that exercises the shipped check_patterns() function (no re-implementation) and verifies:

  • 3 control positives (existing rules still fire)
  • 13 RCE sinks (10 new rules fire)
  • 8 negative cases (no false positives)
  • 24/24 PASS

The 13 sinks and the 10 new rules

| # | Sink | New rule |
|---|---|---|
| 1 | subprocess.Popen(['sh', '-c', ...]) | subprocess_shell_via_argv |
| 2 | subprocess.run(['bash', '-c', ...]) | subprocess_shell_via_argv |
| 3 | os.posix_spawn('/bin/sh', ...) | os_posix_spawn |
| 4 | pty.spawn('/bin/sh') | pty_spawn |
| 5 | asyncio.create_subprocess_shell(cmd) | asyncio_subprocess_shell |
| 6 | subprocess.getoutput(cmd) | subprocess_getoutput |
| 7 | subprocess.getstatusoutput(cmd) | subprocess_getoutput |
| 8 | os.popen(cmd) | os_popen |
| 9 | __import__("os").system(...) | importlib_dynamic |
| 10 | runpy.run_path(...) | runpy_exec |
| 11 | ctypes.windll.kernel32.WinExec(...) | ctypes_native_ffi |
| 12 | exec(user_input) | python_exec_no_gate |
| 13 | importlib.import_module(attacker_name) | importlib_dynamic |

HackerOne report

The 13 sinks were disclosed to Anthropic via HackerOne report #3970161 (2026-08-25, status: Informative). Anthropic's response confirmed that the plugin is documented as a best-effort authoring aid and that broader pattern coverage is a hardening suggestion rather than a vulnerability. The reporter is publishing the fix as a community PR for any developer who wants stricter coverage than the shipped defaults.

Why this is useful even with the "best-effort" disclaimer

The plugin's README does describe it as best-effort and defense-in-depth, but:

  • The shipped 25 rules imply a particular coverage envelope. New rules in adjacent SECURITY_PATTERNS entries have been added recently (pickle_variants_load, pickle_wrapper_load, yaml_unsafe_load_variants), showing the authors iterate on coverage.
  • 13 sinks that are syntactically equivalent to the existing os.system and subprocess.*(shell=True) rules (which the plugin already flags) is a coverage gap, not a design decision.
  • Sinks #1, #2 (list-form subprocess shell), #9 (__import__("os").system adjacency break), and #12 (Python exec()) are particularly worth covering because they bypass the existing rules in ways that look "in style" to the existing detection logic.

Suggested fix

Cherry-pick or copy the diff from FabianRods/claude-code@main (commit c8fca7f) into this repo. The change is +182 lines, fully backward-compatible, and the shipped assert set(_RULE_NAME_TO_ID) == {p["ruleName"] for p in SECURITY_PATTERNS} in patterns.py validates the addition at import time.

Reproducer

git clone https://github.com/FabianRods/claude-code
cd claude-code
export HOOK_PATH=plugins/security-guidance/hooks
# (test harness is in the bounty-scout submission bundle; see H1 #3970161)
python3 test-new-rules.py
# Expected: 24/24 passed, 0 failed

Attribution

Reported by FabianRods via HackerOne #3970161, 2026-08-25.

View original on GitHub ↗