T09 · Insecure Skill Coding Practices
Warning
- Location
- server.js:11
- Finding
- Unauthenticated and Unrestricted External API Proxy<![CDATA[ ## Vulnerability Details **File Location**: `server.js:11-23`, `server.js:31-66`, `server.js:74-109`, and `server.js:115-120` **Vulnerability Type**: Missing authentication, rate limiting, concurrency controls, and restrictive network binding **Risk Level**: Medium ### Vulnerable Code ```js app.get('/api/dealers', async (req, res) => { try { const zip = req.query.zip; if (!zip) return res.status(400).json({ status: "error", message: "Missing required parameter: zip" }); const start = req.query.start ? parseInt(req.query.start, 10) : 0; const count = req.query.count ? parseInt(req.query.count, 10) : 10; const filter = req.query.filter || "mbdealer"; const resultString = await getMbusaDealers(zip, start, count, filter); const resultJson = JSON.parse(resultString); if (resultJson.status === "error") return res.status(502).json(resultJson); res.status(200).json(resultJson); } catch (error) { console.error("Dealer API Error:", error); res.status(500).json({ status: "error", message: "Internal server error" }); } }); ``` ```js app.get('/api/inventory', async (req, res) => { try { if (!req.query.zip) return res.status(400).json({ status: "error", message: "Missing required parameter: zip" }); // Request parameters are forwarded to an external API. const resultString = await getMbusaInventory(params); const resultJson = JSON.parse(resultString); if (resultJson.status === "error") return res.status(502).json(resultJson); res.status(200).json(resultJson); } catch (error) { console.error("New Inventory API Error:", error); res.status(500).json({ status: "error", message: "Internal server error" }); } }); ``` ```js app.get('/api/used-inventory', async (req, res) => { try { if (!req.query.zip) return res.status(400).json({ status: "error", message: "Missing required par ...[truncated 3121 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Restrict local deployments to loopback**: ```js const HOST = process.env.HOST || '127.0.0.1'; app.listen(PORT, HOST, () => { console.log(`Server listening on http://${HOST}:${PORT}`); }); ``` 2. **Require authentication** before allowing access in any network-facing deployment. Use securely generated API keys, OAuth, or authentication supplied by a trusted reverse proxy. 3. **Add per-client rate limits and quotas**, for example with a maintained Express rate-limiting middleware. Apply stricter limits to routes that initiate outbound requests. 4. **Limit concurrent upstream operations** with a queue or semaphore so a burst cannot create an unbounded number of pending outbound requests. 5. **Set outbound request deadlines** using `AbortSignal.timeout()` or an `AbortController`: ```js const response = await fetch(apiUrl, { headers, signal: AbortSignal.timeout(10000) }); ``` 6. **Deploy behind a hardened reverse proxy** with connection limits, request timeouts, TLS, authentication, and network access controls. 7. **Document the exposure model clearly** so users do not infer from the localhost examples that the server is technically restricted to localhost. ]]>
