Skip to content

Commit 44cad99

Browse files
committed
fix: CSP headers and deps alerts
Signed-off-by: Rafael Silva (rafaelsi) <rafaelsi@cisco.com>
1 parent 4b49482 commit 44cad99

14 files changed

Lines changed: 177 additions & 128 deletions

File tree

deployments/docker/backend/Dockerfile.bff

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ WORKDIR /build
55
COPY ./backend .
66
RUN cd ./cmd/bff && go build -o ../../identity-bff
77

8-
RUN apk update \
9-
&& apk add ca-certificates wget \
10-
&& update-ca-certificates
8+
RUN apk upgrade --no-cache openssl && \
9+
apk add --no-cache ca-certificates wget && \
10+
update-ca-certificates
1111

1212
FROM golang:1.24.5-alpine
1313

14+
RUN apk upgrade --no-cache openssl
15+
1416
# Create a group and user
1517
RUN addgroup -S web && adduser -u 1999 -S -G web web
1618

deployments/docker/frontend/Dockerfile

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
FROM node:22-alpine AS build
55

66
# Install packages
7-
RUN apk add --update \
7+
RUN apk upgrade --no-cache openssl && \
8+
apk add --update \
89
libpng-dev \
910
build-base \
1011
nodejs \
@@ -24,7 +25,8 @@ RUN yarn build
2425
FROM alpine:3.22
2526

2627
# Install packages
27-
RUN apk add --update \
28+
RUN apk upgrade --no-cache openssl && \
29+
apk add --update \
2830
gettext \
2931
nginx
3032

@@ -38,16 +40,14 @@ COPY --from=build /home/web/dist ./dist
3840
COPY --chown=web:web ./deployments/docker/frontend/nginx/entrypoint.sh .
3941
COPY --chown=web:web ./deployments/docker/frontend/nginx/nginx.conf ./nginx/nginx.env.conf
4042
COPY --chown=web:web ./deployments/docker/frontend/nginx/csp-header.env.conf ./nginx/csp-header.env.conf
43+
COPY --chown=web:web ./deployments/docker/frontend/nginx/config.js.ssi ./dist/config.js
4144
RUN mkdir ./nginx/logs && \
4245
mkdir ./nginx/tmp && \
4346
chown web:web ./nginx/logs && \
4447
chown web:web ./nginx/tmp
4548

46-
# Add nonce to the script with id "vite-plugin-pwa:inline-sw"
47-
RUN sed -i 's/<script id="vite-plugin-pwa:inline-sw">/<script id="vite-plugin-pwa:inline-sw" nonce="c28133ca-0587-459b-9d44-7015cde0ef83">/g' ./dist/index.html
48-
49-
# Add SSI vars
50-
RUN sed -i 's/<head>/<head>\<script nonce="111dd4a0-4c4e-43a9-8eb4-6446ad2e10c7" language="javascript"\>window.apiUrl=`\<!--#echo var="apiUrl"--\>`;window.logLevel=`\<!--#echo var="logLevel"--\>`;window.authType=`\<!--#echo var="authType"--\>`;window.segmentId=`\<!--#echo var="segmentId"--\>`;window.iamProductId=`\<!--#echo var="iamProductId"--\>`;window.iamUi=`\<!--#echo var="iamUi"--\>`;window.iamApi=`\<!--#echo var="iamApi"--\>`;window.iamOidcClientId=`\<!--#echo var="iamOidcClientId"--\>`;window.iamOidcIssuer=`\<!--#echo var="iamOidcIssuer"--\>`;window.oidcUi=`\<!--#echo var="oidcUi"--\>`;window.oidcClientId=`\<!--#echo var="oidcClientId"--\>`;window.oidcIssuer=`\<!--#echo var="oidcIssuer"--\>`;window.docsUrl=`\<!--#echo var="docsUrl"--\>`;window.mazeId=`\<!--#echo var="mazeId"--\>`;window.iamMultiTenant=`\<!--#echo var="iamMultiTenant"--\>`;window.appBaseName=`\<!--#echo var="appBaseName"--\>`;<\/script>/g' ./dist/index.html
49+
# Inject config.js as an external script instead of an inline script block
50+
RUN sed -i 's|<head>|<head><script src="/config.js"></script>|g' ./dist/index.html
5151

5252
# Set non root user
5353
USER web
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2025 AGNTCY Contributors (https://github.com/agntcy)
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
window.apiUrl=`<!--#echo var="apiUrl"-->`;
5+
window.logLevel=`<!--#echo var="logLevel"-->`;
6+
window.authType=`<!--#echo var="authType"-->`;
7+
window.segmentId=`<!--#echo var="segmentId"-->`;
8+
window.iamProductId=`<!--#echo var="iamProductId"-->`;
9+
window.iamUi=`<!--#echo var="iamUi"-->`;
10+
window.iamApi=`<!--#echo var="iamApi"-->`;
11+
window.iamOidcClientId=`<!--#echo var="iamOidcClientId"-->`;
12+
window.iamOidcIssuer=`<!--#echo var="iamOidcIssuer"-->`;
13+
window.oidcUi=`<!--#echo var="oidcUi"-->`;
14+
window.oidcClientId=`<!--#echo var="oidcClientId"-->`;
15+
window.oidcIssuer=`<!--#echo var="oidcIssuer"-->`;
16+
window.docsUrl=`<!--#echo var="docsUrl"-->`;
17+
window.mazeId=`<!--#echo var="mazeId"-->`;
18+
window.iamMultiTenant=`<!--#echo var="iamMultiTenant"-->`;
19+
window.appBaseName=`<!--#echo var="appBaseName"-->`;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
add_header Content-Security-Policy "base-uri 'self'; object-src 'none'; script-src 'self' segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co https://fonts.gstatic.com 'nonce-111dd4a0-4c4e-43a9-8eb4-6446ad2e10c7' 'nonce-e3b5e669-1c54-41b8-877c-1d9a8ce049ba' 'nonce-c28133ca-0587-459b-9d44-7015cde0ef83' 'nonce-a762833a-23e0-400b-b00a-9350d6f7bc69' 'nonce-211adc2e-df7d-4cd2-9694-bf0993603f8f'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; manifest-src 'self' data:; default-src 'self' segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co https://fonts.gstatic.com; connect-src 'self' http://localhost:4000 segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co; img-src 'self' segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co; frame-ancestors 'self';";
1+
add_header Content-Security-Policy "base-uri 'self'; object-src 'none'; script-src 'self' segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co https://fonts.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; manifest-src 'self' data:; default-src 'self' segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co https://fonts.gstatic.com; connect-src 'self' http://localhost:4000 segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co; img-src 'self' data: segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co; frame-src 'self' https://www.youtube.com https://youtube.com; frame-ancestors 'self';";
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
add_header Content-Security-Policy "base-uri 'self'; object-src 'none'; script-src 'self' segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co https://fonts.gstatic.com ${VITE_OIDC_UI} ${VITE_OIDC_ISSUER} 'nonce-111dd4a0-4c4e-43a9-8eb4-6446ad2e10c7' 'nonce-e3b5e669-1c54-41b8-877c-1d9a8ce049ba' 'nonce-c28133ca-0587-459b-9d44-7015cde0ef83' 'nonce-a762833a-23e0-400b-b00a-9350d6f7bc69' 'nonce-211adc2e-df7d-4cd2-9694-bf0993603f8f'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; manifest-src 'self' data:; default-src 'self' segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co https://fonts.gstatic.com ${VITE_OIDC_UI} ${VITE_OIDC_ISSUER} ${VITE_API_URL}; connect-src 'self' http://localhost:4000 segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co ${VITE_OIDC_UI} ${VITE_OIDC_ISSUER} ${VITE_API_URL}; img-src 'self' segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co ${VITE_OIDC_UI} ${VITE_OIDC_ISSUER}; frame-src 'self' https://www.youtube.com https://youtube.com; frame-ancestors 'self';";
1+
add_header Content-Security-Policy "base-uri 'self'; object-src 'none'; script-src 'self' segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co https://fonts.gstatic.com ${VITE_OIDC_UI} ${VITE_OIDC_ISSUER}; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; manifest-src 'self' data:; default-src 'self' segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co https://fonts.gstatic.com ${VITE_OIDC_UI} ${VITE_OIDC_ISSUER} ${VITE_API_URL}; connect-src 'self' http://localhost:4000 segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co ${VITE_OIDC_UI} ${VITE_OIDC_ISSUER} ${VITE_API_URL}; img-src 'self' data: segment.com *.segment.com *.segment.io okta.com *.okta.com *.eticloud.io *.outshift.ai *.outshift.com *.oryapis.com *.duosecurity.com *.auth0.com *.oribi.io *.maze.co ${VITE_OIDC_UI} ${VITE_OIDC_ISSUER}; frame-src 'self' https://www.youtube.com https://youtube.com; frame-ancestors 'self';";

deployments/docker/frontend/nginx/nginx.conf

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,23 @@ http {
6969
application/manifest+json webmanifest;
7070
}
7171

72+
# static files with no sensitive content - cache with revalidation
73+
location ~* \.(ico|png|svg|webp|woff|woff2|ttf|otf|eot)$ {
74+
add_header X-Content-Type-Options "nosniff";
75+
add_header X-Frame-Options "SAMEORIGIN";
76+
add_header Cache-Control "public, max-age=86400, must-revalidate";
77+
add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()";
78+
include /home/web/nginx/csp-header.conf;
79+
try_files $uri =404;
80+
}
81+
82+
# robots.txt and sitemap - no-store to avoid caching crawl instructions
83+
location ~* ^/(robots\.txt|sitemap\.xml)$ {
84+
add_header X-Content-Type-Options "nosniff";
85+
add_header Cache-Control "no-store";
86+
try_files $uri =404;
87+
}
88+
7289
# all assets contain hash in filename, cache forever
7390
location ^~ /assets/ {
7491
charset utf-8;
@@ -91,17 +108,14 @@ http {
91108
try_files $uri =404;
92109
}
93110

94-
location / {
95-
autoindex off;
96-
expires off;
111+
# Runtime config injected as a plain JS file served from 'self' — no inline script needed
112+
location = /config.js {
97113
charset utf-8;
114+
default_type application/javascript;
98115
add_header X-Content-Type-Options "nosniff";
99-
add_header X-Frame-Options "SAMEORIGIN";
100-
add_header Cache-Control "public, max-age=0, s-maxage=0, must-revalidate" always;
101-
add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()";
102-
include /home/web/nginx/csp-header.conf;
103-
try_files $uri $uri/ /index.html;
116+
add_header Cache-Control "no-store";
104117
ssi on;
118+
ssi_types application/javascript;
105119
set $apiUrl "${VITE_API_URL}";
106120
set $logLevel "${VITE_APP_LOG_LEVEL}";
107121
set $authType "${VITE_AUTH_TYPE}";
@@ -118,6 +132,19 @@ http {
118132
set $docsUrl "${VITE_DOCS_URL}";
119133
set $mazeId "${VITE_MAZE_ID}";
120134
set $appBaseName "${VITE_APP_BASE_NAME}";
135+
try_files /config.js =404;
136+
}
137+
138+
location / {
139+
autoindex off;
140+
expires off;
141+
charset utf-8;
142+
add_header X-Content-Type-Options "nosniff";
143+
add_header X-Frame-Options "SAMEORIGIN";
144+
add_header Cache-Control "public, max-age=0, s-maxage=0, must-revalidate" always;
145+
add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()";
146+
include /home/web/nginx/csp-header.conf;
147+
try_files $uri $uri/ /index.html;
121148
}
122149
}
123150
}

frontend/package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"clsx": "^2.1.1",
3636
"idb-keyval": "^6.2.2",
3737
"lucide-react": "^0.522.0",
38-
"qs": "^6.15.1",
38+
"qs": "^6.15.2",
3939
"react": "^18.2.0",
4040
"react-dom": "^18.2.0",
4141
"react-qr-code": "^2.0.18",
@@ -79,8 +79,8 @@
7979
"@typescript-eslint/parser": "^8.38.0",
8080
"@vite-pwa/assets-generator": "^1.0.0",
8181
"@vitejs/plugin-react": "^4.3.4",
82-
"@vitest/coverage-v8": "3.2.4",
83-
"@vitest/ui": "3.2.4",
82+
"@vitest/coverage-v8": "3.2.6",
83+
"@vitest/ui": "3.2.6",
8484
"copy-to-clipboard": "^3.3.3",
8585
"date-fns": "^4.1.0",
8686
"eslint": "^9.21.0",
@@ -116,7 +116,7 @@
116116
"user-agent-data-types": "^0.4.2",
117117
"vite": "^6.4.2",
118118
"vite-plugin-svgr": "^4.3.0",
119-
"vitest": "^3.2.4",
119+
"vitest": "^3.2.6",
120120
"workbox-cacheable-response": "^7.3.0",
121121
"workbox-core": "^7.3.0",
122122
"workbox-expiration": "^7.3.0",
@@ -127,6 +127,8 @@
127127
"zod": "^3.24.2"
128128
},
129129
"resolutions": {
130+
"qs": "^6.15.2",
131+
"vitest": "^3.2.6",
130132
"rollup": "^4.60.3",
131133
"minimatch": "^10.2.5",
132134
"flatted": "^3.4.2",

frontend/public/maze-loader.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Copyright 2026 Copyright AGNTCY Contributors (https://github.com/agntcy)
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
(function (m, a, z, e) {
7+
var s, t;
8+
try {
9+
t = m.sessionStorage.getItem('maze-us');
10+
} catch (err) {}
11+
12+
if (!t) {
13+
t = new Date().getTime();
14+
try {
15+
m.sessionStorage.setItem('maze-us', t);
16+
} catch (err) {}
17+
}
18+
19+
s = a.createElement('script');
20+
s.src = z + '?apiKey=' + e;
21+
s.async = true;
22+
a.getElementsByTagName('head')[0].appendChild(s);
23+
m.mazeUniversalSnippetApiKey = e;
24+
})(window, document, 'https://snippet.maze.co/maze-universal-loader.js', window.__MAZE_API_KEY__);

frontend/src/components/shared/maze/maze.tsx

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,22 @@
66
import config from '@/config';
77
import {Helmet} from 'react-helmet-async';
88

9+
declare global {
10+
interface Window {
11+
__MAZE_API_KEY__: string;
12+
}
13+
}
14+
915
export const Maze = () => {
1016
if (!config.MAZE_ID) {
1117
return null;
1218
}
1319

20+
window.__MAZE_API_KEY__ = config.MAZE_ID;
21+
1422
return (
1523
<Helmet>
16-
<script nonce="211adc2e-df7d-4cd2-9694-bf0993603f8f">
17-
{`
18-
(function (m, a, z, e) {
19-
var s, t;
20-
try {
21-
t = m.sessionStorage.getItem('maze-us');
22-
} catch (err) {}
23-
24-
if (!t) {
25-
t = new Date().getTime();
26-
try {
27-
m.sessionStorage.setItem('maze-us', t);
28-
} catch (err) {}
29-
}
30-
31-
s = a.createElement('script');
32-
s.src = z + '?apiKey=' + e;
33-
s.async = true;
34-
a.getElementsByTagName('head')[0].appendChild(s);
35-
m.mazeUniversalSnippetApiKey = e;
36-
})(window, document, 'https://snippet.maze.co/maze-universal-loader.js', '${config.MAZE_ID}');
37-
`}
38-
</script>
24+
<script src="/maze-loader.js" async></script>
3925
</Helmet>
4026
);
4127
};

frontend/src/lib/sw.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {globalConfig} from '@/config/global';
1818
const ICON_PATH = '/pwa-192x192.png';
1919
const BADGE_PATH = '/pwa-64x64.png';
2020

21-
// NOTE: pay attention to the API endpoint
2221
const API_ENDPOINT = `${config.API_HOST}/v1alpha1/auth/approve_token`;
2322

2423
declare let self: ServiceWorkerGlobalScope;
@@ -169,7 +168,6 @@ self.addEventListener('push', async (event) => {
169168
return;
170169
}
171170

172-
// Add error handling for IndexedDB operations
173171
try {
174172
await notificationUtils.addNotification({
175173
...notificationData,
@@ -257,19 +255,15 @@ self.addEventListener('notificationclick', (event) => {
257255
}
258256
});
259257

260-
// Disable Workbox dev logs in production
261258
self.__WB_DISABLE_DEV_LOGS = true;
262259

263260
void self.skipWaiting();
264261
clientsClaim();
265262

266-
// Skip Maze analytics script - let it handle its own requests
267263
registerRoute(({url}) => url.hostname === 'snippet.maze.co', new NetworkOnly());
268264

269-
// Skip caching for v1alpha1 API endpoints - always go to network
270265
registerRoute(({url}) => url.pathname.includes('v1alpha1'), new NetworkOnly());
271266

272-
// Avoid caching, force always go to the server (but exclude v1alpha1)
273267
registerRoute(
274268
({url}) => !url.pathname.includes('v1alpha1'),
275269
new NetworkFirst({
@@ -278,7 +272,6 @@ registerRoute(
278272
})
279273
);
280274

281-
// Cache CSS, JS, and Web Worker requests with a Stale While Revalidate strategy
282275
registerRoute(
283276
({request}) =>
284277
request.destination === 'style' ||
@@ -291,14 +284,12 @@ registerRoute(
291284
})
292285
);
293286

294-
// Cache images with a Cache First strategy
295287
registerRoute(
296288
({request}) => request.destination === 'image',
297289
new CacheFirst({
298290
cacheName: 'images',
299291
plugins: [
300292
new CacheableResponsePlugin({statuses: [200]}),
301-
// 50 entries max, 30 days max
302293
new ExpirationPlugin({maxEntries: 50, maxAgeSeconds: 60 * 60 * 24 * 30})
303294
]
304295
})

0 commit comments

Comments
 (0)