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.
- Dispatch API authentication —
/api/dispatchrequires ax-dispatch-secretheader matchingDISPATCH_SECRETfrom 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 whenSUPABASE_URL/SUPABASE_SERVICE_ROLE_KEYare 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 —
.envandceo-agent.config.jsonare 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 only —
npm run webandnpm run web:startexplicitly bind to 127.0.0.1, not all network interfaces, since this is a single-local-user tool by default. off_limitsreal code enforcement (ADR-010) — every agent'soff_limitslist (registry/agent-registry.json) is a structured{id, label, restricts, enforceable}entry, checked at bothSkillExecutor.run()andBaseBridge.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 realrestrictsentry.- Stripe webhook signature verification (
app/api/webhooks/stripe) — real HMAC-SHA256 verification of the raw request body againstSTRIPE_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.
- 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 audityourself before production use; this scaffold doesn't automate it. - Tracked transitive dependency advisories —
GHSA-qx2v-qp2m-jg93,GHSA-6g55-p6wh-862q, andGHSA-r28c-9q8g-f849(PostCSS), plusGHSA-f88m-g3jw-g9cj(sharp/libvips), are transitive-only through Next.js; the availablenpm audit fix --forcewould perform a breaking downgrade to Next 9.3.3, so these remain tracked pending a safe non-breaking upstream fix. Confirmed vianpm auditbefore 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 transitivearchiver/glob/uuidchain — addingexceljs(spreadsheet-generation skill) pulls in 9 advisories (8 high, 1 moderate:archiver,archiver-utils,brace-expansion,glob,minimatch,readdir-glob,rimraf,zip-stream,uuid— seeGHSA-w5hq-g745-h8pqfor theuuidone), all nested underexceljs@4.4.0's own pinnedarchiver@^5.0.0anduuid@^8.3.0.exceljs@4.4.0is 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 --forcewould downgradeexceljsto3.4.0, itself a breaking change with no guarantee the same transitive chain resolves. Confirmed vianpm lsthatdocxandpdf-lib(added alongsideexceljsfor the same document-creation skills) introduce nothing new. Blast-radius note: this scaffold'sgenerate_spreadsheetskill only writes workbooks from structured input the caller controls (title/headers/rows) viaWorkbook.xlsx.writeBuffer()— it never reads or unzips an untrusted.xlsxfile 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) genuinelyoff_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 pendingwaiting_for_approvalrun 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.
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