From c42bbbad4fecd3fb12acc010294560cef7f5426a Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Mon, 12 Jan 2026 10:56:09 -0600 Subject: [PATCH 01/12] Add placeholder for new CSP header for search api --- .../stacks/frontend_deployment_stack/distribution.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/distribution.py b/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/distribution.py index ce1bff8e0..4165793cd 100644 --- a/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/distribution.py +++ b/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/distribution.py @@ -57,6 +57,7 @@ def generate_csp_lambda_code( replacements = { '##WEB_FRONTEND##': persistent_stack_values.ui_domain_name, '##DATA_API##': persistent_stack_values.api_domain_name, + '##SEARCH_API##': 'TODO', '##S3_UPLOAD_URL_STATE##': f'{persistent_stack_values.bulk_uploads_bucket_name}{S3_URL_SUFFIX}', '##S3_UPLOAD_URL_PROVIDER##': f'{persistent_stack_values.provider_users_bucket_name}{S3_URL_SUFFIX}', '##COGNITO_STAFF##': persistent_stack_values.staff_cognito_domain, From f59f1eef4aab718f8f233395bc1898d785043e2e Mon Sep 17 00:00:00 2001 From: John Sandoval Date: Mon, 12 Jan 2026 10:07:38 -0700 Subject: [PATCH 02/12] Update CSP lambda function --- .../lambdas/nodejs/cloudfront-csp/index.js | 3 +++ .../lambdas/nodejs/cloudfront-csp/test/index.test.js | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/index.js b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/index.js index 023e59629..9068048ba 100644 --- a/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/index.js +++ b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/index.js @@ -19,6 +19,7 @@ const environmentValues = { webFrontend: `##WEB_FRONTEND##`, dataApi: `##DATA_API##`, + searchApi: `##SEARCH_API##`, s3UploadUrlState: `##S3_UPLOAD_URL_STATE##`, s3UploadUrlProvider: `##S3_UPLOAD_URL_PROVIDER##`, cognitoStaff: `##COGNITO_STAFF##`, @@ -62,6 +63,7 @@ const getEnvironmentUrls = () => { const environmentUrls = {}; environmentUrls.dataApi = getFullyQualified(environmentValues.dataApi); + environmentUrls.searchApi = getFullyQualified(environmentValues.searchApi); environmentUrls.s3UploadUrlState = getFullyQualified(environmentValues.s3UploadUrlState); environmentUrls.s3UploadUrlProvider = getFullyQualified(environmentValues.s3UploadUrlProvider); environmentUrls.cognitoStaff = getFullyQualified(environmentValues.cognitoStaff); @@ -237,6 +239,7 @@ const setCspHeader = (headers = {}) => { buildSrcString('connect-src', [ 'self', domains.dataApi, + domains.searchApi, domains.s3UploadUrlState, domains.s3UploadUrlProvider, domains.cognitoStaff, diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/test/index.test.js b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/test/index.test.js index 8aefe2f8d..3d7e73b8e 100644 --- a/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/test/index.test.js +++ b/backend/compact-connect-ui-app/lambdas/nodejs/cloudfront-csp/test/index.test.js @@ -20,6 +20,7 @@ const { const environmentValues = { webFrontend: 'app.compactconnect.org', dataApi: 'api.compactconnect.org', + searchApi: 'search.compactconnect.org', s3UploadUrlState: 'prod-persistentstack-bulkuploadsbucketda4bdcd0-zq5o0q8uqq5i.s3.amazonaws.com', s3UploadUrlProvider: 'prod-persistentstack-providerusersbucket5c7b202b-ffpgh4fyozwk.s3.amazonaws.com', cognitoStaff: 'staff-auth.compactconnect.org', @@ -46,6 +47,7 @@ const prepareLambdaForTest = () => { const replacements = { '##WEB_FRONTEND##': environmentValues.webFrontend, '##DATA_API##': environmentValues.dataApi, + '##SEARCH_API##': environmentValues.searchApi, '##S3_UPLOAD_URL_STATE##': environmentValues.s3UploadUrlState, '##S3_UPLOAD_URL_PROVIDER##': environmentValues.s3UploadUrlProvider, '##COGNITO_STAFF##': environmentValues.cognitoStaff, @@ -69,6 +71,7 @@ const prepareLambdaForTest = () => { const buildCspHeaders = (environment) => { const dataApiUrl = (environment?.dataApi) ? `https://${environment.dataApi}` : ''; + const searchApiUrl = (environment?.searchApi) ? `https://${environment.searchApi}` : ''; const s3UploadUrlState = (environment?.s3UploadUrlState) ? `https://${environment.s3UploadUrlState}` : ''; const s3UploadUrlProvider = (environment?.s3UploadUrlProvider) ? `https://${environment.s3UploadUrlProvider}` : ''; const cognitoStaffUrl = (environment?.cognitoStaff) ? `https://${environment.cognitoStaff}` : ''; @@ -150,6 +153,7 @@ const buildCspHeaders = (environment) => { const cspConnectSrc = [ '\'self\'', dataApiUrl, + searchApiUrl, s3UploadUrlState, s3UploadUrlProvider, cognitoStaffUrl, From 27568e23f86e7f82a5ae2af48381f12f0afa3b82 Mon Sep 17 00:00:00 2001 From: John Sandoval Date: Mon, 12 Jan 2026 10:11:18 -0700 Subject: [PATCH 03/12] Update CSP lambda npm audit to match project --- backend/compact-connect-ui-app/lambdas/nodejs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/compact-connect-ui-app/lambdas/nodejs/package.json b/backend/compact-connect-ui-app/lambdas/nodejs/package.json index d5b458edd..8ffb19d76 100644 --- a/backend/compact-connect-ui-app/lambdas/nodejs/package.json +++ b/backend/compact-connect-ui-app/lambdas/nodejs/package.json @@ -9,7 +9,7 @@ "test:csp": "mocha cloudfront-csp/test", "lint": "eslint '**/*.js' --no-error-on-unmatched-pattern", "test": "mocha cloudfront-csp/test", - "audit:dependencies": "yarn audit --groups dependencies --level moderate" + "audit:dependencies": "/bin/bash -c 'yarn audit --groups dependencies --level moderate; [[ $? -ge 4 ]] && exit 1 || exit 0'" }, "author": "Inspiring Apps", "license": "UNLICENSED", From 407a5f7ed44d3296a797b725dc2ac3f346b91fc5 Mon Sep 17 00:00:00 2001 From: Landon Shumway Date: Mon, 12 Jan 2026 11:18:38 -0600 Subject: [PATCH 04/12] Add search domain to ssm parameter --- .../common_constructs/frontend_app_config_utility.py | 10 +++++++++- .../stacks/frontend_deployment_stack/deployment.py | 2 +- .../stacks/frontend_deployment_stack/distribution.py | 2 +- .../stacks/persistent_stack/__init__.py | 6 +++++- .../tests/app/test_frontend_app_config.py | 9 ++++++++- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/backend/common-cdk/common_constructs/frontend_app_config_utility.py b/backend/common-cdk/common_constructs/frontend_app_config_utility.py index 1323a994b..fcdd04883 100644 --- a/backend/common-cdk/common_constructs/frontend_app_config_utility.py +++ b/backend/common-cdk/common_constructs/frontend_app_config_utility.py @@ -41,15 +41,17 @@ def set_staff_cognito_values(self, domain_name: str, client_id: str) -> None: self._config['staff_cognito_domain'] = domain_name self._config['staff_cognito_client_id'] = client_id - def set_domain_names(self, ui_domain_name: str, api_domain_name: str) -> None: + def set_domain_names(self, ui_domain_name: str, api_domain_name: str, search_api_domain_name: str) -> None: """ Set UI and API domain names. :param ui_domain_name: The domain name for the UI application :param api_domain_name: The domain name for the API + :param search_api_domain_name: The domain name for the search API """ self._config['ui_domain_name'] = ui_domain_name self._config['api_domain_name'] = api_domain_name + self._config['search_api_domain_name'] = search_api_domain_name def set_license_bulk_uploads_bucket_name(self, bucket_name: str) -> None: """ @@ -200,6 +202,7 @@ def _create_dummy_values() -> 'PersistentStackFrontendAppConfigValues': 'provider_cognito_client_id': 'test-provider-client-id', 'ui_domain_name': 'test-ui.example.com', 'api_domain_name': 'test-api.example.com', + 'search_api_domain_name': 'test-search-api.example.com', 'bulk_uploads_bucket_name': 'test-bulk-uploads-bucket-name', 'provider_users_bucket_name': 'test-provider-users-bucket-name', # if we are working with dummy values, no need to run an actual bundle @@ -227,6 +230,11 @@ def api_domain_name(self) -> str: """Get the domain name for the API.""" return self._config['api_domain_name'] + @property + def search_api_domain_name(self) -> str: + """Get the domain name for the search API.""" + return self._config['search_api_domain_name'] + @property def bulk_uploads_bucket_name(self) -> str: """Get the name of the bulk uploads bucket.""" diff --git a/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/deployment.py b/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/deployment.py index 7af4d997f..dc6f714f1 100644 --- a/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/deployment.py +++ b/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/deployment.py @@ -62,7 +62,7 @@ def __init__( 'VUE_APP_ROBOTS_META': robots_meta, 'VUE_APP_API_STATE_ROOT': f'{HTTPS_PREFIX}{persistent_stack_app_config_values.api_domain_name}', 'VUE_APP_API_LICENSE_ROOT': f'{HTTPS_PREFIX}{persistent_stack_app_config_values.api_domain_name}', - 'VUE_APP_API_SEARCH_ROOT': f'{HTTPS_PREFIX}search.{persistent_stack_app_config_values.api_domain_name}', + 'VUE_APP_API_SEARCH_ROOT': f'{HTTPS_PREFIX}{persistent_stack_app_config_values.search_api_domain_name}', 'VUE_APP_API_USER_ROOT': f'{HTTPS_PREFIX}{persistent_stack_app_config_values.api_domain_name}', 'VUE_APP_COGNITO_REGION': 'us-east-1', 'VUE_APP_COGNITO_AUTH_DOMAIN_STAFF': f'{HTTPS_PREFIX}{persistent_stack_app_config_values.staff_cognito_domain}', diff --git a/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/distribution.py b/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/distribution.py index 4165793cd..d9a3e781e 100644 --- a/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/distribution.py +++ b/backend/compact-connect-ui-app/stacks/frontend_deployment_stack/distribution.py @@ -57,7 +57,7 @@ def generate_csp_lambda_code( replacements = { '##WEB_FRONTEND##': persistent_stack_values.ui_domain_name, '##DATA_API##': persistent_stack_values.api_domain_name, - '##SEARCH_API##': 'TODO', + '##SEARCH_API##': persistent_stack_values.search_api_domain_name, '##S3_UPLOAD_URL_STATE##': f'{persistent_stack_values.bulk_uploads_bucket_name}{S3_URL_SUFFIX}', '##S3_UPLOAD_URL_PROVIDER##': f'{persistent_stack_values.provider_users_bucket_name}{S3_URL_SUFFIX}', '##COGNITO_STAFF##': persistent_stack_values.staff_cognito_domain, diff --git a/backend/compact-connect/stacks/persistent_stack/__init__.py b/backend/compact-connect/stacks/persistent_stack/__init__.py index 4909c69bd..43992a3fa 100644 --- a/backend/compact-connect/stacks/persistent_stack/__init__.py +++ b/backend/compact-connect/stacks/persistent_stack/__init__.py @@ -491,7 +491,11 @@ def _create_frontend_app_config_parameter(self): ) # Add UI and API domain names - frontend_app_config.set_domain_names(ui_domain_name=self.ui_domain_name, api_domain_name=self.api_domain_name) + frontend_app_config.set_domain_names( + ui_domain_name=self.ui_domain_name, + api_domain_name=self.api_domain_name, + search_api_domain_name=self.search_api_domain_name, + ) # Add bucket names needed for CSP Lambda frontend_app_config.set_license_bulk_uploads_bucket_name(bucket_name=self.bulk_uploads_bucket.bucket_name) diff --git a/backend/compact-connect/tests/app/test_frontend_app_config.py b/backend/compact-connect/tests/app/test_frontend_app_config.py index f25d6b4a9..7f94c4ab3 100644 --- a/backend/compact-connect/tests/app/test_frontend_app_config.py +++ b/backend/compact-connect/tests/app/test_frontend_app_config.py @@ -19,7 +19,11 @@ def test_setters_set_expected_fields(self): # Set values util.set_staff_cognito_values(domain_name='staff-domain.example.com', client_id='staff-client-123') - util.set_domain_names(ui_domain_name='ui.example.com', api_domain_name='api.example.com') + util.set_domain_names( + ui_domain_name='ui.example.com', + api_domain_name='api.example.com', + search_api_domain_name='search.example.com', + ) # Get JSON representation config_json = util.get_config_json() @@ -32,6 +36,7 @@ def test_setters_set_expected_fields(self): 'staff_cognito_client_id': 'staff-client-123', 'ui_domain_name': 'ui.example.com', 'api_domain_name': 'api.example.com', + 'search_api_domain_name': 'search.example.com', }, config_dict, ) @@ -46,6 +51,7 @@ def test_getters_return_expected_values(self): 'staff_cognito_client_id': 'staff-client-123', 'ui_domain_name': 'ui.example.com', 'api_domain_name': 'api.example.com', + 'search_api_domain_name': 'search.example.com', } ) ) @@ -55,6 +61,7 @@ def test_getters_return_expected_values(self): self.assertEqual(util.staff_cognito_client_id, 'staff-client-123') self.assertEqual(util.ui_domain_name, 'ui.example.com') self.assertEqual(util.api_domain_name, 'api.example.com') + self.assertEqual(util.search_api_domain_name, 'search.example.com') class TestProviderUserStackFrontendAppConfigUtility(TestCase): From 85e00f867d62e475bb00ae6d5c65c57e7bc32bb2 Mon Sep 17 00:00:00 2001 From: John Sandoval Date: Mon, 12 Jan 2026 10:24:49 -0700 Subject: [PATCH 05/12] Update CSP lambda test hashes --- .../BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json | 2 +- ...FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json | 2 +- .../ProdFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json | 2 +- ...FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json | 2 +- .../SandboxUI-FrontendDeploymentStack-UI_DISTRIBUTION.json | 2 +- ...FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json | 2 +- .../TestFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json | 2 +- ...FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json index dfa9bf6bc..1f9fa45ac 100644 --- a/backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json +++ b/backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION.json @@ -39,7 +39,7 @@ { "EventType": "viewer-response", "LambdaFunctionARN": { - "Ref": "CSPFunctionCurrentVersionB61A66110cf6f5eb1eee31df1d009e18023c5c92" + "Ref": "CSPFunctionCurrentVersionB61A661142a45b1ad5f2f8922ed787b34cd1917c" } } ], diff --git a/backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json b/backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json index b5ec5e8a0..4d74acbc5 100644 --- a/backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json +++ b/backend/compact-connect-ui-app/tests/resources/snapshots/BetaFrontend-FrontendDeploymentStack-UI_DISTRIBUTION_LAMBDA_FUNCTION.json @@ -2,7 +2,7 @@ "Type": "AWS::Lambda::Function", "Properties": { "Code": { - "ZipFile": "//\n// index.js\n// CompactConnect\n//\n// Created by InspiringApps on 7/22/2024.\n//\n\n// ============================================================================\n// CONFIGURATION =\n// ============================================================================\n/**\n * Configuration of supported domains.\n *\n * These values are injected into the lambda function at build time. See the\n * `generate_csp_lambda_code` function in\n * backend/compact-connect/stacks/frontend_deployment_stack/distribution.py\n * @type {object}\n */\nconst environmentValues = {\n webFrontend: `test-ui.example.com`,\n dataApi: `test-api.example.com`,\n s3UploadUrlState: `test-bulk-uploads-bucket-name.s3.amazonaws.com`,\n s3UploadUrlProvider: `test-provider-users-bucket-name.s3.amazonaws.com`,\n cognitoStaff: `test-staff-domain`,\n cognitoProvider: `test-provider-domain`,\n};\n\n// ============================================================================\n// HELPERS =\n// ============================================================================\n/**\n * Get the request domain from the lambda event record.\n * @param {object} eventRecord The cloudfront record from the lambda event.\n * @return {string} The bare domain of the request domain.\n */\n\n/**\n * Get a fully qualified domain URI with the protocol scheme.\n * @param {string} domain The bare domain string.\n * @return {string} The fully-qualified domain string.\n */\nconst getFullyQualified = (domain) => {\n const protocol = 'https://';\n let fullyQualified = '';\n\n if (domain && typeof domain === 'string' && !domain.startsWith(protocol)) {\n fullyQualified = `${protocol}${domain}`;\n }\n\n return fullyQualified;\n};\n\n/**\n * Helper to get the fully-qualified domains for connected services.\n * @return {object} A map of fully-qualified domains for the environment.\n * @return {string} dataApi The data API fully-qualified domain.\n * @return {string} s3UploadUrlState The S3 fully-qualified domain for uploading state files.\n * @return {string} s3UploadUrlProvider The S3 fully-qualified domain for uploading provider files.\n * @return {string} cognitoStaff The Cognito fully-qualified domain for authenticating staff users.\n */\nconst getEnvironmentUrls = () => {\n const environmentUrls = {};\n\n environmentUrls.dataApi = getFullyQualified(environmentValues.dataApi);\n environmentUrls.s3UploadUrlState = getFullyQualified(environmentValues.s3UploadUrlState);\n environmentUrls.s3UploadUrlProvider = getFullyQualified(environmentValues.s3UploadUrlProvider);\n environmentUrls.cognitoStaff = getFullyQualified(environmentValues.cognitoStaff);\n environmentUrls.cognitoProvider = getFullyQualified(environmentValues.cognitoProvider);\n\n return environmentUrls;\n};\n\n/**\n * Helper to escape CSP src keywords.\n * @param {string} keyword The standard keyword value.\n * @return {string} The escaped keyword value.\n */\nconst srcKeywordEscape = (keyword) => {\n let escaped = '';\n\n if (keyword && typeof keyword === 'string') {\n escaped = `'${keyword}'`.toLowerCase();\n }\n\n return escaped;\n};\n\n/**\n * Helper to automatically escape and prep CSP src keyword values (by reference).\n * @param {Array} srcList The CSP src list.\n * @param {string} [listName=''] Optional src list name for logging.\n */\nconst srcKeywordsEscape = (srcList, listName = '') => {\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#keyword_values\n const srcKeywordsConfig = [\n { value: 'self', isAllowed: true },\n { value: 'none', isAllowed: true },\n { value: 'strict-dynamic', isAllowed: true },\n { value: 'report-sample', isAllowed: true },\n { value: 'inline-speculation-rules', isAllowed: true },\n { value: 'unsafe-inline', isAllowed: false },\n { value: 'unsafe-eval', isAllowed: false },\n { value: 'unsafe-hashes', isAllowed: false },\n { value: 'wasm-unsafe-eval', isAllowed: false },\n ];\n const srcKeywords = srcKeywordsConfig.map((config) => config.value.toLowerCase());\n\n if (Array.isArray(srcList)) {\n srcList.forEach((srcItem, idx) => {\n const isString = typeof srcItem === 'string';\n\n if (!isString) {\n srcList[idx] = '';\n } else {\n const srcItemLowerCase = srcItem.toLowerCase();\n\n if (srcKeywords.includes(srcItemLowerCase)) {\n const keywordConfig = srcKeywordsConfig.find(\n (config) => srcItemLowerCase === config.value.toLowerCase()\n );\n\n if (keywordConfig) {\n if (!keywordConfig.isAllowed) {\n console.warn(`${listName} ${srcItem} keyword is not allowed in srcKeywordsConfig policy. We likely should not be using this keyword for security reasons.`.trim());\n srcList[idx] = '';\n } else {\n srcList[idx] = srcKeywordEscape(srcItem);\n }\n }\n }\n }\n });\n }\n};\n\n/**\n * Helper to build a CSP src group list string from input params.\n * @param {string} name src name for the CSP group.\n * @param {Array} list The static src list for the CSP group.\n * @return {string} The prepped src list string;\n */\nconst buildSrcString = (name = '', list = []) => {\n let srcString = '';\n\n if (Array.isArray(list)) {\n srcKeywordsEscape(list, name);\n srcString = `${name} ${list.join(' ')};`;\n }\n\n return srcString;\n};\n\n// ============================================================================\n// RESPONSE HEADERS =\n// ============================================================================\n/**\n * Set the CSP header on the response (by reference).\n * @param {object} [headers={}] The event response headers (updated by reference).\n */\nconst setCspHeader = (headers = {}) => {\n const domains = getEnvironmentUrls();\n const cognitoIdpUrl = 'https://cognito-idp.us-east-1.amazonaws.com';\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy\n headers['content-security-policy'] = [{\n key: 'Content-Security-Policy',\n value: `${[\n `default-src 'none';`,\n buildSrcString('manifest-src', [\n 'self',\n ]),\n buildSrcString('script-src', [\n 'self',\n 'https://www.google.com/recaptcha/',\n 'https://www.gstatic.com/recaptcha/',\n 'https://jstest.authorize.net/',\n 'https://js.authorize.net/',\n ]),\n buildSrcString('script-src-elem', [\n 'self',\n 'https://www.google.com/recaptcha/',\n 'https://www.gstatic.com/recaptcha/',\n 'https://jstest.authorize.net/',\n 'https://js.authorize.net/',\n ]),\n buildSrcString('script-src-attr', [\n 'self',\n ]),\n buildSrcString('worker-src', [\n 'self',\n ]),\n buildSrcString('style-src', [\n 'self',\n 'https://fonts.googleapis.com',\n 'https://www.gstatic.com/recaptcha/',\n ]),\n buildSrcString('style-src-elem', [\n 'self',\n 'https://fonts.googleapis.com',\n 'https://jstest.authorize.net/',\n 'https://js.authorize.net/',\n `'sha256-YwWQHXh4Vw0oD2Oo8pV9huEF2sE9mD8i5nZUuHzEg9A='`, //