ci: add build + typecheck workflow and fix link-check Node version - #115
Conversation
Nothing in CI built or typechecked this repo, so a PR could go green and still be broken. Dependabot's TypeScript 7 bump (#113) is a live example: Vercel passes it because the Docusaurus build never runs tsc, but `npm run typecheck` fails on it with TS5102 (baseUrl removed). Add a CI workflow running typecheck and build on every PR and push to main. While here: the existing link-check workflow has been failing at its "Build documentation" step for about three months. It pins node-version 18, but @docusaurus/core 3.10 declares engines.node >=20, so the build could never succeed. Bumped it to 24 to match opsimate-docs/dockerfile, and corrected the stale engines.node in opsimate-docs/package.json, which still claimed >=18.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughChangesNode.js CI alignment
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
.github/workflows/ci.yml (1)
25-25: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winInclude both lockfiles in the npm cache key.
The repository has separate root and
opsimate-docslockfiles.setup-nodeuses the dependency file in the repository root by default. Changes toopsimate-docs/package-lock.jsonwill not invalidate this cache.cache-dependency-pathsupports multiple dependency files. (github.com)♻️ Proposed fix
node-version: '24' cache: 'npm' + cache-dependency-path: | + package-lock.json + opsimate-docs/package-lock.json🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/ci.yml at line 25, Update the setup-node npm cache configuration near the cache setting to define cache-dependency-path with both the root package-lock.json and opsimate-docs/package-lock.json, so changes to either lockfile invalidate the cache.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ci.yml:
- Around line 16-17: Update the checkout step using actions/checkout@v7 to set
persist-credentials to false, and add an explicit top-level permissions
declaration granting only the required access for this pull_request workflow,
preserving later steps unless they require authenticated Git operations.
In `@opsimate-docs/package.json`:
- Line 49: Regenerate opsimate-docs/package-lock.json from the updated
engines.node constraint in package.json so its root metadata records >=20.0
instead of >=18.0. Use the package manager from the opsimate-docs project and
commit the resulting lockfile metadata without unrelated dependency changes.
---
Nitpick comments:
In @.github/workflows/ci.yml:
- Line 25: Update the setup-node npm cache configuration near the cache setting
to define cache-dependency-path with both the root package-lock.json and
opsimate-docs/package-lock.json, so changes to either lockfile invalidate the
cache.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 87391f58-fb70-4840-be30-b3218c381aeb
📒 Files selected for processing (3)
.github/workflows/ci.yml.github/workflows/link-check.ymlopsimate-docs/package.json
| - name: Checkout repository | ||
| uses: actions/checkout@v7 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
grep -q 'persist-credentials: false' .github/workflows/ci.yml
grep -q 'contents: read' .github/workflows/ci.ymlRepository: OpsiMate/documentation
Length of output: 160
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- ci.yml excerpt ---'
sed -n '1,180p' .github/workflows/ci.yml
printf '\n%s\n' '--- action checkout occurrences ---'
rg -n "actions/checkout|persist-credentials|permissions:|contents:|npm ci|npm run" .github/workflows/ci.yml || true
printf '\n%s\n' '--- package scripts and lifecycle hooks ---'
python3 - <<'PY'
import json
from pathlib import Path
p=Path('package.json')
if p.exists():
data=json.loads(p.read_text())
print(json.dumps(data.get('scripts', {}), indent=2))
else:
print('package.json not found')
PYRepository: OpsiMate/documentation
Length of output: 1246
🌐 Web query:
actions/checkout README persist-credentials true false default token checkout token configured as git credential
💡 Result:
The persist-credentials input for the actions/checkout GitHub Action controls whether the repository's authentication token or SSH key is configured in the local git configuration [1][2]. When persist-credentials is set to true (which is the default value) [1][3][2], the action configures the provided authentication token or SSH key into the local git environment [1][2]. This allows subsequent steps in your workflow to run authenticated git commands (e.g., git push or git pull) without needing to manually re-authenticate [1][4][2]. Key technical points regarding this behavior include: - Security Mechanism: The action automatically removes the configured credentials during the post-job cleanup step [1][4][2]. - Implementation: In more recent versions of the action, credentials are stored in a separate file within the runner's temporary directory rather than directly in the.git/config file, which improves security [1]. - Opt-out: You can disable this behavior by explicitly setting persist-credentials: false in your workflow step [1][4]. Doing so prevents the action from configuring git with the token, which is often recommended if you do not need to perform authenticated git operations in subsequent steps, as it limits the exposure of the token within the job [5][6]. While there has been community discussion and proposals to change the default value to false to enhance security, as of August 2026, the default remains true [7][6].
Citations:
- 1: https://github.com/actions/checkout/tree/v6.0.2?tab=readme-ov-file
- 2: https://github.com/actions/checkout/blob/main/action.yml
- 3: https://github.com/actions/checkout/blob/34e114876b0b11c390a56381ad16ebd13914f8d5/action.yml
- 4: https://github.com/marketplace/actions/checkout?version=v3.6.0
- 5: Remove
persist-credentialsor change the default tofalseactions/checkout#485 - 6: [security] Escalate concerning default
persist-credentials=trueplease actions/checkout#2312 - 7: Change the default value of persist-credentials to false actions/checkout#1687
🌐 Web query:
github actions /README.md contents read pull request token permissions
💡 Result:
To read a file like README.md from your repository within a GitHub Actions workflow, the GITHUB_TOKEN requires read access to the contents scope [1][2][3]. For standard workflows, you can explicitly set this in your workflow YAML file using the permissions key [1][4]: permissions: contents: read Key Considerations: 1. Permission Inheritance: If you define a permissions key in your workflow, any permissions you do not explicitly list are set to none [1]. Therefore, if your workflow needs to perform other tasks (such as checking out code or making API calls), ensure those scopes are also included [1][5]. 2. Default Behavior: If no permissions are specified in the workflow, the token inherits the default settings configured at the repository or organization level [1][2][6]. Many organizations configure these defaults to be read-only for security [1][6]. 3. Pull Requests from Forks: For security reasons, GitHub automatically restricts the GITHUB_TOKEN to read-only access for pull requests originating from public forks, regardless of your workflow or repository-level permission settings [1][7][5]. If your workflow must perform write operations triggered by a fork, you typically need to use the pull_request_target event instead, which runs with elevated trust using the base repository's context [7][5]. However, use caution with this event to avoid exposing repository secrets to potentially untrusted code [7]. To verify your current token permissions during a workflow run, you can use the GitHub CLI (gh) within a step: - run: gh auth status - run: gh api /repos/{owner}/{repo}/actions/permissions For reading repository files, standard actions like actions/checkout generally handle authentication automatically, provided the workflow has the necessary contents: read permission [7][2].
Citations:
- 1: https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/
- 2: https://adhdecode.com/articles/github-actions/github-actions-workflow-permissions/
- 3: https://github.github.com/gh-aw/reference/permissions/
- 4: https://docs.github.com/en/actions/tutorials/authenticate-with-github_token
- 5: https://stackoverflow.com/questions/78444367/github-token-permission-in-workflow-changes-to-read-after-setting-it-to-write
- 6: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository
- 7: https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target
🌐 Web query:
github actions malicious repository contents read token fetch code npm ci
💡 Result:
Running code from untrusted repositories within GitHub Actions workflows poses a critical security risk, often leading to Remote Code Execution (RCE) on the runner and subsequent theft of secrets, such as the GITHUB_TOKEN or other configured API credentials [1][2][3]. Common Attack Patterns: - Pwn Requests: Workflows using the pull_request_target trigger can accidentally execute untrusted code from forked repositories with the permissions of the base repository [4][5]. If such a workflow checks out the PR head branch and runs commands like npm install or build scripts, it executes attacker-controlled code [4]. - Compromised Actions: Malicious actors may compromise third-party GitHub Actions (e.g., via tag hijacking) to inject malicious payloads into your workflow, which are then executed with the runner's full privileges [3]. - Dependency Exploitation: Commands like npm install or npm ci execute lifecycle scripts (e.g., preinstall, postinstall) found in project dependencies [6][7]. If a malicious dependency is present, these scripts execute arbitrary code during the CI/CD process [6][7]. Security Risks of npm ci/install: - Lifecycle Scripts: npm automatically runs scripts defined in package.json during installation [7]. These scripts provide an easy vector for RCE if a compromised package is included [6][7]. - Lockfile Manipulation: Attackers can inject malicious dependencies by modifying the package-lock.json file in a pull request [8]. While npm ci is safer than npm install in CI environments because it enforces the lockfile, it still installs whatever the lockfile specifies, meaning it will install a malicious package if the lockfile has been poisoned [8]. Hardening Recommendations: - Minimize Permissions: Explicitly define permissions for every workflow using a top-level permissions block (e.g., permissions: {}) and grant only the absolute minimum required access [9][10]. - Disable Lifecycle Scripts: Use npm ci --ignore-scripts to prevent the execution of malicious install-time scripts [6][7]. - Secure Triggers: Be extremely cautious with pull_request_target. Use it only when necessary and ensure workflows do not check out or execute code from untrusted PRs directly [4][5]. Consider splitting workflows into two: one that runs untrusted code in a low-trust context and another that only processes validated output [4]. - Use Egress Filtering: Implement tools (e.g., Harden-Runner) to restrict the network traffic of your workflows, preventing the exfiltration of stolen tokens to external servers [10][4]. - Pin Dependencies and Actions: Always pin third-party actions to a specific, immutable commit SHA rather than a branch or tag [8]. - Use Trusted Publishers: Replace long-lived credentials (like npm tokens) with OIDC-based short-lived tokens (Trusted Publishing) to reduce the impact of a potential credential theft [11][6].
Citations:
- 1: https://www.stepsecurity.io/blog/hackerbot-claw-github-actions-exploitation
- 2: https://www.invisirisk.com/blog/hackerbot-claw-ai-driven-pull-request-exploits-in-github-actions-ci-cd/
- 3: https://sean.dev/2026/06/codfish/semantic-release-action-compromised/
- 4: https://www.endorlabs.com/learn/how-a-misconfigured-ci-workflow-became-an-npm-supply-chain-compromise
- 5: https://github.blog/security/supply-chain-security/disrupting-supply-chain-attacks-on-npm-and-github-actions/
- 6: https://cloud.hacktricks.wiki/en/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-npm-supply-chain-abuse.html
- 7: https://blog.authon.dev/why-npm-supply-chain-attacks-keep-happening-and-how-to-harden-your-installs
- 8: https://shipwithai.io/blog/npm-ci-security-team-playbook/
- 9: https://github.com/github/docs/blob/main/content/actions/reference/security/secure-use.md
- 10: https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/GitHub_Actions_Security_Cheat_Sheet.md
- 11: https://blog.pypi.org/posts/2025-09-16-github-actions-token-exfiltration/
🌐 Web query:
actions/checkout v8 README persist-credentials v7 persist-credentials
💡 Result:
As of August 1, 2026, there is no official version 8 of the actions/checkout GitHub Action [1]. The current major version is v7 [1][2]. Regarding the persist-credentials input: Functionality: This input determines whether the authentication token (or SSH key) used to clone the repository is configured in the local git configuration [3][4][2]. When enabled (the default), it allows subsequent steps in your workflow to execute authenticated git commands (such as git push or git fetch) without needing to re-authenticate [3][5][2]. Security Context: The default value for persist-credentials is true [4][2]. Because the token is stored in the git configuration, any subsequent step in the same job can theoretically access this credential [6][7]. To enhance security and prevent accidental exposure or unauthorized use of the token by later steps, it is a common best practice to explicitly set persist-credentials: false if your workflow does not require running authenticated git commands after the checkout is complete [6][7]. Changes in behavior: While the functional purpose of persist-credentials remains consistent, there have been improvements in how these credentials are stored. For example, in v6, the action was updated to store credentials in a file under $RUNNER_TEMP rather than directly in the repository's .git/config file to improve security [8][9]. If you are seeing references to "v8" in your specific context, it may be an error or a misunderstanding, as official releases have only reached v7 [1]. Always refer to the official GitHub repository for the most accurate and up-to-date documentation regarding inputs and their defaults [10][2].
Citations:
- 1: https://github.com/actions/checkout/releases
- 2: https://github.com/actions/checkout/?tab=readme-ov-file
- 3: https://github.com/actions/checkout/blob/v4/README.md
- 4: https://github.com/actions/checkout/blob/v4/action.yml
- 5: https://github.com/marketplace/actions/checkout
- 6: Remove
persist-credentialsor change the default tofalseactions/checkout#485 - 7: Change the default value of persist-credentials to false actions/checkout#1687
- 8: https://github.com/actions/checkout/tree/v6.0.0
- 9: https://github.com/actions/checkout?v=1.1.1
- 10: https://github.com/actions/checkout
Disable persisted checkout credentials for this PR build.
This PR workflow runs checkout on pull_request, then runs npm ci and package scripts. actions/checkout@v7 keeps the GitHub token configured for later Git commands unless persist-credentials: false is set. Add an explicit top-level permission, and disable persisting the checkout token unless the later steps need authenticated Git access.
🔒 Proposed fix
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
+permissions:
+ contents: read
+
jobs:
@@
- name: Checkout repository
uses: actions/checkout@v7
+ with:
+ persist-credentials: false🧰 Tools
🪛 zizmor (1.28.0)
[warning] 16-17: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci.yml around lines 16 - 17, Update the checkout step
using actions/checkout@v7 to set persist-credentials to false, and add an
explicit top-level permissions declaration granting only the required access for
this pull_request workflow, preserving later steps unless they require
authenticated Git operations.
Source: Linters/SAST tools
| }, | ||
| "engines": { | ||
| "node": ">=18.0" | ||
| "node": ">=20.0" |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json
with open("opsimate-docs/package.json") as file:
manifest = json.load(file)
with open("opsimate-docs/package-lock.json") as file:
lockfile = json.load(file)
expected = manifest["engines"]["node"]
actual = lockfile["packages"][""]["engines"]["node"]
assert actual == expected, f"lockfile has {actual!r}; manifest has {expected!r}"
PYRepository: OpsiMate/documentation
Length of output: 295
Regenerate the docs lockfile for the new engine constraint.
opsimate-docs/package-lock.json still records engines.node as >=18.0, while opsimate-docs/package.json declares >=20.0. Regenerate the lockfile from opsimate-docs and commit the matching metadata so install workflows do not reuse stale lockfile metadata.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@opsimate-docs/package.json` at line 49, Regenerate
opsimate-docs/package-lock.json from the updated engines.node constraint in
package.json so its root metadata records >=20.0 instead of >=18.0. Use the
package manager from the opsimate-docs project and commit the resulting lockfile
metadata without unrelated dependency changes.
Why
Nothing in CI builds or typechecks this repo. A PR can go green and still be broken — and one currently is: #113 (TypeScript 7) passes Vercel but fails
npm run typecheck, because the Docusaurus build never invokestsc. Without this workflow there's no signal that would have caught it.What
A
CIworkflow that runs on every PR and push tomain:npm ci(root workspace)npm run typecheckinopsimate-docsnpm run buildBoth steps verified locally against current
main— typecheck clean, build succeeds.Also fixed: link-check has been broken for ~3 months
The existing
link-check.ymlhas been failing every scheduled run, at the Build documentation step. Cause:It pins Node 18, which Docusaurus refuses to run on — so the build step could never pass, and the link check that depends on it never ran at all. Bumped to Node 24, matching
opsimate-docs/dockerfile.Also corrected
opsimate-docs/package.json, which still declaredengines.node: ">=18.0"— stale since the Docusaurus 3.10 bump and actively misleading about what this project runs on.Note for a follow-up
There are two lockfiles —
package-lock.jsonat the root andopsimate-docs/package-lock.json— which is unusual for an npm workspace and means the two can drift. This workflow uses the root one. Worth reconciling separately; I didn't touch it here to keep this PR reviewable.🤖 Generated with Claude Code
Summary by CodeRabbit