-
Notifications
You must be signed in to change notification settings - Fork 293
Added required secret check file #3419
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
base: main
Are you sure you want to change the base?
Changes from all commits
046399d
7130b77
095b628
2a3e722
fba3698
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,134 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: Secret Fields Check | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pull_request: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| types: [opened, synchronize, reopened] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| secret-fields-check: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runs-on: ubuntu-22.04 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timeout-minutes: 5 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| strategy: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| matrix: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| node-version: [22.x] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Checkout | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fetch-depth: 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Setup Node.js | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: actions/setup-node@v4 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| node-version: ${{ matrix.node-version }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cache: yarn | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Install dependencies | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| run: yarn install --frozen-lockfile | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - name: Check PR description | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id: pr_desc | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| uses: actions/github-script@v7 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| script: | | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const pr = context.payload.pull_request; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const body = (pr?.body || "").toLowerCase(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const patterns = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /-\s*\[x\]\s*required secret checkout/i, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /-\s*\[x\]\s*required secret checkout/i, |
Copilot
AI
Nov 26, 2025
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.
[nitpick] The comment on line 41 says "The new checkbox you provided (checked)" but doesn't clearly explain what this pattern is checking for. Consider updating the comment to be more descriptive, e.g.:
// Pattern to match the security review checkbox from the PR template| // The new checkbox you provided (checked) | |
| // Pattern to match the security review checkbox from the PR template (checked) |
Copilot
AI
Nov 26, 2025
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.
The workflow only checks for obj.settings but ignores action-level secret fields. Since the CLI command's description states it returns "secret fields for each action", the workflow should also iterate through action keys in the parsed output to detect secret fields in individual actions, not just destination settings.
This is related to the CLI bug where actions aren't being checked, but even if that's fixed, this workflow logic needs to be updated to handle action-level findings.
| for (const [destination, obj] of Object.entries(parsed)) { | |
| if (Array.isArray(obj.settings) && obj.settings.length > 0) { | |
| findings.push({ | |
| destination, | |
| settings: obj.settings | |
| }); | |
| } | |
| for (const [destination, obj] of Object.entries(parsed)) { | |
| // Check destination-level settings | |
| if (Array.isArray(obj.settings) && obj.settings.length > 0) { | |
| findings.push({ | |
| destination, | |
| settings: obj.settings | |
| }); | |
| } | |
| // Check action-level secret fields | |
| if (obj.actions && typeof obj.actions === "object") { | |
| for (const [actionName, actionObj] of Object.entries(obj.actions)) { | |
| if (Array.isArray(actionObj) && actionObj.length > 0) { | |
| findings.push({ | |
| destination, | |
| action: actionName, | |
| fields: actionObj | |
| }); | |
| } | |
| } | |
| } |
Copilot
AI
Nov 26, 2025
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.
The findings input is declared but never used in the script. The script retrieves findings via core.getInput("findings") on line 82, but GitHub Actions doesn't automatically pass declared inputs to the script context unless they're referenced in the with section as ${{ steps.list.outputs.findings }}.
The current approach works because step outputs are available via core.getInput(), but the findings: declaration on line 80 is misleading and should be removed to avoid confusion.
| findings: ${{ steps.list.outputs.findings }} |
Copilot
AI
Nov 26, 2025
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.
The error message "This PR cannot be merged until these are removed or converted to secure secrets" is unclear and potentially misleading. The fields themselves aren't removed - they need to be marked with type: 'password' to properly protect sensitive data.
Consider a clearer message such as:
⚠️ **Action Required**: The following fields appear to contain sensitive data based on their names but are not using `type: 'password'`. Please review these fields and update them to use `type: 'password'` if they contain API keys, tokens, passwords, or other secrets.| This PR cannot be merged until these are removed or converted to secure secrets. | |
| ⚠️ **Action Required**: The following fields appear to contain sensitive data based on their names but are not using \`type: 'password'\`. Please review these fields and update them to use \`type: 'password'\` if they contain API keys, tokens, passwords, or other secrets. |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,98 @@ | ||||||
| import { Command, flags } from '@oclif/command' | ||||||
| import { loadDestination } from '../lib/destinations' | ||||||
| import globby from 'globby' | ||||||
| import type { DestinationDefinition as CloudDestinationDefinition } from '@segment/actions-core' | ||||||
| import type { BrowserDestinationDefinition } from '@segment/destinations-manifest' | ||||||
|
|
||||||
| export default class ValidateSecretFields extends Command { | ||||||
| public static enableJsonFlag = true | ||||||
|
|
||||||
| static description = `Returns a list of secret fields for each action in the destination.` | ||||||
|
||||||
|
|
||||||
| static examples = [ | ||||||
| `$ ./bin/run validate-secret-fields`, | ||||||
| `$ ./bin/run validate-secret-fields -p ./packages/destination-actions/src/destinations/hubspot/index.ts` | ||||||
| ] | ||||||
|
|
||||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||||
| static flags: flags.Input<any> = { | ||||||
| help: flags.help({ char: 'h' }), | ||||||
| path: flags.string({ | ||||||
| char: 'p', | ||||||
| description: 'file path for the integration(s). Accepts glob patterns.', | ||||||
| multiple: true | ||||||
| }) | ||||||
| } | ||||||
|
|
||||||
| static args = [] | ||||||
|
|
||||||
| async run() { | ||||||
| const { flags } = this.parse(ValidateSecretFields) | ||||||
|
|
||||||
| const globs = flags.path || [ | ||||||
| './packages/*/src/destinations/*/index.ts', | ||||||
| './packages/browser-destinations/destinations/*/src/index.ts' | ||||||
| ] | ||||||
|
|
||||||
| const files = await globby(globs, { | ||||||
| expandDirectories: false, | ||||||
| gitignore: true, | ||||||
| ignore: ['node_modules'] | ||||||
| }) | ||||||
|
|
||||||
| const destinationMap: Record<string, Record<string, string[]>> = {} | ||||||
| const actionToSecretFields: Record<string, string[]> = {} | ||||||
|
||||||
|
|
||||||
| const cloudDestinationRegex = new RegExp(/packages\/destination-actions\/src\/destinations\/([^/]+)\/*/i) | ||||||
| const browserDestinationRegex = new RegExp(/packages\/browser-destinations\/destinations\/([^/]+)\/*/i) | ||||||
|
|
||||||
| for (const file of files) { | ||||||
| let filePath = file | ||||||
| // extract the destination folder name from the file path | ||||||
| const cloudMatch = cloudDestinationRegex.exec(file) | ||||||
| const browserMatch = browserDestinationRegex.exec(file) | ||||||
| if (cloudMatch) { | ||||||
| const destination = cloudMatch[1] | ||||||
| if (destinationMap[destination]) { | ||||||
| continue | ||||||
| } | ||||||
| filePath = `packages/destination-actions/src/destinations/${cloudMatch[1]}/index.ts` | ||||||
| } else if (browserMatch) { | ||||||
| const destination = browserMatch[1] | ||||||
| if (destinationMap[destination]) { | ||||||
| continue | ||||||
| } | ||||||
| filePath = `packages/browser-destinations/destinations/${browserMatch[1]}/src/index.ts` | ||||||
| } | ||||||
|
|
||||||
| const destination = await loadDestination(filePath).catch((error) => { | ||||||
| this.debug(`Couldn't load ${filePath}: ${error.message}`) | ||||||
| return null | ||||||
| }) | ||||||
|
|
||||||
| if (!destination) { | ||||||
| continue | ||||||
| } | ||||||
|
|
||||||
| const settings = { | ||||||
| ...(destination as BrowserDestinationDefinition).settings, | ||||||
| ...(destination as CloudDestinationDefinition).authentication?.fields | ||||||
| } | ||||||
|
|
||||||
| destinationMap[destination.name] = { settings: [] } | ||||||
| const secretFieldPattern = /(^|[^A-Za-z0-9])(key|token|secret|password|code)($|[^A-Za-z0-9])/i | ||||||
|
||||||
| const secretFieldPattern = /(^|[^A-Za-z0-9])(key|token|secret|password|code)($|[^A-Za-z0-9])/i | |
| const secretFieldPattern = /^(api.?key|access.?token|auth.?token|secret|password|client.?secret)$/i |
Copilot
AI
Nov 26, 2025
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.
This line overwrites the destinationMap[destination.name] object that was just populated with settings on line 82-88, replacing it with a shallow merge that includes the empty actionToSecretFields object. This results in the correct structure but is confusing and inefficient.
Instead, initialize the object once with the correct structure:
destinationMap[destination.name] = {
settings: [],
...actionToSecretFields
}Then populate the settings array in the loop.
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.
Converting the PR body to lowercase (line 36) but then testing against case-insensitive regex patterns (with the
iflag) is redundant and can cause issues. The pattern on line 42 will never match because it checks for\*\*(literal asterisks for markdown bold) after lowercasing the text, but lowercase conversion doesn't affect asterisks.Remove the
.toLowerCase()call since all patterns already use theiflag for case-insensitive matching: