Skip to content

fix(cc-widgets): remediate P1 security scan findings - #726

Open
mkesavan13 wants to merge 2 commits into
nextfrom
secscan/widgets-p1
Open

fix(cc-widgets): remediate P1 security scan findings#726
mkesavan13 wants to merge 2 commits into
nextfrom
secscan/widgets-p1

Conversation

@mkesavan13

@mkesavan13 mkesavan13 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

COMPLETES #https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-833337

This pull request addresses

SPARK-833337 reports six medium security findings in Contact Center widgets. This PR addresses five findings: WF-05, WF-06, WF-07, WF-08, and WF-10.

CG-01 / AC-1 is intentionally deferred because it requires changing .github/workflows/update-dependencies.yml, and the Jira-to-PR automation token does not have the workflow permission required to push GitHub Actions workflow changes. The deferment is also recorded in the PR conversation.

by making the following changes

  • Adds OAuth state validation to mitigate CSRF in the React sample application (WF-05).
  • Removes localStorage persistence for integrationEnv and uses build-time configuration (WF-06).
  • Replaces the ambiguous DN prefix character class with explicit alternation and adds regression coverage (WF-07).
  • Adds a synchronous useRef in-flight guard to prevent duplicate digital-channel initialization and adds regression coverage (WF-08).
  • Restricts window.store exposure to non-production builds in the React and web-component samples (WF-10).
  • Updates the affected cc-components and cc-digital-channels specifications.

Change Type

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Tooling change
  • Internal code refactor

The following scenarios were tested

  • The testing is done with the amplify link

Automated verification performed:

  • Spec-currency gate passed.
  • Compile gate passed.
  • Unit-test gate passed.
  • cc-components: 819 passed, 4 skipped.
  • cc-digital-channels: 26 passed.
  • Repository commit hooks completed with zero test failures and zero lint errors.

The GAI Coding Policy And Copyright Annotation Best Practices

  • GAI was not used (or, no additional notation is required)
  • Code was generated entirely by GAI
  • GAI was used to create a draft that was subsequently customized or modified
  • Coder created a draft manually that was non-substantively modified by GAI (e.g., refactoring was performed by GAI on manually written code)
  • Tool used for AI assistance (GitHub Copilot / Other - specify)
    • Github Copilot
    • Other - Claude Code through Jira-to-PR automation
  • This PR is related to
    • Feature
    • Defect fix
    • Tech Debt
    • Automation

Checklist before merging

  • I have not skipped any automated checks
  • All existing and new tests passed
  • I have updated the testing document
  • I have tested the functionality with amplify link

Make sure to have followed the contributing guidelines before submitting.

@mkesavan13

Copy link
Copy Markdown
Contributor Author

Scope note for SPARK-833337:

The original remediation also changed .github/workflows/update-dependencies.yml for CG-01 / AC-1. GitHub rejected that branch push because the Jira-to-PR automation token does not have the workflow permission required to modify GitHub Actions workflow files.

For this PR, that workflow-file change was intentionally restored to origin/next, allowing the remaining five fixes (WF-05, WF-06, WF-07, WF-08, and WF-10) to be pushed and reviewed. CG-01 / AC-1 is not fixed by this PR and requires a follow-up change pushed by a credential with workflow-write permission or by an authorized human.

@akulakum
akulakum marked this pull request as ready for review July 24, 2026 07:06
@akulakum
akulakum requested a review from a team as a code owner July 24, 2026 07:06
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Unknown error
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3f88d95b6f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +3 to +5
if (process.env.NODE_ENV !== 'production') {
// @ts-expect-error: expose store for debugging in non-production builds only
window.store = store;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore a production-safe store path for the WC sample

When samples-cc-wc-app is built with NODE_ENV=production, this guard skips assigning window.store, but app.js still starts with const store = window.store and later calls store.setCurrentTheme()/store.init(). That makes the production web-component sample throw instead of initializing any widgets; if the global exposure must be removed, app.js needs another production-safe way to receive the imported store before this assignment is gated.

Useful? React with 👍 / 👎.

Comment on lines +80 to +82
const [integrationEnv, setintegrationEnv] = useState(
process.env.REACT_APP_INTEGRATION_ENV === 'true'
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve integration-env state across OAuth redirects

When a user enables Integration Env in the React sample and starts OAuth, the full-page redirect remounts the app; this new initializer no longer reads the previous localStorage value and relies on process.env.REACT_APP_INTEGRATION_ENV, but the sample webpack config only shims process and does not define that variable. The callback therefore comes back with integrationEnv reset to false, so the returned integration token is initialized against the production U2C/client configuration unless the user manually toggles it again.

Useful? React with 👍 / 👎.

}
// Initialize the digital channels app only once per session
if (!isDigitalChannelsInitialized) {
initInFlightRef.current = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Allow Digital Channels init to retry after a failure

If initializeApp(dataCenter, jwtToken) rejects once due to a transient token/network/datacenter issue, this new in-flight flag stays true because the catch block only logs the error while isDigitalChannelsInitialized remains false. Any later effect run for a refreshed token or task will return at the guard and the digital-channels widget will keep rendering null until the hook is unmounted; reset the ref on failure or in a finally path that distinguishes success from failure.

Useful? React with 👍 / 👎.

@akulakum akulakum added the validated Indicates that the PR is ready for actions label Jul 27, 2026
@akulakum akulakum changed the title SPARK-833337: Remediate five medium security findings fix(cc-widgets): remediate P1 security scan findings Jul 27, 2026
@akulakum akulakum added validated Indicates that the PR is ready for actions and removed validated Indicates that the PR is ready for actions labels Jul 27, 2026
@github-actions github-actions Bot removed the validated Indicates that the PR is ready for actions label Jul 30, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c7dae08aa8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +33 to +34
if (initInFlightRef.current) {
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reset the init guard when the store resets its session

When a host keeps DigitalChannels mounted across an agent logout/login, the first successful initialization leaves initInFlightRef.current true permanently. cleanUpStore() explicitly resets isDigitalChannelsInitialized to false on logout (packages/contact-center/store/src/storeEventsWrapper.ts:1152-1168), but when the next task arrives this guard returns before reinitialization; the local initialized state also remains true, so Engage can render for the new session without calling initializeApp again. Re-key or clear the guard when the store starts a new session.

Useful? React with 👍 / 👎.

@Kesari3008 Kesari3008 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are we addressing below security scan finding in this PR:

CG-01 — GitHub Actions script injection via client_payload.version (.github/workflows/update-dependencies.yml)

I don;t see the change for it.


webex.once('ready', () => {
webex.authorization.initiateLogin();
// Pass our state token so the SDK embeds it in the authorize URL;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Use multi-line comment instead of using ?? for each comment line

webex.authorization.initiateLogin();
// Pass our state token so the SDK embeds it in the authorize URL;
// the redirect handler validates the returned state against sessionStorage.
webex.authorization.initiateLogin({state: {app_state: stateToken}});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is this a concern only for React App and not in meeting widget samples app and web components app

return savedintegrationEnv === 'true';
});
const [integrationEnv, setintegrationEnv] = useState(
process.env.REACT_APP_INTEGRATION_ENV === 'true'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why are we setting it true always

| `CC-COMPONENTS-R-008` | `IncomingTaskComponent` renders the standard `Task` with Answer/Decline when an `incomingTask` is present and renders nothing (hidden) when it is absent; Accept/Decline invoke `accept(task)`/`reject(task)`. | Avoids a stray empty notification when no task; routes accept/decline intent up. | `src/components/task/IncomingTask/incoming-task.tsx`, `src/components/task/IncomingTask/incoming-task.utils.tsx` (`extractIncomingTaskData`) | `tests/components/task/IncomingTask/incoming-task.tsx`, `tests/components/task/IncomingTask/incoming-task.utils.tsx` | None | PRESENT |
| `CC-COMPONENTS-R-009` | `TaskListComponent` renders nothing when the task list is empty, otherwise renders one row per task; campaign preview tasks render `CampaignTask` (instead of `Task`) only when `hasCampaignPreviewEnabled` (default true) and the task is a campaign preview. | List must collapse when empty and switch row UI for campaign previews per the feature flag. | `src/components/task/TaskList/task-list.tsx`, `src/components/task/TaskList/task-list.utils.ts` (`isTaskListEmpty`, `getTasksArray`, `isCampaignPreviewTask`, `getActiveCampaignPreviewId`) | `tests/components/task/TaskList/task-list.tsx`, `tests/components/task/TaskList/task-list.utils.tsx` | None | PRESENT |
| `CC-COMPONENTS-R-010` | `OutdialCallComponent` validates the entered destination, supports dialpad / ANI / address-book tabs, disables the outdial action while a telephony task is active, and calls `startOutdial(destination, origin?)`. | Outbound dialing must validate input and not start a second call over an active one. | `src/components/task/OutdialCall/outdial-call.tsx`, `src/components/task/OutdialCall/constants.ts` | `tests/components/task/OutdialCall/out-dial-call.tsx` | None | PRESENT |
| `CC-COMPONENTS-R-016` | The DN validation regex in `OutdialCallComponent` uses the explicit alternation `(\+\|1)` (not the char-class `[+1]`) for the first-character prefix in branches 1 and 2. Both patterns match the same inputs, but the alternation form makes the intent — a `+` (international) or `1` (North-American) prefix — unambiguous to readers and static-analysis tools. | Security scan WF-07: char-class `[+1]` was flagged for regex-intent ambiguity; explicit alternation removes the finding without changing accepted inputs. +12345678901 must remain valid. | `src/components/task/OutdialCall/outdial-call.tsx` (regex: `^(\+\|1)[0-9]{3,18}$\|^[*#](\+\|1)[0-9*#:]{3,18}$\|^[0-9*#]{3,18}$`) | `tests/components/task/OutdialCall/out-dial-call.tsx` "outdial DN regex — explicit prefix intent (WF-07)" | None | PRESENT |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shouldn't R-016 come at last after R-015 entry

@@ -27,8 +28,14 @@ export const useDigitalChannelsInit = (props: DigitalChannelsInitHookProps) => {
}

const initialize = async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It would be worth checking if there is really a possibility of double initialization here and if due to some race condition that might happen, verify this fix if this is the best way to solve it

@Kesari3008 Kesari3008 added the validated Indicates that the PR is ready for actions label Jul 30, 2026
// Branch 2: *# prefix with +/1 following; Branch 3: digits/special only (no prefix required)
const regExForDnSpecialChars = useMemo(
() => new RegExp('^[+1][0-9]{3,18}$|^[*#][+1][0-9*#:]{3,18}$|^[0-9*#]{3,18}$'),
() => new RegExp('^(\\+|1)[0-9]{3,18}$|^[*#](\\+|1)[0-9*#:]{3,18}$|^[0-9*#]{3,18}$'),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why we adding \ here ? It would be good to check which numbers we wanna match.
If we only want to match numbers starrting with +1 then just replaicng [+1] with +1 should be enough. If we want to allow all E.164 numbers in order to enable international dialing then it should +?.

Please check and update the regex accordingly

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

Labels

validated Indicates that the PR is ready for actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants