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. ]]>
