Skip to content

Security

Felipe Marzochi edited this page Sep 6, 2026 · 3 revisions

Security

Source: docs/security/THREAT-MODEL.md and docs/security/SECURITY-ASSESSMENT.md in the repository.

System overview

EGC is a local-first AI memory and orchestration runtime. It has no network services, no authentication surface, and no multi-user access model. The attack surface is limited to the npm package and its dependencies, the two MCP servers running as local stdio processes, the GitHub Actions CI/CD pipeline, and session hooks that process transcript data.

Trust boundaries

Boundary Description
Local filesystem EGC reads and writes state files at ~/.egc/. Access is local-user-only.
AI tool sockets MCP servers communicate with AI tools via stdio. No network exposure.
External dependencies npm packages, pinned via package-lock.json, audited via Dependabot.
GitHub Actions CI/CD runs in ephemeral sandboxes with minimal permissions.

Actors

Actor Trust level
Local user Fully trusted
Contributor Partially trusted: submits PRs, cannot merge without review
Dependency author Untrusted (third-party npm packages)
AI tool (Claude Code, etc.) Trusted at runtime: calls MCP tools, runs in the same user context
GitHub Actions runner Trusted, ephemeral, sandboxed
PR author from a fork Untrusted: fork code does not access repository secrets
Other sessions on the bus Untrusted data: event payloads are never executed as instructions

Key mitigations

  • Supply chain: dependencies locked via package-lock.json in every package (root, both MCP servers, the dashboard), Dependabot monitors for vulnerabilities, dependency-review.yml blocks PRs introducing high-severity dependencies, npm audit runs on every push, Socket Security and OpenSSF Scorecard run on the repository. Every open advisory in every lockfile was cleared in v1.1.21 (#1338).
  • Hardening round (v1.1.21): an eighteen-step pass across the memory server, the installer, the hooks and the dashboard, landed in seventeen pull requests (#1356 to #1363, #1365 to #1373), with tighter defaults and stricter checks on what EGC accepts from disk, from the network and from the tools it talks to. Every step carries its own tests and went through review on its pull request; no documented command changed.
  • Command injection: egc-guardian's validate_command validates every tool call before execution. Shell commands are built from whitelisted patterns rather than raw string interpolation; execSync was replaced with spawnSync plus argv tokenization to remove a shell-injection surface (v1.1.8).
  • Untrusted content: validate_content gives the AI an advisory heuristic scan for prompt-injection patterns in fetched pages, third-party files, API responses and fork diffs before it trusts them. It flags, never blocks.
  • Credential leakage: session hooks sanitize transcript content before writing to disk; environment variable names such as GEMINI_TRANSCRIPT_PATH and EGC_SESSION_ID are replaced with placeholders in log output; state files contain only structured metadata, never raw transcripts. Populated memory never reaches a commit: the propagation files ship as empty structure, a pre-commit hook blocks accidental staging, a git clean filter zeroes the blob even when hooks are bypassed, and a CI guard catches anything that slips past.
  • CI/CD: fork pull requests do not get repository secrets; pull_request_target is not used in any workflow; workflows default to permissions: contents: read; the release workflow only triggers on maintainer-pushed version tags; third-party actions are pinned to commit SHAs; CodeQL and ClusterFuzzLite run on pull requests.
  • State encryption: state files under ~/.egc/state/ are encrypted at rest with AES-256-GCM. A TOCTOU race in the encryption key generation path (loadOrCreateEncKey), where two concurrent egc-memory processes starting before the key file existed could each generate a different key, was fixed in v1.1.9 by making key publication atomic (write-to-temp plus fs.linkSync). Team sync seals every shared file with the team key plus an HMAC-SHA256 signature and rejects anything that does not open with both.
  • Session bus: event delivery is exactly-once across overlapping readers (compare-and-swap on the event cursor, v1.1.19, #1271), payloads are size-capped, claims are fail-fast, and a multi-process chaos harness covers SIGKILL lock recovery, contested claims, writer-death atomicity and per-sender ordering.
  • Credential path denylist: egc-guardian blocks writes to the specific credential files each AI tool stores (OAuth tokens, session files, API keys) rather than whole config directories, a change made in v1.1.8 after the previous whole-directory block was found to break legitimate functionality without adding real security.
  • Destructive-CLI hard blocks: docker system prune, docker rm/rmi, docker run --privileged or host mounts, gh repo delete, gh api -X DELETE, and prisma migrate reset / --force-reset / db execute return a hard DANGEROUS verdict instead of an advisory warning (v1.1.16, #1041). An absolute-path bypass (/bin/rm, /usr/bin/mv) was closed the same release (#1012).
  • Bash hook dispatcher fails closed: an error in the dispatcher's own plumbing used to fail open, silently disabling every guard (Guardian validate, GateGuard) for that command. It now fails closed instead (v1.1.16, #1019), the most critical finding of that release's security audit.
  • auto_learn write-target validation: target_file is checked against protected paths and must stay inside the project root, closing a write-outside-sandbox path (v1.1.16, #1009).
  • core.hooksPath bypass closed: git hook-path overrides are now matched case-insensitively, closing a DCO/CI bypass (v1.1.16, #1013).
  • republish.yml command injection closed: the version input is validated instead of interpolated directly into a shell command (v1.1.16, #1027).
  • Docker hardened: images run as a non-root user via a multi-stage build that keeps the build toolchain out of the final image, and a .dockerignore keeps .git, .env, and node_modules out of the build context (v1.1.16, #1036, #1028).

Known limitations

EGC has no server component, no authentication, and no network services. Its security posture depends on the security of the host machine and the AI tool integrations it runs inside. Prompt injection from external content read by the AI remains an AI-tool-level concern: validate_content helps the AI notice it, but only the tool can refuse to act on it.

Reviewing critical code paths

Path Risk Protection
mcp/servers/egc-guardian/src/index.ts (validate_command) High: gates shell execution Reviewed on every change
scripts/install.sh / scripts/install.ps1 Medium: modifies global AI tool configs Verified in CI across Linux, macOS, Windows
scripts/hooks/session-end.js Medium: reads transcript, writes to disk Bounded stdin (1 MB cap), structured error handling
mcp/servers/egc-memory/ Low: reads/writes state files only No shell execution, pure file I/O

Reporting a vulnerability

Report privately through GitHub: github.com/Fmarzochi/EGC/security (Security Advisories). Do not open a public issue for a security finding.

Contribution requirement: concurrent-access tests

Any change that reads or writes a file shared across concurrent EGC processes (the encryption key, state files under ~/.egc/state/, install-state files, or any lockfile-like resource under ~/.egc/) must include a concurrent-access regression test before it can be merged. This requirement exists because CodeQL, SonarCloud, and the full OS/Node/package-manager test matrix cannot reason about interleaving between two independent process executions, the bug class that caused the v1.1.9 encryption-key TOCTOU race. See tests/egc-memory-encryption.test.js for the reference pattern.

Clone this wiki locally