-
Notifications
You must be signed in to change notification settings - Fork 0
ci: strengthen CI workflows with type checking and release safeguards #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a768b8d
62b2f16
ffc4d0f
1f5aa76
b1cc062
c528c8d
7119ab1
bee4b12
a36aee2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| subtrack: | ||
| - changed-files: | ||
| - any-glob-to-any-file: subtrack/** | ||
|
|
||
| docs: | ||
| - changed-files: | ||
| - any-glob-to-any-file: docs/** | ||
|
|
||
| ci: | ||
| - changed-files: | ||
| - any-glob-to-any-file: .github/workflows/** | ||
| - any-glob-to-any-file: .github/labeler.yml | ||
|
|
||
| dependencies: | ||
| - changed-files: | ||
| - any-glob-to-any-file: subtrack/package.json | ||
| - any-glob-to-any-file: package.json | ||
| - any-glob-to-any-file: pnpm-lock.yaml |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| name: check | ||
|
|
||
| on: | ||
| workflow_call: | ||
|
|
||
| jobs: | ||
| check: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - uses: pnpm/action-setup@v4 | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 22 | ||
| cache: pnpm | ||
|
|
||
| - run: pnpm install --frozen-lockfile | ||
|
|
||
| - name: Type check | ||
| working-directory: ./subtrack | ||
| run: pnpm lint:types | ||
|
|
||
| - run: pnpm build | ||
|
|
||
| - run: pnpm test | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| name: codeql | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
| pull_request: | ||
| branches: [main] | ||
| schedule: | ||
| - cron: "0 6 * * 1" | ||
|
|
||
| permissions: | ||
| contents: read | ||
| security-events: write | ||
|
|
||
| jobs: | ||
| analyze: | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| language: [javascript-typescript] | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - uses: github/codeql-action/init@v3 | ||
| with: | ||
| languages: ${{ matrix.language }} | ||
|
|
||
| - uses: github/codeql-action/analyze@v3 | ||
|
Comment on lines
+23
to
+29
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify unpinned actions across workflows
rg -nP '^\s*-\s*uses:\s*[^@]+@(?![0-9a-f]{40}$)[^ \n]+' .github/workflowsRepository: nazozokc/subtrack Length of output: 1836 🏁 Script executed: #!/bin/bash
# Check for security policy or pinning requirements
find . -maxdepth 2 -type f \( -name "*SECURITY*" -o -name "*POLICY*" -o -name "*security*" -o -name "*policy*" \) 2>/dev/null | head -20Repository: nazozokc/subtrack Length of output: 43 🏁 Script executed: #!/bin/bash
# Check codeql.yml file content to see exact state
cat -n .github/workflows/codeql.ymlRepository: nazozokc/subtrack Length of output: 772 🌐 Web query:
💡 Result: The primary security risk of using tags or branch names (e.g., Citations:
Pin all GitHub Actions to immutable commit SHAs. These Suggested hardening- - uses: actions/checkout@v4
+ - uses: actions/checkout@<full-length-commit-sha> # v4
- - uses: github/codeql-action/init@v3
+ - uses: github/codeql-action/init@<full-length-commit-sha> # v3
- - uses: github/codeql-action/analyze@v3
+ - uses: github/codeql-action/analyze@<full-length-commit-sha> # v3🧰 Tools🪛 zizmor (1.25.2)[warning] 23-23: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false (artipacked) [error] 23-23: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) [error] 25-25: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) [error] 29-29: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| name: dependency-review | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [main] | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| review: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/dependency-review-action@v4 | ||
|
Comment on lines
+14
to
+15
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, examine the actual workflow file to confirm current state
cat -n .github/workflows/dependency-review.yml | head -20Repository: nazozokc/subtrack Length of output: 463 🌐 Web query:
💡 Result: Pinning GitHub Actions to a full-length commit SHA is a critical security best practice designed to mitigate supply chain risks by ensuring that the code executed in your workflows is immutable [1][2][3]. Why Pin to a Commit SHA Unlike version tags (e.g., Citations:
🌐 Web query:
💡 Result: The recommended security practice for using actions/checkout (and all third-party GitHub Actions) is to pin them to a full-length, 40-character commit SHA rather than a version tag (e.g., Citations:
Pin workflow actions to full commit SHAs instead of version tags. The actions Suggested hardening- - uses: actions/checkout@v4
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- - uses: actions/dependency-review-action@v4
+ - uses: actions/dependency-review-action@c9b6f9d9f36f5e5c5e5c5e5c5e5c5e5c5e5c5e5c # v4.1.0🧰 Tools🪛 zizmor (1.25.2)[warning] 14-14: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false (artipacked) [error] 14-14: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) [error] 15-15: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| with: | ||
| fail-on-severity: high | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| name: labeler | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [main] | ||
|
|
||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
|
|
||
| jobs: | ||
| label: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/labeler@v5 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pin GitHub Action to a commit SHA Line 15 uses a mutable tag ( Suggested hardening- - uses: actions/labeler@v5
+ - uses: actions/labeler@<full-length-commit-sha>🧰 Tools🪛 zizmor (1.25.2)[error] 15-15: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,58 +1,105 @@ | ||
| name: Publish to npm | ||
| name: Release | ||
|
|
||
| on: | ||
| release: | ||
| types: | ||
| - published | ||
| types: [published] | ||
| workflow_dispatch: | ||
| inputs: | ||
| dry-run: | ||
| description: "Validate without publishing" | ||
| type: boolean | ||
| default: true | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| jobs: | ||
| check: | ||
| permissions: | ||
| contents: read | ||
| uses: ./.github/workflows/check.yml | ||
|
|
||
| publish: | ||
| needs: check | ||
| if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' | ||
| runs-on: ubuntu-latest | ||
|
|
||
| permissions: | ||
| contents: read | ||
| contents: write | ||
| id-token: write | ||
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| persist-credentials: false | ||
|
|
||
| - name: Setup pnpm | ||
| uses: pnpm/action-setup@v4 | ||
| - uses: pnpm/action-setup@v4 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 24 | ||
| node-version: 22 | ||
| registry-url: https://registry.npmjs.org | ||
| cache: pnpm | ||
|
|
||
| - name: Set package.json version from release tag | ||
| - name: Validate version tag | ||
| if: github.event_name == 'release' | ||
| working-directory: ./subtrack | ||
| run: | | ||
| VERSION="${GITHUB_REF_NAME#v}" | ||
| node -e "const fs=require('fs'); | ||
| const pkg=require('./package.json'); | ||
| pkg.version='$VERSION'; | ||
| fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2)+'\n');" | ||
| cat package.json | ||
| TAG_VERSION="${GITHUB_REF_NAME#v}" | ||
| PKG_VERSION=$(node -p "require('./package.json').version") | ||
|
|
||
| # Must be valid semver | ||
| if ! echo "$TAG_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then | ||
| echo "❌ Invalid semver: $TAG_VERSION" | ||
|
Comment on lines
+48
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tighten tag validation before injecting into Line 44 accepts prefixed matches only, so a crafted suffix can pass validation and be injected at Line 64. Validate the full string and avoid inline JS template injection. Suggested patch- if ! echo "$TAG_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then
+ if ! echo "$TAG_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "❌ Invalid semver: $TAG_VERSION"
exit 1
fi
@@
- node -e "
- const fs = require('fs')
- const pkg = require('./package.json')
- pkg.version = '${{ steps.version.outputs.version }}'
- fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n')
- "
+ VERSION='${{ steps.version.outputs.version }}' node -e "
+ const fs = require('node:fs')
+ const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'))
+ pkg.version = process.env.VERSION
+ fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n')
+ "Also applies to: 61-65 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Install dependencies | ||
| run: pnpm install --frozen-lockfile | ||
| # Package version must match tag (or be incremented from it) | ||
| if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then | ||
| echo "⚠ Package version ($PKG_VERSION) differs from tag ($TAG_VERSION) — will overwrite" | ||
| fi | ||
|
|
||
| - name: Build | ||
| echo "version=$TAG_VERSION" >> "$GITHUB_OUTPUT" | ||
| echo "✅ Version: $TAG_VERSION" | ||
| id: version | ||
|
|
||
| - name: Set package.json version from tag | ||
| if: github.event_name == 'release' | ||
| working-directory: ./subtrack | ||
| run: pnpm build | ||
| run: | | ||
| node -e " | ||
| const fs = require('fs') | ||
| const pkg = require('./package.json') | ||
| pkg.version = '${{ steps.version.outputs.version }}' | ||
| fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n') | ||
| " | ||
|
|
||
| - run: pnpm install --frozen-lockfile | ||
|
|
||
| - run: pnpm build | ||
|
|
||
| - name: Verify build output | ||
| - name: Verify build | ||
| working-directory: ./subtrack | ||
| run: | | ||
| test -f dist/index.mjs | ||
| ls -R dist | ||
| node -e " | ||
| const bytes = require('fs').readFileSync('dist/index.mjs').length; | ||
| const tag = '${{ steps.version.outputs.version }}'; | ||
| console.log('📦 subtrack v' + tag + ' | dist/index.mjs (' + (bytes / 1024).toFixed(1) + ' KB)'); | ||
| " | ||
|
|
||
| - name: Audit dependencies | ||
| - name: Dry run publish | ||
| if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.dry-run == true) | ||
| working-directory: ./subtrack | ||
| run: pnpm audit --audit-level=high | ||
| run: pnpm publish --no-git-checks --provenance --dry-run | ||
|
|
||
| - name: Publish to npm | ||
| if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.dry-run == false) | ||
| working-directory: ./subtrack | ||
| run: pnpm publish --no-git-checks --provenance | ||
|
|
||
| - name: Upload release asset | ||
| if: github.event_name == 'release' | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| working-directory: ./subtrack | ||
| run: | | ||
| TAG="${{ github.event.release.tag_name }}" | ||
| gh release upload "$TAG" dist/index.mjs#subtrack.mjs | ||
| echo "✅ Uploaded subtrack.mjs to $TAG" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: nazozokc/subtrack
Length of output: 695
🏁 Script executed:
# Check the file size first to avoid reading truncated output wc -l .github/workflows/check.ymlRepository: nazozokc/subtrack
Length of output: 92
🌐 Web query:
GitHub Actions security best practices tag vs SHA pinning immutable reference💡 Result:
For GitHub Actions, pinning to a full-length commit SHA is the recommended security best practice, whereas using tags (e.g., v4) is considered a mutable, less secure reference [1][2][3]. Why SHA Pinning is the Security Standard: - Immutability: Git tags can be moved, deleted, or recreated to point to different commits [4][5]. If a repository is compromised, an attacker can update an existing tag to point to malicious code, and your workflows will automatically run that new code without any changes to your workflow file [4][6][5]. - Protection against Retargeting: Pinning to a full-length commit SHA (e.g.,
@a1b2c3d4...) ensures your workflow always executes the exact, immutable code you originally reviewed and trusted, regardless of what happens to tags or branches in the upstream repository [7][4][5]. - Policy Enforcement: GitHub supports organization and repository-level policies that mandate SHA pinning [1][8][4]. When enforced, these policies cause workflows to fail if they use tags or branches, forcing adherence to immutable references [4][2]. Best Practices for Managing Pins: - Use Automation Tools: Manually updating SHAs is inefficient. Tools like Renovate or Dependabot can be configured to automatically open pull requests that bump SHAs while keeping a human-readable version tag in a comment (e.g., uses: owner/action@a1b2c3d4... # v4.2.0) [7][6]. Other tools like pinact can also automate the initial conversion of existing workflows to SHA-pinned references [2]. - Maintain Human-Readable Context: Always include the original tag or version in a trailing comment within the workflow file [7][6]. This allows automated tools to correlate the pinned SHA back to a semantic version for easier tracking [7]. - Consider the Full Dependency Tree: Remember that pinning a top-level action does not protect against "nested" dependencies [2]. Some actions may internally execute other actions, scripts, or Docker images that are not pinned [2]. - Beyond Pinning: While SHA pinning prevents tag-retargeting attacks, it does not protect against malicious code that is already present at the chosen SHA [7][6]. Always vet the actions you use and consider using GitHub’s policy features to explicitly allow or block specific actions across your organization [8][2][6]. As of 2026, GitHub is continuing to evolve its security roadmap to make workflows more deterministic, including plans to introduce a native dependencies section in workflow YAML to lock both direct and transitive dependencies [9].Citations:
🌐 Web query:
GitHub Actions checkout persist-credentials security implications💡 Result:
The persist-credentials setting in the GitHub Actions checkout action controls whether the authentication token (typically GITHUB_TOKEN) used for the checkout process is stored in the runner's local Git configuration [1][2]. By default, this is set to true [1][3]. Security Implications: - Credential Exposure: When enabled (the default), the token is persisted in the environment, making it potentially accessible to subsequent steps or third-party actions within the same workflow [3][4]. If a workflow creates and uploads build artifacts that include the repository directory (e.g., using actions/upload-artifact), there is a risk that these credentials could be inadvertently leaked if not properly excluded [4]. - Improved Security Mechanisms: Recent versions of the checkout action have improved credential security by storing tokens in a separate file under $RUNNER_TEMP rather than directly in.git/config [1][5]. While this reduces the risk of direct leakage through basic file operations, the underlying credential remains accessible to processes running within the job [1][6]. - Best Practices: Security guidance, including the OWASP GitHub Actions Security Cheat Sheet, recommends setting persist-credentials: false explicitly unless the workflow specifically requires the ability to perform authenticated Git operations (like git push) back to the repository [7][3]. If push access is needed, ensure the GITHUB_TOKEN has the minimum required permissions, and avoid relying on persistence if those operations can be performed via other authorized means [3]. For most workflows that only need to download code, disabling this setting is a recommended hardening step to adhere to the principle of least privilege [7][3].
Citations:
persist-credentials=trueplease actions/checkout#2312Harden action execution and token handling in CI.
Lines 10, 12, and 14 use mutable tag pins (
@v4) instead of immutable commit SHAs, increasing supply-chain risk. Line 10's checkout action persists credentials by default and should explicitly disable this since the workflow only reads code. Add an explicit minimal permissions block scoped to read-only.Suggested patch
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 10-10: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 10-10: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 12-12: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
[error] 14-14: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Source: Linters/SAST tools