agileflow 2.98.1 → 2.99.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.99.1] - 2026-02-08
11
+
12
+ ### Added
13
+ - Smart session management with context-aware naming and tmux quick-create keybinds
14
+
15
+ ## [2.99.0] - 2026-02-08
16
+
17
+ ### Added
18
+ - Security Hardening Phase 2 - path traversal, auth, CORS, ReDoS, rate limiting
19
+
10
20
  ## [2.98.1] - 2026-02-07
11
21
 
12
22
  ### Added
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  </p>
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/agileflow?color=brightgreen)](https://www.npmjs.com/package/agileflow)
6
- [![Commands](https://img.shields.io/badge/commands-90-blue)](docs/04-architecture/commands.md)
6
+ [![Commands](https://img.shields.io/badge/commands-91-blue)](docs/04-architecture/commands.md)
7
7
  [![Agents/Experts](https://img.shields.io/badge/agents%2Fexperts-47-orange)](docs/04-architecture/subagents.md)
8
8
  [![Skills](https://img.shields.io/badge/skills-dynamic-purple)](docs/04-architecture/skills.md)
9
9
 
@@ -65,7 +65,7 @@ AgileFlow combines three proven methodologies:
65
65
 
66
66
  | Component | Count | Description |
67
67
  |-----------|-------|-------------|
68
- | [Commands](docs/04-architecture/commands.md) | 90 | Slash commands for agile workflows |
68
+ | [Commands](docs/04-architecture/commands.md) | 91 | Slash commands for agile workflows |
69
69
  | [Agents/Experts](docs/04-architecture/subagents.md) | 47 | Specialized agents with self-improving knowledge bases |
70
70
  | [Skills](docs/04-architecture/skills.md) | Dynamic | Generated on-demand with `/agileflow:skill:create` |
71
71
 
@@ -76,7 +76,7 @@ AgileFlow combines three proven methodologies:
76
76
  Full documentation lives in [`docs/04-architecture/`](docs/04-architecture/):
77
77
 
78
78
  ### Reference
79
- - [Commands](docs/04-architecture/commands.md) - All 90 slash commands
79
+ - [Commands](docs/04-architecture/commands.md) - All 91 slash commands
80
80
  - [Agents/Experts](docs/04-architecture/subagents.md) - 47 specialized agents with self-improving knowledge
81
81
  - [Skills](docs/04-architecture/skills.md) - Dynamic skill generator with MCP integration
82
82
 
package/lib/api-routes.js CHANGED
@@ -26,6 +26,10 @@ const {
26
26
  } = require('./paths');
27
27
  const { SessionRegistry } = require('./session-registry');
28
28
  const { getTaskRegistry } = require('../scripts/lib/task-registry');
29
+ const { validatePath } = require('./validate-paths');
30
+
31
+ // Allow-list regex for resource IDs (epics, stories, tasks, sessions)
32
+ const SAFE_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
29
33
 
30
34
  /**
31
35
  * Get API route handlers
@@ -139,6 +143,10 @@ async function getSessions(sessionRegistry, cache) {
139
143
  * GET /api/sessions/:id - Get session by ID
140
144
  */
141
145
  async function getSessionById(sessionRegistry, id, cache) {
146
+ if (!SAFE_ID_PATTERN.test(id)) {
147
+ return { error: 'Invalid session ID format' };
148
+ }
149
+
142
150
  const cacheKey = `session-${id}`;
143
151
  const cached = cache.get(cacheKey);
144
152
  if (cached) return cached;
@@ -183,7 +191,7 @@ function getStatus(rootDir, cache) {
183
191
  cache.set(cacheKey, result);
184
192
  return result;
185
193
  } catch (error) {
186
- return { error: 'Failed to parse status file', message: error.message };
194
+ return { error: 'Failed to parse status file' };
187
195
  }
188
196
  }
189
197
 
@@ -226,7 +234,7 @@ function getTasks(rootDir, queryParams, cache) {
226
234
  cache.set(cacheKey, result);
227
235
  return result;
228
236
  } catch (error) {
229
- return { error: 'Failed to load tasks', message: error.message };
237
+ return { error: 'Failed to load tasks' };
230
238
  }
231
239
  }
232
240
 
@@ -234,6 +242,10 @@ function getTasks(rootDir, queryParams, cache) {
234
242
  * GET /api/tasks/:id - Get task by ID
235
243
  */
236
244
  function getTaskById(rootDir, id, cache) {
245
+ if (!SAFE_ID_PATTERN.test(id)) {
246
+ return { error: 'Invalid task ID format' };
247
+ }
248
+
237
249
  const cacheKey = `task-${id}`;
238
250
  const cached = cache.get(cacheKey);
239
251
  if (cached) return cached;
@@ -249,7 +261,7 @@ function getTaskById(rootDir, id, cache) {
249
261
  cache.set(cacheKey, task);
250
262
  return task;
251
263
  } catch (error) {
252
- return { error: 'Failed to load task', message: error.message };
264
+ return { error: 'Failed to load task' };
253
265
  }
254
266
  }
255
267
 
@@ -300,7 +312,7 @@ async function getBusMessages(rootDir, queryParams, cache) {
300
312
  cache.set(cacheKey, result);
301
313
  return result;
302
314
  } catch (error) {
303
- return { error: 'Failed to read bus log', message: error.message };
315
+ return { error: 'Failed to read bus log' };
304
316
  }
305
317
  }
306
318
 
@@ -448,7 +460,7 @@ function getEpics(rootDir, cache) {
448
460
  cache.set(cacheKey, result);
449
461
  return result;
450
462
  } catch (error) {
451
- return { error: 'Failed to list epics', message: error.message };
463
+ return { error: 'Failed to list epics' };
452
464
  }
453
465
  }
454
466
 
@@ -456,6 +468,10 @@ function getEpics(rootDir, cache) {
456
468
  * GET /api/epics/:id - Get epic by ID
457
469
  */
458
470
  function getEpicById(rootDir, id, cache) {
471
+ if (!SAFE_ID_PATTERN.test(id)) {
472
+ return { error: 'Invalid epic ID format' };
473
+ }
474
+
459
475
  const cacheKey = `epic-${id}`;
460
476
  const cached = cache.get(cacheKey);
461
477
  if (cached) return cached;
@@ -479,7 +495,7 @@ function getEpicById(rootDir, id, cache) {
479
495
  cache.set(cacheKey, result);
480
496
  return result;
481
497
  } catch (error) {
482
- return { error: 'Failed to read epic', message: error.message };
498
+ return { error: 'Failed to read epic' };
483
499
  }
484
500
  }
485
501
 
@@ -533,7 +549,7 @@ function getStories(rootDir, queryParams, cache) {
533
549
 
534
550
  return { stories: [], count: 0, timestamp: new Date().toISOString() };
535
551
  } catch (error) {
536
- return { error: 'Failed to list stories', message: error.message };
552
+ return { error: 'Failed to list stories' };
537
553
  }
538
554
  }
539
555
 
@@ -541,6 +557,10 @@ function getStories(rootDir, queryParams, cache) {
541
557
  * GET /api/stories/:id - Get story by ID
542
558
  */
543
559
  function getStoryById(rootDir, id, cache) {
560
+ if (!SAFE_ID_PATTERN.test(id)) {
561
+ return { error: 'Invalid story ID format' };
562
+ }
563
+
544
564
  const cacheKey = `story-${id}`;
545
565
  const cached = cache.get(cacheKey);
546
566
  if (cached) return cached;
@@ -569,7 +589,7 @@ function getStoryById(rootDir, id, cache) {
569
589
 
570
590
  return { error: 'Story not found', id };
571
591
  } catch (error) {
572
- return { error: 'Failed to read story', message: error.message };
592
+ return { error: 'Failed to read story' };
573
593
  }
574
594
  }
575
595
 
package/lib/api-server.js CHANGED
@@ -87,13 +87,31 @@ function createApiServer(options = {}) {
87
87
  // Get route handlers
88
88
  const routes = getApiRoutes(rootDir, cache);
89
89
 
90
+ // Localhost CORS allowlist
91
+ const ALLOWED_ORIGINS = [
92
+ `http://localhost:${port}`,
93
+ `http://127.0.0.1:${port}`,
94
+ 'http://localhost:3000',
95
+ 'http://127.0.0.1:3000',
96
+ 'http://localhost:5173',
97
+ 'http://127.0.0.1:5173',
98
+ ];
99
+
90
100
  // Create HTTP server
91
101
  const server = http.createServer(async (req, res) => {
92
- // CORS headers for browser access
93
- res.setHeader('Access-Control-Allow-Origin', '*');
102
+ // Security headers
103
+ res.setHeader('X-Content-Type-Options', 'nosniff');
104
+ res.setHeader('X-Frame-Options', 'DENY');
105
+ res.setHeader('Cache-Control', 'no-store');
106
+ res.setHeader('Content-Type', 'application/json');
107
+
108
+ // CORS - restrict to localhost origins
109
+ const origin = req.headers.origin;
110
+ if (origin && ALLOWED_ORIGINS.some(allowed => origin === allowed)) {
111
+ res.setHeader('Access-Control-Allow-Origin', origin);
112
+ }
94
113
  res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
95
114
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
96
- res.setHeader('Content-Type', 'application/json');
97
115
 
98
116
  // Handle preflight
99
117
  if (req.method === 'OPTIONS') {
@@ -155,7 +173,6 @@ function createApiServer(options = {}) {
155
173
  res.end(
156
174
  JSON.stringify({
157
175
  error: 'Internal server error',
158
- message: error.message,
159
176
  })
160
177
  );
161
178
  }
@@ -78,6 +78,9 @@ const OutboundMessageType = {
78
78
  // User Interaction
79
79
  ASK_USER_QUESTION: 'ask_user_question', // Claude is asking user a question
80
80
 
81
+ // Sessions
82
+ SESSION_LIST: 'session_list', // Session list with sync status
83
+
81
84
  // Errors
82
85
  ERROR: 'error', // General error
83
86
  };
@@ -123,6 +126,9 @@ const InboundMessageType = {
123
126
  INBOX_LIST_REQUEST: 'inbox_list_request', // Request inbox list
124
127
  INBOX_ACTION: 'inbox_action', // Accept/dismiss inbox item
125
128
 
129
+ // File Operations
130
+ OPEN_FILE: 'open_file', // Open file in editor
131
+
126
132
  // User Interaction Response
127
133
  USER_ANSWER: 'user_answer', // User's answer to AskUserQuestion
128
134
  };
@@ -435,6 +441,36 @@ function createInboxItem(item) {
435
441
  };
436
442
  }
437
443
 
444
+ /**
445
+ * Create a project status update message
446
+ * @param {Object} summary - Status summary
447
+ * @param {number} summary.total - Total stories
448
+ * @param {number} summary.done - Completed stories
449
+ * @param {number} summary.inProgress - In-progress stories
450
+ * @param {number} summary.ready - Ready stories
451
+ * @param {number} summary.blocked - Blocked stories
452
+ * @param {Object[]} summary.epics - Epic summaries
453
+ */
454
+ function createStatusUpdate(summary) {
455
+ return {
456
+ type: OutboundMessageType.STATUS_UPDATE,
457
+ ...summary,
458
+ timestamp: new Date().toISOString(),
459
+ };
460
+ }
461
+
462
+ /**
463
+ * Create a session list message with sync status
464
+ * @param {Object[]} sessions - Array of session objects with sync info
465
+ */
466
+ function createSessionList(sessions) {
467
+ return {
468
+ type: OutboundMessageType.SESSION_LIST,
469
+ sessions,
470
+ timestamp: new Date().toISOString(),
471
+ };
472
+ }
473
+
438
474
  /**
439
475
  * Create an AskUserQuestion message
440
476
  * @param {string} toolId - Tool call ID for response correlation
@@ -533,6 +569,8 @@ module.exports = {
533
569
  createInboxList,
534
570
  createInboxItem,
535
571
  createAskUserQuestion,
572
+ createStatusUpdate,
573
+ createSessionList,
536
574
 
537
575
  // Parsing
538
576
  parseInboundMessage,