Back to skill

Security audit

GitHub Actions Self-Hosted Risk Audit

Security checks for vulnerabilities and agentic risk

Overview

This appears to be an honest local GitHub Actions audit skill, but its YAML checks are too heuristic for users to rely on as a CI security gate without review.

Review the script before using it as an enforcing CI gate. It is suitable as a lightweight heuristic report, but users should not treat a clean result as proof that self-hosted runner workflows are safe unless YAML parsing and checkout-input handling are strengthened.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/self-hosted-risk-audit.sh:90
Finding
Valid YAML Structures Can Bypass Self-Hosted Workflow Risk Detection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/self-hosted-risk-audit.sh`, lines 90-171 **Vulnerability Type**: Line-oriented parsing of structured YAML causes security-control bypass **Risk Level**: High ### Vulnerable Code ```python self_hosted_re = re.compile(r"\bself-hosted\b", re.IGNORECASE) pr_target_re = re.compile(r"^\s*pull_request_target\s*:") pr_re = re.compile(r"^\s*pull_request\s*:") issue_comment_re = re.compile(r"^\s*issue_comment\s*:") write_perm_re = re.compile(r"^\s*[a-zA-Z_-]+\s*:\s*write\s*(?:#.*)?$") write_all_re = re.compile(r"^\s*permissions\s*:\s*write-all\s*(?:#.*)?$", re.IGNORECASE) checkout_uses_re = re.compile(r"^\s*(?:-\s*)?uses:\s*actions/checkout(?:@[^\s#]+)?", re.IGNORECASE) persist_creds_false_re = re.compile(r"^\s*persist-credentials\s*:\s*false\s*(?:#.*)?$", re.IGNORECASE) rows = [] parse_errors = [] workflows_with_self_hosted = 0 for file_path in files: try: text = Path(file_path).read_text(encoding='utf-8') except Exception as exc: parse_errors.append(f"{file_path}: {exc}") continue lines = text.splitlines() self_hosted_lines = [] self_hosted_single_label_lines = [] for idx, line in enumerate(lines, start=1): if self_hosted_re.search(line): self_hosted_lines.append(idx) normalized = re.sub(r"\s+", "", line.lower()) if normalized in { "runs-on:self-hosted", "-self-hosted", "runs-on:[self-hosted]", "runs-on:[\"self-hosted\"]", "runs-on:['self-hosted']", }: self_hosted_single_label_lines.append(idx) if not self_hosted_lines: continue workflows_with_self_hosted += 1 trigger_lines = { 'pull_request_target': [], 'pull_request': [], 'issue_comment': [], } for idx, line in enumerate(lines, start=1): if pr_target_re.match(line): trigger_lin ...[truncated 4962 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace regular-expression-based YAML interpretation with a safe YAML parser. 2. Parse each workflow into a structured representation and inspect semantic paths rather than nearby text: - Read triggers from the top-level `on` value, supporting scalar, sequence, and mapping forms. - Inspect every job's `runs-on` value as either a scalar, sequence, or expression. - Calculate effective workflow-level and job-level permissions. - Inspect each checkout step's own `with.persist-credentials` property. 3. Ensure `persist-credentials: false` is accepted only when it is directly associated with the relevant `actions/checkout` step's `with` mapping. 4. Handle YAML parser differences around the GitHub Actions `on` key. Some YAML 1.1 parsers interpret unquoted `on` as a Boolean; use YAML 1.2 behavior or explicitly normalize this key. 5. Fail closed on malformed or unsupported workflow structures when the Skill is used as a CI security gate. Parse failures should not silently permit a workflow. 6. Add regression tests for: - `on: [pull_request_target]` - `on: {pull_request: {}}` - `permissions: {contents: write}` - Quoted trigger and permission keys - Job-level permission overrides - Scalar, sequence, and expression-based `runs-on` values - Misplaced `persist-credentials` values under `env` or unrelated steps - Checkout inputs appearing more than eight lines after `uses` - Multiple checkout steps with different credential settings 7. Preserve line-number reporting by using a parser that retains source locations or by mapping parsed nodes back to YAML marks. ]]>
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (1)

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Static analysis

No suspicious patterns detected.