aegis-bridge 2.16.0 → 2.17.0

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.
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Aegis Dashboard</title>
7
7
  <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🛡️</text></svg>" />
8
- <script type="module" crossorigin src="/dashboard/assets/index-Bvpa0M1m.js"></script>
8
+ <script type="module" crossorigin src="/dashboard/assets/index-iq1EAlmt.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/dashboard/assets/index-DnIfWYzW.css">
10
10
  </head>
11
11
  <body class="bg-[#0a0a0f] text-gray-200 antialiased">
package/dist/server.js CHANGED
@@ -1467,11 +1467,16 @@ app.post('/v1/templates', async (req, reply) => {
1467
1467
  if (!finalData.workDir) {
1468
1468
  return reply.status(400).send({ error: 'workDir is required (provide sessionId or explicit workDir)' });
1469
1469
  }
1470
+ // Issue #1125: Validate workDir for path traversal at template creation time
1471
+ const safeWorkDir = await validateWorkDirWithConfig(finalData.workDir);
1472
+ if (typeof safeWorkDir === 'object') {
1473
+ return reply.status(400).send({ error: `Invalid workDir: ${safeWorkDir.error}`, code: safeWorkDir.code });
1474
+ }
1470
1475
  try {
1471
1476
  const template = await templateStore.createTemplate({
1472
1477
  name,
1473
1478
  description,
1474
- workDir: finalData.workDir,
1479
+ workDir: safeWorkDir,
1475
1480
  prompt: finalData.prompt,
1476
1481
  claudeCommand: finalData.claudeCommand,
1477
1482
  env: finalData.env,
@@ -77,14 +77,21 @@ export function registerWsTerminalRoute(app, sessions, tmux, auth) {
77
77
  socket.close();
78
78
  return;
79
79
  }
80
- const session = sessions.getSession(sessionId);
81
- if (!session) {
82
- sendError(socket, 'Session not found');
83
- socket.close();
84
- return;
85
- }
86
80
  // Check if already authenticated via Bearer header in preHandler
87
81
  const preAuthed = auth.authEnabled && req.headers?.authorization?.startsWith('Bearer ');
82
+ // #1130: When auth is required but not yet provided, do NOT check session
83
+ // existence — that would leak whether a session ID is valid to unauthenticated clients.
84
+ // For pre-authenticated clients (Bearer header) or when auth is disabled, check immediately.
85
+ let session = null;
86
+ const deferSessionCheck = auth.authEnabled && !preAuthed;
87
+ if (!deferSessionCheck) {
88
+ session = sessions.getSession(sessionId);
89
+ if (!session) {
90
+ sendError(socket, 'Session not found');
91
+ socket.close();
92
+ return;
93
+ }
94
+ }
88
95
  // Create subscriber
89
96
  const subscriber = {
90
97
  lastContent: '',
@@ -104,25 +111,27 @@ export function registerWsTerminalRoute(app, sessions, tmux, auth) {
104
111
  }
105
112
  }, AUTH_TIMEOUT_MS);
106
113
  }
107
- // Get or create shared session poll
108
- let poll = sessionPolls.get(sessionId);
109
- if (!poll) {
110
- poll = {
111
- timer: null,
112
- tickCount: 0,
113
- subscribers: new Map(),
114
- };
115
- sessionPolls.set(sessionId, poll);
116
- // Start the shared poll timer
117
- poll.timer = setInterval(async () => {
118
- poll.tickCount++;
119
- await tickPoll(sessionId, sessions, tmux, poll);
120
- }, POLL_INTERVAL_MS);
114
+ // Get or create shared session poll (only after session is confirmed to exist)
115
+ if (session) {
116
+ let poll = sessionPolls.get(sessionId);
117
+ if (!poll) {
118
+ poll = {
119
+ timer: null,
120
+ tickCount: 0,
121
+ subscribers: new Map(),
122
+ };
123
+ sessionPolls.set(sessionId, poll);
124
+ // Start the shared poll timer
125
+ poll.timer = setInterval(async () => {
126
+ poll.tickCount++;
127
+ await tickPoll(sessionId, sessions, tmux, poll);
128
+ }, POLL_INTERVAL_MS);
129
+ }
130
+ poll.subscribers.set(socket, subscriber);
121
131
  }
122
- poll.subscribers.set(socket, subscriber);
123
132
  // Handle pong responses for keep-alive
124
133
  socket.on('pong', () => {
125
- const sub = poll?.subscribers.get(socket);
134
+ const sub = sessionPolls.get(sessionId)?.subscribers.get(socket);
126
135
  if (sub)
127
136
  sub.lastPongAt = Date.now();
128
137
  });
@@ -176,6 +185,29 @@ export function registerWsTerminalRoute(app, sessions, tmux, auth) {
176
185
  clearTimeout(subscriber.authTimer);
177
186
  subscriber.authTimer = null;
178
187
  }
188
+ // #1130: Now that the client is authenticated, check session existence.
189
+ // This was deferred to avoid leaking valid session IDs to unauthenticated clients.
190
+ const authedSession = sessions.getSession(sessionId);
191
+ if (!authedSession) {
192
+ sendError(socket, 'Session not found');
193
+ evictSubscriber(sessionId, socket, subscriber);
194
+ return;
195
+ }
196
+ // Register subscriber to the session poll now that session is confirmed
197
+ let authedPoll = sessionPolls.get(sessionId);
198
+ if (!authedPoll) {
199
+ authedPoll = {
200
+ timer: null,
201
+ tickCount: 0,
202
+ subscribers: new Map(),
203
+ };
204
+ sessionPolls.set(sessionId, authedPoll);
205
+ authedPoll.timer = setInterval(async () => {
206
+ authedPoll.tickCount++;
207
+ await tickPoll(sessionId, sessions, tmux, authedPoll);
208
+ }, POLL_INTERVAL_MS);
209
+ }
210
+ authedPoll.subscribers.set(socket, subscriber);
179
211
  send(socket, { type: 'status', status: 'authenticated' });
180
212
  return;
181
213
  }
@@ -189,9 +221,15 @@ export function registerWsTerminalRoute(app, sessions, tmux, auth) {
189
221
  await sessions.sendMessage(sessionId, msg.text);
190
222
  }
191
223
  else if (msg.type === 'resize') {
224
+ const resizeSession = sessions.getSession(sessionId);
225
+ if (!resizeSession) {
226
+ sendError(socket, 'Session no longer exists');
227
+ evictSubscriber(sessionId, socket, subscriber);
228
+ return;
229
+ }
192
230
  const cols = clamp(msg.cols ?? 80, 10, 500, 80);
193
231
  const rows = clamp(msg.rows ?? 24, 5, 200, 24);
194
- await tmux.resizePane(session.windowId, cols, rows);
232
+ await tmux.resizePane(resizeSession.windowId, cols, rows);
195
233
  }
196
234
  }
197
235
  catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aegis-bridge",
3
- "version": "2.16.0",
3
+ "version": "2.17.0",
4
4
  "type": "module",
5
5
  "description": "Orchestrate Claude Code sessions via API. Create, brief, monitor, refine, ship.",
6
6
  "main": "dist/server.js",