Skip to content

feat(rules): ResearchExtensions (RE001-RE010) — 10 rules from Snyk/StepSecurity/Endor/academic literature#325

Merged
hyperpolymath merged 3 commits into
mainfrom
claude/research-extensions-rules
May 26, 2026
Merged

feat(rules): ResearchExtensions (RE001-RE010) — 10 rules from Snyk/StepSecurity/Endor/academic literature#325
hyperpolymath merged 3 commits into
mainfrom
claude/research-extensions-rules

Conversation

@hyperpolymath
Copy link
Copy Markdown
Owner

Summary

New module Hypatia.Rules.ResearchExtensions captures GitHub Actions workflow defects sourced from commercial CI/CD security tools (Snyk, StepSecurity, Endor Labs, Datadog CI Visibility) and recent academic literature. Complementary to WorkflowHardening (open-source actionlint/zizmor cluster, PR #321) and BaselineHealth (drift, PR #320).

10 rules, all pure-local YAML scans on <repo>/.github/workflows/*.yml plus repo-walk for composite action.yml files.

Rule inventory

ID Detects Severity Provenance
RE001 Workflow accesses secrets.* but does not install step-security/harden-runner :warn StepSecurity Harden-Runner
RE002 Harden-Runner running in egress-policy: audit on a protected-branch trigger (no actual containment) :high StepSecurity production-deployment guidance
RE003 actions/cache key interpolates github.head_ref / PR title / PR body — cache poisoning vector :high Snyk dataflow auditor + 2022 GitHub cache-poisoning advisory
RE004 docker://image:tag or container: image:tag pinned by tag, not by @sha256:<digest> :warn Snyk container scan + GitHub pin-by-digest guide
RE005 Test/spec/check/lint/verify step swallows non-zero exit via continue-on-error: true or || true :warn Datadog CI Visibility flake-masking patterns
RE006 Composite action.yml (using: composite) calls a third-party uses: that isn't SHA-pinned (nested-pinning gap) :warn Endor Labs R-END-05
RE007 Workflow-level top-level env: block contains \${{ secrets.* }} — exposes secret to every step :warn Endor Labs R-END-01 (least-privilege secrets scope)
RE008 if: github.actor == 'dependabot[bot]' — spoofable bot identity on pull_request_target from a fork :critical zizmor bot-conditions + Koishybayev et al. (USENIX Security 2022)
RE009 \${{ fromJSON(secrets.X) }} — parsed fields bypass runner-side log redaction :critical zizmor unredacted-secrets
RE010 workflow_run trigger downloads artifacts without run-id constraint or provenance verification :warn Kermabon-Bobinnec et al. (EuroS&P 2023)

Tests

mix test test/research_extensions_test.exs covers 27 tests:

  • One positive case + one negative case per rule (10 rules × 2 = 20 minimum).
  • Additional negatives where a rule has multiple distinct trigger paths (RE005 has two: continue-on-error: true and \|\| true; RE010 has two clean-state paths: run-id constraint and gh attestation verify).
  • scan/1 facade exercised on a clean workflow, an empty repo, and a multi-finding repo (asserting all dispatch routes to :sustainabot at severity-calibrated confidence).

Design

  • Pure local file scans (no GitHub API).
  • Workflow-file discovery scope: repo-root .github/workflows/*.yml/*.yaml only (mirrors WorkflowHardening / BaselineHealth BH004).
  • RE006 additionally walks the repo (depth ≤ 6) for action.yml files — composite actions can live anywhere.
  • Regex on YAML text rather than parsed YAML, for the same reasons cited in WorkflowHardening.
  • All findings route to :sustainabot (advisory — fixes vary by defect class). Confidence calibrated by severity: critical 0.92, high 0.85, warn 0.75, info 0.60.

Module size

lib/rules/research_extensions.ex — ~900 lines.
test/research_extensions_test.exs — ~400 lines.

No refactor to existing modules.

🤖 Generated with Claude Code

…epSecurity/Endor/academic literature

New Hypatia.Rules.ResearchExtensions module captures GitHub Actions
workflow defects sourced from commercial CI/CD security tools
(Snyk, StepSecurity, Endor Labs, Datadog CI Visibility) and recent
academic literature. Complementary to WorkflowHardening (OSS
actionlint/zizmor cluster, PR #321) and BaselineHealth (drift, PR #320).

Rule inventory:

| ID | Detects | Severity | Provenance |
|---|---|---|---|
| RE001 | Workflow with secrets access lacks step-security/harden-runner | warn | StepSecurity |
| RE002 | Harden-Runner in egress-policy: audit on protected-branch workflow | high | StepSecurity |
| RE003 | actions/cache key interpolates github.head_ref / PR title | high | Snyk dataflow |
| RE004 | Container action pinned by tag, not digest | warn | Snyk container scan |
| RE005 | Test step swallows non-zero exit | warn | Datadog CI |
| RE006 | Composite action calls third-party uses: it doesn't SHA-pin | warn | Endor R-END-05 |
| RE007 | Top-level env: contains secrets reference | warn | Endor R-END-01 |
| RE008 | Bot identity check via spoofable github.actor | critical | zizmor + Koishybayev 2022 |
| RE009 | fromJSON(secrets.X) bypasses runner-side redaction | critical | zizmor unredacted-secrets |
| RE010 | workflow_run consumes artifacts without provenance verification | warn | EuroS&P 2023 |

Module + tests mirror WorkflowHardening shape: scan/2 facade returning
%{findings, total, by_severity, dispatch}; helpers workflow_files/1,
composite_action_files/1, line_number_for_offset/2, group_by_severity/1,
dispatch_recommendations/1. All findings routed to :sustainabot at
confidence calibrated by severity (critical -> 0.92, high -> 0.85,
warn -> 0.75, info -> 0.60). Pure-local file scans; no GitHub API.

Test file: test/research_extensions_test.exs -- 27 tests covering
positive + negative cases per rule plus three scan/1 facade tests.

Module size: ~900 lines. No refactor to existing modules.
@github-actions
Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 103 issues detected

Severity Count
🔴 Critical 0
🟠 High 5
🟡 Medium 98
View findings
[
  {
    "reason": "Docker reference in Nickel config -- Podman/Containerfile highly preferred (Docker permitted) (1 occurrences, CWE-1104)",
    "type": "ncl_docker_not_podman",
    "file": "/home/runner/work/hypatia/hypatia/.machine_readable/svc/k9/hypatia-metadata.k9.ncl",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (2 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/commands/batch.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (1 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/commands/batch.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (1 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/commands/scan.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (1 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/commands/scan.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (2 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/commands/fleet.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (2 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/output.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (2 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/hypatia/hypatia/cli/build.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (3 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/hypatia/hypatia/fixer/src/main.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "expect() in hot path (5 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/hypatia/hypatia/fixer/src/scanner.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@github-actions
Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 104 issues detected

Severity Count
🔴 Critical 0
🟠 High 5
🟡 Medium 99
View findings
[
  {
    "reason": "Docker reference in Nickel config -- Podman/Containerfile highly preferred (Docker permitted) (1 occurrences, CWE-1104)",
    "type": "ncl_docker_not_podman",
    "file": "/home/runner/work/hypatia/hypatia/.machine_readable/svc/k9/hypatia-metadata.k9.ncl",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (2 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/commands/batch.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (1 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/commands/batch.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (1 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/commands/scan.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (1 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/commands/scan.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (2 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/commands/fleet.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (2 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/output.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (2 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/hypatia/hypatia/cli/build.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (3 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/hypatia/hypatia/fixer/src/main.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "expect() in hot path (5 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/hypatia/hypatia/fixer/src/scanner.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@github-actions
Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 105 issues detected

Severity Count
🔴 Critical 0
🟠 High 5
🟡 Medium 100
View findings
[
  {
    "reason": "expect() in hot path (2 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/commands/batch.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "unwrap_or(0) with dangerous default (1 occurrences, CWE-754)",
    "type": "unwrap_dangerous_default",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/commands/scan.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "expect() in hot path (1 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/commands/scan.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "expect() in hot path (2 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/commands/fleet.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "expect() in hot path (2 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/hypatia/hypatia/cli/src/output.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "expect() in hot path (2 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/hypatia/hypatia/cli/build.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "expect() in hot path (2 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/hypatia/hypatia/fixer/src/main.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "expect() in hot path (1 occurrences, CWE-754)",
    "type": "expect_in_hot_path",
    "file": "/home/runner/work/hypatia/hypatia/integration/src/lib.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "low"
  },
  {
    "reason": "1 untracked file(s) -- review and add or .gitignore",
    "type": "GS001",
    "file": ".",
    "action": "review",
    "rule_module": "git_state",
    "severity": "low"
  },
  {
    "reason": "Repository has 3 non-main remote branch(es). Policy: single main branch only.",
    "type": "GS007",
    "file": ".",
    "action": "delete_remote_branches",
    "rule_module": "git_state",
    "severity": "medium"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@hyperpolymath hyperpolymath merged commit ead5826 into main May 26, 2026
30 of 31 checks passed
@hyperpolymath hyperpolymath deleted the claude/research-extensions-rules branch May 26, 2026 01:53
@hyperpolymath hyperpolymath restored the claude/research-extensions-rules branch May 26, 2026 01:54
@hyperpolymath hyperpolymath deleted the claude/research-extensions-rules branch May 26, 2026 01:54
hyperpolymath added a commit that referenced this pull request May 26, 2026
## Summary

Adds delegates on \`Hypatia.Rules\` for the rule families landed in PRs
#320, #321, #322, #323. The new modules were independently usable but
invisible from the top-level facade; this PR closes that gap.

## New facade surface

| Function | Delegates to | Rules |
|---|---|---|
| \`scan_baseline_health/2\` | \`BaselineHealth.scan\` (existing;
docstring updated for BH007) | BH001-BH007 |
| \`scan_workflow_hardening/2\` | \`WorkflowHardening.scan\` |
WH001-WH012 |
| \`scan_supply_chain/2\` | \`SupplyChain.scan\` | SC001-SC011 |
| \`scan_branch_protection/2\` | \`BranchProtection.scan\` (API-backed;
takes \`owner, repo\`) | BP001-BP007 |
| \`scan_all_estate_policies/2\` | — runs all four families and merges
results | 34 rules total |

## Why combined-scan helper

Callers (\`pattern_analyzer\`, \`triangle_router\`,
\`fleet_dispatcher\`) already treat \`Hypatia.Rules\` as the entrypoint.
Without these delegates the new modules are reachable only via
fully-qualified names, which works but inverts the facade pattern
adopted in #316.

## RE001-RE010 (PR #325) not included

PR #325 (ResearchExtensions) is still OPEN at the time of this PR.
Adding its alias here would break compilation on \`main\` for the window
between this PR landing and #325 landing. A follow-up commit will wire
\`ResearchExtensions\` once #325 merges — tracked inline at the alias
block as a \`# wired in follow-up\` comment.

## Test plan

- Existing 18-test \`BaselineHealth\` suite still passes (untouched).
- Module compile-checks pass for the four families + facade.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.

1 participant