Compare commits

..

3 Commits

Author SHA1 Message Date
dependabot[bot] 9170930e8e Bump lodash-es from 4.17.21 to 4.17.23 in /frontend
Bumps [lodash-es](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

---
updated-dependencies:
- dependency-name: lodash-es
  dependency-version: 4.17.23
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-22 01:37:02 +00:00
Zimeng Xiong 81918b00cd chore: release v0.3.1 2026-01-20 13:41:22 -08:00
Zimeng Xiong 3b384dc5fb CSRF token validation failing behind nginx proxy (#38)
Express was not configured to trust proxy headers, causing req.ip to return nginx's internal container IP instead of the actual client IP. In Docker environments, nginx can appear with different internal IPs between requests, causing the CSRF clientId to change and token validation to fail.
2026-01-20 13:39:33 -08:00
7 changed files with 36 additions and 58 deletions
+1 -1
View File
@@ -1 +1 @@
0.3.0
0.3.1
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "backend",
"version": "0.3.0",
"version": "0.3.1",
"description": "",
"main": "index.js",
"scripts": {
+6
View File
@@ -129,6 +129,12 @@ const initializeUploadDir = async () => {
};
const app = express();
// Trust proxy headers (X-Forwarded-For, X-Real-IP) from nginx
// Required for correct client IP detection when running behind a reverse proxy
// This fixes CSRF token validation failures in Docker/K8s environments
app.set("trust proxy", 1);
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
+12 -26
View File
@@ -30,9 +30,7 @@ let activeConfig: SecurityConfig = { ...defaultConfig };
* Configure security settings
* @param config Partial configuration to merge with defaults
*/
export const configureSecuritySettings = (
config: Partial<SecurityConfig>
): void => {
export const configureSecuritySettings = (config: Partial<SecurityConfig>): void => {
activeConfig = { ...activeConfig, ...config };
};
@@ -320,13 +318,10 @@ export const appStateSchema = z
.optional()
.nullable(),
currentItemRoundness: z
.union([
z.enum(["sharp", "round"]),
z.object({
type: z.enum(["round", "sharp"]),
value: z.number().finite().min(0).max(1),
}),
])
.object({
type: z.enum(["round", "sharp"]),
value: z.number().finite().min(0).max(1),
})
.optional()
.nullable(),
currentItemFontSize: z
@@ -432,19 +427,10 @@ export const sanitizeDrawingData = (data: {
];
// Dangerous URL protocols to block entirely
const dangerousProtocols = [
/^javascript:/i,
/^vbscript:/i,
/^data:text\/html/i,
];
const dangerousProtocols = [/^javascript:/i, /^vbscript:/i, /^data:text\/html/i];
// Suspicious patterns for security validation within data URLs
const suspiciousPatterns = [
/<script/i,
/javascript:/i,
/on\w+\s*=/i,
/<iframe/i,
];
const suspiciousPatterns = [/<script/i, /javascript:/i, /on\w+\s*=/i, /<iframe/i];
// Maximum size for dataURL (configurable, default 10MB to prevent DoS)
const MAX_DATAURL_SIZE = activeConfig.maxDataUrlSize;
@@ -462,8 +448,8 @@ export const sanitizeDrawingData = (data: {
const normalizedValue = value.toLowerCase();
// First, check for dangerous protocols - block these entirely
const hasDangerousProtocol = dangerousProtocols.some(
(pattern) => pattern.test(value)
const hasDangerousProtocol = dangerousProtocols.some((pattern) =>
pattern.test(value)
);
if (hasDangerousProtocol) {
@@ -479,8 +465,8 @@ export const sanitizeDrawingData = (data: {
if (isSafeImageType) {
// Check for suspicious content and size limits
const hasSuspiciousContent = suspiciousPatterns.some(
(pattern) => pattern.test(value)
const hasSuspiciousContent = suspiciousPatterns.some((pattern) =>
pattern.test(value)
);
const isTooLarge = value.length > MAX_DATAURL_SIZE;
@@ -584,7 +570,7 @@ const getCsrfSecret = (): Buffer => {
const envLabel = process.env.NODE_ENV ? ` (${process.env.NODE_ENV})` : "";
console.warn(
`[security] CSRF_SECRET is not set${envLabel}. Using an ephemeral per-process secret. ` +
"For horizontal scaling (k8s), set CSRF_SECRET to the same value on all instances."
"For horizontal scaling (k8s), set CSRF_SECRET to the same value on all instances."
);
return cachedCsrfSecret;
};
+15 -28
View File
@@ -1,12 +1,12 @@
{
"name": "frontend",
"version": "0.1.8",
"version": "0.3.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "frontend",
"version": "0.1.8",
"version": "0.3.1",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/utilities": "^3.2.2",
@@ -162,7 +162,6 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -517,7 +516,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
@@ -561,7 +559,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
}
@@ -2612,7 +2609,8 @@
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
@@ -2777,7 +2775,6 @@
"integrity": "sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -2789,7 +2786,6 @@
"integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/react": "*"
}
@@ -2860,7 +2856,6 @@
"integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.47.0",
"@typescript-eslint/types": "8.47.0",
@@ -3224,7 +3219,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3275,6 +3269,7 @@
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=8"
}
@@ -3504,7 +3499,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754",
@@ -3836,7 +3830,6 @@
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz",
"integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10"
}
@@ -4210,7 +4203,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"peer": true,
"engines": {
"node": ">=12"
}
@@ -4450,7 +4442,8 @@
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/dompurify": {
"version": "3.1.6",
@@ -4669,7 +4662,6 @@
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -5503,7 +5495,6 @@
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.11.0.tgz",
"integrity": "sha512-zKfoBBD1uDw3rljwHkt0fWuja1B76R7CjznuBO+mSX6jpsO1EBeWNRKpeaQho9yPI/pvCv4recGfgOXGxwPZvQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12.20.0"
},
@@ -5790,9 +5781,9 @@
"license": "MIT"
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
"integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
"license": "MIT"
},
"node_modules/lodash.debounce": {
@@ -5851,6 +5842,7 @@
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"lz-string": "bin/bin.js"
}
@@ -6880,7 +6872,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
@@ -7046,6 +7037,7 @@
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
@@ -7061,6 +7053,7 @@
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10"
},
@@ -7116,7 +7109,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -7129,7 +7121,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -7143,7 +7134,8 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/react-refresh": {
"version": "0.18.0",
@@ -7858,7 +7850,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -7997,7 +7988,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -8187,7 +8177,6 @@
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -8281,7 +8270,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -8608,7 +8596,6 @@
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
"dev": true,
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
"version": "0.3.0",
"version": "0.3.1",
"type": "module",
"scripts": {
"dev": "vite",
-1
View File
@@ -301,7 +301,6 @@ export const Editor: React.FC = () => {
try {
const persistableAppState = {
...appState,
viewBackgroundColor: appState?.viewBackgroundColor || '#ffffff',
gridSize: appState?.gridSize || null,
};