Skip to content

Commit 6f55c09

Browse files
feat(core): ARC-78 added CSP
feat(security): implement CSP via react-helmet-async in React frontend
1 parent 576af26 commit 6f55c09

File tree

7 files changed

+136
-10
lines changed

7 files changed

+136
-10
lines changed

.env.development.example

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
VITE_CSP_DEFAULT_SRC=
2+
VITE_CSP_SCRIPT_SRC=
3+
VITE_CSP_STYLE_SRC=
4+
VITE_CSP_IMG_SRC=
5+
VITE_CSP_CONNECT_SRC=
6+
VITE_CSP_OBJECT_SRC=
7+
VITE_CSP_BASE_URI=
8+
VITE_CSP_FORM_ACTION=
9+
VITE_CSP_BLOCK_ALL_MIXED_CONTENT=

.env.production.example

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
VITE_CSP_DEFAULT_SRC=
2+
VITE_CSP_SCRIPT_SRC=
3+
VITE_CSP_STYLE_SRC=
4+
VITE_CSP_IMG_SRC=
5+
VITE_CSP_CONNECT_SRC=
6+
VITE_CSP_OBJECT_SRC=
7+
VITE_CSP_BASE_URI=
8+
VITE_CSP_FORM_ACTION=
9+
VITE_CSP_BLOCK_ALL_MIXED_CONTENT=

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
.env.test.local
2020
.env.production.local
2121
.env.production
22+
.env.development
2223

2324
npm-debug.log*
2425
yarn-debug.log*

package-lock.json

Lines changed: 56 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"react-beautiful-dnd": "^13.1.1",
4444
"react-copy-to-clipboard": "^5.1.0",
4545
"react-dom": "^18.2.0",
46+
"react-helmet-async": "^2.0.5",
4647
"react-idle-timer": "^5.7.2",
4748
"react-redux": "^8.1.0",
4849
"react-router": "^6.4.3",

src/Components/CSP.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import {Helmet} from 'react-helmet-async';
2+
/**
3+
* The `CSPMeta` function generates a Content Security Policy (CSP) meta tag using environment
4+
* variables that start with the prefix `VITE_CSP_`. It converts these variable names into
5+
* standard CSP directive keys by stripping the prefix and formatting them.
6+
* If the value is `'true'`, only the directive key is added (for flags like `block-all-mixed-content`).
7+
* If the value includes `'env:VITE_*'`, it dynamically resolves the referenced environment variable.
8+
* The resulting CSP string is injected into the document head using a Helmet meta tag.
9+
*/
10+
11+
const CSPMeta = () => {
12+
const env = import.meta.env;
13+
const directives: string[] = [];
14+
Object.entries(env).forEach(([key, rawValue]) => {
15+
if (!key.startsWith('VITE_CSP_')) return;
16+
17+
const directiveKey = key
18+
.replace(/^VITE_CSP_/, '')
19+
.replace(/_/g, '-')
20+
.toLowerCase();
21+
22+
const value = rawValue?.trim();
23+
24+
if (!value) return;
25+
if (value === 'true') {
26+
directives.push(`${directiveKey};`);
27+
return;
28+
}
29+
const parts = value
30+
.split(' ')
31+
.map((p: string) => {
32+
if (p.startsWith('env:')) {
33+
const envVarKey = p.slice(4);
34+
return env[envVarKey] ?? null;
35+
}
36+
return p;
37+
})
38+
.filter(Boolean);
39+
40+
if (parts.length > 0) {
41+
directives.push(`${directiveKey} ${parts.join(' ')};`);
42+
}
43+
});
44+
45+
const csp = directives.join(' ').trim();
46+
47+
return (
48+
<Helmet>
49+
<meta httpEquiv="Content-Security-Policy" content={csp} />
50+
</Helmet>
51+
);
52+
};
53+
54+
export default CSPMeta;

src/index.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import React from 'react';
22
import ReactDOM from 'react-dom/client';
3+
import {HelmetProvider} from 'react-helmet-async';
34
import AppWrapper from './AppWrapper';
4-
5+
import CSPMeta from './Components/CSP';
56
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
67

78
root.render(
89
<React.StrictMode>
9-
<AppWrapper />
10+
<HelmetProvider>
11+
<CSPMeta />
12+
<AppWrapper />
13+
</HelmetProvider>
1014
</React.StrictMode>,
1115
);

0 commit comments

Comments
 (0)