Skip to content

Conversation

@TabishB
Copy link
Contributor

@TabishB TabishB commented Jan 28, 2026

Summary

  • New openspec dashboard command spins up a local web server with interactive UI
  • Displays active changes with artifact status and progress
  • Shows specs organized by domain with requirement counts
  • Includes archive history with collapsible file browsing
  • Users can view any artifact's rendered markdown in a modal overlay

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a dashboard CLI command that launches an interactive web-based interface for viewing OpenSpec data locally.
    • Dashboard includes tabs for viewing Changes, Specs, and Archive with progress tracking and integrated Markdown viewer.
    • Configurable --port option (default 3000) for hosting the dashboard server.

✏️ Tip: You can customize this high-level summary in your review settings.

Introduces a new `openspec dashboard` command that spins up a local HTTP server and opens an interactive web dashboard. The dashboard displays active changes with their artifact status, main specs organized by domain, and archive history. Users can view any artifact's rendered markdown content in a modal viewer.

Features:
- Summary stats showing specs, requirements, and change counts
- Changes tab with expandable cards showing artifact status and progress
- Specs tab organized by domain with requirement counts
- Archive tab with collapsible history of completed changes
- Markdown viewer overlay for viewing artifact content
- Automatic browser launch and graceful shutdown
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

The changes introduce a new dashboard CLI command that launches a local HTTP server to display OpenSpec data interactively. The implementation includes a self-contained DashboardCommand that scans change logs, specifications, and archives, then serves a single-page dashboard with Markdown viewing capabilities and real-time progress tracking.

Changes

Cohort / File(s) Summary
CLI Dashboard Command
src/cli/index.ts
Added new top-level dashboard command with --port option (default 3000) that instantiates and executes DashboardCommand with error handling.
Dashboard Server Implementation
src/core/dashboard.ts
New DashboardCommand class providing complete HTTP server with endpoints for /api/data, /api/artifact, and /api/archive-files. Scans openspec directory for changes (with progress tracking), specs (with requirement parsing), and archive entries. Serves integrated HTML UI with tabs, Markdown viewer, theming, and browser auto-launch on startup.

Sequence Diagrams

sequenceDiagram
    actor User
    participant CLI
    participant DashboardCommand
    participant FileSystem
    participant HTTPServer
    participant Browser

    User->>CLI: dashboard --port 3000
    CLI->>DashboardCommand: execute('.', { port: 3000 })
    DashboardCommand->>FileSystem: validate openspec directory
    DashboardCommand->>FileSystem: scan changes, specs, archive
    DashboardCommand->>HTTPServer: start server on port 3000
    DashboardCommand->>Browser: open browser
    Browser->>HTTPServer: GET /
    HTTPServer->>Browser: return HTML dashboard UI
    Browser->>HTTPServer: GET /api/data
    HTTPServer->>FileSystem: read changes & specs metadata
    HTTPServer->>Browser: return JSON data
    Browser->>Browser: render dashboard with stats
Loading
sequenceDiagram
    participant Browser
    participant HTTPServer
    participant FileSystem
    participant MarkdownParser

    Browser->>Browser: user clicks on change/artifact
    Browser->>HTTPServer: GET /api/artifact?path=...
    HTTPServer->>FileSystem: read artifact markdown file
    HTTPServer->>HTTPServer: validate path (prevent traversal)
    HTTPServer->>Browser: return file content
    Browser->>Browser: render markdown in viewer overlay
    
    alt archive file request
        Browser->>HTTPServer: GET /api/archive-files?name=...
        HTTPServer->>FileSystem: list markdown files in archive path
        HTTPServer->>Browser: return file list
        Browser->>Browser: lazy-load and display files
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A dashboard hops into view,
With tabs and stats, all shiny and new,
Markdown viewers and archives too,
The server spins up—what a thing to do!
*hops excitedly*

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: adding a new dashboard web command to the CLI.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vibe-kanban-cloud
Copy link

Review Complete

Your review story is ready!

View Story

Comment !reviewfast on this PR to re-generate the story.

@greptile-apps
Copy link

greptile-apps bot commented Jan 28, 2026

Greptile Overview

Greptile Summary

Adds a new openspec dashboard command that launches a local web server with an interactive UI for viewing specs, changes, and archives. The dashboard displays real-time progress tracking, artifact statuses, and allows users to view markdown content in a modal overlay.

Major changes:

  • New HTTP server in src/core/dashboard.ts serving a single-page application
  • Client-side JavaScript for rendering tabs, stats, and markdown content
  • Integration with existing task progress and artifact graph utilities
  • Browser auto-launch on server start

Critical issues found:

  • XSS vulnerability in markdown rendering - user content inserted via innerHTML without sanitization
  • Command injection risk in browser launch using exec()
  • Multiple regex syntax errors with excessive escaping that will prevent proper markdown parsing
  • Potential XSS in dynamically generated onclick attributes

The concept is solid and follows patterns from the existing view command, but security issues must be addressed before merging.

Confidence Score: 1/5

  • Not safe to merge - contains XSS vulnerabilities and command injection risk
  • Multiple critical security vulnerabilities present: XSS in markdown rendering (innerHTML without sanitization), command injection in exec(), and broken regex patterns that will cause runtime errors. These must be fixed before deployment.
  • Critical attention needed for src/core/dashboard.ts - fix XSS vulnerabilities and regex syntax errors

Important Files Changed

Filename Overview
src/cli/index.ts Added new dashboard command registration with port option handling
src/core/dashboard.ts New web dashboard with HTTP server, markdown rendering, and file serving - contains XSS and command injection vulnerabilities

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as CLI (index.ts)
    participant Dashboard as DashboardCommand
    participant Server as HTTP Server
    participant Browser
    participant FS as File System

    User->>CLI: openspec dashboard --port 3000
    CLI->>Dashboard: new DashboardCommand()
    CLI->>Dashboard: execute('.', {port: 3000})
    
    Dashboard->>FS: Check openspecDir exists
    FS-->>Dashboard: Directory found
    
    Dashboard->>Server: createServer()
    Server->>Server: Listen on port 3000
    
    Dashboard->>Browser: exec(open/start/xdg-open)
    Browser->>Server: GET /
    Server->>Browser: Return HTML dashboard
    
    Browser->>Server: GET /api/data
    Server->>FS: getChanges(openspecDir)
    Server->>FS: getSpecs(openspecDir)
    Server->>FS: getArchive(openspecDir)
    FS-->>Server: Changes, Specs, Archive data
    Server->>Browser: JSON response
    
    Browser->>Browser: Render tabs and stats
    
    User->>Browser: Click on artifact
    Browser->>Server: GET /api/artifact?path=...
    Server->>FS: readArtifact(openspecDir, path)
    FS-->>Server: Markdown content
    Server->>Browser: JSON with content
    Browser->>Browser: renderMarkdown() & display
    
    User->>Browser: Click archive
    Browser->>Server: GET /api/archive-files?name=...
    Server->>FS: listMarkdownFiles(archiveDir)
    FS-->>Server: File list
    Server->>Browser: JSON with files
    
    User->>CLI: Ctrl+C
    Dashboard->>Server: server.close()
    Server-->>Dashboard: Shutdown complete
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 7 comments

Edit Code Review Agent Settings | Greptile

const res = await fetch('/api/artifact?path=' + encodeURIComponent(path));
const json = await res.json();
document.getElementById('viewer-title').textContent = title || path;
document.getElementById('viewer-content').innerHTML = renderMarkdown(json.content || 'File not found.');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XSS vulnerability - renderMarkdown returns HTML that's inserted via innerHTML without sanitization. Malicious markdown in spec files could execute JavaScript in users' browsers.

Suggested change
document.getElementById('viewer-content').innerHTML = renderMarkdown(json.content || 'File not found.');
document.getElementById('viewer-content').textContent = json.content || 'File not found.';

Or use a proper markdown library with XSS protection like marked with DOMPurify.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/core/dashboard.ts
Line: 441:441

Comment:
XSS vulnerability - `renderMarkdown` returns HTML that's inserted via `innerHTML` without sanitization. Malicious markdown in spec files could execute JavaScript in users' browsers.

```suggestion
  document.getElementById('viewer-content').textContent = json.content || 'File not found.';
```

Or use a proper markdown library with XSS protection like `marked` with DOMPurify.

How can I resolve this? If you propose a fix, please make it concise.

: process.platform === 'win32'
? 'start'
: 'xdg-open';
exec(`${openCmd} ${url}`);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Command injection risk - exec is called with unsanitized url variable. While url is constructed from localhost and a validated port, this pattern is risky. Use spawn or execFile with array arguments instead.

Suggested change
exec(`${openCmd} ${url}`);
const { spawn } = require('node:child_process');
spawn(openCmd, [url], { detached: true, stdio: 'ignore' }).unref();
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/core/dashboard.ts
Line: 742:742

Comment:
Command injection risk - `exec` is called with unsanitized `url` variable. While `url` is constructed from localhost and a validated port, this pattern is risky. Use `spawn` or `execFile` with array arguments instead.

```suggestion
        const { spawn } = require('node:child_process');
        spawn(openCmd, [url], { detached: true, stdio: 'ignore' }).unref();
```

How can I resolve this? If you propose a fix, please make it concise.

\${change.artifacts.map(a => {
const isClickable = a.status === 'done';
const clickAttr = isClickable
? \`class="artifact-name clickable" onclick="openViewer('changes/\${change.name}/\${a.outputPath}', '\${change.name} / \${a.id}')"\`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential XSS - change.name and a.id are inserted into onclick attributes without escaping. If directory/artifact names contain quotes or special chars, this could break out of the attribute context.

Use encodeURIComponent for the path parameter and escape the title parameter, or better yet, use event listeners instead of inline onclick.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/core/dashboard.ts
Line: 542:542

Comment:
Potential XSS - `change.name` and `a.id` are inserted into `onclick` attributes without escaping. If directory/artifact names contain quotes or special chars, this could break out of the attribute context.

Use `encodeURIComponent` for the path parameter and escape the title parameter, or better yet, use event listeners instead of inline `onclick`.

How can I resolve this? If you propose a fix, please make it concise.

let html = escapeHtml(src);
// Code blocks
html = html.replace(/\`\`\`(\\w*?)\\n([\\s\\S]*?)\`\`\`/g, '<pre><code>$2</code></pre>');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regex will fail to match code blocks with language specifiers. The \w*? uses excessive escaping - should be (\w*) not (\\w*?).

Suggested change
html = html.replace(/\`\`\`(\\w*?)\\n([\\s\\S]*?)\`\`\`/g, '<pre><code>$2</code></pre>');
html = html.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>');
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/core/dashboard.ts
Line: 455:455

Comment:
Regex will fail to match code blocks with language specifiers. The `\w*?` uses excessive escaping - should be `(\w*)` not `(\\w*?)`.

```suggestion
  html = html.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>');
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +467 to +468
html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');
html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same regex escaping issue - \\*\\* and \\* should be \*\* and \* (single backslash for JavaScript string, becomes one backslash in regex).

Suggested change
html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');
html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/core/dashboard.ts
Line: 467:468

Comment:
Same regex escaping issue - `\\*\\*` and `\\*` should be `\*\*` and `\*` (single backslash for JavaScript string, becomes one backslash in regex).

```suggestion
  html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
  html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +477 to +478
html = html.replace(/^- \\[x\\] (.+)$/gm, '<li style="list-style:none">&#9745; $1</li>');
html = html.replace(/^- \\[ \\] (.+)$/gm, '<li style="list-style:none">&#9744; $1</li>');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Over-escaped regex patterns - \\[x\\] should be \[x\] and \\[ \\] should be \[ \].

Suggested change
html = html.replace(/^- \\[x\\] (.+)$/gm, '<li style="list-style:none">&#9745; $1</li>');
html = html.replace(/^- \\[ \\] (.+)$/gm, '<li style="list-style:none">&#9744; $1</li>');
html = html.replace(/^- \[x\] (.+)$/gm, '<li style="list-style:none">&#9745; $1</li>');
html = html.replace(/^- \[ \] (.+)$/gm, '<li style="list-style:none">&#9744; $1</li>');
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/core/dashboard.ts
Line: 477:478

Comment:
Over-escaped regex patterns - `\\[x\\]` should be `\[x\]` and `\\[ \\]` should be `\[ \]`.

```suggestion
  html = html.replace(/^- \[x\] (.+)$/gm, '<li style="list-style:none">&#9745; $1</li>');
  html = html.replace(/^- \[ \] (.+)$/gm, '<li style="list-style:none">&#9744; $1</li>');
```

How can I resolve this? If you propose a fix, please make it concise.

html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
// Links
html = html.replace(/\\[(.+?)\\]\\((.+?)\\)/g, '<a href="$2" target="_blank">$1</a>');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Over-escaped regex - \\[(.+?)\\]\\((.+?)\\) should be \[(.+?)\]\((.+?)\).

Suggested change
html = html.replace(/\\[(.+?)\\]\\((.+?)\\)/g, '<a href="$2" target="_blank">$1</a>');
html = html.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" target="_blank">$1</a>');
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/core/dashboard.ts
Line: 484:484

Comment:
Over-escaped regex - `\\[(.+?)\\]\\((.+?)\\)` should be `\[(.+?)\]\((.+?)\)`.

```suggestion
  html = html.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" target="_blank">$1</a>');
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/cli/index.ts`:
- Around line 205-213: Validate the --port value before calling
DashboardCommand.execute: parse options?.port to an integer, ensure it's a
positive integer within the valid TCP port range (1–65535), and if invalid throw
or print a clear error and exit early instead of calling new
DashboardCommand().execute; update the dashboard command action handler that
constructs DashboardCommand to perform this check and only pass a valid port (or
undefined) to DashboardCommand.execute.

In `@src/core/dashboard.ts`:
- Around line 158-167: The current containment check in readArtifact using
startsWith is insecure; replace it with a path.relative-based guard (e.g.,
extract a helper isSubpath(parent, child) that computes rel =
path.relative(parent, child) and returns rel !== '' && !rel.startsWith('..') &&
!path.isAbsolute(rel) or adjust for allowing same dir) and use that helper in
readArtifact and the archive listing logic (the code handling archive listings
elsewhere) to ensure resolved paths cannot escape the openspecDir; preserve the
existing fs.existsSync and readFileSync behavior and return null on failures as
before.
- Around line 669-671: Replace the wildcard CORS and open binding: remove or
stop setting res.setHeader('Access-Control-Allow-Origin', '*') in the dashboard
response handling and instead either drop the header entirely (preferred if the
UI is served by this same server) or set it to the server's same‑origin value;
also change the server bind from listening on all interfaces to bind to
127.0.0.1 (update the server.listen/createServer call that currently uses
0.0.0.0 or no host) so the dashboard is only accessible locally. Ensure you
update all occurrences (the res.setHeader(...) lines and the server.listen(...)
invocation(s) referenced in this module).

Comment on lines +205 to +213
program
.command('dashboard')
.description('Open an interactive web dashboard for specs and changes')
.option('--port <port>', 'Port to run the server on', '3000')
.action(async (options?: { port?: string }) => {
try {
const dashboardCommand = new DashboardCommand();
await dashboardCommand.execute('.', { port: options?.port ? parseInt(options.port, 10) : undefined });
} catch (error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Validate --port input before launching the server.

Negative or out‑of‑range values will cause server.listen to throw. Validate early and return a clear error.

✅ Proposed fix (range validation)
   .action(async (options?: { port?: string }) => {
     try {
-      const dashboardCommand = new DashboardCommand();
-      await dashboardCommand.execute('.', { port: options?.port ? parseInt(options.port, 10) : undefined });
+      const rawPort = options?.port ? Number(options.port) : undefined;
+      if (rawPort !== undefined && (!Number.isInteger(rawPort) || rawPort < 1 || rawPort > 65535)) {
+        throw new Error(`Invalid port "${options?.port}". Use an integer between 1 and 65535.`);
+      }
+      const dashboardCommand = new DashboardCommand();
+      await dashboardCommand.execute('.', { port: rawPort });
     } catch (error) {
       console.log(); // Empty line for spacing
       ora().fail(`Error: ${(error as Error).message}`);
       process.exit(1);
     }
   });
🤖 Prompt for AI Agents
In `@src/cli/index.ts` around lines 205 - 213, Validate the --port value before
calling DashboardCommand.execute: parse options?.port to an integer, ensure it's
a positive integer within the valid TCP port range (1–65535), and if invalid
throw or print a clear error and exit early instead of calling new
DashboardCommand().execute; update the dashboard command action handler that
constructs DashboardCommand to perform this check and only pass a valid port (or
undefined) to DashboardCommand.execute.

Comment on lines +158 to +167
function readArtifact(openspecDir: string, artifactPath: string): string | null {
// Resolve to prevent path traversal
const resolved = path.resolve(openspecDir, artifactPath);
if (!resolved.startsWith(path.resolve(openspecDir))) return null;
if (!fs.existsSync(resolved)) return null;
try {
return fs.readFileSync(resolved, 'utf-8');
} catch {
return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Harden path containment checks to prevent traversal.

The current startsWith check is bypassable via .. segments (e.g., resolving to a sibling path that still shares the same prefix). That allows reading/listing files outside the intended directory. Use a path.relative-based guard and reuse it for both artifact reads and archive listings.

🔒 Proposed fix (shared subpath guard)
+function isSubpath(root: string, target: string): boolean {
+  const rel = path.relative(root, target);
+  return !rel.startsWith('..') && !path.isAbsolute(rel);
+}
+
 function readArtifact(openspecDir: string, artifactPath: string): string | null {
   // Resolve to prevent path traversal
-  const resolved = path.resolve(openspecDir, artifactPath);
-  if (!resolved.startsWith(path.resolve(openspecDir))) return null;
+  const root = path.resolve(openspecDir);
+  const resolved = path.resolve(root, artifactPath);
+  if (!isSubpath(root, resolved)) return null;
   if (!fs.existsSync(resolved)) return null;
   try {
     return fs.readFileSync(resolved, 'utf-8');
   } catch {
     return null;
   }
 }
 ...
       if (url.pathname === '/api/archive-files') {
         const name = url.searchParams.get('name') || '';
-        const archiveItemDir = path.join(openspecDir, 'changes', 'archive', name);
+        const archiveRoot = path.join(openspecDir, 'changes', 'archive');
+        const archiveItemDir = path.resolve(archiveRoot, name);
         // Prevent path traversal
-        if (!path.resolve(archiveItemDir).startsWith(path.resolve(openspecDir))) {
+        if (!isSubpath(archiveRoot, archiveItemDir)) {
           res.writeHead(400, { 'Content-Type': 'application/json' });
           res.end(JSON.stringify({ error: 'Invalid path' }));
           return;
         }

Also applies to: 702-711

🤖 Prompt for AI Agents
In `@src/core/dashboard.ts` around lines 158 - 167, The current containment check
in readArtifact using startsWith is insecure; replace it with a
path.relative-based guard (e.g., extract a helper isSubpath(parent, child) that
computes rel = path.relative(parent, child) and returns rel !== '' &&
!rel.startsWith('..') && !path.isAbsolute(rel) or adjust for allowing same dir)
and use that helper in readArtifact and the archive listing logic (the code
handling archive listings elsewhere) to ensure resolved paths cannot escape the
openspecDir; preserve the existing fs.existsSync and readFileSync behavior and
return null on failures as before.

Comment on lines +669 to +671
// CORS headers for local dev
res.setHeader('Access-Control-Allow-Origin', '*');

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Limit dashboard exposure to localhost and remove wildcard CORS.

Listening on all interfaces plus Access-Control-Allow-Origin: * allows any origin/network host to read dashboard data. Bind to 127.0.0.1 and only allow same‑origin (or drop CORS entirely since the UI is served by this server).

🔒 Proposed fix (localhost bind + same‑origin CORS)
-    const server = http.createServer(async (req, res) => {
-      const url = new URL(req.url || '/', `http://localhost:${port}`);
-
-      // CORS headers for local dev
-      res.setHeader('Access-Control-Allow-Origin', '*');
+    const host = '127.0.0.1';
+    const origin = `http://${host}:${port}`;
+    const server = http.createServer(async (req, res) => {
+      const url = new URL(req.url || '/', origin);
+      // Same-origin only (UI served by this server)
+      if (req.headers.origin === origin) {
+        res.setHeader('Access-Control-Allow-Origin', origin);
+      }
...
-      server.listen(port, () => {
-        const url = `http://localhost:${port}`;
+      server.listen(port, host, () => {
+        const url = origin;
         console.log(chalk.bold('\nOpenSpec Dashboard'));
         console.log(`Server running at ${chalk.cyan(url)}`);
         console.log(chalk.dim('Press Ctrl+C to stop.\n'));

Also applies to: 730-742

🤖 Prompt for AI Agents
In `@src/core/dashboard.ts` around lines 669 - 671, Replace the wildcard CORS and
open binding: remove or stop setting
res.setHeader('Access-Control-Allow-Origin', '*') in the dashboard response
handling and instead either drop the header entirely (preferred if the UI is
served by this same server) or set it to the server's same‑origin value; also
change the server bind from listening on all interfaces to bind to 127.0.0.1
(update the server.listen/createServer call that currently uses 0.0.0.0 or no
host) so the dashboard is only accessible locally. Ensure you update all
occurrences (the res.setHeader(...) lines and the server.listen(...)
invocation(s) referenced in this module).

@TabishB TabishB changed the title Add openspec dashboard web command [Without OpenSpec] Add openspec dashboard web command Jan 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants