@pixelbyte-software/pixcode 1.53.28 → 1.54.1

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.
Files changed (75) hide show
  1. package/dist/assets/{index-BBDYzJ7B.js → index-CLO8YURv.js} +148 -148
  2. package/dist/index.html +1 -1
  3. package/dist-server/server/claude-sdk.js +6 -0
  4. package/dist-server/server/claude-sdk.js.map +1 -1
  5. package/dist-server/server/cli.js +67 -0
  6. package/dist-server/server/cli.js.map +1 -1
  7. package/dist-server/server/index.js +278 -54
  8. package/dist-server/server/index.js.map +1 -1
  9. package/dist-server/server/middleware/account-lockout.js +101 -0
  10. package/dist-server/server/middleware/account-lockout.js.map +1 -0
  11. package/dist-server/server/middleware/auth.js +71 -9
  12. package/dist-server/server/middleware/auth.js.map +1 -1
  13. package/dist-server/server/middleware/rate-limiter.js +62 -0
  14. package/dist-server/server/middleware/rate-limiter.js.map +1 -0
  15. package/dist-server/server/routes/agent.js +3 -4
  16. package/dist-server/server/routes/agent.js.map +1 -1
  17. package/dist-server/server/routes/auth.js +75 -13
  18. package/dist-server/server/routes/auth.js.map +1 -1
  19. package/dist-server/server/routes/codex.js +1 -1
  20. package/dist-server/server/routes/codex.js.map +1 -1
  21. package/dist-server/server/routes/diagnostics.js +6 -3
  22. package/dist-server/server/routes/diagnostics.js.map +1 -1
  23. package/dist-server/server/routes/gemini.js +1 -1
  24. package/dist-server/server/routes/gemini.js.map +1 -1
  25. package/dist-server/server/routes/git.js +9 -9
  26. package/dist-server/server/routes/git.js.map +1 -1
  27. package/dist-server/server/routes/live-view.js +2 -2
  28. package/dist-server/server/routes/live-view.js.map +1 -1
  29. package/dist-server/server/routes/plugins.js +12 -12
  30. package/dist-server/server/routes/plugins.js.map +1 -1
  31. package/dist-server/server/routes/projects.js +2 -2
  32. package/dist-server/server/routes/projects.js.map +1 -1
  33. package/dist-server/server/routes/qwen.js +1 -1
  34. package/dist-server/server/routes/qwen.js.map +1 -1
  35. package/dist-server/server/routes/remote.js +1 -1
  36. package/dist-server/server/routes/remote.js.map +1 -1
  37. package/dist-server/server/routes/webhooks.js +1 -1
  38. package/dist-server/server/routes/webhooks.js.map +1 -1
  39. package/dist-server/server/services/startup-update.js +31 -6
  40. package/dist-server/server/services/startup-update.js.map +1 -1
  41. package/dist-server/server/services/telegram/bot.js +6 -4
  42. package/dist-server/server/services/telegram/bot.js.map +1 -1
  43. package/dist-server/server/setup-wizard.js +246 -0
  44. package/dist-server/server/setup-wizard.js.map +1 -0
  45. package/dist-server/server/utils/plugin-loader.js +3 -0
  46. package/dist-server/server/utils/plugin-loader.js.map +1 -1
  47. package/dist-server/server/utils/port-access.js +25 -8
  48. package/dist-server/server/utils/port-access.js.map +1 -1
  49. package/dist-server/server/utils/security-log.js +89 -0
  50. package/dist-server/server/utils/security-log.js.map +1 -0
  51. package/package.json +1 -1
  52. package/server/claude-sdk.js +7 -0
  53. package/server/cli.js +67 -0
  54. package/server/index.js +287 -55
  55. package/server/middleware/account-lockout.js +112 -0
  56. package/server/middleware/auth.js +72 -9
  57. package/server/middleware/rate-limiter.js +82 -0
  58. package/server/routes/agent.js +3 -4
  59. package/server/routes/auth.js +86 -13
  60. package/server/routes/codex.js +1 -1
  61. package/server/routes/diagnostics.js +6 -3
  62. package/server/routes/gemini.js +1 -1
  63. package/server/routes/git.js +9 -9
  64. package/server/routes/live-view.js +2 -2
  65. package/server/routes/plugins.js +12 -12
  66. package/server/routes/projects.js +2 -2
  67. package/server/routes/qwen.js +1 -1
  68. package/server/routes/remote.js +1 -1
  69. package/server/routes/webhooks.js +1 -1
  70. package/server/services/startup-update.js +31 -6
  71. package/server/services/telegram/bot.js +6 -4
  72. package/server/setup-wizard.js +263 -0
  73. package/server/utils/plugin-loader.js +4 -0
  74. package/server/utils/port-access.js +26 -8
  75. package/server/utils/security-log.js +84 -0
@@ -2,7 +2,7 @@ import os from 'os';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
  import readline from 'readline';
5
- import { execSync } from 'child_process';
5
+ import { execSync, spawnSync } from 'child_process';
6
6
 
7
7
  import { c } from './colors.js';
8
8
 
@@ -65,7 +65,7 @@ function tryUfw(port) {
65
65
  const status = execSync('ufw status', { stdio: 'pipe', encoding: 'utf8' });
66
66
  // UFW inactive = no rule needed; the port is already reachable.
67
67
  if (!/active/i.test(status)) return 'inactive';
68
- execSync(`sudo -n ufw allow ${port}/tcp`, { stdio: 'pipe' });
68
+ execSync('sudo -n ufw allow ' + String(port) + '/tcp', { stdio: 'pipe' });
69
69
  return 'applied';
70
70
  } catch {
71
71
  return null;
@@ -77,7 +77,7 @@ function tryFirewalld(port) {
77
77
  execSync('command -v firewall-cmd', { stdio: 'pipe' });
78
78
  const state = execSync('firewall-cmd --state', { stdio: 'pipe', encoding: 'utf8' });
79
79
  if (!/running/i.test(state)) return 'inactive';
80
- execSync(`sudo -n firewall-cmd --add-port=${port}/tcp --permanent`, { stdio: 'pipe' });
80
+ execSync('sudo -n firewall-cmd --add-port=' + String(port) + '/tcp --permanent', { stdio: 'pipe' });
81
81
  execSync('sudo -n firewall-cmd --reload', { stdio: 'pipe' });
82
82
  return 'applied';
83
83
  } catch {
@@ -89,12 +89,19 @@ function tryFirewalld(port) {
89
89
 
90
90
  function applyWindowsRule(port) {
91
91
  const ruleName = `Pixcode-${port}`;
92
- const cmd = `netsh advfirewall firewall add rule name="${ruleName}" dir=in action=allow protocol=TCP localport=${port}`;
92
+ // Use spawnSync with an argument array instead of execSync with a shell
93
+ // string to prevent command injection. Port is validated as an integer
94
+ // at the entry point, but this is defense-in-depth.
93
95
  try {
94
- execSync(cmd, { stdio: 'pipe' });
95
- return { ok: true, cmd, ruleName };
96
+ spawnSync('netsh', [
97
+ 'advfirewall', 'firewall', 'add', 'rule',
98
+ `name=${ruleName}`,
99
+ 'dir=in', 'action=allow', 'protocol=TCP',
100
+ `localport=${port}`,
101
+ ], { stdio: 'pipe', shell: false });
102
+ return { ok: true, ruleName };
96
103
  } catch (error) {
97
- return { ok: false, cmd, ruleName, error: error.message };
104
+ return { ok: false, ruleName, error: error.message };
98
105
  }
99
106
  }
100
107
 
@@ -125,6 +132,16 @@ function macFirewallHint(port) {
125
132
  * without any firewall change.
126
133
  */
127
134
  export async function ensurePortOpen(port) {
135
+ // Validate port is a safe integer to prevent command injection via the
136
+ // firewall commands below. The caller passes SERVER_PORT which comes from
137
+ // env, so this is defense-in-depth against a tampered environment.
138
+ const safePort = Number.parseInt(port, 10);
139
+ if (!Number.isInteger(safePort) || safePort < 1 || safePort > 65535) {
140
+ console.log(`${c.dim('[INFO]')} Skipping firewall setup: invalid port value.`);
141
+ return;
142
+ }
143
+ port = safePort;
144
+
128
145
  const state = loadState();
129
146
  const key = `port:${port}`;
130
147
  const entry = state[key];
@@ -185,8 +202,9 @@ export async function ensurePortOpen(port) {
185
202
  console.log(`${c.ok('[OK]')} Firewall rule "${result.ruleName}" added.`);
186
203
  saveState({ ...state, [key]: { decision: 'allow', status: 'applied', via: 'windows' } });
187
204
  } else {
205
+ const manualCmd = `netsh advfirewall firewall add rule name="Pixcode-${port}" dir=in action=allow protocol=TCP localport=${port}`;
188
206
  console.log(`${c.tip('[TIP]')} Adding the rule needs Administrator. Run this in an elevated PowerShell:`);
189
- console.log(` ${c.bright(result.cmd)}`);
207
+ console.log(` ${c.bright(manualCmd)}`);
190
208
  saveState({ ...state, [key]: { decision: 'allow', status: 'manual' } });
191
209
  }
192
210
  return;
@@ -0,0 +1,84 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+
5
+ const LOG_DIR = path.join(os.homedir(), '.pixcode', 'logs');
6
+ const SECURITY_LOG_PATH = path.join(LOG_DIR, 'security.log');
7
+ const MAX_LOG_SIZE = 10 * 1024 * 1024;
8
+ const MAX_LOG_FILES = 5;
9
+
10
+ function ensureLogDir() {
11
+ try {
12
+ if (!fs.existsSync(LOG_DIR)) {
13
+ fs.mkdirSync(LOG_DIR, { recursive: true, mode: 0o700 });
14
+ }
15
+ } catch {
16
+ // Non-fatal: if we can't create the log dir, we fall back to console
17
+ }
18
+ }
19
+
20
+ function sanitizeForLog(value) {
21
+ if (typeof value !== 'string') {
22
+ try {
23
+ value = String(value ?? '');
24
+ } catch {
25
+ return '[unserializable]';
26
+ }
27
+ }
28
+ return value
29
+ .replace(/[\r\n\t]/g, ' ')
30
+ .replace(/\x1b\[[0-9;]*m/g, '')
31
+ .slice(0, 500);
32
+ }
33
+
34
+ function rotateLogIfNeeded() {
35
+ try {
36
+ if (!fs.existsSync(SECURITY_LOG_PATH)) return;
37
+ const stats = fs.statSync(SECURITY_LOG_PATH);
38
+ if (stats.size < MAX_LOG_SIZE) return;
39
+ for (let i = MAX_LOG_FILES - 1; i >= 1; i--) {
40
+ const oldPath = `${SECURITY_LOG_PATH}.${i}`;
41
+ const newPath = `${SECURITY_LOG_PATH}.${i + 1}`;
42
+ if (fs.existsSync(oldPath)) {
43
+ try { fs.renameSync(oldPath, newPath); } catch { /* noop */ }
44
+ }
45
+ }
46
+ try { fs.renameSync(SECURITY_LOG_PATH, `${SECURITY_LOG_PATH}.1`); } catch { /* noop */ }
47
+ } catch {
48
+ // Non-fatal
49
+ }
50
+ }
51
+
52
+ export function securityLog(event, details = {}) {
53
+ const entry = {
54
+ timestamp: new Date().toISOString(),
55
+ event,
56
+ ip: sanitizeForLog(details.ip),
57
+ userId: details.userId != null ? String(details.userId) : null,
58
+ username: details.username ? sanitizeForLog(details.username) : null,
59
+ endpoint: details.endpoint ? sanitizeForLog(details.endpoint) : null,
60
+ method: details.method ? sanitizeForLog(details.method) : null,
61
+ statusCode: details.statusCode || null,
62
+ reason: details.reason ? sanitizeForLog(details.reason) : null,
63
+ userAgent: details.userAgent ? sanitizeForLog(details.userAgent) : null,
64
+ };
65
+
66
+ const line = JSON.stringify(entry);
67
+
68
+ try {
69
+ ensureLogDir();
70
+ rotateLogIfNeeded();
71
+ fs.appendFileSync(SECURITY_LOG_PATH, line + '\n', { mode: 0o600 });
72
+ } catch {
73
+ // Fall back to console if file logging fails
74
+ console.warn('[SECURITY]', line);
75
+ }
76
+ }
77
+
78
+ export function getClientIp(req) {
79
+ const forwarded = req?.headers?.['x-forwarded-for'];
80
+ if (typeof forwarded === 'string' && forwarded.trim()) {
81
+ return forwarded.split(',')[0].trim();
82
+ }
83
+ return req?.socket?.remoteAddress || req?.ip || 'unknown';
84
+ }