2025-11-29 19:26:20 -05:00
|
|
|
const express = require('express');
|
|
|
|
|
const http = require('http');
|
|
|
|
|
const WebSocket = require('ws');
|
|
|
|
|
const mysql = require('mysql2/promise');
|
|
|
|
|
const crypto = require('crypto');
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
const vm = require('vm');
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
const rateLimit = require('express-rate-limit');
|
|
|
|
|
const cronParser = require('cron-parser');
|
2025-11-29 19:26:20 -05:00
|
|
|
require('dotenv').config();
|
|
|
|
|
|
|
|
|
|
const app = express();
|
|
|
|
|
const server = http.createServer(app);
|
|
|
|
|
const wss = new WebSocket.Server({ server });
|
|
|
|
|
|
|
|
|
|
// Middleware
|
|
|
|
|
app.use(express.json());
|
|
|
|
|
app.use(express.static('public'));
|
|
|
|
|
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
// Rate limiting
|
|
|
|
|
const apiLimiter = rateLimit({
|
|
|
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
|
|
|
max: 300,
|
|
|
|
|
standardHeaders: true,
|
|
|
|
|
legacyHeaders: false,
|
|
|
|
|
message: { error: 'Too many requests, please try again later' }
|
|
|
|
|
});
|
|
|
|
|
const executionLimiter = rateLimit({
|
|
|
|
|
windowMs: 60 * 1000, // 1 minute
|
|
|
|
|
max: 20,
|
|
|
|
|
standardHeaders: true,
|
|
|
|
|
legacyHeaders: false,
|
|
|
|
|
message: { error: 'Too many execution requests, please slow down' }
|
|
|
|
|
});
|
|
|
|
|
app.use('/api/', apiLimiter);
|
|
|
|
|
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
// Named constants for timeouts and limits
|
|
|
|
|
const PROMPT_TIMEOUT_MS = 60 * 60 * 1000; // 60 min — how long a prompt waits for user input
|
|
|
|
|
const COMMAND_TIMEOUT_MS = 120_000; // 2 min — workflow step command timeout
|
|
|
|
|
const QUICK_CMD_TIMEOUT_MS = 60_000; // 1 min — quick/direct/gandalf command timeout
|
|
|
|
|
const WEBHOOK_TIMEOUT_MS = 10_000; // 10 s — outbound webhook HTTP timeout
|
|
|
|
|
const WAIT_STEP_MAX_MS = 24 * 60 * 60_000; // 24 h — cap on workflow wait step
|
|
|
|
|
const GOTO_MAX_VISITS = 100; // max times a step may be revisited via goto
|
|
|
|
|
const WORKER_STALE_MINUTES = 5; // minutes before a worker is marked offline
|
|
|
|
|
const SERVER_VERSION = '1.0.0';
|
|
|
|
|
|
|
|
|
|
// Request logging middleware
|
|
|
|
|
app.use((req, res, next) => {
|
|
|
|
|
const start = Date.now();
|
|
|
|
|
res.on('finish', () => {
|
|
|
|
|
console.log(`[HTTP] ${req.method} ${req.path} ${res.statusCode} ${Date.now() - start}ms`);
|
|
|
|
|
});
|
|
|
|
|
next();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Content-Type guard for JSON endpoints
|
|
|
|
|
function requireJSON(req, res, next) {
|
|
|
|
|
const ct = req.headers['content-type'] || '';
|
|
|
|
|
if (!ct.includes('application/json')) {
|
|
|
|
|
return res.status(415).json({ error: 'Content-Type must be application/json' });
|
|
|
|
|
}
|
|
|
|
|
next();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate and parse a webhook URL; returns { ok, url, reason }
|
|
|
|
|
function validateWebhookUrl(raw) {
|
|
|
|
|
if (!raw) return { ok: true, url: null };
|
|
|
|
|
let url;
|
|
|
|
|
try { url = new URL(raw); } catch { return { ok: false, reason: 'Invalid URL format' }; }
|
|
|
|
|
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
|
|
|
return { ok: false, reason: 'Webhook URL must use http or https' };
|
|
|
|
|
}
|
|
|
|
|
const host = url.hostname.toLowerCase();
|
|
|
|
|
if (
|
|
|
|
|
host === 'localhost' ||
|
|
|
|
|
host === '::1' ||
|
|
|
|
|
/^127\./.test(host) ||
|
|
|
|
|
/^10\./.test(host) ||
|
|
|
|
|
/^192\.168\./.test(host) ||
|
|
|
|
|
/^172\.(1[6-9]|2\d|3[01])\./.test(host) ||
|
|
|
|
|
/^169\.254\./.test(host) ||
|
|
|
|
|
/^fe80:/i.test(host)
|
|
|
|
|
) {
|
|
|
|
|
return { ok: false, reason: 'Webhook URL must not point to a private/internal address' };
|
|
|
|
|
}
|
|
|
|
|
return { ok: true, url };
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-29 19:26:20 -05:00
|
|
|
// Database pool
|
|
|
|
|
const pool = mysql.createPool({
|
|
|
|
|
host: process.env.DB_HOST,
|
|
|
|
|
port: process.env.DB_PORT || 3306,
|
|
|
|
|
user: process.env.DB_USER,
|
|
|
|
|
password: process.env.DB_PASSWORD,
|
|
|
|
|
database: process.env.DB_NAME,
|
|
|
|
|
waitForConnections: true,
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
connectionLimit: 50,
|
2025-11-29 19:26:20 -05:00
|
|
|
queueLimit: 0
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Initialize database tables
|
|
|
|
|
async function initDatabase() {
|
|
|
|
|
const connection = await pool.getConnection();
|
|
|
|
|
try {
|
|
|
|
|
await connection.query(`
|
|
|
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
|
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
|
|
|
username VARCHAR(255) UNIQUE NOT NULL,
|
|
|
|
|
display_name VARCHAR(255),
|
|
|
|
|
email VARCHAR(255),
|
|
|
|
|
groups TEXT,
|
|
|
|
|
last_login TIMESTAMP,
|
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
|
|
|
)
|
|
|
|
|
`);
|
|
|
|
|
|
2026-01-07 22:11:07 -05:00
|
|
|
// Database schema is managed manually - migrations removed after direct database fixes
|
2026-01-07 20:28:47 -05:00
|
|
|
|
2025-11-29 19:26:20 -05:00
|
|
|
await connection.query(`
|
|
|
|
|
CREATE TABLE IF NOT EXISTS workers (
|
|
|
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
|
|
|
name VARCHAR(255) UNIQUE NOT NULL,
|
|
|
|
|
status VARCHAR(50) NOT NULL,
|
|
|
|
|
last_heartbeat TIMESTAMP NULL,
|
|
|
|
|
api_key VARCHAR(255),
|
|
|
|
|
metadata JSON,
|
|
|
|
|
INDEX idx_status (status),
|
|
|
|
|
INDEX idx_heartbeat (last_heartbeat)
|
|
|
|
|
)
|
|
|
|
|
`);
|
|
|
|
|
|
|
|
|
|
await connection.query(`
|
|
|
|
|
CREATE TABLE IF NOT EXISTS workflows (
|
|
|
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
|
|
|
name VARCHAR(255) NOT NULL,
|
|
|
|
|
description TEXT,
|
|
|
|
|
definition JSON NOT NULL,
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
webhook_url VARCHAR(500) NULL,
|
2025-11-29 19:26:20 -05:00
|
|
|
created_by VARCHAR(255),
|
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
|
|
INDEX idx_name (name)
|
|
|
|
|
)
|
|
|
|
|
`);
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
// Add webhook_url to existing workflows table if missing
|
|
|
|
|
await connection.query(`
|
|
|
|
|
ALTER TABLE workflows ADD COLUMN IF NOT EXISTS webhook_url VARCHAR(500) NULL
|
|
|
|
|
`).catch(() => {});
|
2025-11-29 19:26:20 -05:00
|
|
|
|
|
|
|
|
await connection.query(`
|
|
|
|
|
CREATE TABLE IF NOT EXISTS executions (
|
|
|
|
|
id VARCHAR(36) PRIMARY KEY,
|
2026-01-07 20:27:02 -05:00
|
|
|
workflow_id VARCHAR(36) NULL,
|
2025-11-29 19:26:20 -05:00
|
|
|
status VARCHAR(50) NOT NULL,
|
|
|
|
|
started_by VARCHAR(255),
|
|
|
|
|
started_at TIMESTAMP NULL,
|
|
|
|
|
completed_at TIMESTAMP NULL,
|
|
|
|
|
logs JSON,
|
|
|
|
|
INDEX idx_workflow (workflow_id),
|
|
|
|
|
INDEX idx_status (status),
|
|
|
|
|
INDEX idx_started (started_at)
|
|
|
|
|
)
|
|
|
|
|
`);
|
|
|
|
|
|
2026-01-07 23:13:27 -05:00
|
|
|
await connection.query(`
|
|
|
|
|
CREATE TABLE IF NOT EXISTS scheduled_commands (
|
|
|
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
|
|
|
name VARCHAR(255) NOT NULL,
|
|
|
|
|
command TEXT NOT NULL,
|
|
|
|
|
worker_ids JSON NOT NULL,
|
|
|
|
|
schedule_type VARCHAR(50) NOT NULL,
|
|
|
|
|
schedule_value VARCHAR(255) NOT NULL,
|
|
|
|
|
enabled BOOLEAN DEFAULT TRUE,
|
|
|
|
|
created_by VARCHAR(255),
|
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
|
last_run TIMESTAMP NULL,
|
|
|
|
|
next_run TIMESTAMP NULL,
|
|
|
|
|
INDEX idx_enabled (enabled),
|
|
|
|
|
INDEX idx_next_run (next_run)
|
|
|
|
|
)
|
|
|
|
|
`);
|
|
|
|
|
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
// Recover stale executions from a previous server crash
|
|
|
|
|
const [staleExecs] = await connection.query("SELECT id FROM executions WHERE status = 'running'");
|
|
|
|
|
if (staleExecs.length > 0) {
|
|
|
|
|
for (const exec of staleExecs) {
|
|
|
|
|
await connection.query(
|
|
|
|
|
"UPDATE executions SET status = 'failed', completed_at = NOW() WHERE id = ?",
|
|
|
|
|
[exec.id]
|
|
|
|
|
);
|
|
|
|
|
await connection.query(
|
|
|
|
|
"UPDATE executions SET logs = JSON_ARRAY_APPEND(COALESCE(logs, '[]'), '$', CAST(? AS JSON)) WHERE id = ?",
|
|
|
|
|
[JSON.stringify({ action: 'server_restart_recovery', message: 'Execution marked failed due to server restart', timestamp: new Date().toISOString() }), exec.id]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
console.log(`[Recovery] Marked ${staleExecs.length} stale execution(s) as failed`);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-29 19:26:20 -05:00
|
|
|
console.log('Database tables initialized successfully');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Database initialization error:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
} finally {
|
|
|
|
|
connection.release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-03 16:04:22 -05:00
|
|
|
// Auto-cleanup old executions (runs hourly)
|
2026-01-07 22:50:39 -05:00
|
|
|
async function cleanupOldExecutions() {
|
|
|
|
|
try {
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
const retentionDays = parseInt(process.env.EXECUTION_RETENTION_DAYS) || 30;
|
2026-01-07 22:50:39 -05:00
|
|
|
const [result] = await pool.query(
|
|
|
|
|
`DELETE FROM executions
|
|
|
|
|
WHERE status IN ('completed', 'failed')
|
|
|
|
|
AND started_at < DATE_SUB(NOW(), INTERVAL ? DAY)`,
|
|
|
|
|
[retentionDays]
|
|
|
|
|
);
|
2026-03-03 16:04:22 -05:00
|
|
|
if (result.affectedRows > 0) {
|
|
|
|
|
console.log(`[Cleanup] Removed ${result.affectedRows} executions older than ${retentionDays} day(s)`);
|
|
|
|
|
}
|
2026-01-07 22:50:39 -05:00
|
|
|
} catch (error) {
|
|
|
|
|
console.error('[Cleanup] Error removing old executions:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-03 16:04:22 -05:00
|
|
|
// Run cleanup hourly
|
|
|
|
|
setInterval(cleanupOldExecutions, 60 * 60 * 1000);
|
2026-01-07 22:50:39 -05:00
|
|
|
// Run cleanup on startup
|
|
|
|
|
cleanupOldExecutions();
|
|
|
|
|
|
2026-01-07 23:13:27 -05:00
|
|
|
// Scheduled Commands Processor
|
|
|
|
|
async function processScheduledCommands() {
|
|
|
|
|
try {
|
|
|
|
|
const [schedules] = await pool.query(
|
|
|
|
|
`SELECT * FROM scheduled_commands
|
|
|
|
|
WHERE enabled = TRUE
|
|
|
|
|
AND (next_run IS NULL OR next_run <= NOW())`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for (const schedule of schedules) {
|
2026-03-12 17:30:32 -04:00
|
|
|
// Atomically claim this run by advancing next_run before doing any work.
|
|
|
|
|
// If two scheduler instances race, only the one that updates a row proceeds.
|
|
|
|
|
const claimNextRun = calculateNextRun(schedule.schedule_type, schedule.schedule_value);
|
|
|
|
|
const [claimed] = await pool.query(
|
|
|
|
|
`UPDATE scheduled_commands SET next_run = ?, last_run = NOW()
|
|
|
|
|
WHERE id = ? AND (next_run IS NULL OR next_run <= NOW())`,
|
|
|
|
|
[claimNextRun, schedule.id]
|
|
|
|
|
);
|
|
|
|
|
if (claimed.affectedRows === 0) {
|
|
|
|
|
console.log(`[Scheduler] Skipping "${schedule.name}" - already claimed by another run`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Also skip if a previous run is still active
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
const [runningExecs] = await pool.query(
|
|
|
|
|
"SELECT id FROM executions WHERE started_by = ? AND status = 'running'",
|
|
|
|
|
[`scheduler:${schedule.name}`]
|
|
|
|
|
);
|
|
|
|
|
if (runningExecs.length > 0) {
|
|
|
|
|
console.log(`[Scheduler] Skipping "${schedule.name}" - previous execution still running`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-07 23:13:27 -05:00
|
|
|
console.log(`[Scheduler] Running scheduled command: ${schedule.name}`);
|
|
|
|
|
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
// Handle both string (raw SQL) and object (auto-parsed by MySQL2 JSON column)
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
let workerIds;
|
|
|
|
|
try {
|
|
|
|
|
workerIds = typeof schedule.worker_ids === 'string'
|
|
|
|
|
? JSON.parse(schedule.worker_ids)
|
|
|
|
|
: schedule.worker_ids;
|
|
|
|
|
} catch {
|
|
|
|
|
console.error(`[Scheduler] Invalid worker_ids JSON for "${schedule.name}" — skipping`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-01-07 23:13:27 -05:00
|
|
|
|
|
|
|
|
// Execute command on each worker
|
|
|
|
|
for (const workerId of workerIds) {
|
|
|
|
|
const workerWs = workers.get(workerId);
|
|
|
|
|
if (workerWs && workerWs.readyState === WebSocket.OPEN) {
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
const executionId = crypto.randomUUID();
|
2026-01-07 23:13:27 -05:00
|
|
|
|
|
|
|
|
// Create execution record
|
|
|
|
|
await pool.query(
|
|
|
|
|
'INSERT INTO executions (id, workflow_id, status, started_by, started_at, logs) VALUES (?, ?, ?, ?, NOW(), ?)',
|
|
|
|
|
[executionId, null, 'running', `scheduler:${schedule.name}`, JSON.stringify([{
|
|
|
|
|
step: 'scheduled_command',
|
|
|
|
|
action: 'command_sent',
|
|
|
|
|
worker_id: workerId,
|
|
|
|
|
command: schedule.command,
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
}])]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Send command to worker
|
|
|
|
|
workerWs.send(JSON.stringify({
|
|
|
|
|
type: 'execute_command',
|
|
|
|
|
execution_id: executionId,
|
|
|
|
|
command: schedule.command,
|
|
|
|
|
worker_id: workerId,
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
timeout: 5 * 60_000 // SCHEDULED_CMD_TIMEOUT
|
2026-01-07 23:13:27 -05:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
broadcast({ type: 'execution_started', execution_id: executionId, workflow_id: null });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-12 17:30:32 -04:00
|
|
|
// next_run and last_run already updated atomically above when we claimed the slot
|
2026-01-07 23:13:27 -05:00
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('[Scheduler] Error processing scheduled commands:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function calculateNextRun(scheduleType, scheduleValue) {
|
|
|
|
|
const now = new Date();
|
|
|
|
|
|
|
|
|
|
if (scheduleType === 'interval') {
|
|
|
|
|
// Interval in minutes
|
|
|
|
|
const minutes = parseInt(scheduleValue);
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
if (isNaN(minutes) || minutes <= 0) throw new Error(`Invalid interval value: ${scheduleValue}`);
|
2026-01-07 23:13:27 -05:00
|
|
|
return new Date(now.getTime() + minutes * 60000);
|
|
|
|
|
} else if (scheduleType === 'daily') {
|
|
|
|
|
// Daily at HH:MM
|
|
|
|
|
const [hours, minutes] = scheduleValue.split(':').map(Number);
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
if (isNaN(hours) || isNaN(minutes)) throw new Error(`Invalid daily time format: ${scheduleValue}`);
|
2026-01-07 23:13:27 -05:00
|
|
|
const next = new Date(now);
|
|
|
|
|
next.setHours(hours, minutes, 0, 0);
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
if (next <= now) next.setDate(next.getDate() + 1);
|
2026-01-07 23:13:27 -05:00
|
|
|
return next;
|
|
|
|
|
} else if (scheduleType === 'hourly') {
|
|
|
|
|
// Every N hours
|
|
|
|
|
const hours = parseInt(scheduleValue);
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
if (isNaN(hours) || hours <= 0) throw new Error(`Invalid hourly value: ${scheduleValue}`);
|
2026-01-07 23:13:27 -05:00
|
|
|
return new Date(now.getTime() + hours * 3600000);
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
} else if (scheduleType === 'cron') {
|
|
|
|
|
// Full cron expression e.g. "0 2 * * 0" (Sundays at 2am)
|
|
|
|
|
const interval = cronParser.parseExpression(scheduleValue, { currentDate: now });
|
|
|
|
|
return interval.next().toDate();
|
2026-01-07 23:13:27 -05:00
|
|
|
}
|
|
|
|
|
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
throw new Error(`Unknown schedule type: ${scheduleType}`);
|
2026-01-07 23:13:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Run scheduler every minute
|
|
|
|
|
setInterval(processScheduledCommands, 60 * 1000);
|
|
|
|
|
// Initial run on startup
|
|
|
|
|
setTimeout(processScheduledCommands, 5000);
|
|
|
|
|
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
// Mark workers offline when their heartbeat goes stale
|
|
|
|
|
async function markStaleWorkersOffline() {
|
|
|
|
|
try {
|
|
|
|
|
const [stale] = await pool.query(
|
|
|
|
|
`SELECT id FROM workers WHERE status = 'online'
|
|
|
|
|
AND last_heartbeat < DATE_SUB(NOW(), INTERVAL ? MINUTE)`,
|
|
|
|
|
[WORKER_STALE_MINUTES]
|
|
|
|
|
);
|
|
|
|
|
for (const w of stale) {
|
|
|
|
|
await pool.query(`UPDATE workers SET status='offline' WHERE id=?`, [w.id]);
|
|
|
|
|
broadcast({ type: 'worker_update', worker_id: w.id, status: 'offline' });
|
|
|
|
|
console.log(`[Worker] Marked stale worker ${w.id} offline`);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('[Worker] Stale check error:', error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
setInterval(markStaleWorkersOffline, 60_000);
|
|
|
|
|
|
2025-11-29 19:26:20 -05:00
|
|
|
// WebSocket connections
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
const browserClients = new Set(); // Browser UI connections
|
|
|
|
|
const workerClients = new Set(); // Worker agent connections
|
2026-01-07 22:23:02 -05:00
|
|
|
const workers = new Map(); // Map worker_id -> WebSocket connection
|
|
|
|
|
|
2025-11-29 19:26:20 -05:00
|
|
|
wss.on('connection', (ws) => {
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
// Default to browser client until worker_connect identifies it as a worker
|
|
|
|
|
browserClients.add(ws);
|
2026-01-07 20:20:18 -05:00
|
|
|
|
|
|
|
|
// Handle incoming messages from workers
|
|
|
|
|
ws.on('message', async (data) => {
|
|
|
|
|
try {
|
|
|
|
|
const message = JSON.parse(data.toString());
|
|
|
|
|
|
|
|
|
|
if (message.type === 'command_result') {
|
|
|
|
|
// Handle command result from worker
|
2026-01-07 23:19:12 -05:00
|
|
|
const { execution_id, command_id, worker_id, success, stdout, stderr, duration, timestamp } = message;
|
2026-01-07 20:20:18 -05:00
|
|
|
|
|
|
|
|
// Add result to execution logs
|
|
|
|
|
await addExecutionLog(execution_id, {
|
|
|
|
|
step: 'command_execution',
|
|
|
|
|
action: 'command_result',
|
2026-01-07 23:19:12 -05:00
|
|
|
command_id: command_id, // Include command_id for workflow tracking
|
2026-01-07 20:20:18 -05:00
|
|
|
worker_id: worker_id,
|
|
|
|
|
success: success,
|
|
|
|
|
stdout: stdout,
|
|
|
|
|
stderr: stderr,
|
|
|
|
|
duration: duration,
|
|
|
|
|
timestamp: timestamp || new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-07 23:19:12 -05:00
|
|
|
// For non-workflow executions, update status immediately
|
|
|
|
|
// For workflow executions, the workflow engine will update status
|
2026-03-04 16:26:18 -05:00
|
|
|
const [execution] = await pool.query('SELECT workflow_id, started_by FROM executions WHERE id = ?', [execution_id]);
|
|
|
|
|
const startedBy = execution.length > 0 ? (execution[0].started_by || '') : '';
|
|
|
|
|
const isAutomated = startedBy.startsWith('gandalf:') || startedBy.startsWith('scheduler:');
|
2026-01-07 23:19:12 -05:00
|
|
|
if (execution.length > 0 && !execution[0].workflow_id) {
|
|
|
|
|
// Only update status for quick commands (no workflow_id)
|
|
|
|
|
const finalStatus = success ? 'completed' : 'failed';
|
|
|
|
|
await updateExecutionStatus(execution_id, finalStatus);
|
|
|
|
|
}
|
2026-01-07 22:23:02 -05:00
|
|
|
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
// Resolve any pending event-driven command promise (eliminates DB polling)
|
|
|
|
|
if (command_id) {
|
|
|
|
|
const resolver = _commandResolvers.get(command_id);
|
|
|
|
|
if (resolver) {
|
|
|
|
|
resolver({ success, stdout, stderr });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Broadcast result to browser clients only
|
2026-01-07 20:20:18 -05:00
|
|
|
broadcast({
|
|
|
|
|
type: 'command_result',
|
|
|
|
|
execution_id: execution_id,
|
|
|
|
|
worker_id: worker_id,
|
|
|
|
|
success: success,
|
|
|
|
|
stdout: stdout,
|
2026-03-04 16:26:18 -05:00
|
|
|
stderr: stderr,
|
|
|
|
|
is_automated: isAutomated,
|
2026-01-07 20:20:18 -05:00
|
|
|
});
|
|
|
|
|
|
2026-03-13 13:48:35 -04:00
|
|
|
if (!success) console.warn(`[Worker] Command failed for execution ${execution_id} on worker ${worker_id}`);
|
2026-01-07 20:20:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (message.type === 'workflow_result') {
|
|
|
|
|
// Handle workflow result from worker
|
|
|
|
|
const { execution_id, worker_id, success, message: resultMessage, timestamp } = message;
|
|
|
|
|
|
|
|
|
|
// Add final result to logs
|
|
|
|
|
await addExecutionLog(execution_id, {
|
|
|
|
|
step: 'workflow_completion',
|
|
|
|
|
action: 'workflow_result',
|
|
|
|
|
worker_id: worker_id,
|
|
|
|
|
success: success,
|
|
|
|
|
message: resultMessage,
|
|
|
|
|
timestamp: timestamp || new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Update execution status
|
|
|
|
|
const finalStatus = success ? 'completed' : 'failed';
|
|
|
|
|
await updateExecutionStatus(execution_id, finalStatus);
|
|
|
|
|
|
|
|
|
|
// Broadcast completion to all clients
|
|
|
|
|
broadcast({
|
|
|
|
|
type: 'workflow_result',
|
|
|
|
|
execution_id: execution_id,
|
|
|
|
|
status: finalStatus,
|
|
|
|
|
success: success,
|
|
|
|
|
message: resultMessage
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log(`Workflow result received for execution ${execution_id}: ${finalStatus}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (message.type === 'worker_connect') {
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
// Authenticate worker — reject if api_key is provided but wrong
|
|
|
|
|
const { worker_id, worker_name, api_key } = message;
|
|
|
|
|
if (api_key && api_key !== process.env.WORKER_API_KEY) {
|
|
|
|
|
console.warn(`[Security] Worker connection rejected: invalid API key from "${worker_name}"`);
|
|
|
|
|
ws.close(4001, 'Unauthorized');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Move from browser set to worker set
|
|
|
|
|
browserClients.delete(ws);
|
|
|
|
|
workerClients.add(ws);
|
2026-01-07 20:20:18 -05:00
|
|
|
console.log(`Worker connected: ${worker_name} (${worker_id})`);
|
|
|
|
|
|
2026-01-07 22:29:23 -05:00
|
|
|
// Find the database worker ID by name
|
|
|
|
|
const [dbWorkers] = await pool.query(
|
|
|
|
|
'SELECT id FROM workers WHERE name = ?',
|
|
|
|
|
[worker_name]
|
2026-01-07 20:20:18 -05:00
|
|
|
);
|
|
|
|
|
|
2026-01-07 22:29:23 -05:00
|
|
|
if (dbWorkers.length > 0) {
|
|
|
|
|
const dbWorkerId = dbWorkers[0].id;
|
|
|
|
|
|
2026-03-12 17:30:32 -04:00
|
|
|
// Clean up any stale entry for this db worker before storing the new one
|
|
|
|
|
// (handles reconnect: old runtime-ID entry would otherwise linger).
|
|
|
|
|
for (const [key, val] of workers) {
|
|
|
|
|
if (val.dbWorkerId === dbWorkerId && val !== ws) workers.delete(key);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-07 22:29:23 -05:00
|
|
|
// Store worker WebSocket connection using BOTH IDs
|
|
|
|
|
workers.set(worker_id, ws); // Runtime ID
|
|
|
|
|
workers.set(dbWorkerId, ws); // Database ID
|
|
|
|
|
|
|
|
|
|
// Store mapping for cleanup
|
|
|
|
|
ws.workerId = worker_id;
|
|
|
|
|
ws.dbWorkerId = dbWorkerId;
|
|
|
|
|
|
|
|
|
|
console.log(`Mapped worker: runtime_id=${worker_id}, db_id=${dbWorkerId}, name=${worker_name}`);
|
|
|
|
|
|
|
|
|
|
// Update worker status to online
|
|
|
|
|
await pool.query(
|
|
|
|
|
`UPDATE workers SET status='online', last_heartbeat=NOW() WHERE id=?`,
|
|
|
|
|
[dbWorkerId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Broadcast worker status update with database ID
|
|
|
|
|
broadcast({
|
|
|
|
|
type: 'worker_update',
|
|
|
|
|
worker_id: dbWorkerId,
|
|
|
|
|
status: 'online'
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
console.log(`Worker ${worker_name} not found in database, will be created on heartbeat`);
|
|
|
|
|
}
|
2026-01-07 20:20:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (message.type === 'pong') {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
// Use the DB worker ID stored on connect; fall back to message payload
|
|
|
|
|
const dbId = ws.dbWorkerId || message.worker_id;
|
|
|
|
|
if (dbId) {
|
|
|
|
|
await pool.query(
|
|
|
|
|
`UPDATE workers SET last_heartbeat=NOW() WHERE id=?`,
|
|
|
|
|
[dbId]
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-01-07 20:20:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('WebSocket message error:', error);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-07 22:23:02 -05:00
|
|
|
ws.on('close', () => {
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
browserClients.delete(ws);
|
|
|
|
|
workerClients.delete(ws);
|
2026-01-07 22:29:23 -05:00
|
|
|
if (ws.workerId) {
|
|
|
|
|
workers.delete(ws.workerId);
|
|
|
|
|
console.log(`Worker ${ws.workerId} (runtime ID) disconnected`);
|
|
|
|
|
}
|
|
|
|
|
if (ws.dbWorkerId) {
|
|
|
|
|
workers.delete(ws.dbWorkerId);
|
|
|
|
|
console.log(`Worker ${ws.dbWorkerId} (database ID) disconnected`);
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
// Mark worker offline in DB
|
|
|
|
|
pool.query(`UPDATE workers SET status='offline' WHERE id=?`, [ws.dbWorkerId])
|
|
|
|
|
.then(() => broadcast({ type: 'worker_update', worker_id: ws.dbWorkerId, status: 'offline' }))
|
|
|
|
|
.catch(err => console.error('[Worker] Failed to mark worker offline:', err));
|
2026-01-07 22:23:02 -05:00
|
|
|
}
|
|
|
|
|
});
|
2025-11-29 19:26:20 -05:00
|
|
|
});
|
|
|
|
|
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
// Broadcast to browser clients only (NOT worker agents)
|
2025-11-29 19:26:20 -05:00
|
|
|
function broadcast(data) {
|
2026-03-12 17:30:32 -04:00
|
|
|
// Snapshot the Set before iterating — a close event during iteration would
|
|
|
|
|
// otherwise modify the Set in-place, causing skipped or double-visited entries.
|
|
|
|
|
const snapshot = Array.from(browserClients);
|
|
|
|
|
snapshot.forEach(client => {
|
2025-11-29 19:26:20 -05:00
|
|
|
if (client.readyState === WebSocket.OPEN) {
|
|
|
|
|
client.send(JSON.stringify(data));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
// Helper function to add log entry to execution (atomic — no read-modify-write race condition)
|
2026-01-08 22:03:00 -05:00
|
|
|
async function addExecutionLog(executionId, logEntry) {
|
|
|
|
|
try {
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
await pool.query(
|
|
|
|
|
"UPDATE executions SET logs = JSON_ARRAY_APPEND(COALESCE(logs, '[]'), '$', CAST(? AS JSON)) WHERE id = ?",
|
|
|
|
|
[JSON.stringify(logEntry), executionId]
|
|
|
|
|
);
|
2026-01-08 22:03:00 -05:00
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`[Workflow] Error adding execution log:`, error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper function to update execution status
|
|
|
|
|
async function updateExecutionStatus(executionId, status) {
|
|
|
|
|
try {
|
|
|
|
|
await pool.query(
|
|
|
|
|
'UPDATE executions SET status = ?, completed_at = NOW() WHERE id = ?',
|
|
|
|
|
[status, executionId]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
broadcast({
|
|
|
|
|
type: 'execution_status',
|
|
|
|
|
execution_id: executionId,
|
|
|
|
|
status: status
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log(`[Workflow] Execution ${executionId} status updated to: ${status}`);
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
|
|
|
|
|
// Fire webhook if configured on the workflow and execution reached a terminal state
|
|
|
|
|
if (status === 'completed' || status === 'failed') {
|
|
|
|
|
fireWebhook(executionId, status).catch(err => {
|
|
|
|
|
console.error(`[Webhook] Delivery error for execution ${executionId}:`, err.message);
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-01-08 22:03:00 -05:00
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`[Workflow] Error updating execution status:`, error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
// Fire optional webhook on workflow completion/failure
|
|
|
|
|
async function fireWebhook(executionId, status) {
|
|
|
|
|
const [rows] = await pool.query(
|
|
|
|
|
`SELECT e.id, e.workflow_id, e.started_by, e.started_at, e.completed_at,
|
|
|
|
|
w.webhook_url, w.name as workflow_name
|
|
|
|
|
FROM executions e
|
|
|
|
|
LEFT JOIN workflows w ON e.workflow_id = w.id
|
|
|
|
|
WHERE e.id = ?`,
|
|
|
|
|
[executionId]
|
|
|
|
|
);
|
|
|
|
|
if (!rows.length || !rows[0].webhook_url) return;
|
|
|
|
|
|
|
|
|
|
const exec = rows[0];
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
const { ok, url, reason } = validateWebhookUrl(exec.webhook_url);
|
|
|
|
|
if (!ok) {
|
|
|
|
|
console.warn(`[Webhook] Skipping invalid stored URL for execution ${executionId}: ${reason}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
const payload = {
|
|
|
|
|
execution_id: executionId,
|
|
|
|
|
workflow_id: exec.workflow_id,
|
|
|
|
|
workflow_name: exec.workflow_name,
|
|
|
|
|
status,
|
|
|
|
|
started_by: exec.started_by,
|
|
|
|
|
started_at: exec.started_at,
|
|
|
|
|
completed_at: exec.completed_at,
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
};
|
|
|
|
|
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
const response = await fetch(exec.webhook_url, {
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
method: 'POST',
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
headers: { 'Content-Type': 'application/json', 'User-Agent': 'PULSE-Webhook/1.0' },
|
|
|
|
|
body: JSON.stringify(payload),
|
|
|
|
|
signal: AbortSignal.timeout(WEBHOOK_TIMEOUT_MS)
|
|
|
|
|
});
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
console.log(`[Webhook] ${url.hostname} responded ${response.status} for execution ${executionId}`);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-29 19:26:20 -05:00
|
|
|
// Authelia SSO Middleware
|
|
|
|
|
async function authenticateSSO(req, res, next) {
|
|
|
|
|
// Check for Authelia headers
|
|
|
|
|
const remoteUser = req.headers['remote-user'];
|
|
|
|
|
const remoteName = req.headers['remote-name'];
|
|
|
|
|
const remoteEmail = req.headers['remote-email'];
|
|
|
|
|
const remoteGroups = req.headers['remote-groups'];
|
|
|
|
|
|
|
|
|
|
if (!remoteUser) {
|
|
|
|
|
return res.status(401).json({
|
|
|
|
|
error: 'Not authenticated',
|
|
|
|
|
message: 'Please access this service through auth.lotusguild.org'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if user is in allowed groups (admin or employee)
|
|
|
|
|
const groups = remoteGroups ? remoteGroups.split(',').map(g => g.trim()) : [];
|
|
|
|
|
const allowedGroups = ['admin', 'employee'];
|
|
|
|
|
const hasAccess = groups.some(g => allowedGroups.includes(g));
|
|
|
|
|
|
|
|
|
|
if (!hasAccess) {
|
|
|
|
|
return res.status(403).json({
|
|
|
|
|
error: 'Access denied',
|
|
|
|
|
message: 'You must be in admin or employee group'
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Store/update user in database
|
|
|
|
|
try {
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
const userId = crypto.randomUUID();
|
2025-11-29 19:26:20 -05:00
|
|
|
await pool.query(
|
|
|
|
|
`INSERT INTO users (id, username, display_name, email, groups, last_login)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, NOW())
|
|
|
|
|
ON DUPLICATE KEY UPDATE
|
|
|
|
|
display_name=VALUES(display_name),
|
|
|
|
|
email=VALUES(email),
|
|
|
|
|
groups=VALUES(groups),
|
|
|
|
|
last_login=NOW()`,
|
|
|
|
|
[userId, remoteUser, remoteName, remoteEmail, remoteGroups]
|
|
|
|
|
);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error updating user:', error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Attach user info to request
|
|
|
|
|
req.user = {
|
|
|
|
|
username: remoteUser,
|
|
|
|
|
name: remoteName || remoteUser,
|
|
|
|
|
email: remoteEmail || '',
|
|
|
|
|
groups: groups,
|
|
|
|
|
isAdmin: groups.includes('admin')
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
next();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-03 16:04:22 -05:00
|
|
|
// Gandalf machine-to-machine API key auth
|
|
|
|
|
function authenticateGandalf(req, res, next) {
|
|
|
|
|
const apiKey = req.headers['x-gandalf-api-key'];
|
|
|
|
|
if (!apiKey || apiKey !== process.env.GANDALF_API_KEY) {
|
|
|
|
|
return res.status(401).json({ error: 'Unauthorized' });
|
|
|
|
|
}
|
|
|
|
|
req.user = { username: 'gandalf:link_stats', isAdmin: false };
|
|
|
|
|
next();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-07 23:19:12 -05:00
|
|
|
// Workflow Execution Engine
|
2026-03-03 16:20:05 -05:00
|
|
|
|
|
|
|
|
// Substitute {{param_name}} placeholders in a command string.
|
|
|
|
|
// Only alphanumeric + safe punctuation allowed in substituted values.
|
|
|
|
|
function applyParams(command, params) {
|
|
|
|
|
return command.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
|
|
|
if (!(key in params)) return match;
|
|
|
|
|
const val = String(params[key]).trim();
|
|
|
|
|
if (!/^[a-zA-Z0-9._:@\-\/]+$/.test(val)) {
|
|
|
|
|
throw new Error(`Unsafe value for workflow parameter "${key}"`);
|
|
|
|
|
}
|
|
|
|
|
return val;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-03 16:55:02 -05:00
|
|
|
// Evaluate a condition string against execution state and params.
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
// Uses vm.runInNewContext with a timeout to avoid arbitrary code execution risk.
|
2026-03-03 16:55:02 -05:00
|
|
|
function evalCondition(condition, state, params) {
|
|
|
|
|
try {
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
const context = vm.createContext({ state, params, promptResponse: state.promptResponse });
|
|
|
|
|
return !!vm.runInNewContext(condition, context, { timeout: 100 });
|
2026-03-03 16:55:02 -05:00
|
|
|
} catch (e) {
|
2026-03-12 17:30:32 -04:00
|
|
|
console.warn(`[Workflow] evalCondition error (treated as false): ${e.message} — condition: ${condition}`);
|
2026-03-03 16:55:02 -05:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Per-execution mutable state (params + user-keyed state dict).
|
|
|
|
|
// Survives across step boundaries; cleaned up when execution ends.
|
|
|
|
|
const _executionState = new Map(); // executionId → { params, state }
|
|
|
|
|
|
|
|
|
|
// Pending prompt resolvers — set when a prompt step is waiting for user input.
|
|
|
|
|
const _executionPrompts = new Map(); // executionId → resolve fn
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
const _commandResolvers = new Map(); // commandId → resolve fn (event-driven result delivery)
|
2026-03-03 16:55:02 -05:00
|
|
|
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
async function executeWorkflowSteps(executionId, workflowId, definition, username, params = {}, dryRun = false) {
|
2026-03-03 16:55:02 -05:00
|
|
|
_executionState.set(executionId, { params, state: {} });
|
|
|
|
|
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
// Global execution timeout
|
|
|
|
|
const maxMinutes = parseInt(process.env.EXECUTION_MAX_MINUTES) || 60;
|
|
|
|
|
const executionDeadline = Date.now() + maxMinutes * 60000;
|
|
|
|
|
|
2026-01-07 23:19:12 -05:00
|
|
|
try {
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
console.log(`[Workflow] Starting execution ${executionId} for workflow ${workflowId}${dryRun ? ' [DRY RUN]' : ''}`);
|
2026-01-07 23:19:12 -05:00
|
|
|
|
|
|
|
|
const steps = definition.steps || [];
|
|
|
|
|
|
2026-03-03 16:55:02 -05:00
|
|
|
// Build step-id → index map for goto support
|
|
|
|
|
const stepIdMap = new Map();
|
|
|
|
|
steps.forEach((step, i) => { if (step.id) stepIdMap.set(step.id, i); });
|
|
|
|
|
|
|
|
|
|
let currentIndex = 0;
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
const stepVisits = new Array(steps.length).fill(0);
|
2026-03-03 16:55:02 -05:00
|
|
|
|
|
|
|
|
while (currentIndex < steps.length) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
// Detect goto infinite loops
|
|
|
|
|
stepVisits[currentIndex] = (stepVisits[currentIndex] || 0) + 1;
|
|
|
|
|
if (stepVisits[currentIndex] > GOTO_MAX_VISITS) {
|
|
|
|
|
await addExecutionLog(executionId, {
|
|
|
|
|
action: 'workflow_error',
|
|
|
|
|
error: `Infinite loop detected at step ${currentIndex + 1} (visited ${stepVisits[currentIndex]} times)`,
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
await updateExecutionStatus(executionId, 'failed');
|
|
|
|
|
return;
|
|
|
|
|
}
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
// Enforce global execution timeout
|
|
|
|
|
if (Date.now() > executionDeadline) {
|
|
|
|
|
await addExecutionLog(executionId, {
|
|
|
|
|
action: 'execution_timeout',
|
|
|
|
|
message: `Execution exceeded maximum runtime of ${maxMinutes} minutes`,
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
await updateExecutionStatus(executionId, 'failed');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-03 16:55:02 -05:00
|
|
|
const step = steps[currentIndex];
|
|
|
|
|
const execState = _executionState.get(executionId);
|
2026-03-12 17:30:32 -04:00
|
|
|
if (!execState) {
|
|
|
|
|
// State was lost (server restart or bug) — fail cleanly rather than throwing TypeError
|
|
|
|
|
await addExecutionLog(executionId, {
|
|
|
|
|
action: 'workflow_error',
|
|
|
|
|
error: 'Execution state lost unexpectedly; aborting.',
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
await updateExecutionStatus(executionId, 'failed');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-03-03 16:55:02 -05:00
|
|
|
const stepLabel = step.name || step.id || `Step ${currentIndex + 1}`;
|
|
|
|
|
console.log(`[Workflow] ${executionId} — step ${currentIndex + 1}: ${stepLabel}`);
|
2026-01-07 23:19:12 -05:00
|
|
|
|
|
|
|
|
await addExecutionLog(executionId, {
|
2026-03-03 16:55:02 -05:00
|
|
|
step: currentIndex + 1, step_name: stepLabel, action: 'step_started',
|
2026-01-07 23:19:12 -05:00
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
2026-03-03 16:55:02 -05:00
|
|
|
broadcast({ type: 'workflow_step_started', execution_id: executionId,
|
|
|
|
|
step: currentIndex + 1, step_name: stepLabel });
|
2026-01-07 23:19:12 -05:00
|
|
|
|
2026-03-03 16:55:02 -05:00
|
|
|
let gotoId = step.goto || null; // may be overridden by prompt routes
|
2026-01-07 23:19:12 -05:00
|
|
|
|
|
|
|
|
if (step.type === 'execute') {
|
2026-03-03 16:55:02 -05:00
|
|
|
const condOk = !step.condition || evalCondition(step.condition, execState.state, execState.params);
|
|
|
|
|
if (condOk) {
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
if (dryRun) {
|
|
|
|
|
await addExecutionLog(executionId, {
|
|
|
|
|
step: currentIndex + 1, step_name: stepLabel, action: 'dry_run_skipped',
|
|
|
|
|
command: step.command, targets: step.targets || ['all'],
|
|
|
|
|
message: '[DRY RUN] Command not executed',
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
const success = await executeCommandStep(executionId, step, currentIndex + 1, params);
|
|
|
|
|
if (!success) {
|
|
|
|
|
await updateExecutionStatus(executionId, 'failed');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-03-03 16:55:02 -05:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
await addExecutionLog(executionId, {
|
|
|
|
|
step: currentIndex + 1, action: 'step_skipped',
|
|
|
|
|
reason: 'condition false', timestamp: new Date().toISOString()
|
|
|
|
|
});
|
2026-01-07 23:19:12 -05:00
|
|
|
}
|
2026-03-03 16:55:02 -05:00
|
|
|
|
2026-01-07 23:19:12 -05:00
|
|
|
} else if (step.type === 'prompt') {
|
2026-03-03 16:55:02 -05:00
|
|
|
const response = await executePromptStep(executionId, step, currentIndex + 1);
|
|
|
|
|
if (response !== null) {
|
|
|
|
|
execState.state[step.key || 'lastPrompt'] = response;
|
|
|
|
|
execState.state.promptResponse = response; // backwards compat
|
|
|
|
|
// Prompt routes override goto
|
|
|
|
|
if (step.routes && step.routes[response]) {
|
|
|
|
|
gotoId = step.routes[response];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else if (step.type === 'wait') {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
let ms = parseFloat(step.duration || 5) * 1000;
|
|
|
|
|
if (isNaN(ms) || ms < 0) ms = 5000;
|
|
|
|
|
if (ms > WAIT_STEP_MAX_MS) ms = WAIT_STEP_MAX_MS;
|
2026-01-07 23:19:12 -05:00
|
|
|
await addExecutionLog(executionId, {
|
2026-03-03 16:55:02 -05:00
|
|
|
step: currentIndex + 1, action: 'waiting', duration_ms: ms,
|
2026-01-07 23:19:12 -05:00
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
2026-03-03 16:55:02 -05:00
|
|
|
await new Promise(r => setTimeout(r, ms));
|
2026-01-07 23:19:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await addExecutionLog(executionId, {
|
2026-03-03 16:55:02 -05:00
|
|
|
step: currentIndex + 1, step_name: stepLabel, action: 'step_completed',
|
2026-01-07 23:19:12 -05:00
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
2026-03-03 16:55:02 -05:00
|
|
|
broadcast({ type: 'workflow_step_completed', execution_id: executionId,
|
|
|
|
|
step: currentIndex + 1, step_name: stepLabel });
|
|
|
|
|
|
|
|
|
|
// Determine next step
|
|
|
|
|
if (gotoId === 'end' || gotoId === '__end__') {
|
|
|
|
|
break;
|
|
|
|
|
} else if (gotoId) {
|
|
|
|
|
if (stepIdMap.has(gotoId)) {
|
|
|
|
|
currentIndex = stepIdMap.get(gotoId);
|
|
|
|
|
} else {
|
|
|
|
|
await addExecutionLog(executionId, {
|
|
|
|
|
action: 'goto_error', target: gotoId,
|
|
|
|
|
message: `No step with id "${gotoId}" found`,
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
currentIndex++;
|
|
|
|
|
}
|
2026-01-07 23:19:12 -05:00
|
|
|
}
|
|
|
|
|
|
2026-03-03 16:55:02 -05:00
|
|
|
await updateExecutionStatus(executionId, 'completed');
|
|
|
|
|
console.log(`[Workflow] Execution ${executionId} completed`);
|
2026-01-07 23:19:12 -05:00
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`[Workflow] Execution ${executionId} error:`, error);
|
|
|
|
|
await addExecutionLog(executionId, {
|
2026-03-03 16:55:02 -05:00
|
|
|
action: 'workflow_error', error: error.message,
|
2026-01-07 23:19:12 -05:00
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
await updateExecutionStatus(executionId, 'failed');
|
2026-03-03 16:55:02 -05:00
|
|
|
} finally {
|
|
|
|
|
_executionState.delete(executionId);
|
|
|
|
|
_executionPrompts.delete(executionId);
|
2026-01-07 23:19:12 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-03 16:55:02 -05:00
|
|
|
// Pause execution and wait for user to respond via POST /api/executions/:id/respond.
|
|
|
|
|
// Resolves with the chosen option string, or null on 60-minute timeout.
|
|
|
|
|
async function executePromptStep(executionId, step, stepNumber) {
|
|
|
|
|
const message = step.message || 'Please choose an option:';
|
|
|
|
|
const options = step.options || ['Continue'];
|
|
|
|
|
|
|
|
|
|
await addExecutionLog(executionId, {
|
|
|
|
|
step: stepNumber, step_name: step.name, action: 'prompt',
|
|
|
|
|
message, options, timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
broadcast({
|
|
|
|
|
type: 'execution_prompt',
|
|
|
|
|
execution_id: executionId,
|
|
|
|
|
prompt: { message, options, step: stepNumber, step_name: step.name }
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return new Promise(resolve => {
|
|
|
|
|
const timer = setTimeout(() => {
|
|
|
|
|
_executionPrompts.delete(executionId);
|
|
|
|
|
console.warn(`[Workflow] Prompt timed out for execution ${executionId}`);
|
|
|
|
|
resolve(null);
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
}, PROMPT_TIMEOUT_MS);
|
|
|
|
|
|
|
|
|
|
_executionPrompts.set(executionId, {
|
|
|
|
|
resolve: (response) => {
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
_executionPrompts.delete(executionId);
|
|
|
|
|
resolve(response);
|
|
|
|
|
},
|
|
|
|
|
options
|
2026-03-03 16:55:02 -05:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-03 16:20:05 -05:00
|
|
|
async function executeCommandStep(executionId, step, stepNumber, params = {}) {
|
2026-01-07 23:19:12 -05:00
|
|
|
try {
|
2026-03-03 16:20:05 -05:00
|
|
|
let command = step.command;
|
|
|
|
|
if (Object.keys(params).length > 0) {
|
|
|
|
|
command = applyParams(command, params);
|
|
|
|
|
}
|
2026-01-07 23:19:12 -05:00
|
|
|
const targets = step.targets || ['all'];
|
|
|
|
|
|
|
|
|
|
// Determine which workers to target
|
|
|
|
|
let targetWorkerIds = [];
|
|
|
|
|
|
|
|
|
|
if (targets.includes('all')) {
|
|
|
|
|
// Get all online workers
|
|
|
|
|
const [onlineWorkers] = await pool.query('SELECT id FROM workers WHERE status = ?', ['online']);
|
|
|
|
|
targetWorkerIds = onlineWorkers.map(w => w.id);
|
|
|
|
|
} else {
|
|
|
|
|
// Specific worker IDs or names
|
|
|
|
|
for (const target of targets) {
|
|
|
|
|
// Try to find by ID first, then by name
|
|
|
|
|
const [workerById] = await pool.query('SELECT id FROM workers WHERE id = ?', [target]);
|
|
|
|
|
if (workerById.length > 0) {
|
|
|
|
|
targetWorkerIds.push(workerById[0].id);
|
|
|
|
|
} else {
|
|
|
|
|
const [workerByName] = await pool.query('SELECT id FROM workers WHERE name = ?', [target]);
|
|
|
|
|
if (workerByName.length > 0) {
|
|
|
|
|
targetWorkerIds.push(workerByName[0].id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (targetWorkerIds.length === 0) {
|
|
|
|
|
await addExecutionLog(executionId, {
|
|
|
|
|
step: stepNumber,
|
|
|
|
|
action: 'no_workers',
|
|
|
|
|
message: 'No workers available for this step',
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Execute command on each target worker and wait for results
|
|
|
|
|
const results = [];
|
2026-03-13 13:48:35 -04:00
|
|
|
let sentCount = 0;
|
2026-01-07 23:19:12 -05:00
|
|
|
|
|
|
|
|
for (const workerId of targetWorkerIds) {
|
|
|
|
|
const workerWs = workers.get(workerId);
|
|
|
|
|
|
|
|
|
|
if (!workerWs || workerWs.readyState !== WebSocket.OPEN) {
|
|
|
|
|
await addExecutionLog(executionId, {
|
|
|
|
|
step: stepNumber,
|
|
|
|
|
action: 'worker_offline',
|
|
|
|
|
worker_id: workerId,
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2026-03-13 13:48:35 -04:00
|
|
|
sentCount++;
|
2026-01-07 23:19:12 -05:00
|
|
|
|
|
|
|
|
// Send command to worker
|
2026-03-12 17:36:35 -04:00
|
|
|
let commandId = crypto.randomUUID();
|
2026-01-07 23:19:12 -05:00
|
|
|
|
|
|
|
|
await addExecutionLog(executionId, {
|
|
|
|
|
step: stepNumber,
|
|
|
|
|
action: 'command_sent',
|
|
|
|
|
worker_id: workerId,
|
|
|
|
|
command: command,
|
|
|
|
|
command_id: commandId,
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-12 17:36:35 -04:00
|
|
|
// Per-step timeout override: step.timeout (seconds) overrides global default
|
|
|
|
|
const stepTimeoutMs = step.timeout
|
|
|
|
|
? Math.min(Math.max(parseInt(step.timeout, 10) * 1000, 5000), 600000)
|
|
|
|
|
: COMMAND_TIMEOUT_MS;
|
|
|
|
|
|
2026-01-07 23:19:12 -05:00
|
|
|
workerWs.send(JSON.stringify({
|
|
|
|
|
type: 'execute_command',
|
|
|
|
|
execution_id: executionId,
|
|
|
|
|
command_id: commandId,
|
|
|
|
|
command: command,
|
|
|
|
|
worker_id: workerId,
|
2026-03-12 17:36:35 -04:00
|
|
|
timeout: stepTimeoutMs
|
2026-01-07 23:19:12 -05:00
|
|
|
}));
|
|
|
|
|
|
2026-03-12 17:36:35 -04:00
|
|
|
// Per-step retry: step.retries (default 0) with step.retryDelayMs (default 2000)
|
|
|
|
|
const maxRetries = Math.min(parseInt(step.retries || 0, 10), 5);
|
|
|
|
|
const retryDelayMs = Math.min(parseInt(step.retryDelayMs || 2000, 10), 30000);
|
|
|
|
|
|
|
|
|
|
let result;
|
|
|
|
|
let attempt = 0;
|
|
|
|
|
while (true) {
|
|
|
|
|
result = await waitForCommandResult(executionId, commandId, stepTimeoutMs);
|
|
|
|
|
if (result.success || attempt >= maxRetries) break;
|
|
|
|
|
attempt++;
|
|
|
|
|
await addExecutionLog(executionId, {
|
|
|
|
|
step: stepNumber,
|
|
|
|
|
action: 'command_retry',
|
|
|
|
|
worker_id: workerId,
|
|
|
|
|
attempt,
|
|
|
|
|
max_retries: maxRetries,
|
|
|
|
|
message: `Command failed, retrying (attempt ${attempt}/${maxRetries})...`,
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
await new Promise(r => setTimeout(r, retryDelayMs));
|
|
|
|
|
// Re-send command for retry
|
|
|
|
|
const retryCommandId = crypto.randomUUID();
|
|
|
|
|
await addExecutionLog(executionId, {
|
|
|
|
|
step: stepNumber, action: 'command_sent', worker_id: workerId,
|
|
|
|
|
command: command, command_id: retryCommandId, timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
workerWs.send(JSON.stringify({
|
|
|
|
|
type: 'execute_command', execution_id: executionId, command_id: retryCommandId,
|
|
|
|
|
command: command, worker_id: workerId, timeout: stepTimeoutMs
|
|
|
|
|
}));
|
|
|
|
|
commandId = retryCommandId; // update for next waitForCommandResult call
|
|
|
|
|
}
|
2026-01-07 23:19:12 -05:00
|
|
|
results.push(result);
|
|
|
|
|
|
|
|
|
|
if (!result.success) {
|
2026-03-12 17:36:35 -04:00
|
|
|
// Command failed after all retries, workflow should stop
|
2026-01-07 23:19:12 -05:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 13:48:35 -04:00
|
|
|
// If every target worker was offline, treat as step failure
|
|
|
|
|
if (sentCount === 0) {
|
|
|
|
|
await addExecutionLog(executionId, {
|
|
|
|
|
step: stepNumber,
|
|
|
|
|
action: 'no_workers',
|
|
|
|
|
message: 'All target workers were offline when step executed',
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true; // all sent commands succeeded (failures return false above)
|
2026-01-07 23:19:12 -05:00
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(`[Workflow] Error executing command step:`, error);
|
|
|
|
|
await addExecutionLog(executionId, {
|
|
|
|
|
step: stepNumber,
|
|
|
|
|
action: 'step_error',
|
|
|
|
|
error: error.message,
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
// Wait for a command result using event-driven promise resolution.
|
|
|
|
|
// The resolver is stored in _commandResolvers and called immediately when
|
|
|
|
|
// command_result arrives via WebSocket — no DB polling required.
|
2026-01-07 23:19:12 -05:00
|
|
|
async function waitForCommandResult(executionId, commandId, timeout) {
|
|
|
|
|
return new Promise((resolve) => {
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
const timer = setTimeout(() => {
|
|
|
|
|
_commandResolvers.delete(commandId);
|
|
|
|
|
resolve({ success: false, error: 'Command timeout' });
|
|
|
|
|
}, timeout);
|
2026-01-07 23:19:12 -05:00
|
|
|
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
_commandResolvers.set(commandId, (result) => {
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
_commandResolvers.delete(commandId);
|
|
|
|
|
resolve(result);
|
|
|
|
|
});
|
2026-01-07 23:19:12 -05:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-29 19:26:20 -05:00
|
|
|
// Routes - All protected by SSO
|
|
|
|
|
app.get('/api/user', authenticateSSO, (req, res) => {
|
|
|
|
|
res.json(req.user);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.get('/api/workflows', authenticateSSO, async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const [rows] = await pool.query('SELECT * FROM workflows ORDER BY created_at DESC');
|
|
|
|
|
res.json(rows);
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
console.error('[API] GET /api/workflows error:', error);
|
|
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2025-11-29 19:26:20 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-03 16:55:02 -05:00
|
|
|
app.get('/api/workflows/:id', authenticateSSO, async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const [rows] = await pool.query('SELECT * FROM workflows WHERE id = ?', [req.params.id]);
|
|
|
|
|
if (rows.length === 0) return res.status(404).json({ error: 'Not found' });
|
|
|
|
|
const wf = rows[0];
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
let definition = {};
|
|
|
|
|
try { definition = JSON.parse(wf.definition || '{}'); } catch { /* corrupt definition — return empty */ }
|
|
|
|
|
res.json({ ...wf, definition });
|
2026-03-03 16:55:02 -05:00
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
console.error('[API] GET /api/workflows/:id error:', error);
|
|
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2026-03-03 16:55:02 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
app.post('/api/workflows', authenticateSSO, requireJSON, async (req, res) => {
|
2025-11-29 19:26:20 -05:00
|
|
|
try {
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
const { name, description, definition, webhook_url } = req.body;
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
if (!name || !definition) return res.status(400).json({ error: 'name and definition are required' });
|
|
|
|
|
|
|
|
|
|
const webhookCheck = validateWebhookUrl(webhook_url);
|
|
|
|
|
if (!webhookCheck.ok) return res.status(400).json({ error: webhookCheck.reason });
|
2026-01-07 23:30:50 -05:00
|
|
|
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
const id = crypto.randomUUID();
|
2026-01-07 23:30:50 -05:00
|
|
|
|
2025-11-29 19:26:20 -05:00
|
|
|
await pool.query(
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
'INSERT INTO workflows (id, name, description, definition, webhook_url, created_by) VALUES (?, ?, ?, ?, ?, ?)',
|
|
|
|
|
[id, name, description, JSON.stringify(definition), webhook_url || null, req.user.username]
|
2025-11-29 19:26:20 -05:00
|
|
|
);
|
2026-01-07 23:30:50 -05:00
|
|
|
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
res.json({ id, name, description, definition, webhook_url: webhook_url || null });
|
2025-11-29 19:26:20 -05:00
|
|
|
broadcast({ type: 'workflow_created', workflow_id: id });
|
|
|
|
|
} catch (error) {
|
2026-01-07 23:30:50 -05:00
|
|
|
console.error('[Workflow] Error creating workflow:', error);
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2025-11-29 19:26:20 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.delete('/api/workflows/:id', authenticateSSO, async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
// Only admins can delete workflows
|
|
|
|
|
if (!req.user.isAdmin) {
|
|
|
|
|
return res.status(403).json({ error: 'Admin access required' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await pool.query('DELETE FROM workflows WHERE id = ?', [req.params.id]);
|
|
|
|
|
res.json({ success: true });
|
|
|
|
|
broadcast({ type: 'workflow_deleted', workflow_id: req.params.id });
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2025-11-29 19:26:20 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.get('/api/workers', authenticateSSO, async (req, res) => {
|
|
|
|
|
try {
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
const [rows] = await pool.query('SELECT id, name, status, last_heartbeat, metadata FROM workers ORDER BY name');
|
2025-11-29 19:26:20 -05:00
|
|
|
res.json(rows);
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
console.error('[API] GET /api/workers error:', error);
|
|
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2025-11-29 19:26:20 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.post('/api/workers/heartbeat', async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { worker_id, name, metadata } = req.body;
|
|
|
|
|
const apiKey = req.headers['x-api-key'];
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
|
|
|
|
|
// Verify API key — reject missing or wrong keys
|
|
|
|
|
if (!apiKey || apiKey !== process.env.WORKER_API_KEY) {
|
2025-11-29 19:26:20 -05:00
|
|
|
return res.status(401).json({ error: 'Invalid API key' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
`INSERT INTO workers (id, name, status, last_heartbeat, api_key, metadata)
|
|
|
|
|
VALUES (?, ?, 'online', NOW(), ?, ?)
|
|
|
|
|
ON DUPLICATE KEY UPDATE
|
|
|
|
|
status='online',
|
|
|
|
|
last_heartbeat=NOW(),
|
|
|
|
|
metadata=VALUES(metadata)`,
|
|
|
|
|
[worker_id, name, apiKey, JSON.stringify(metadata)]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
broadcast({ type: 'worker_update', worker_id, status: 'online' });
|
|
|
|
|
res.json({ success: true });
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
console.error('[Worker] Heartbeat error:', error);
|
|
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2025-11-29 19:26:20 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
app.post('/api/executions', executionLimiter, authenticateSSO, requireJSON, async (req, res) => {
|
2025-11-29 19:26:20 -05:00
|
|
|
try {
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
const { workflow_id, params = {}, dry_run = false } = req.body;
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
if (!workflow_id) return res.status(400).json({ error: 'workflow_id is required' });
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
const id = crypto.randomUUID();
|
2026-01-07 23:19:12 -05:00
|
|
|
|
|
|
|
|
// Get workflow definition
|
|
|
|
|
const [workflows] = await pool.query('SELECT * FROM workflows WHERE id = ?', [workflow_id]);
|
|
|
|
|
if (workflows.length === 0) {
|
|
|
|
|
return res.status(404).json({ error: 'Workflow not found' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const workflow = workflows[0];
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
let definition;
|
|
|
|
|
try {
|
|
|
|
|
definition = typeof workflow.definition === 'string' ? JSON.parse(workflow.definition) : workflow.definition;
|
|
|
|
|
} catch {
|
|
|
|
|
return res.status(500).json({ error: 'Workflow definition is corrupt' });
|
|
|
|
|
}
|
2026-01-07 23:19:12 -05:00
|
|
|
|
2026-03-03 16:20:05 -05:00
|
|
|
// Validate required params
|
|
|
|
|
const paramDefs = definition.params || [];
|
|
|
|
|
for (const pd of paramDefs) {
|
|
|
|
|
if (pd.required && !params[pd.name]) {
|
|
|
|
|
return res.status(400).json({ error: `Missing required parameter: ${pd.label || pd.name}` });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-07 23:19:12 -05:00
|
|
|
// Create execution record
|
2026-03-03 16:20:05 -05:00
|
|
|
const initLogs = Object.keys(params).length > 0
|
|
|
|
|
? [{ action: 'params', params, timestamp: new Date().toISOString() }]
|
|
|
|
|
: [];
|
2025-11-29 19:26:20 -05:00
|
|
|
await pool.query(
|
|
|
|
|
'INSERT INTO executions (id, workflow_id, status, started_by, started_at, logs) VALUES (?, ?, ?, ?, NOW(), ?)',
|
2026-03-03 16:20:05 -05:00
|
|
|
[id, workflow_id, 'running', req.user.username, JSON.stringify(initLogs)]
|
2025-11-29 19:26:20 -05:00
|
|
|
);
|
2026-01-07 23:19:12 -05:00
|
|
|
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
if (dry_run) {
|
|
|
|
|
broadcast({ type: 'execution_started', execution_id: id, workflow_id, dry_run: true });
|
|
|
|
|
} else {
|
|
|
|
|
broadcast({ type: 'execution_started', execution_id: id, workflow_id });
|
|
|
|
|
}
|
2026-01-07 23:19:12 -05:00
|
|
|
|
|
|
|
|
// Start workflow execution asynchronously
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
executeWorkflowSteps(id, workflow_id, definition, req.user.username, params, dry_run).catch(err => {
|
2026-01-07 23:19:12 -05:00
|
|
|
console.error(`[Workflow] Execution ${id} failed:`, err);
|
|
|
|
|
});
|
|
|
|
|
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
res.json({ id, workflow_id, status: 'running', dry_run: !!dry_run });
|
2025-11-29 19:26:20 -05:00
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
console.error('[Execution] Error starting execution:', error);
|
|
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2025-11-29 19:26:20 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.get('/api/executions', authenticateSSO, async (req, res) => {
|
|
|
|
|
try {
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
const limit = Math.min(parseInt(req.query.limit) || 50, 1000);
|
2026-01-07 22:50:39 -05:00
|
|
|
const offset = parseInt(req.query.offset) || 0;
|
2026-03-03 16:04:22 -05:00
|
|
|
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
const conditions = [];
|
|
|
|
|
const queryParams = [];
|
|
|
|
|
|
|
|
|
|
// Existing filter
|
|
|
|
|
if (req.query.hide_internal === 'true') {
|
|
|
|
|
conditions.push("e.started_by NOT LIKE 'gandalf:%' AND e.started_by NOT LIKE 'scheduler:%'");
|
|
|
|
|
}
|
|
|
|
|
// New filters
|
|
|
|
|
if (req.query.status) {
|
|
|
|
|
conditions.push('e.status = ?');
|
|
|
|
|
queryParams.push(req.query.status);
|
|
|
|
|
}
|
|
|
|
|
if (req.query.workflow_id) {
|
|
|
|
|
conditions.push('e.workflow_id = ?');
|
|
|
|
|
queryParams.push(req.query.workflow_id);
|
|
|
|
|
}
|
|
|
|
|
if (req.query.started_by) {
|
|
|
|
|
conditions.push('e.started_by = ?');
|
|
|
|
|
queryParams.push(req.query.started_by);
|
|
|
|
|
}
|
|
|
|
|
if (req.query.after) {
|
|
|
|
|
conditions.push('e.started_at >= ?');
|
|
|
|
|
queryParams.push(new Date(req.query.after));
|
|
|
|
|
}
|
|
|
|
|
if (req.query.before) {
|
|
|
|
|
conditions.push('e.started_at <= ?');
|
|
|
|
|
queryParams.push(new Date(req.query.before));
|
|
|
|
|
}
|
|
|
|
|
if (req.query.search) {
|
|
|
|
|
conditions.push('(w.name LIKE ? OR e.started_by LIKE ?)');
|
|
|
|
|
const term = `%${req.query.search}%`;
|
|
|
|
|
queryParams.push(term, term);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const whereClause = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
|
2026-01-07 22:50:39 -05:00
|
|
|
|
2025-11-29 19:26:20 -05:00
|
|
|
const [rows] = await pool.query(
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
`SELECT e.id, e.workflow_id, e.status, e.started_by, e.started_at, e.completed_at,
|
|
|
|
|
w.name as workflow_name
|
|
|
|
|
FROM executions e
|
|
|
|
|
LEFT JOIN workflows w ON e.workflow_id = w.id
|
|
|
|
|
${whereClause}
|
|
|
|
|
ORDER BY e.started_at DESC LIMIT ? OFFSET ?`,
|
|
|
|
|
[...queryParams, limit, offset]
|
2025-11-29 19:26:20 -05:00
|
|
|
);
|
2026-01-07 22:50:39 -05:00
|
|
|
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
const [countRows] = await pool.query(
|
|
|
|
|
`SELECT COUNT(*) as total FROM executions e LEFT JOIN workflows w ON e.workflow_id = w.id ${whereClause}`,
|
|
|
|
|
queryParams
|
|
|
|
|
);
|
2026-01-07 22:50:39 -05:00
|
|
|
const total = countRows[0].total;
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
executions: rows,
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
total,
|
|
|
|
|
limit,
|
|
|
|
|
offset,
|
2026-01-07 22:50:39 -05:00
|
|
|
hasMore: offset + rows.length < total
|
|
|
|
|
});
|
2025-11-29 19:26:20 -05:00
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.delete('/api/executions/completed', authenticateSSO, async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
if (!req.user.isAdmin) return res.status(403).json({ error: 'Admin access required' });
|
|
|
|
|
const [result] = await pool.query(
|
|
|
|
|
"DELETE FROM executions WHERE status IN ('completed', 'failed')"
|
|
|
|
|
);
|
|
|
|
|
broadcast({ type: 'executions_bulk_deleted' });
|
|
|
|
|
res.json({ success: true, deleted: result.affectedRows });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('[Execution] Bulk delete error:', error);
|
|
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2025-11-29 19:26:20 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-07 22:36:51 -05:00
|
|
|
app.delete('/api/executions/:id', authenticateSSO, async (req, res) => {
|
|
|
|
|
try {
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
if (!req.user.isAdmin) return res.status(403).json({ error: 'Admin access required' });
|
2026-01-07 22:36:51 -05:00
|
|
|
await pool.query('DELETE FROM executions WHERE id = ?', [req.params.id]);
|
|
|
|
|
broadcast({ type: 'execution_deleted', execution_id: req.params.id });
|
|
|
|
|
res.json({ success: true });
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
console.error('[Execution] Error deleting execution:', error);
|
|
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2026-01-07 22:36:51 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-08 22:11:59 -05:00
|
|
|
app.post('/api/executions/:id/abort', authenticateSSO, async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const executionId = req.params.id;
|
|
|
|
|
|
|
|
|
|
// Check if execution exists and is running
|
|
|
|
|
const [execution] = await pool.query('SELECT status FROM executions WHERE id = ?', [executionId]);
|
|
|
|
|
|
|
|
|
|
if (execution.length === 0) {
|
|
|
|
|
return res.status(404).json({ error: 'Execution not found' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (execution[0].status !== 'running') {
|
|
|
|
|
return res.status(400).json({ error: 'Execution is not running' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add abort log entry
|
|
|
|
|
await addExecutionLog(executionId, {
|
|
|
|
|
action: 'execution_aborted',
|
|
|
|
|
aborted_by: req.user.username,
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Update execution status to failed
|
|
|
|
|
await updateExecutionStatus(executionId, 'failed');
|
|
|
|
|
|
2026-03-03 16:55:02 -05:00
|
|
|
// Unblock any pending prompt so the thread can exit
|
|
|
|
|
const pending = _executionPrompts.get(executionId);
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
if (pending) pending.resolve(null);
|
2026-03-03 16:55:02 -05:00
|
|
|
|
2026-01-08 22:11:59 -05:00
|
|
|
console.log(`[Execution] Execution ${executionId} aborted by ${req.user.username}`);
|
|
|
|
|
|
|
|
|
|
res.json({ success: true });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('[Execution] Error aborting execution:', error);
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2026-01-08 22:11:59 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-03 16:55:02 -05:00
|
|
|
// Respond to a pending prompt in a running execution
|
|
|
|
|
app.post('/api/executions/:id/respond', authenticateSSO, async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const { response } = req.body;
|
|
|
|
|
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
if (!response || typeof response !== 'string') {
|
|
|
|
|
return res.status(400).json({ error: 'response is required' });
|
|
|
|
|
}
|
2026-03-03 16:55:02 -05:00
|
|
|
|
|
|
|
|
const pending = _executionPrompts.get(id);
|
|
|
|
|
if (!pending) return res.status(404).json({ error: 'No pending prompt for this execution' });
|
|
|
|
|
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
// Validate response is one of the allowed options
|
|
|
|
|
if (pending.options && !pending.options.includes(response)) {
|
|
|
|
|
return res.status(400).json({
|
|
|
|
|
error: `Invalid response. Allowed values: ${pending.options.join(', ')}`
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-03 16:55:02 -05:00
|
|
|
await addExecutionLog(id, {
|
|
|
|
|
action: 'prompt_response', response,
|
|
|
|
|
responded_by: req.user.username,
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
broadcast({ type: 'prompt_response', execution_id: id, response });
|
|
|
|
|
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
pending.resolve(response);
|
2026-03-03 16:55:02 -05:00
|
|
|
res.json({ success: true });
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2026-03-03 16:55:02 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Edit a workflow definition (admin only)
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
app.put('/api/workflows/:id', authenticateSSO, requireJSON, async (req, res) => {
|
2026-03-03 16:55:02 -05:00
|
|
|
try {
|
|
|
|
|
if (!req.user.isAdmin) return res.status(403).json({ error: 'Admin only' });
|
|
|
|
|
|
|
|
|
|
const { id } = req.params;
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
const { name, description, definition, webhook_url } = req.body;
|
2026-03-03 16:55:02 -05:00
|
|
|
|
|
|
|
|
if (!name || !definition) return res.status(400).json({ error: 'name and definition required' });
|
|
|
|
|
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
const webhookCheck = validateWebhookUrl(webhook_url);
|
|
|
|
|
if (!webhookCheck.ok) return res.status(400).json({ error: webhookCheck.reason });
|
|
|
|
|
|
2026-03-03 16:55:02 -05:00
|
|
|
let defObj;
|
|
|
|
|
try {
|
|
|
|
|
defObj = typeof definition === 'string' ? JSON.parse(definition) : definition;
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
} catch {
|
2026-03-03 16:55:02 -05:00
|
|
|
return res.status(400).json({ error: 'Invalid JSON in definition' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const [result] = await pool.query(
|
Add rate limiting, cron scheduling, webhooks, dry-run, execution filtering, and UX improvements
- Rate limiting: 300 req/15min general, 20 req/min on POST /api/executions
- Cron schedule type support using cron-parser for full cron expressions
- Webhook notifications: POST to workflow webhook_url on execution complete/failed
- Dry-run mode: simulate workflow execution without running any commands
- Global execution timeout via EXECUTION_MAX_MINUTES env var (default 60min)
- Execution filtering: status, workflow_id, started_by, after, before, search
- Event-driven command result delivery (replaces 500ms DB polling)
- Atomic log appends via JSON_ARRAY_APPEND (no read-modify-write race)
- Separate browserClients/workerClients sets (workers no longer receive broadcasts)
- Stale execution cleanup on startup (mark running→failed after crash)
- Scheduler overlap prevention (skip if same workflow already running)
- Frontend: webhook_url field in create/edit workflow modals
- Frontend: dry-run checkbox in workflow param modal
- Frontend: ESC closes modals, ws.onerror handler added
- Frontend: selectedExecutions changed from Array to Set (O(1) ops)
- Frontend: XSS fixes via escapeHtml() on all user-controlled innerHTML
- Frontend: param modal keydown listener deduplication fix
- Remove unused npm packages (bcryptjs, body-parser, cors, js-yaml, jsonwebtoken)
- Add express-rate-limit and cron-parser dependencies
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 23:06:09 -04:00
|
|
|
'UPDATE workflows SET name=?, description=?, definition=?, webhook_url=?, updated_at=NOW() WHERE id=?',
|
|
|
|
|
[name, description || '', JSON.stringify(defObj), webhook_url || null, id]
|
2026-03-03 16:55:02 -05:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (result.affectedRows === 0) return res.status(404).json({ error: 'Workflow not found' });
|
|
|
|
|
|
|
|
|
|
broadcast({ type: 'workflow_updated', workflow_id: id });
|
|
|
|
|
res.json({ success: true });
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
console.error('[Workflow] Error updating workflow:', error);
|
|
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2026-03-03 16:55:02 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-07 23:13:27 -05:00
|
|
|
// Scheduled Commands API
|
|
|
|
|
app.get('/api/scheduled-commands', authenticateSSO, async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const [schedules] = await pool.query(
|
|
|
|
|
'SELECT * FROM scheduled_commands ORDER BY created_at DESC'
|
|
|
|
|
);
|
|
|
|
|
res.json(schedules);
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2026-01-07 23:13:27 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
app.post('/api/scheduled-commands', authenticateSSO, requireJSON, async (req, res) => {
|
2026-01-07 23:13:27 -05:00
|
|
|
try {
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
if (!req.user.isAdmin) return res.status(403).json({ error: 'Admin access required' });
|
2026-01-07 23:13:27 -05:00
|
|
|
const { name, command, worker_ids, schedule_type, schedule_value } = req.body;
|
|
|
|
|
|
|
|
|
|
if (!name || !command || !worker_ids || !schedule_type || !schedule_value) {
|
|
|
|
|
return res.status(400).json({ error: 'Missing required fields' });
|
|
|
|
|
}
|
|
|
|
|
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
// Validate schedule_value for numeric types
|
|
|
|
|
if (schedule_type === 'interval' || schedule_type === 'hourly') {
|
|
|
|
|
const n = parseInt(schedule_value, 10);
|
|
|
|
|
if (!Number.isInteger(n) || n <= 0) {
|
|
|
|
|
return res.status(400).json({ error: `schedule_value for type "${schedule_type}" must be a positive integer` });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
const id = crypto.randomUUID();
|
2026-01-07 23:13:27 -05:00
|
|
|
const nextRun = calculateNextRun(schedule_type, schedule_value);
|
|
|
|
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
`INSERT INTO scheduled_commands
|
|
|
|
|
(id, name, command, worker_ids, schedule_type, schedule_value, created_by, next_run)
|
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
[id, name, command, JSON.stringify(worker_ids), schedule_type, schedule_value, req.user.username, nextRun]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
res.json({ success: true, id });
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2026-01-07 23:13:27 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.put('/api/scheduled-commands/:id/toggle', authenticateSSO, async (req, res) => {
|
|
|
|
|
try {
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
if (!req.user.isAdmin) return res.status(403).json({ error: 'Admin access required' });
|
2026-01-07 23:13:27 -05:00
|
|
|
const { id } = req.params;
|
|
|
|
|
const [current] = await pool.query('SELECT enabled FROM scheduled_commands WHERE id = ?', [id]);
|
|
|
|
|
|
|
|
|
|
if (current.length === 0) {
|
|
|
|
|
return res.status(404).json({ error: 'Schedule not found' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const newEnabled = !current[0].enabled;
|
|
|
|
|
await pool.query('UPDATE scheduled_commands SET enabled = ? WHERE id = ?', [newEnabled, id]);
|
|
|
|
|
|
|
|
|
|
res.json({ success: true, enabled: newEnabled });
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2026-01-07 23:13:27 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.delete('/api/scheduled-commands/:id', authenticateSSO, async (req, res) => {
|
|
|
|
|
try {
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
if (!req.user.isAdmin) return res.status(403).json({ error: 'Admin access required' });
|
2026-01-07 23:13:27 -05:00
|
|
|
const { id } = req.params;
|
|
|
|
|
await pool.query('DELETE FROM scheduled_commands WHERE id = ?', [id]);
|
|
|
|
|
res.json({ success: true });
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2026-01-07 23:13:27 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-03 16:04:22 -05:00
|
|
|
// Internal M2M API for Gandalf
|
|
|
|
|
app.post('/api/internal/command', authenticateGandalf, async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { worker_id, command } = req.body;
|
|
|
|
|
if (!worker_id || !command) {
|
|
|
|
|
return res.status(400).json({ error: 'worker_id and command are required' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const workerWs = workers.get(worker_id);
|
|
|
|
|
if (!workerWs || workerWs.readyState !== WebSocket.OPEN) {
|
|
|
|
|
return res.status(400).json({ error: 'Worker not connected' });
|
|
|
|
|
}
|
|
|
|
|
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
const executionId = crypto.randomUUID();
|
2026-03-03 16:04:22 -05:00
|
|
|
|
|
|
|
|
await pool.query(
|
|
|
|
|
'INSERT INTO executions (id, workflow_id, status, started_by, started_at, logs) VALUES (?, ?, ?, ?, NOW(), ?)',
|
|
|
|
|
[executionId, null, 'running', req.user.username, JSON.stringify([{
|
|
|
|
|
step: 'internal_command',
|
|
|
|
|
action: 'command_sent',
|
|
|
|
|
worker_id: worker_id,
|
|
|
|
|
command: command,
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
}])]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
workerWs.send(JSON.stringify({
|
|
|
|
|
type: 'execute_command',
|
|
|
|
|
execution_id: executionId,
|
|
|
|
|
command: command,
|
|
|
|
|
worker_id: worker_id,
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
timeout: QUICK_CMD_TIMEOUT_MS
|
2026-03-03 16:04:22 -05:00
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
res.json({ execution_id: executionId });
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2026-03-03 16:04:22 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.get('/api/internal/executions/:id', authenticateGandalf, async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const [rows] = await pool.query('SELECT * FROM executions WHERE id = ?', [req.params.id]);
|
|
|
|
|
if (rows.length === 0) {
|
|
|
|
|
return res.status(404).json({ error: 'Not found' });
|
|
|
|
|
}
|
|
|
|
|
const execution = rows[0];
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
let logs = [];
|
|
|
|
|
try { logs = JSON.parse(execution.logs || '[]'); } catch { logs = []; }
|
|
|
|
|
res.json({ ...execution, logs });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('[API] GET /api/internal/executions/:id error:', error);
|
|
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Detailed health + stats (auth required)
|
|
|
|
|
app.get('/api/health', authenticateSSO, async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
await pool.query('SELECT 1');
|
|
|
|
|
const [workerRows] = await pool.query('SELECT status, COUNT(*) as count FROM workers GROUP BY status');
|
|
|
|
|
const workerCounts = Object.fromEntries(workerRows.map(r => [r.status, Number(r.count)]));
|
2026-03-03 16:04:22 -05:00
|
|
|
res.json({
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
status: 'ok',
|
|
|
|
|
version: SERVER_VERSION,
|
|
|
|
|
uptime_seconds: Math.floor(process.uptime()),
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
workers: { online: workerCounts.online || 0, offline: workerCounts.offline || 0 },
|
|
|
|
|
database: 'connected'
|
2026-03-03 16:04:22 -05:00
|
|
|
});
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
console.error('[Health] /api/health error:', error);
|
|
|
|
|
res.status(500).json({ status: 'error', timestamp: new Date().toISOString() });
|
2026-03-03 16:04:22 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-29 19:26:20 -05:00
|
|
|
// Health check (no auth required)
|
|
|
|
|
app.get('/health', async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
await pool.query('SELECT 1');
|
|
|
|
|
res.json({
|
|
|
|
|
status: 'ok',
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
database: 'connected',
|
|
|
|
|
auth: 'authelia-sso'
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
status: 'error',
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
database: 'disconnected',
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
error: 'Internal server error'
|
2025-11-29 19:26:20 -05:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-11-30 13:03:18 -05:00
|
|
|
// Get execution details with logs
|
|
|
|
|
app.get('/api/executions/:id', authenticateSSO, async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const [rows] = await pool.query('SELECT * FROM executions WHERE id = ?', [req.params.id]);
|
|
|
|
|
if (rows.length === 0) {
|
|
|
|
|
return res.status(404).json({ error: 'Not found' });
|
|
|
|
|
}
|
2026-01-08 22:06:25 -05:00
|
|
|
|
2025-11-30 13:03:18 -05:00
|
|
|
const execution = rows[0];
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
let parsedLogs = [];
|
|
|
|
|
try {
|
|
|
|
|
parsedLogs = typeof execution.logs === 'string' ? JSON.parse(execution.logs || '[]') : (execution.logs || []);
|
|
|
|
|
} catch { parsedLogs = []; }
|
2026-03-03 16:55:02 -05:00
|
|
|
const waitingForInput = _executionPrompts.has(req.params.id);
|
|
|
|
|
let pendingPrompt = null;
|
|
|
|
|
if (waitingForInput) {
|
|
|
|
|
for (let i = parsedLogs.length - 1; i >= 0; i--) {
|
|
|
|
|
if (parsedLogs[i].action === 'prompt') {
|
|
|
|
|
pendingPrompt = { message: parsedLogs[i].message, options: parsedLogs[i].options };
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-08 22:06:25 -05:00
|
|
|
|
2025-11-30 13:03:18 -05:00
|
|
|
res.json({
|
|
|
|
|
...execution,
|
2026-03-03 16:55:02 -05:00
|
|
|
logs: parsedLogs,
|
|
|
|
|
waiting_for_input: waitingForInput,
|
|
|
|
|
prompt: pendingPrompt,
|
2025-11-30 13:03:18 -05:00
|
|
|
});
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
console.error('[API] GET /api/executions/:id error:', error);
|
|
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2025-11-30 13:03:18 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Delete worker (admin only)
|
|
|
|
|
app.delete('/api/workers/:id', authenticateSSO, async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
if (!req.user.isAdmin) {
|
|
|
|
|
return res.status(403).json({ error: 'Admin access required' });
|
|
|
|
|
}
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
|
2025-11-30 13:03:18 -05:00
|
|
|
await pool.query('DELETE FROM workers WHERE id = ?', [req.params.id]);
|
|
|
|
|
res.json({ success: true });
|
|
|
|
|
broadcast({ type: 'worker_deleted', worker_id: req.params.id });
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2025-11-30 13:03:18 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
// Send direct command to specific worker
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
app.post('/api/workers/:id/command', authenticateSSO, requireJSON, async (req, res) => {
|
2025-11-30 13:03:18 -05:00
|
|
|
try {
|
|
|
|
|
const { command } = req.body;
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
if (!command || typeof command !== 'string' || !command.trim()) {
|
|
|
|
|
return res.status(400).json({ error: 'command is required' });
|
|
|
|
|
}
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
const executionId = crypto.randomUUID();
|
2026-01-07 20:24:11 -05:00
|
|
|
const workerId = req.params.id;
|
|
|
|
|
|
|
|
|
|
// Create execution record in database
|
|
|
|
|
await pool.query(
|
|
|
|
|
'INSERT INTO executions (id, workflow_id, status, started_by, started_at, logs) VALUES (?, ?, ?, ?, NOW(), ?)',
|
|
|
|
|
[executionId, null, 'running', req.user.username, JSON.stringify([{
|
|
|
|
|
step: 'quick_command',
|
|
|
|
|
action: 'command_sent',
|
|
|
|
|
worker_id: workerId,
|
|
|
|
|
command: command,
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
}])]
|
|
|
|
|
);
|
|
|
|
|
|
2026-01-07 22:23:02 -05:00
|
|
|
// Send command via WebSocket to specific worker
|
2025-11-30 13:03:18 -05:00
|
|
|
const commandMessage = {
|
|
|
|
|
type: 'execute_command',
|
|
|
|
|
execution_id: executionId,
|
|
|
|
|
command: command,
|
2026-01-07 20:24:11 -05:00
|
|
|
worker_id: workerId,
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
timeout: QUICK_CMD_TIMEOUT_MS
|
2025-11-30 13:03:18 -05:00
|
|
|
};
|
|
|
|
|
|
2026-01-07 22:23:02 -05:00
|
|
|
const workerWs = workers.get(workerId);
|
|
|
|
|
if (!workerWs || workerWs.readyState !== WebSocket.OPEN) {
|
|
|
|
|
return res.status(400).json({ error: 'Worker not connected' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
workerWs.send(JSON.stringify(commandMessage));
|
|
|
|
|
console.log(`Command sent to worker ${workerId}: ${command}`);
|
2025-11-30 13:03:18 -05:00
|
|
|
|
2026-01-07 20:24:11 -05:00
|
|
|
broadcast({ type: 'execution_started', execution_id: executionId, workflow_id: null });
|
2025-11-30 13:03:18 -05:00
|
|
|
res.json({ success: true, execution_id: executionId });
|
|
|
|
|
} catch (error) {
|
Security hardening, bug fixes, and backend improvements
Security:
- validateWebhookUrl() rejects non-http/https and private/internal IPs
- Validate webhook URL on workflow create and update
- Replace error.message in all HTTP 500 responses with 'Internal server error'
- Add requireJSON middleware (HTTP 415 if Content-Type wrong) on POST/PUT routes
- Reject missing API keys in worker heartbeat (not just wrong ones)
- Validate prompt response against allowed options before accepting
Bugs fixed:
- goto infinite loop protection: stepVisits[] counter, fails at GOTO_MAX_VISITS (100)
- wait step: validate duration (no NaN/negative), cap at WAIT_STEP_MAX_MS (24h)
- _executionPrompts now stores {resolve, options} for option validation
- JSON.parse wrapped in try/catch: workflows/:id, executions/:id, internal/executions/:id, POST /api/executions, scheduler worker_ids
- pong handler uses ws.dbWorkerId (set on connect) not message.worker_id
- Worker disconnect now marks worker offline in DB and broadcasts update
- command validation (type + empty check) on POST /api/workers/:id/command
- workflow_id required check on POST /api/executions
Performance & reliability:
- markStaleWorkersOffline() runs every 60s, marks workers without recent heartbeat offline
- Named constants: PROMPT_TIMEOUT_MS, COMMAND_TIMEOUT_MS, QUICK_CMD_TIMEOUT_MS,
WEBHOOK_TIMEOUT_MS, WAIT_STEP_MAX_MS, GOTO_MAX_VISITS, WORKER_STALE_MINUTES
New features:
- GET /api/health (auth required): version, uptime, worker counts
- DELETE /api/executions/completed: bulk delete finished executions (admin)
- schedule_value positive-integer validation for interval/hourly schedule types
- Request logging middleware: [HTTP] METHOD /path STATUS Xms
Code quality:
- All console.log on error paths changed to console.error
- Removed stray debug console.log in POST /api/workflows
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 10:59:07 -04:00
|
|
|
res.status(500).json({ error: 'Internal server error' });
|
2025-11-30 13:03:18 -05:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Security hardening, bug fixes, and performance improvements
Security fixes:
- Replace new Function() condition eval with vm.runInNewContext() (RCE fix)
- Add admin checks to DELETE executions, all scheduled-commands endpoints
- Remove api_key from GET /api/workers response (was exposed to all employees)
- Separate browserClients/workerClients sets; broadcast() now sends to browsers only
- Add worker WebSocket auth: reject if api_key provided but invalid
- Fix XSS: escapeHtml() on step_name, duration, worker_id, user info, execution_id
Bug fixes:
- Replace DB-polling waitForCommandResult with event-driven _commandResolvers Map
- Replace non-atomic addExecutionLog with JSON_ARRAY_APPEND (fixes concurrent write race)
- Add stale execution recovery on startup: running→failed with log entry
- Fix calculateNextRun returning null for unknown types (now throws)
- Fix scheduler overlap: skip if previous execution still running
- Fix JSON double-parse on worker_ids column
- Fix switchTab() bare event.target reference
- Fix selectedExecutions Array→Set (O(1) lookups, fixes performance regression)
- Fix param modal event listener leak (delegated handler, removes before re-adding)
- Add ws.onerror handler (was silently swallowing WebSocket errors)
- Move misplaced routes to before server.listen()
Performance/cleanup:
- DB connection pool 10→50
- EXECUTION_RETENTION_DAYS default 1→30 (matches docs)
- Remove unused packages: bcryptjs, body-parser, cors, js-yaml, jsonwebtoken
- Remove generateUUID() wrapper, use crypto.randomUUID() directly
- Remove dead example workflow constants
- Add ESC key handler to close modals
- Fix clearCompletedExecutions limit 1000→9999
- Add security notice to README.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 22:53:25 -04:00
|
|
|
// Start server
|
|
|
|
|
const PORT = process.env.PORT || 8080;
|
|
|
|
|
const HOST = process.env.HOST || '0.0.0.0';
|
|
|
|
|
|
|
|
|
|
initDatabase().then(() => {
|
|
|
|
|
server.listen(PORT, HOST, () => {
|
|
|
|
|
console.log(`PULSE Server running on http://${HOST}:${PORT}`);
|
|
|
|
|
console.log(`Connected to MariaDB at ${process.env.DB_HOST}`);
|
|
|
|
|
console.log(`Authentication: Authelia SSO`);
|
|
|
|
|
console.log(`Worker API Key configured: ${process.env.WORKER_API_KEY ? 'Yes' : 'No'}`);
|
|
|
|
|
});
|
|
|
|
|
}).catch(err => {
|
|
|
|
|
console.error('Failed to start server:', err);
|
|
|
|
|
process.exit(1);
|
|
|
|
|
});
|