Skip to content

Security: tradersurfer/ceo-agent

Security

SECURITY.md

Security Posture

This document describes the current security model of the CEO Agent scaffold honestly — what's handled, what isn't yet, and what you're responsible for as an installer.

What's handled

  • Dispatch API authentication/api/dispatch requires a x-dispatch-secret header matching DISPATCH_SECRET from your .env, checked with a timing-safe comparison (crypto.timingSafeEqual).
  • Rate limiting — the dispatch and chat endpoints limit each caller (by IP) to 30 requests/minute. In-memory sliding window by default (sufficient for a single-instance deployment); a Supabase-backed shared limiter (ceo-core/RateLimiter.js) is used automatically when SUPABASE_URL/SUPABASE_SERVICE_ROLE_KEY are configured, for multi-instance deployments. The shared limiter is a count-then-insert sliding window, not a database-transaction-guarded atomic counter — under genuinely concurrent multi-instance bursts at the exact edge of the limit, a narrow, bounded overshoot is possible. It shares state across instances, which the in-memory limiter cannot do at all; it does not claim hard atomicity.
  • Secrets never committed.env and ceo-agent.config.json are gitignored by default. Never commit real API keys, webhook secrets, or tokens.
  • Secrets never logged — the setup wizard and chat IDE never echo back API keys or tokens in console output.
  • Task-type allowlisting — every agent bridge (SalesIntakeBridge, OnboardingCommsBridge, DisputeAgentBridge, HermesBridge) only accepts pre-approved task types, approvers, and projects, configured via env vars (CEO_AGENT_APPROVERS, CEO_AGENT_PROJECTS).
  • No hardcoded credentials or personal paths anywhere in this codebase.
  • Web dashboard binds to localhost onlynpm run web and npm run web:start explicitly bind to 127.0.0.1, not all network interfaces, since this is a single-local-user tool by default.
  • off_limits real code enforcement (ADR-010) — every agent's off_limits list (registry/agent-registry.json) is a structured {id, label, restricts, enforceable} entry, checked at both SkillExecutor.run() and BaseBridge.validatePermissions() — a genuinely restricted skill/task type is hard-blocked before it runs, not just documented. Honest limit: today's registry data has zero live-firing entries, because no current skill actually performs a Bucket-C-class action (moving money, deleting files, etc.) yet, and Bucket A/B entries (relational constraints; category-only entries whose real violation depends on an unverifiable qualifier like "unsolicited") are deliberately marked non-enforceable rather than false-blocking legitimate work. The mechanism is real and tested (tests/OffLimitsEnforcement.test.js); it starts firing automatically the moment a future skill/task type populates a real restricts entry.
  • Stripe webhook signature verification (app/api/webhooks/stripe) — real HMAC-SHA256 verification of the raw request body against STRIPE_WEBHOOK_SECRET, fails closed on any missing/invalid/stale signature (lib/stripeWebhookVerify.js). The secret is read for comparison only and never appears in a log or response body.

What's NOT yet handled — you are responsible for these

  • Model API key rotation — this scaffold doesn't rotate or manage OpenRouter/model provider keys. Rotate them yourself if compromised.
  • Sandboxing of agent execution — Hermes and other agent runtimes are not sandboxed by this scaffold. If you wire a real Hermes runtime, apply your own process isolation.
  • Dependency auditing — run npm audit yourself before production use; this scaffold doesn't automate it.
  • Tracked transitive dependency advisoriesGHSA-qx2v-qp2m-jg93, GHSA-6g55-p6wh-862q, and GHSA-r28c-9q8g-f849 (PostCSS), plus GHSA-f88m-g3jw-g9cj (sharp/libvips), are transitive-only through Next.js; the available npm audit fix --force would perform a breaking downgrade to Next 9.3.3, so these remain tracked pending a safe non-breaking upstream fix. Confirmed via npm audit before and after adding this project's markdown-rendering dependencies (react-markdown, remark-gfm, marked, marked-terminal) that none of the four introduce anything new — the audit output is byte-for-byte identical.
  • exceljs's transitive archiver/glob/uuid chain — adding exceljs (spreadsheet-generation skill) pulls in 9 advisories (8 high, 1 moderate: archiver, archiver-utils, brace-expansion, glob, minimatch, readdir-glob, rimraf, zip-stream, uuid — see GHSA-w5hq-g745-h8pq for the uuid one), all nested under exceljs@4.4.0's own pinned archiver@^5.0.0 and uuid@^8.3.0. exceljs@4.4.0 is the latest non-prerelease release (published Dec 2024) and has not bumped either dependency, so there is no non-breaking upstream fix; npm audit fix --force would downgrade exceljs to 3.4.0, itself a breaking change with no guarantee the same transitive chain resolves. Confirmed via npm ls that docx and pdf-lib (added alongside exceljs for the same document-creation skills) introduce nothing new. Blast-radius note: this scaffold's generate_spreadsheet skill only writes workbooks from structured input the caller controls (title/headers/rows) via Workbook.xlsx.writeBuffer() — it never reads or unzips an untrusted .xlsx file and never passes caller-controlled data into a glob pattern, which is the exploit surface for most of the listed advisories (ReDoS via crafted glob/brace patterns, malicious archive entries). Re-evaluate this entry if a future skill starts reading untrusted spreadsheets through this same dependency.
  • Major-version dependency upgrades — Next.js, React, and TypeScript have major-version upgrades available (15→16, 18→19, and 5→7 respectively) that have not yet been evaluated. These are deferred until after v0.1 to avoid introducing breaking changes immediately before release, not because they are known to be vulnerable.
  • Human-in-the-loop enforcement beyond task-type allowlisting and off_limits — the bridges block unauthorized task types and (per ADR-010, above) genuinely off_limits-restricted actions, but there is still no additional confirmation step for a high-stakes action that isn't already captured by one of those two mechanisms (e.g. sending real client emails, real financial transactions) beyond what you configure in your own runtime wiring. Hermes's async lifecycle (HermesGatewayClient.awaitResolution()) never auto-approves a pending waiting_for_approval run either — a real human decision is required for that path specifically — but that's narrower than general high-stakes-action confirmation.
  • TLS/HTTPS — this scaffold assumes you're deploying behind infrastructure (Vercel, a reverse proxy, etc.) that terminates TLS. It does not handle this itself.

Reporting a vulnerability

This is an early-stage open-source scaffold. If you find a security issue, please open an issue on this repository (or contact JECI Group, LLC directly if the issue involves exposed credentials — do not post exposed secrets publicly).

Copyright (c) 2026 JECI Group, LLC

There aren't any published security advisories