Skip to content

Fix code scanning alerts: workflow permissions and polynomial ReDoS - #37

Merged
dduugg merged 1 commit into
mainfrom
fix-code-scanning-alerts
Aug 17, 2026
Merged

Fix code scanning alerts: workflow permissions and polynomial ReDoS#37
dduugg merged 1 commit into
mainfrom
fix-code-scanning-alerts

Conversation

@dduugg

@dduugg dduugg commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Add explicit least-privilege permissions: blocks to the three
reusable-workflow callers that lacked one
(actions/missing-workflow-permissions, alerts #1, #3, #4). A caller's
permissions become the ceiling for the called workflow, so each grant
covers exactly what the shared-config workflow needs:

  • cd.yml -> contents: write: shared-config's cd.yml checks out with
    persisted credentials and runs publish-rubygems-action, which does a
    raw git push of the release tag, then gh release create.
  • stale.yml -> issues: write + pull-requests: write: actions/stale
    comments on and closes both stale issues and stale PRs.
  • triage.yml -> issues: write: the callee runs
    gh issue edit --add-label triage; the callee's own job already
    declares issues: write, and the caller must not clamp below it.

ci.yml and codeql.yml already declare permissions and are untouched.

Fix the polynomial ReDoS in Utils.underscore (rb/polynomial-redos,
alert #5). The acronym-splitting step /([A-Z]+)([A-Z][a-z])/ is
quadratic on long runs of uppercase letters that never reach the
required uppercase-lowercase pair, since [A-Z]+ can start matching at
any offset within the run. Replacing it with the zero-width
/(?<=[A-Z])(?=[A-Z][a-z])/ and an _ replacement is linear: the
lookahead only matches immediately before the final uppercase of a run
that is followed by a lowercase letter, and the lookbehind requires at
least one preceding uppercase, which is exactly what [A-Z]+ required.

Equivalence was checked exhaustively over all strings up to length 6
from the alphabet [A B a b 1 - : _], over 200k random mixed-case strings
up to length 24, and against the named cases now pinned in
spec/lib/code_teams/utils_spec.rb. The other gsub/tr steps are unchanged.

Alerts resolved

  • #5 rb/polynomial-redos (high) — lib/code_teams/utils.rb:13
  • #4 actions/missing-workflow-permissions (medium) — .github/workflows/triage.yml:9
  • #3 actions/missing-workflow-permissions (medium) — .github/workflows/cd.yml:11
  • #1 actions/missing-workflow-permissions (medium) — .github/workflows/stale.yml:8

Verification

  • Every job in every flagged workflow now has an effective permissions: block (cross-checked by parsing the YAML against the alert list).
  • actionlint output is byte-identical to main — no new findings introduced.
  • Test suite, type check and linter all pass locally.
  • Regex/argv rewrites were fuzz-checked for exact equivalence against the originals.
  • codeql.yml untouched.

Add explicit least-privilege `permissions:` blocks to the three
reusable-workflow callers that lacked one
(actions/missing-workflow-permissions, alerts #1, #3, #4). A caller's
permissions become the ceiling for the called workflow, so each grant
covers exactly what the shared-config workflow needs:

- cd.yml -> `contents: write`: shared-config's cd.yml checks out with
  persisted credentials and runs publish-rubygems-action, which does a
  raw `git push` of the release tag, then `gh release create`.
- stale.yml -> `issues: write` + `pull-requests: write`: actions/stale
  comments on and closes both stale issues and stale PRs.
- triage.yml -> `issues: write`: the callee runs
  `gh issue edit --add-label triage`; the callee's own job already
  declares `issues: write`, and the caller must not clamp below it.

ci.yml and codeql.yml already declare permissions and are untouched.

Fix the polynomial ReDoS in `Utils.underscore` (rb/polynomial-redos,
alert #5). The acronym-splitting step `/([A-Z]+)([A-Z][a-z])/` is
quadratic on long runs of uppercase letters that never reach the
required uppercase-lowercase pair, since `[A-Z]+` can start matching at
any offset within the run. Replacing it with the zero-width
`/(?<=[A-Z])(?=[A-Z][a-z])/` and an `_` replacement is linear: the
lookahead only matches immediately before the final uppercase of a run
that is followed by a lowercase letter, and the lookbehind requires at
least one preceding uppercase, which is exactly what `[A-Z]+` required.

Equivalence was checked exhaustively over all strings up to length 6
from the alphabet [A B a b 1 - : _], over 200k random mixed-case strings
up to length 24, and against the named cases now pinned in
spec/lib/code_teams/utils_spec.rb. The other gsub/tr steps are unchanged.
@dduugg
dduugg requested a review from a team as a code owner August 17, 2026 19:34
@github-project-automation github-project-automation Bot moved this to Triage in Modularity Aug 17, 2026
@dduugg
dduugg merged commit 687c620 into main Aug 17, 2026
9 checks passed
@dduugg
dduugg deleted the fix-code-scanning-alerts branch August 17, 2026 19:40
@github-project-automation github-project-automation Bot moved this from Triage to Done in Modularity Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant