Skip to content

Conversation

@AnkitSegment
Copy link
Contributor

@AnkitSegment AnkitSegment commented Nov 17, 2025

Jira ticket: https://twilio-engineering.atlassian.net/browse/STRATCONN-6226

In this PR, I’ve added a GitHub Action check that scans for common security parameters, Failed the Github Action and raises a warning if any of those parameters are not of type password.

Testing

Include any additional information about the testing you have completed to
ensure your changes behave as expected. For a speedy review, please check
any of the tasks you completed below during your testing.

  • Added unit tests for new functionality
  • Tested end-to-end using the local server
  • [If destination is already live] Tested for backward compatibility of destination. Note: New required fields are a breaking change.
  • [Segmenters] Tested in the staging environment
  • [Segmenters] [If applicable for this change] Tested for regression with Hadron.

Security Review

Please ensure sensitive data is properly protected in your integration.

  • Reviewed all field definitions for sensitive data (API keys, tokens, passwords, client secrets) and confirmed they use type: 'password'

@github-actions
Copy link
Contributor

github-actions bot commented Nov 18, 2025

⚠️ Secret Fields Detected

The following destinations require secret settings values that require review:

  • Destination: Actable Predictive
    • Settings: client_secret
  • Destination: Adjust (Actions)
    • Settings: default_app_token, default_event_token
  • Destination: Adobe Target Cloud Mode
    • Settings: client_code
  • Destination: Airship (Actions)
    • Settings: app_key
  • Destination: Clay
    • Settings: connection_key, secret_key
  • Destination: Criteo
    • Settings: api_key
  • Destination: m3ter
    • Settings: access_key_id
  • Destination: Movable Ink
    • Settings: password
  • Destination: Rehook
    • Settings: api_key, api_secret
  • Destination: Segment
    • Settings: source_write_key
  • Destination: Spiffy
    • Settings: api_key
  • Destination: Twilio
    • Settings: token
  • Destination: Usermaven (Actions)
    • Settings: api_key, server_token

This PR cannot be merged until these are removed or converted to secure secrets.

@AnkitSegment AnkitSegment marked this pull request as ready for review November 18, 2025 09:24
@AnkitSegment AnkitSegment requested a review from a team as a code owner November 18, 2025 09:24
@AnkitSegment AnkitSegment changed the base branch from staging to main November 18, 2025 09:37
@codecov
Copy link

codecov bot commented Nov 18, 2025

Codecov Report

❌ Patch coverage is 20.45455% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 31.86%. Comparing base (ef1e87b) to head (fba3698).
⚠️ Report is 12 commits behind head on main.

Files with missing lines Patch % Lines
...ackages/cli/src/commands/validate-secret-fields.ts 20.45% 35 Missing ⚠️

❌ Your patch check has failed because the patch coverage (20.45%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.
❌ Your project check has failed because the head coverage (31.86%) is below the target coverage (70.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3419      +/-   ##
==========================================
- Coverage   32.57%   31.86%   -0.72%     
==========================================
  Files          14       15       +1     
  Lines         703      747      +44     
  Branches      119      126       +7     
==========================================
+ Hits          229      238       +9     
- Misses        474      509      +35     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a GitHub Action workflow and CLI command to automatically detect fields with secret-like names (containing "key", "token", "secret", "password", or "code") that aren't using type: 'password' for proper security protection. The goal is to prevent accidentally exposing sensitive credentials in destination configurations.

Key changes:

  • New CLI command validate-secret-fields to scan destination settings and authentication fields for secret-like patterns
  • GitHub Actions workflow that runs on PRs, scans for secret fields, and posts warnings if any are found
  • Ability to skip the check by marking the security review checkbox in the PR description

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 10 comments.

File Description
packages/cli/src/commands/validate-secret-fields.ts New CLI command to identify fields with secret-like names that aren't marked as password type. Currently only checks destination-level settings/authentication, not action fields.
.github/workflows/required-secret-check.yml GitHub Actions workflow that runs the CLI command and comments on PRs when potential secret fields are detected. Can be bypassed via PR description checkbox.

export default class ValidateSecretFields extends Command {
public static enableJsonFlag = true

static description = `Returns a list of secret fields for each action in the destination.`
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description states the command "Returns a list of secret fields for each action in the destination", but the current implementation only checks destination-level settings and authentication fields, not individual actions. The description should be updated to accurately reflect what the command does, or the implementation should be fixed to match the description.

Copilot uses AI. Check for mistakes.
}

destinationMap[destination.name] = { settings: [] }
const secretFieldPattern = /(^|[^A-Za-z0-9])(key|token|secret|password|code)($|[^A-Za-z0-9])/i
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern /(^|[^A-Za-z0-9])(key|token|secret|password|code)($|[^A-Za-z0-9])/i will incorrectly flag common non-secret fields that contain "code" as a substring, such as:

  • postalCode (very common for address data)
  • countryCode
  • statusCode
  • currencyCode

The pattern should be more specific or exclude known false positives. Consider using word boundaries or a more selective pattern like /(api.?key|access.?token|auth.?token|secret|password|client.?secret)/i that targets actual secret field naming patterns.

Suggested change
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 uses AI. Check for mistakes.
Comment on lines +90 to +93
destinationMap[destination.name] = {
...destinationMap[destination.name],
...actionToSecretFields
}
Copy link

Copilot AI Nov 26, 2025

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.

Copilot uses AI. Check for mistakes.
if: steps.pr_desc.outputs.skip != 'true'
uses: actions/github-script@v7
with:
findings: ${{ steps.list.outputs.findings }}
Copy link

Copilot AI Nov 26, 2025

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.

Suggested change
findings: ${{ steps.list.outputs.findings }}

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +70
for (const [destination, obj] of Object.entries(parsed)) {
if (Array.isArray(obj.settings) && obj.settings.length > 0) {
findings.push({
destination,
settings: obj.settings
});
}
Copy link

Copilot AI Nov 26, 2025

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.

Suggested change
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 uses AI. Check for mistakes.
with:
script: |
const pr = context.payload.pull_request;
const body = (pr?.body || "").toLowerCase();
Copy link

Copilot AI Nov 26, 2025

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 i flag) 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 the i flag for case-insensitive matching:

const body = pr?.body || "";
Suggested change
const body = (pr?.body || "").toLowerCase();
const body = pr?.body || "";

Copilot uses AI. Check for mistakes.
})

const destinationMap: Record<string, Record<string, string[]>> = {}
const actionToSecretFields: Record<string, string[]> = {}
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable actionToSecretFields is declared but never populated with any values. According to line 10, the command should "Returns a list of secret fields for each action in the destination", but the code only checks settings/authentication fields and never iterates through destination.actions to check action fields. This means action-level fields with secret-like names will not be detected.

To fix this, add logic similar to list-required-fields.ts (lines 89-98) to iterate through actions and check their fields for secret patterns.

Copilot uses AI. Check for mistakes.
const patterns = [
/-\s*\[x\]\s*required secret checkout/i,
/required secret checkout\s*:\s*true/i,
// The new checkbox you provided (checked)
Copy link

Copilot AI Nov 26, 2025

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
Suggested change
// The new checkbox you provided (checked)
// Pattern to match the security review checkbox from the PR template (checked)

Copilot uses AI. Check for mistakes.

${lines}

This PR cannot be merged until these are removed or converted to secure secrets.
Copy link

Copilot AI Nov 26, 2025

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.
Suggested change
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.

Copilot uses AI. Check for mistakes.
const body = (pr?.body || "").toLowerCase();

const patterns = [
/-\s*\[x\]\s*required secret checkout/i,
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The pattern /-\s*\[x\]\s*required secret checkout/i on line 39 doesn't match anything in the documented PR template patterns. This appears to be a legacy pattern that should be removed if it's no longer in use, or documented if it's still valid.

Suggested change
/-\s*\[x\]\s*required secret checkout/i,

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@itsarijitray itsarijitray left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!
Make sure to resolve the field issues before merging this change.

Some of the copilot comments are also good. Try to incorporate them if possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants