diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000..4c09409 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,10 @@ +name: "MikroTik Manager CodeQL config" + +# Rate limiting is enforced globally in Express — `app.use('/api', rateLimitRedis(...))` +# in backend/src/index.ts, plus dedicated limiters on auth/login/TOTP/OIDC routes. +# CodeQL's data flow can't attribute a global app-level middleware to each individual +# route handler, so `js/missing-rate-limiting` fires on ~every DB/fs handler as a false +# positive. Exclude it rather than carry dozens of non-actionable "high" alerts. +query-filters: + - exclude: + id: js/missing-rate-limiting diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0753c72..2dfd89e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -33,6 +33,8 @@ jobs: languages: ${{ matrix.language }} # CodeQL queries to run — security-extended adds more security checks queries: security-extended + # Filters out non-actionable rules (see file for rationale) + config-file: ./.github/codeql/codeql-config.yml - name: Autobuild uses: github/codeql-action/autobuild@v4 diff --git a/README.md b/README.md index 0f61740..c69fc45 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A self-hosted, full-stack network management platform for MikroTik devices. Monitor, configure, and manage your entire MikroTik infrastructure — routers, switches, and wireless access points — from a single web interface. -![Version](https://img.shields.io/badge/version-0.17.1_Beta-blue) +![Version](https://img.shields.io/badge/version-0.17.2_Beta-blue) ![License](https://img.shields.io/badge/license-AGPLv3-blue) ![Docker](https://img.shields.io/badge/docker-compose-2496ED?logo=docker&logoColor=white) ![TypeScript](https://img.shields.io/badge/TypeScript-5.3-3178C6?logo=typescript&logoColor=white) diff --git a/backend/package.json b/backend/package.json index fc76a86..1048882 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "mikrotik-manager-backend", - "version": "0.17.1-beta", + "version": "0.17.2-beta", "description": "Mikrotik Network Management Platform - Backend API", "main": "dist/index.js", "scripts": { diff --git a/backend/src/routes/automation.ts b/backend/src/routes/automation.ts index a21d392..085b920 100644 --- a/backend/src/routes/automation.ts +++ b/backend/src/routes/automation.ts @@ -8,6 +8,16 @@ import { reportService, computeNextRun } from '../services/ReportService'; const router = Router(); router.use(requireAuth); +// Linear email check — no backtracking regex (avoids ReDoS on user-supplied lists). +function isValidEmail(addr: string): boolean { + if (/\s/.test(addr)) return false; + const at = addr.indexOf('@'); + if (at <= 0 || at !== addr.lastIndexOf('@')) return false; + const domain = addr.slice(at + 1); + const dot = domain.lastIndexOf('.'); + return dot > 0 && dot < domain.length - 1; +} + // ─── API tokens (admin only — a token can never mint more tokens) ───────────── router.get('/tokens', requireAdmin, async (_req: Request, res: Response) => { @@ -117,7 +127,7 @@ router.post('/reports', requireWrite, async (req: Request, res: Response) => { if (!name || !name.trim()) return res.status(400).json({ error: 'name is required' }); if (!['daily', 'weekly', 'monthly'].includes(frequency ?? '')) return res.status(400).json({ error: 'frequency must be daily, weekly or monthly' }); const rcpts = (recipients ?? '').split(',').map(r => r.trim()).filter(Boolean); - if (rcpts.length === 0 || rcpts.some(r => !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(r))) { + if (rcpts.length === 0 || rcpts.some(r => !isValidEmail(r))) { return res.status(400).json({ error: 'recipients must be comma-separated email addresses' }); } diff --git a/backend/src/routes/guestWifi.ts b/backend/src/routes/guestWifi.ts index 7715073..28befdb 100644 --- a/backend/src/routes/guestWifi.ts +++ b/backend/src/routes/guestWifi.ts @@ -87,9 +87,19 @@ router.post('/setup', requireWrite, async (req: Request, res: Response) => { // Human-friendly voucher codes: no ambiguous chars (0/O, 1/I/L) const CODE_CHARS = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'; function generateCode(len = 8): string { - const bytes = randomBytes(len); + const n = CODE_CHARS.length; + // Rejection sampling: discard bytes at or above the largest multiple of n so + // the modulo is uniform (avoids the slight bias of a raw byte % n). + const limit = Math.floor(256 / n) * n; let out = ''; - for (let i = 0; i < len; i++) out += CODE_CHARS[bytes[i] % CODE_CHARS.length]; + while (out.length < len) { + for (const b of randomBytes(len)) { + if (b < limit) { + out += CODE_CHARS[b % n]; + if (out.length === len) break; + } + } + } return `${out.slice(0, 4)}-${out.slice(4)}`; } diff --git a/frontend/package.json b/frontend/package.json index 1b53d37..6962123 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "mikrotik-manager-frontend", - "version": "0.17.1-beta", + "version": "0.17.2-beta", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/version.ts b/frontend/src/version.ts index 2e02954..682b463 100644 --- a/frontend/src/version.ts +++ b/frontend/src/version.ts @@ -1 +1 @@ -export const APP_VERSION = 'v0.17.1 Beta'; +export const APP_VERSION = 'v0.17.2 Beta';