Conversation
Force asset_pack_id to match the URL parameter instead of trusting the client-supplied value in the request body.
Add authentication and ownership checks to GET /assets and GET /assets/:id. Assets are now filtered by the caller's address or the default address, preventing cross-user asset discovery.
| this.router.get( | ||
| '/assets/:id', | ||
| withCors, | ||
| withAuthentication, |
Check failure
Code scanning / CodeQL
Missing rate limiting High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 1 day ago
In general, the fix is to introduce a rate-limiting middleware (for example, using express-rate-limit) and apply it to the sensitive routes that perform authentication/authorization and database access. This middleware should be placed in the route definition before server.handleRequest(...) so that obviously abusive traffic is rejected early, without hitting the application logic or database.
In this file, the best approach without changing existing functionality is:
- Import a rate-limiting middleware implementation (e.g.,
express-rate-limit). - Define a limiter instance configured for these asset routes (for example, allowing a reasonable number of requests per 15-minute window).
- Apply this limiter to the
/assets/:idand/assetsGET routes by inserting it into the middleware chain afterwithCorsandwithAuthenticationbut beforewithAssetExists/server.handleRequest(...).
This preserves existing behavior, adds only rejection in case of too many requests, and ensures that authenticated users are each rate-limited according to the configuration (or, if using IP-based default, at least globally throttled).
Concretely, within src/Asset/Asset.router.ts:
- Add
import rateLimit from 'express-rate-limit'to the import section. - Inside
mount(), define aconst assetRateLimiter = rateLimit({...}). - Update the two
this.router.get(...)blocks for/assets/:idand/assetsto includeassetRateLimiterin their middleware lists.
| @@ -13,6 +13,7 @@ | ||
| import { getDefaultEthAddress } from '../AssetPack/utils' | ||
| import { Asset } from './Asset.model' | ||
| import { withAuthentication, AuthRequest } from '../middleware/authentication' | ||
| import rateLimit from 'express-rate-limit' | ||
|
|
||
| const DEFAULT_ETH_ADDRESS = getDefaultEthAddress() | ||
|
|
||
| @@ -24,6 +25,10 @@ | ||
| AssetPack, | ||
| 'assetPackId' | ||
| ) | ||
| const assetRateLimiter = rateLimit({ | ||
| windowMs: 15 * 60 * 1000, // 15 minutes | ||
| max: 100, // limit each IP to 100 requests per windowMs for asset routes | ||
| }) | ||
|
|
||
| /** | ||
| * CORS for the OPTIONS header | ||
| @@ -58,6 +63,7 @@ | ||
| '/assets/:id', | ||
| withCors, | ||
| withAuthentication, | ||
| assetRateLimiter, | ||
| withAssetExists, | ||
| server.handleRequest(this.getAsset) | ||
| ) | ||
| @@ -69,6 +75,7 @@ | ||
| '/assets', | ||
| withCors, | ||
| withAuthentication, | ||
| assetRateLimiter, | ||
| server.handleRequest(this.getAssets) | ||
| ) | ||
| } |
| @@ -73,7 +73,8 @@ | ||
| "prom-client": "^13.1.0", | ||
| "pull-stream": "^3.6.12", | ||
| "tslint": "^6.1.3", | ||
| "uuid": "^8.3.2" | ||
| "uuid": "^8.3.2", | ||
| "express-rate-limit": "^8.3.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@swc/core": "^1.2.102", |
| Package | Version | Security advisories |
| express-rate-limit (npm) | 8.3.1 | None |
| this.router.get( | ||
| '/assets', | ||
| withCors, | ||
| withAuthentication, |
Check failure
Code scanning / CodeQL
Missing rate limiting High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 1 day ago
In general, to fix missing rate limiting on an Express route, you add a rate-limiting middleware (for example using express-rate-limit) and plug it into the handler chain before the expensive operation. This limits how many requests a client can make to that endpoint in a given time window, mitigating denial-of-service risks.
For this specific code, the minimal, non-breaking fix is to (1) import express-rate-limit, (2) define a rate limiter instance suitable for the /assets listing endpoint, and (3) insert that limiter middleware into the /assets route before server.handleRequest(this.getAssets). To avoid altering other routes’ behavior, we will only apply the limiter to the /assets route, not globally. Concretely: at the top of src/Asset/Asset.router.ts we add an import for express-rate-limit. Inside mount(), before defining the routes, we create a const assetsRateLimiter = rateLimit({ ... }) with reasonable defaults, e.g., limiting requests per IP for that route. Then, in the .get('/assets', ...) handler chain, we add assetsRateLimiter between withAuthentication and server.handleRequest(this.getAssets). No existing functions need to change their signatures or logic; we just enrich the middleware chain.
| @@ -13,6 +13,7 @@ | ||
| import { getDefaultEthAddress } from '../AssetPack/utils' | ||
| import { Asset } from './Asset.model' | ||
| import { withAuthentication, AuthRequest } from '../middleware/authentication' | ||
| import rateLimit from 'express-rate-limit' | ||
|
|
||
| const DEFAULT_ETH_ADDRESS = getDefaultEthAddress() | ||
|
|
||
| @@ -24,6 +25,10 @@ | ||
| AssetPack, | ||
| 'assetPackId' | ||
| ) | ||
| const assetsRateLimiter = rateLimit({ | ||
| windowMs: 15 * 60 * 1000, // 15 minutes | ||
| max: 100, // limit each IP to 100 requests per windowMs for /assets | ||
| }) | ||
|
|
||
| /** | ||
| * CORS for the OPTIONS header | ||
| @@ -69,6 +74,7 @@ | ||
| '/assets', | ||
| withCors, | ||
| withAuthentication, | ||
| assetsRateLimiter, | ||
| server.handleRequest(this.getAssets) | ||
| ) | ||
| } |
| @@ -73,7 +73,8 @@ | ||
| "prom-client": "^13.1.0", | ||
| "pull-stream": "^3.6.12", | ||
| "tslint": "^6.1.3", | ||
| "uuid": "^8.3.2" | ||
| "uuid": "^8.3.2", | ||
| "express-rate-limit": "^8.3.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@swc/core": "^1.2.102", |
| Package | Version | Security advisories |
| express-rate-limit (npm) | 8.3.1 | None |
Pull Request Test Coverage Report for Build 22962474366Details
💛 - Coveralls |
| await Promise.all(assets.map((asset) => new Asset(asset).upsert())) | ||
| await Promise.all( | ||
| assets.map((asset) => | ||
| new Asset({ ...asset, asset_pack_id: id }).upsert() |
There was a problem hiding this comment.
What if, instead of rewriting the assets with the asset pack id from the request, we return a 400 if one of them does not have the correct id?
| INNER JOIN ${SQL.raw(AssetPack.tableName)} ap ON a.asset_pack_id = ap.id | ||
| WHERE a.id = ANY(${ids}) | ||
| AND ap.eth_address = ANY(${ethAddresses}) | ||
| AND ap.is_deleted = FALSE`) |
There was a problem hiding this comment.
Why do we need the is_deleted condition here and not in the original query?
There was a problem hiding this comment.
good catch, will add it to the original query, as we don't want to show deleted assets even using the id.
This PR fixes the upserting of assets' parent IDs to another parent.
Additionally, adds auth to the assets endpoint.