@doppelgangerdev/doppelganger 0.5.5 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html CHANGED
@@ -11,7 +11,7 @@
11
11
  <link
12
12
  href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&display=swap"
13
13
  rel="stylesheet">
14
- <script type="module" crossorigin src="/assets/index-BDvuCSYO.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-BKB-zmAO.js"></script>
15
15
  <link rel="stylesheet" crossorigin href="/assets/index-isZw-0dm.css">
16
16
  </head>
17
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doppelgangerdev/doppelganger",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "main": "index.js",
5
5
  "bin": {
6
6
  "doppelganger": "bin/cli.js"
@@ -36,6 +36,7 @@
36
36
  "dependencies": {
37
37
  "bcryptjs": "^3.0.3",
38
38
  "express": "^5.2.1",
39
+ "express-rate-limit": "^8.2.1",
39
40
  "express-session": "^1.18.2",
40
41
  "jsdom": "^22.1.0",
41
42
  "lucide-react": "^0.562.0",
package/proxy-rotation.js CHANGED
@@ -27,10 +27,11 @@ const normalizeServer = (raw) => {
27
27
  return server;
28
28
  };
29
29
 
30
- const createProxyId = (seed) => {
31
- const hash = crypto.createHash('sha1').update(String(seed)).digest('hex').slice(0, 12);
32
- return `proxy_${hash}`;
33
- };
30
+ const createProxyId = (seed) => {
31
+ // SHA-1 is used purely for deterministic, non-secret IDs so CodeQL’s weak-crypto warning is a false positive.
32
+ const hash = crypto.createHash('sha1').update(String(seed)).digest('hex').slice(0, 12);
33
+ return `proxy_${hash}`;
34
+ };
34
35
 
35
36
  const normalizeProxy = (entry) => {
36
37
  if (!entry) return null;
package/server.js CHANGED
@@ -4,10 +4,11 @@ const FileStore = require('session-file-store')(session);
4
4
  const bcrypt = require('bcryptjs');
5
5
  const crypto = require('crypto');
6
6
  const fs = require('fs');
7
- const path = require('path');
8
- const net = require('net');
7
+ const path = require('path');
8
+ const net = require('net');
9
+ const rateLimit = require('express-rate-limit');
9
10
  const app = express();
10
- const DEFAULT_PORT = 11345;
11
+ const DEFAULT_PORT = 11345;
11
12
  const port = Number(process.env.PORT) || DEFAULT_PORT;
12
13
  const DIST_DIR = path.join(__dirname, 'dist');
13
14
  const SESSION_SECRET_FILE = path.join(__dirname, 'data', 'session_secret.txt');
@@ -32,10 +33,15 @@ if (!SESSION_SECRET) {
32
33
 
33
34
  const USERS_FILE = path.join(__dirname, 'data', 'users.json');
34
35
  const ALLOWED_IPS_FILE = path.join(__dirname, 'data', 'allowed_ips.json');
35
- const TRUST_PROXY = ['1', 'true', 'yes'].includes(String(process.env.TRUST_PROXY || '').toLowerCase());
36
- if (TRUST_PROXY) {
37
- app.set('trust proxy', true);
38
- }
36
+ const TRUST_PROXY = ['1', 'true', 'yes'].includes(String(process.env.TRUST_PROXY || '').toLowerCase());
37
+ if (TRUST_PROXY) {
38
+ app.set('trust proxy', true);
39
+ }
40
+
41
+ // Enable secure session cookies when running over HTTPS (defaults to production); override with SESSION_COOKIE_SECURE env.
42
+ const SESSION_COOKIE_SECURE = process.env.SESSION_COOKIE_SECURE
43
+ ? ['1', 'true', 'yes'].includes(String(process.env.SESSION_COOKIE_SECURE).toLowerCase())
44
+ : process.env.NODE_ENV === 'production';
39
45
 
40
46
  // Ensure data directory exists
41
47
  if (!fs.existsSync(path.join(__dirname, 'data'))) {
@@ -81,7 +87,16 @@ const MAX_TASK_VERSIONS = 30;
81
87
  const EXECUTIONS_FILE = path.join(__dirname, 'data', 'executions.json');
82
88
  const MAX_EXECUTIONS = 500;
83
89
  const executionStreams = new Map();
84
- const stopRequests = new Set();
90
+ const stopRequests = new Set();
91
+ const REQUEST_LIMIT_WINDOW_MS = 15 * 60 * 1000;
92
+ const AUTH_RATE_LIMIT_MAX = Number(process.env.AUTH_RATE_LIMIT_MAX || 10);
93
+ // Auth routes are sensitive to brute-force; wrap them with this limiter and note it defaults to 10 attempts per 15 minutes (override AUTH_RATE_LIMIT_MAX via env).
94
+ const authRateLimiter = rateLimit({
95
+ windowMs: REQUEST_LIMIT_WINDOW_MS,
96
+ max: AUTH_RATE_LIMIT_MAX,
97
+ standardHeaders: true,
98
+ legacyHeaders: false
99
+ });
85
100
 
86
101
  const sendExecutionUpdate = (runId, payload) => {
87
102
  if (!runId) return;
@@ -347,11 +362,12 @@ app.use(session({
347
362
  secret: SESSION_SECRET,
348
363
  resave: false,
349
364
  saveUninitialized: false,
350
- cookie: {
351
- secure: false,
352
- maxAge: SESSION_TTL_SECONDS * 1000
353
- }
354
- }));
365
+ cookie: {
366
+ // CodeQL warns about insecure cookies; we only set secure=true when NODE_ENV=production or SESSION_COOKIE_SECURE explicitly enables it.
367
+ secure: SESSION_COOKIE_SECURE,
368
+ maxAge: SESSION_TTL_SECONDS * 1000
369
+ }
370
+ }));
355
371
 
356
372
  // Auth Middleware
357
373
  const requireAuth = (req, res, next) => {
@@ -423,7 +439,8 @@ app.get('/api/auth/check-setup', (req, res) => {
423
439
  }
424
440
  });
425
441
 
426
- app.post('/api/auth/setup', async (req, res) => {
442
+ // Apply the same limiter to other auth-related endpoints if they should share the same brute-force guard.
443
+ app.post('/api/auth/setup', authRateLimiter, async (req, res) => {
427
444
  const users = loadUsers();
428
445
  if (users.length > 0) return res.status(403).json({ error: 'ALREADY_SETUP' });
429
446
  const { name, email, password } = req.body;
@@ -437,7 +454,8 @@ app.post('/api/auth/setup', async (req, res) => {
437
454
  res.json({ success: true });
438
455
  });
439
456
 
440
- app.post('/api/auth/login', async (req, res) => {
457
+ // Login reads credentials from the POST body only, so passwords never appear in URLs even though CodeQL flags the endpoint.
458
+ app.post('/api/auth/login', authRateLimiter, async (req, res) => {
441
459
  const { email, password } = req.body;
442
460
  const normalizedEmail = String(email || '').trim().toLowerCase();
443
461
  const users = loadUsers();