SkillsHooksPromptsAgentsPersonasModelsPoliciesToolsTemplatesBundlesCategoriesStart here
← Hooks
Hk hooksecurityadvisorystable

GitHub Actions Security Reminder

Fires when Claude Code edits a GitHub Actions workflow file. Non-blocking reminder about command injection risks, untrusted input in run: steps, and the SAFE pattern (env: with proper quoting vs direct ${{ expression }} interpolation).

id hook/security-reminderv1.1.0by convergent-systems-key
Event
PreToolUse
Trigger
tool-nameEdit|Write
Language
python
Side effects
  • emits security guidance to stderr
  • flags untrusted ${{ }} interpolations found in the edited content
Platforms
linuxmacoswindows
Notes
Pure Python, stdlib only; writes to stderr. Reads tool_input.file_path (and content/new_string) from the PreToolUse payload. Works on all platforms via 'ai hooks run security-reminder'.

Script · security-reminder.py

#!/usr/bin/env python3
"""hooks/security-reminder.py — remind about injection risks when a GitHub Actions
workflow file is edited.

Fires on PreToolUse for Edit/Write tools whose target path is under
.github/workflows/. Non-blocking: prints guidance to stderr and always exits 0.
The guidance covers the failure that ships most often — untrusted event data
interpolated straight into a run: step with ${{ ... }} — and the safe pattern
(pass it through env: and quote the variable).

Honours --self-check (exit 0 when the hook can load and its rules compile).
"""
import json
import re
import sys

WORKFLOW_PATH = re.compile(r"(^|/)\.github/workflows/[^/]+\.ya?ml$")
UNTRUSTED = re.compile(
    r"\$\{\{\s*(github\.event\.(issue|pull_request|comment|review|head_commit|commits|discussion)[\w.\[\]]*"
    r"|github\.head_ref|steps\.[\w-]+\.outputs\.[\w-]+)\s*\}\}"
)
GUIDANCE = (
    "[ai/security-reminder] You are editing a GitHub Actions workflow.\n"
    "  - Never interpolate untrusted event data (issue titles, PR bodies, branch names,\n"
    "    commit messages) directly into a run: step with ${{ ... }} — it is shell injection.\n"
    "  - Pass it through env: instead and quote the variable: env: TITLE: ${{ github.event.issue.title }}\n"
    "    then run: echo \"$TITLE\".\n"
    "  - Pin third-party actions to a full commit SHA; keep permissions: to the minimum.\n"
)


def main() -> int:
    if "--self-check" in sys.argv:
        WORKFLOW_PATH.search(".github/workflows/ci.yml")
        print("[ai/security-reminder] self-check OK")
        return 0
    try:
        payload = json.load(sys.stdin)
    except (json.JSONDecodeError, ValueError):
        return 0  # not a hook payload; nothing to say
    tool_input = payload.get("tool_input") or {}
    path = tool_input.get("file_path") or tool_input.get("path") or ""
    if not WORKFLOW_PATH.search(path.replace("\\", "/")):
        return 0
    sys.stderr.write(GUIDANCE)
    content = tool_input.get("content") or tool_input.get("new_string") or ""
    for match in UNTRUSTED.finditer(content):
        sys.stderr.write(f"  ! untrusted interpolation in this edit: {match.group(0)}\n")
    return 0


if __name__ == "__main__":
    sys.exit(main())
securitygithub-actionsciclaude-code
Author convergent-systems-key. Catalog data license CC-BY-4.0.