Skip to content

[Medium] CI/CD: workflow & config hardening (least-privilege permissions, pinned versions, safe markup, CSP) #45

Description

@Jose-Gael-Cruz-Lopez

Summary

A set of CI/CD and config hardening gaps that individually are Medium/Low but share a theme (least privilege, pinning, and safe markup generation). Filed as one grouped issue with a checklist.

Location & Evidence

1. Missing least-privilege permissions: blocks — both workflows fall back to the repo/org default GITHUB_TOKEN scopes (potentially read/write all).

  • .github/workflows/contribution_approved.yml (no permissions: key; pushes to main + comments)
  • .github/workflows/update_readmes.yml (no permissions: key; pushes to main)
  • Contrast .github/workflows/auto_extract.yml:7-9 which does scope them.

2. Unpinned action and dependency versions (supply-chain / reproducibility)

  • All workflows use floating tags: actions/checkout@v4, actions/setup-python@v5, actions/github-script@v7.
  • .github/workflows/auto_extract.yml:33-35 installs unpinned deps ignoring requirements.txt:
      - name: Install dependencies
        run: pip install requests beautifulsoup4 openai
  • .github/scripts/requirements.txt uses >= (no upper bound / lockfile).

3. Incomplete HTML escaping in generated markup

  • .github/scripts/util.py:108-117 builds <a href="{url}">… with no escaping of " in url.
  • .github/scripts/generate_gallery.py:39-40 esc() escapes & < > but not ":
def esc(text):
    return (str(text).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;"))

A value containing " breaks out of the href/src/alt attribute.

4. Step output echoed into run: (same anti-pattern as the injection issue, lower risk here)

  • .github/workflows/update_readmes.yml:52-54:
        run: |
          echo "Error updating README: ${{ steps.update.outputs.error_message }}"

5. dangerouslyAllowSVG enabled without a Content-Security-Policy on next/image.

  • web/next.config.ts:7-16:
images: { remotePatterns: [ /* raw.githubusercontent... */ ], dangerouslyAllowSVG: true },

Impact

Broader-than-needed token scopes amplify the impact of any workflow injection; floating action tags allow a compromised upstream tag to run in CI with write access; incomplete escaping allows attribute-injection into the generated README (low real-world impact because GitHub sanitizes README HTML and the web app escapes via React, but it is still latent); dangerouslyAllowSVG without a CSP is a stored-XSS vector the day any user-influenced SVG can be served same-origin.

Recommended fix

  • Add explicit permissions: to contribution_approved.yml (contents: write, issues: write) and update_readmes.yml (contents: write).
  • Pin actions to commit SHAs (or at least full versions) and install Python deps from a pinned requirements.txt (pip install -r .github/scripts/requirements.txt).
  • Extend esc()/format_link to also escape " (and ideally ').
  • Move error_message in update_readmes.yml to env: + $ERROR_MSG.
  • Add images.contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;" and contentDispositionType: "attachment" in next.config.ts.

Acceptance criteria

  • Every workflow declares an explicit minimal permissions: block.
  • Actions and Python deps are version/SHA-pinned.
  • Generated href/src/alt attributes are safe against a " in the value.
  • next.config.ts pairs dangerouslyAllowSVG with a CSP.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ci-cdCI / CD / workflow / configsecuritySecurity vulnerability or hardeningseverity:mediumMedium severity

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions