mirror of
https://github.com/zen-browser/rices.git
synced 2025-07-07 08:55:40 +02:00
Upload size control and security improvements
- Limit the maximum upload file size to 500 KB. - Minify CSS in the `userChrome` and `userContent` fields. - Sanitize uploaded JSON to remove XSS references (create and update).
This commit is contained in:
parent
9bf1fe8e0d
commit
aaee70f6df
4 changed files with 165 additions and 24 deletions
122
package-lock.json
generated
122
package-lock.json
generated
|
@ -31,6 +31,7 @@
|
||||||
"@nestjs/testing": "^10.0.0",
|
"@nestjs/testing": "^10.0.0",
|
||||||
"@nestjs/throttler": "^6.3.0",
|
"@nestjs/throttler": "^6.3.0",
|
||||||
"@supabase/supabase-js": "^2.47.10",
|
"@supabase/supabase-js": "^2.47.10",
|
||||||
|
"@types/csso": "^5.0.4",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/leo-profanity": "^1.5.4",
|
"@types/leo-profanity": "^1.5.4",
|
||||||
|
@ -39,6 +40,7 @@
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
|
"csso": "^5.0.5",
|
||||||
"eslint": "^8.0.0",
|
"eslint": "^8.0.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
|
@ -51,7 +53,8 @@
|
||||||
"ts-loader": "^9.4.3",
|
"ts-loader": "^9.4.3",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"typescript": "^5.1.3"
|
"typescript": "^5.1.3",
|
||||||
|
"xss": "^1.0.15"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
|
@ -2303,6 +2306,23 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/css-tree": {
|
||||||
|
"version": "2.3.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/css-tree/-/css-tree-2.3.9.tgz",
|
||||||
|
"integrity": "sha512-g1FE6xkPDP4tsccmTd6jIugjKZdxIDqAf9h2pc+4LsGgYbOyfa9phNjBHYbm6FtwIlNfT1NBx3f2zSeqO7aRAw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/csso": {
|
||||||
|
"version": "5.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/csso/-/csso-5.0.4.tgz",
|
||||||
|
"integrity": "sha512-W/FsRkm/9c04x9ON+bj+HQ0cSgNkG1LvcfuBCpkP7cpikM7+RkrNFLGtiofb++xBG6KGMUycLoDbi9/K621ZCw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/css-tree": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/eslint": {
|
"node_modules/@types/eslint": {
|
||||||
"version": "9.6.1",
|
"version": "9.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
|
||||||
|
@ -3314,27 +3334,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/base64-js": {
|
|
||||||
"version": "1.5.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
|
||||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/before-after-hook": {
|
"node_modules/before-after-hook": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
||||||
|
@ -4095,6 +4094,42 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-tree": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mdn-data": "2.0.28",
|
||||||
|
"source-map-js": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
|
||||||
|
"npm": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cssfilter": {
|
||||||
|
"version": "0.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
|
||||||
|
"integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/csso": {
|
||||||
|
"version": "5.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz",
|
||||||
|
"integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"css-tree": "~2.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
|
||||||
|
"npm": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
|
@ -6975,6 +7010,13 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mdn-data": {
|
||||||
|
"version": "2.0.28",
|
||||||
|
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
|
||||||
|
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "CC0-1.0"
|
||||||
|
},
|
||||||
"node_modules/media-typer": {
|
"node_modules/media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
|
@ -8501,6 +8543,16 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/source-map-js": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-support": {
|
"node_modules/source-map-support": {
|
||||||
"version": "0.5.21",
|
"version": "0.5.21",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||||
|
@ -9713,6 +9765,30 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/xss": {
|
||||||
|
"version": "1.0.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/xss/-/xss-1.0.15.tgz",
|
||||||
|
"integrity": "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "^2.20.3",
|
||||||
|
"cssfilter": "0.0.10"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"xss": "bin/xss"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xss/node_modules/commander": {
|
||||||
|
"version": "2.20.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/xtend": {
|
"node_modules/xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
@ -9792,4 +9868,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
"@nestjs/testing": "^10.0.0",
|
"@nestjs/testing": "^10.0.0",
|
||||||
"@nestjs/throttler": "^6.3.0",
|
"@nestjs/throttler": "^6.3.0",
|
||||||
"@supabase/supabase-js": "^2.47.10",
|
"@supabase/supabase-js": "^2.47.10",
|
||||||
|
"@types/csso": "^5.0.4",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/leo-profanity": "^1.5.4",
|
"@types/leo-profanity": "^1.5.4",
|
||||||
|
@ -50,6 +51,7 @@
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
|
"csso": "^5.0.5",
|
||||||
"eslint": "^8.0.0",
|
"eslint": "^8.0.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
|
@ -62,6 +64,7 @@
|
||||||
"ts-loader": "^9.4.3",
|
"ts-loader": "^9.4.3",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"typescript": "^5.1.3"
|
"typescript": "^5.1.3",
|
||||||
|
"xss": "^1.0.15"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
|
BadRequestException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { RicesService } from './rices.service';
|
import { RicesService } from './rices.service';
|
||||||
|
|
||||||
|
@ -45,6 +46,9 @@ export class RicesController {
|
||||||
) {
|
) {
|
||||||
const contentString =
|
const contentString =
|
||||||
typeof content === 'string' ? content : JSON.stringify(content);
|
typeof content === 'string' ? content : JSON.stringify(content);
|
||||||
|
|
||||||
|
this.validateFileSize(contentString); // Validate file size
|
||||||
|
|
||||||
return this.ricesService.create(contentString, token, headers);
|
return this.ricesService.create(contentString, token, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +92,9 @@ export class RicesController {
|
||||||
) {
|
) {
|
||||||
const contentString =
|
const contentString =
|
||||||
typeof content === 'string' ? content : JSON.stringify(content);
|
typeof content === 'string' ? content : JSON.stringify(content);
|
||||||
|
|
||||||
|
this.validateFileSize(contentString); // Validate file size
|
||||||
|
|
||||||
return this.ricesService.update(slug, token, contentString, headers);
|
return this.ricesService.update(slug, token, contentString, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,4 +128,14 @@ export class RicesController {
|
||||||
await this.ricesService.moderateRemove(slug);
|
await this.ricesService.moderateRemove(slug);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private validateFileSize(content: string) {
|
||||||
|
const sizeInBytes = Buffer.byteLength(content, 'utf-8');
|
||||||
|
const maxSizeInBytes = 1 * 1024 * 512; // 1 MB
|
||||||
|
if (sizeInBytes > maxSizeInBytes) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
`The uploaded content exceeds the size limit of 512 KB.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@ import {
|
||||||
ConflictException,
|
ConflictException,
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
|
||||||
|
import xss from 'xss';
|
||||||
|
import { minify } from 'csso';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { generateSlug } from './utils/slug.util';
|
import { generateSlug } from './utils/slug.util';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
@ -43,6 +46,10 @@ export class RicesService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.validateJsonStructure(content);
|
this.validateJsonStructure(content);
|
||||||
|
|
||||||
|
content = this.sanitizeJson(content);
|
||||||
|
content = this.minimizeJson(content);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new BadRequestException('Invalid json request');
|
throw new BadRequestException('Invalid json request');
|
||||||
|
@ -190,6 +197,10 @@ export class RicesService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.validateJsonStructure(content);
|
this.validateJsonStructure(content);
|
||||||
|
|
||||||
|
content = this.sanitizeJson(content);
|
||||||
|
content = this.minimizeJson(content);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new BadRequestException('Invalid json request');
|
throw new BadRequestException('Invalid json request');
|
||||||
|
@ -346,4 +357,38 @@ export class RicesService {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Método para minificar los campos CSS
|
||||||
|
private minimizeJson(jsonString: string): string {
|
||||||
|
const json = JSON.parse(jsonString);
|
||||||
|
|
||||||
|
['userChrome', 'userContent'].forEach((key) => {
|
||||||
|
if (json[key] && typeof json[key] === 'string') {
|
||||||
|
json[key] = minify(json[key]).css;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return JSON.stringify(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sanitizeJson(jsonString: string): string {
|
||||||
|
const json = JSON.parse(jsonString);
|
||||||
|
|
||||||
|
const sanitizedJson = Object.keys(json).reduce(
|
||||||
|
(acc, key) => {
|
||||||
|
const value = json[key];
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
acc[key] = xss(value); // Limpia las cadenas de texto
|
||||||
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
|
acc[key] = JSON.parse(this.sanitizeJson(JSON.stringify(value))); // Recursión para objetos anidados
|
||||||
|
} else {
|
||||||
|
acc[key] = value; // Otros tipos permanecen igual
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, unknown>,
|
||||||
|
);
|
||||||
|
|
||||||
|
return JSON.stringify(sanitizedJson);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue