-
Notifications
You must be signed in to change notification settings - Fork 2
chore(ci): harden issue-fields backfill and record verification #1405
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
41a8449
a92a603
9cfac6a
c170804
a440b8b
65259ac
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 |
|---|---|---|
|
|
@@ -6,7 +6,8 @@ name: Issue Fields • Bulk Backfill | |
| # | ||
| # PREREQUISITES | ||
| # ───────────── | ||
| # 1. vars.LS_APP_ID — GitHub App ID (integer) | ||
| # 1. vars.LS_APP_CLIENT_ID — GitHub App client ID (preferred) | ||
| # vars.LS_APP_ID — Legacy GitHub App ID (fallback) | ||
| # 2. secrets.LS_APP_PRIVATE_KEY — GitHub App private key (PEM) | ||
| # 3. vars.LS_PROJECT_NUMBER — Projects v2 board number (integer, e.g. 33) | ||
| # OR vars.LS_PROJECT_URL — Full URL (e.g. https://github.com/orgs/…/projects/33) | ||
|
|
@@ -74,13 +75,14 @@ jobs: | |
|
|
||
| - name: Create GitHub App token | ||
| id: app-token | ||
| uses: actions/create-github-app-token@v2 | ||
| uses: actions/create-github-app-token@v3 | ||
| with: | ||
| app-id: ${{ vars.LS_APP_ID }} | ||
| client-id: ${{ vars.LS_APP_CLIENT_ID != '' && vars.LS_APP_CLIENT_ID || vars.LS_APP_ID }} | ||
| private-key: ${{ secrets.LS_APP_PRIVATE_KEY }} | ||
| owner: ${{ github.repository_owner }} | ||
|
Comment on lines
+78
to
+82
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. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== workflow excerpt =="
sed -n '1,280p' .github/workflows/issue-fields-backfill.yml
echo
echo "== related tokens/permissions =="
rg -n "create-github-app-token|permissions:|id-token|contents:|pull-requests:|issues:" .github/workflows/issue-fields-backfill.yml .github/workflows || true
echo
echo "== package/action versions nearby =="
rg -n "actions/create-github-app-token" .github/workflows || trueRepository: lightspeedwp/.github Length of output: 21518 🌐 Web query:
💡 Result: The actions/create-github-app-token version 3 action is used to generate an installation access token for a GitHub App within a workflow [1][2]. Below is the summary of the requested inputs and default permission behavior. Input Parameters: - owner: Optional. Specifies the owner (user or organization) of the GitHub App installation [1][3]. If left empty, it defaults to the current repository owner [1][4]. - repositories: Optional. A comma or newline-separated list of repository names to which the token should have access [1][3]. - If owner is provided and repositories is empty, the token is scoped to all repositories in that owner's installation [1][3]. - If both owner and repositories are empty, the token is scoped to only the current repository [1][3]. - permission-: The action supports granular permission control via inputs prefixed with permission- (e.g., permission-pull-requests) [1][4]. These are dynamically generated based on the GitHub OpenAPI specification [5]. Default Permission Behavior: By default, if no specific permissions are requested via the permission- inputs, the generated installation token inherits all permissions that were granted to the GitHub App installation [1][4][6]. Important Considerations: - It is recommended to explicitly list the specific permissions required for your use case to adhere to the principle of least privilege, consistent with GitHub's guidelines for controlling GITHUB_TOKEN permissions [1][4]. - You cannot grant the token permissions or access to repositories that the App installation itself does not already possess [5][6]. - The version 3 release introduced several changes, including the use of client-id (replacing the deprecated app-id) and support for enterprise-level GitHub Apps [7][8]. Citations:
🌐 Web query:
💡 Result: In the actions/create-github-app-token@v3 action, the permission- prefix is used to explicitly define the permissions granted to the generated installation access token [1]. By default, the token inherits all permissions assigned to the GitHub App installation [1][2]. However, it is recommended to explicitly list only the permissions required for your specific use case to follow the principle of least privilege [1]. To configure these permissions, you prefix the desired permission key with permission- in your workflow configuration (e.g., permission-pull-requests for pull-requests) [1]. For example: - To grant read access to pull requests: permission-pull-requests: read - To grant write access to metadata: permission-metadata: write Important considerations: 1. Installation vs. App Permissions: The permissions you set must be granted to the GitHub App installation itself [1][2]. If you request a permission that the installation does not possess, the action will return an error [1]. 2. Administrative Approval: If you add new permissions to an app after it has been installed, an account administrator must approve these changes before they become effective on the installation [1][2]. 3. Repositories Input: The repositories input allows you to scope the token to specific repositories [1][2]. If this input is omitted, the token is scoped to the current repository by default [2]. If the owner input is provided but repositories is empty, the token is scoped to all repositories in that installation [1][2]. Citations:
Whittle the GitHub App token down a smidge. The 🔐 Suggested tightening owner: ${{ github.repository_owner }}
+ repositories: ${{ github.event.repository.name }}
+ # Add only the required permission-* inputs after confirming app scopes.
id-token: write🧰 Tools🪛 zizmor (1.28.0)[error] 81-81: dangerous use of GitHub App tokens (github-app): token granted access to all repositories for this owner's app installation (github-app) [error] 77-77: dangerous use of GitHub App tokens (github-app): app token inherits blanket installation permissions (github-app) 🤖 Prompt for AI AgentsSources: Path instructions, MCP tools, Linters/SAST tools |
||
|
|
||
| - name: Run bulk backfill | ||
| uses: actions/github-script@v7 | ||
| uses: actions/github-script@v9 | ||
| env: | ||
| APP_TOKEN: ${{ steps.app-token.outputs.token }} | ||
| DRY_RUN: ${{ github.event.inputs.dry_run }} | ||
|
|
@@ -114,6 +116,17 @@ jobs: | |
| (config?.organization_issue_fields?.enabled_issue_types ?? []).map(s => s.toLowerCase()) | ||
| ); | ||
|
|
||
| function resolveProjectNumberFromUrl(url) { | ||
| try { | ||
| const segments = new URL(url).pathname.split('/').filter(Boolean); | ||
| const projectsIdx = segments.indexOf('projects'); | ||
| if (projectsIdx < 0 || projectsIdx === segments.length - 1) return Number.NaN; | ||
| return parseInt(segments[projectsIdx + 1], 10); | ||
| } catch { | ||
| return Number.NaN; | ||
| } | ||
| } | ||
|
Comment on lines
+119
to
+128
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. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== file info =="
wc -l .github/workflows/issue-fields-backfill.yml 2>/dev/null || true
echo "== relevant sections =="
sed -n '100,135p' .github/workflows/issue-fields-backfill.yml 2>/dev/null || true
sed -n '205,235p' ..github/workflows/issue-fields-backfill.yml 2>/dev/null || true
echo "== parseInt usage in file =="
rg -n "parseInt|resolveProjectNumberFromUrl|projectNumber|MAX|NaN|Int!" .github/workflows/issue-fields-backfill.yml 2>/dev/null || true
echo "== deterministic parseInt/coercion probe =="
node - <<'JS'
const inputs = ['123', '123junk', '0123', '1.5', '1.23e2', '123abcXYZ', '9007199254740992', '2147483648', '2147483647', '2147483646'];
for (const s of inputs) {
console.log(`${s.padEnd(23)} parseInt=`, parseInt(s, 10),
`Number=`, Number(s),
`digitsOnly=`, /^\d+$/.test(s),
`safePositiveInt=`, Number.isSafeInteger(Number(s)) && Number(s) > 0);
}
JSRepository: lightspeedwp/.github Length of output: 4032 Enforce a strict project-number parser.
Applies to lines 119-124 and 246-247. 🤖 Prompt for AI AgentsSource: MCP tools |
||
|
|
||
| // ─── Resolve project URL/number ───────────────────────────── | ||
| let projectUrl = process.env.PROJECT_URL; | ||
| const projectNumber = parseInt(process.env.PROJECT_NUMBER || '0', 10); | ||
|
|
@@ -143,24 +156,54 @@ jobs: | |
|
|
||
| // ─── Step 2 (optional): Native issue type sync ─────────────── | ||
| if (syncNativeTypes) { | ||
| core.info('Fetching org issue types...'); | ||
| core.info('Fetching issue types...'); | ||
| let orgTypeMap = new Map(); | ||
| let orgFetchError = ''; | ||
|
|
||
| try { | ||
| const typeData = await github.graphql(` | ||
| query($org: String!) { | ||
| organization(login: $org) { | ||
| issueTypes(first: 50) { | ||
| nodes { id name isEnabled } | ||
| nodes { id name } | ||
| } | ||
| } | ||
| } | ||
| `, { org: owner }); | ||
| for (const node of typeData.organization.issueTypes.nodes) { | ||
| if (node.isEnabled !== false) orgTypeMap.set(node.name.toLowerCase(), node.id); | ||
| orgTypeMap.set(node.name.toLowerCase(), node.id); | ||
| } | ||
| core.info(`Org has ${orgTypeMap.size} enabled issue type(s): ${[...orgTypeMap.keys()].join(', ')}`); | ||
| core.info(`Organization has ${orgTypeMap.size} issue type(s): ${[...orgTypeMap.keys()].join(', ')}`); | ||
| } catch (err) { | ||
| core.warning(`Could not fetch org issue types: ${err.message}`); | ||
| orgFetchError = err.message; | ||
| core.warning( | ||
| `Could not fetch org issue types: ${err.message}. ` + | ||
| 'Attempting repository-scoped issue type fallback.' | ||
| ); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| if (orgTypeMap.size === 0) { | ||
| try { | ||
| const repoTypeData = await github.graphql(` | ||
| query($owner: String!, $repo: String!) { | ||
| repository(owner: $owner, name: $repo) { | ||
| issueTypes(first: 50) { | ||
| nodes { id name } | ||
| } | ||
| } | ||
| } | ||
| `, { owner, repo }); | ||
| for (const node of repoTypeData.repository.issueTypes.nodes) { | ||
| orgTypeMap.set(node.name.toLowerCase(), node.id); | ||
| } | ||
| core.info(`Repository fallback has ${orgTypeMap.size} issue type(s): ${[...orgTypeMap.keys()].join(', ')}`); | ||
| } catch (err) { | ||
| core.warning( | ||
| `Could not fetch repository issue types: ${err.message}. ` + | ||
| `${orgFetchError ? `Org query error: ${orgFetchError}. ` : ''}` + | ||
| 'Ensure the app token has permission to read issue types at org or repository scope.' | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| for (const issue of issues) { | ||
|
|
@@ -178,12 +221,12 @@ jobs: | |
| if (!dryRun) { | ||
| try { | ||
| await github.graphql(` | ||
| mutation($issueId: ID!, $typeId: ID) { | ||
| updateIssue(input: { id: $issueId, typeId: $typeId }) { | ||
| mutation($issueId: ID!, $issueTypeId: ID) { | ||
| updateIssue(input: { id: $issueId, issueTypeId: $issueTypeId }) { | ||
| issue { id issueType { name } } | ||
| } | ||
| } | ||
| `, { issueId: issue.node_id, typeId }); | ||
| `, { issueId: issue.node_id, issueTypeId: typeId }); | ||
| report.nativeTypeSet.push(`#${issue.number} → ${targetName}`); | ||
| } catch (err) { | ||
| report.errors.push(`#${issue.number} native type failed: ${err.message}`); | ||
|
|
@@ -199,34 +242,43 @@ jobs: | |
| core.info(`Syncing project fields to ${projectUrl}...`); | ||
|
|
||
| // Resolve project node ID and fields | ||
| const [, , , orgOrUser, , projNumStr] = new URL(projectUrl).pathname.split('/'); | ||
| const projNum = parseInt(projNumStr, 10); | ||
| const projNumFromUrl = resolveProjectNumberFromUrl(projectUrl); | ||
| const projNum = Number.isInteger(projectNumber) && projectNumber > 0 | ||
| ? projectNumber | ||
| : projNumFromUrl; | ||
|
Comment on lines
+245
to
+248
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. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Reject conflicting project identifiers. When 🤖 Prompt for AI Agents |
||
|
|
||
| let projectId, fieldMeta; | ||
| try { | ||
| const projData = await github.graphql(` | ||
| query($owner: String!, $number: Int!) { | ||
| organization(login: $owner) { | ||
| projectV2(number: $number) { | ||
| id | ||
| fields(first: 30) { | ||
| nodes { | ||
| ... on ProjectV2Field { id name dataType } | ||
| ... on ProjectV2SingleSelectField { id name dataType options { id name } } | ||
| if (!Number.isInteger(projNum) || projNum <= 0) { | ||
| core.warning( | ||
| `Could not parse a valid project number. ` + | ||
| `PROJECT_NUMBER="${process.env.PROJECT_NUMBER || ''}" PROJECT_URL="${projectUrl}"` | ||
| ); | ||
|
Comment on lines
+251
to
+255
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. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win The documented fail-fast guardrail is not implemented.
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| } else { | ||
| try { | ||
| const projData = await github.graphql(` | ||
| query($owner: String!, $number: Int!) { | ||
| organization(login: $owner) { | ||
| projectV2(number: $number) { | ||
| id | ||
| fields(first: 30) { | ||
| nodes { | ||
| ... on ProjectV2Field { id name dataType } | ||
| ... on ProjectV2SingleSelectField { id name dataType options { id name } } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| `, { owner, number: projNum }); | ||
| projectId = projData.organization.projectV2.id; | ||
| fieldMeta = {}; | ||
| for (const f of projData.organization.projectV2.fields.nodes) { | ||
| fieldMeta[f.name.toLowerCase()] = f; | ||
| } | ||
| `, { owner, number: projNum }); | ||
| projectId = projData.organization.projectV2.id; | ||
| fieldMeta = {}; | ||
| for (const f of projData.organization.projectV2.fields.nodes) { | ||
| fieldMeta[f.name.toLowerCase()] = f; | ||
| core.info(`Project "${projectId}" fields: ${Object.keys(fieldMeta).join(', ')}`); | ||
| } catch (err) { | ||
| core.warning(`Could not access project: ${err.message}`); | ||
| } | ||
| core.info(`Project "${projectId}" fields: ${Object.keys(fieldMeta).join(', ')}`); | ||
| } catch (err) { | ||
| core.warning(`Could not access project: ${err.message}`); | ||
| } | ||
|
|
||
| if (projectId) { | ||
|
|
||
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.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: lightspeedwp/.github
Length of output: 228
🌐 Web query:
actions/create-github-app-token v3 README client-id app-input README💡 Result:
In the actions/create-github-app-token v3 documentation, the client-id input is the recommended way to provide the GitHub App identifier for authentication [1][2]. While the action still supports app-id for backwards compatibility, it has been officially deprecated [1][2]. The action's runtime logic is configured to prefer client-id; if both inputs are provided, client-id takes precedence [2][3]. If neither input is provided, the action will return an error [2][3]. To use the action in a workflow, you should store your GitHub App's Client ID in a repository variable (e.g., APP_CLIENT_ID) and your private key in a repository secret (e.g., APP_PRIVATE_KEY), and then reference them in your workflow step [4][1][5]: - uses: actions/create-github-app-token@v3 id: app-token with: client-id: ${{ vars.APP_CLIENT_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} The documentation does not refer to an input named app-input; rather, it uses client-id (the recommended input) or the legacy app-id to specify the application identity [1][2][5].
Citations:
client-idinput and deprecateapp-idactions/create-github-app-token#353client-idinput and deprecateapp-idactions/create-github-app-token#353🌐 Web query:
ReviewDog actionlint command config actionlint GitHub Actions input unknown💡 Result:
The error "input unknown" or "not defined" when using the reviewdog/action-actionlint GitHub Action typically occurs because the input parameter you are attempting to use is not supported by the specific version of the action you have configured [1][2]. To resolve this issue, check the following steps: 1. Verify Valid Inputs: Ensure the input name you are using is defined in the action's configuration. The currently supported inputs for reviewdog/action-actionlint are [3][4]: - github_token - tool_name - level - reporter - filter_mode - fail_level - fail_on_error (deprecated) - reviewdog_flags - actionlint_flags 2. Check Your Version: If you are trying to use a newer feature like 'fail_level', ensure you are using a recent version of the action that supports it [2]. If you are pinned to an older version (e.g., using a specific tag or hash), the newer inputs will not be recognized, resulting in the "not defined" error [2]. Update your workflow's
usesstatement to point to a later version or the latest release [5][4]. 3. Common Pitfall: Users often encounter this error when trying to add unsupported parameters, such as 'workdir', which is not a valid input for this action [1]. Since actionlint automatically targets the repository's workflow directory, specific working directory overrides are generally not supported as action inputs [1]. 4. Distinguish Action vs. Tool: Remember thatactionlintis the underlying tool, andreviewdog/action-actionlintis the wrapper [5][6]. Errors about "unknown inputs" usually refer to the wrapper'swith:configuration, not the flags passed to the actionlint tool itself [2]. If you need to pass additional configuration to the actionlint tool, use theactionlint_flagsinput instead [5][3][4].Citations:
fail_level? reviewdog/action-actionlint#149🌐 Web query:
github actions create-github-app-token v3 client-id💡 Result:
In version 3 of the
actions/create-github-app-tokenaction, theclient-idinput was introduced as the recommended parameter for identifying the GitHub App [1][2]. Key details regarding the use ofclient-idinclude: - Recommendation: It is now the preferred way to specify the GitHub App, replacing the legacyapp-idinput [3][2]. - Backwards Compatibility: The action still acceptsapp-idfor backwards compatibility, though it is marked as deprecated [2][4]. - Precedence: If bothclient-idandapp-idare provided, the action will prioritizeclient-id[2]. - Configuration: You should store your App's Client ID in your repository or organization variables (e.g., asAPP_CLIENT_ID) and pass it to the action via the workflow file [3][5]: - uses: actions/create-github-app-token@v3 with: client-id: ${{ vars.APP_CLIENT_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} Ensure that the corresponding private key for the GitHub App is stored as a repository secret and passed to theprivate-keyinput [3][6]. If neitherclient-idnorapp-idis provided, the action will return an error [2].Citations:
client-idinput and deprecateapp-idactions/create-github-app-token#353🏁 Script executed:
Repository: lightspeedwp/.github
Length of output: 8449
🌐 Web query:
rhysd actionlint releases unknown inputs create-github-app-token client-id💡 Result:
The issue where actionlint reports unknown inputs or missing required inputs when using actions/create-github-app-token@v3 with the client-id input is a known false-positive caused by stale metadata in actionlint's internal registry [1][2]. When actions/create-github-app-token updated its v3 action.yml to introduce client-id as the canonical input (deprecating the legacy app-id), actionlint's bundled definition of popular actions was not immediately synchronized [1][2]. This caused actionlint to: - Incorrectly report that the required app-id input was missing [1][3][2]. - Incorrectly report that the valid client-id input was undefined [1][3][2]. This has been addressed in recent updates to actionlint [3][2]. To resolve this, ensure you are using an up-to-date version of actionlint [4][5]. If you are on an older version and cannot upgrade, you can suppress these specific false-positives by adding the following to your.github/actionlint.yaml configuration file [1]: paths:.github/workflows//*.{yml,yaml}: ignore: - 'missing input "app-id" which is required by action "actions/create-github-app-token@' - 'input "client-id" is not defined in action "actions/create-github-app-token@'
Citations:
actions/create-github-app-token@v3client-idinput rhysd/actionlint#669actions/create-github-app-tokenrhysd/actionlint#652Refresh actionlint’s action metadata or pin the validator.
actions/create-github-app-token@v3legitimately supportsclient-id, but older actionlint metadata can still flag it as unknown and block the required zero-error workflow validation. Bump the validator or patch its action registry; don’t fall back to the deprecatedapp-id.🧰 Tools
🪛 actionlint (1.7.12)
[error] 78-78: missing input "app-id" which is required by action "actions/create-github-app-token@v3". all required inputs are "app-id", "private-key"
(action)
[error] 80-80: input "client-id" is not defined in action "actions/create-github-app-token@v3". available inputs are "app-id", "github-api-url", "owner", "permission-actions", "permission-administration", "permission-checks", "permission-codespaces", "permission-contents", "permission-custom-properties-for-organizations", "permission-dependabot-secrets", "permission-deployments", "permission-email-addresses", "permission-enterprise-custom-properties-for-organizations", "permission-environments", "permission-followers", "permission-git-ssh-keys", "permission-gpg-keys", "permission-interaction-limits", "permission-issues", "permission-members", "permission-metadata", "permission-organization-administration", "permission-organization-announcement-banners", "permission-organization-copilot-seat-management", "permission-organization-custom-org-roles", "permission-organization-custom-properties", "permission-organization-custom-roles", "permission-organization-events", "permission-organization-hooks", "permission-organization-packages", "permission-organization-personal-access-token-requests", "permission-organization-personal-access-tokens", "permission-organization-plan", "permission-organization-projects", "permission-organization-secrets", "permission-organization-self-hosted-runners", "permission-organization-user-blocking", "permission-packages", "permission-pages", "permission-profile", "permission-pull-requests", "permission-repository-custom-properties", "permission-repository-hooks", "permission-repository-projects", "permission-secret-scanning-alerts", "permission-secrets", "permission-security-events", "permission-single-file", "permission-starring", "permission-statuses", "permission-team-discussions", "permission-vulnerability-alerts", "permission-workflows", "private-key", "repositories", "skip-token-revoke"
(action)
🪛 zizmor (1.28.0)
[error] 78-78: dangerous use of GitHub App tokens (github-app): app token inherits blanket installation permissions
(github-app)
🤖 Prompt for AI Agents
Source: Linters/SAST tools