Codumentor logo Codumentor

Plugins in Production

Most of what a real Codumentor deployment does — workspace isolation, web search, the Office editors, scheduled goals, external coding agents, per-user secrets — comes from plugins. A production config typically enables a dozen or more.

This page is the operator's view: which plugins need something installed on the host or configured outside codumentor.yaml, and how to provide it. It is not the full catalog.

Enabling a plugin is always the same: add a {module, class, args} entry to the plugins: list. The work below is the extra setup some plugins need to actually function.

Prerequisites at a glance

Plugin(s)What the admin must provideHow
workspace_isolation, branch_switching, scm_push, external_agent (sandbox tier ≥ 1), isolated subagentsLinux + bubblewrap with overlay support (apt install bubblewrap on current distros) + overlayfs + (Ubuntu 24.04+) userns allowed for bwrapWorkspace isolation prerequisites below
mermaid_render, webretrieve (JS rendering)Playwright + a Chromium browser binary. On Ubuntu 24.04+ also decide how the browser sandbox gets its userns grantpip install playwright && playwright install chromium, then Browser sandbox on Ubuntu 24.04+
ripgrepThe rg binary on PATHapt install ripgrep (or your distro's package)
svnA Subversion clientapt install subversion
databaseDB access. Native pure-Python drivers ship bundled (pg8000, PyMySQL, oracledb, python-tds). Only mode: cli (or mode: auto with no driver) needs an external clientpsql / mysql / go-sqlcmd per engine
read_aloud, voice_mode, Telegram voiceAn OpenAI-compatible STT/TTS endpoint + API key via the top-level speech: blockSpeech (read-aloud / voice) below
google_workspaceA Google Cloud OAuth 2.0 client (id + secret) and the Gmail/Calendar/Docs/Sheets/Drive APIs enabled; also oauth + auth.jwt_secretGoogle Workspace
websearchA search-provider API key (Tavily, Brave, Serper, Google CSE, …)provider_config.api_key: "${TAVILY_API_KEY}"
external_agentThe external CLI binaries installed and logged in (claude, codex, …)Install each CLI, then run its login as the service user so the auth store lands in $HOME (e.g. claude /login, codex login)
redmine, bug_reportA reachable Redmine instance + API keyredmine: each user stores a redmine_api_key secret; bug_report: instance api_key in config
user_secrets, model_profiles, oauthauth.jwt_secret set, or these stores are written unencryptedAuthentication → jwt_secret
goals, scheduler_toolsThe scheduler + job queue (enabled by default)scheduler.enabled: true (default); see Configuration → scheduler
doc_editor, xlsx_editor, pptx_editor, filesync (SMB/UNC)Nothing extra — python-docx, openpyxl, python-pptx, smbprotocol ship in requirements.txt

Portable/frozen builds: the bundled Python dependencies above are included in the executable. Host binaries (bubblewrap, ripgrep, svn, Chromium, DB CLIs) and the external coding-agent CLIs are not bundled — install them on the host regardless of how you run Codumentor.

Workspace isolation prerequisites

The workspace_isolation plugin sandboxes each conversation in a per-session bubblewrap overlay filesystem. The scm_push and external_agent (tier ≥ 1) plugins, and subagents under subagent_isolation: isolated, all run inside this same sandbox — so these requirements apply to all of them.

RequirementMinimumNotes
OSLinuxUbuntu 22.04+ / Debian 12+ recommended. (No Windows/macOS host sandbox.)
Kernel5.11+For unprivileged overlayfs
bubblewrap0.8.0+Needs overlay support (--overlay-src) and must not be setuid — see below
AppArmorNeeds userns allowed for the bwrap binary if apparmor_restrict_unprivileged_userns=1 (Ubuntu 24.04+)

1. Check what you have

# Convenience checker shipped with the plugin — feature-detects everything below:
./src/codumentor/plugins/workspace_isolation/setup/check.sh

The two things it looks for individually:

bwrap --help | grep overlay-src   # no output → your bwrap lacks overlay support
bwrap --ro-bind / / --unshare-user /bin/true   # "setting up uid map: Permission denied" → userns blocked

2. Install

On current distros the packaged bubblewrap is sufficient — you do not need to build it.

sudo apt install bubblewrap

Ubuntu 26.04 ships bubblewrap 0.11.1 and Debian 13 ships 0.11.x; both have overlay support and are not setuid. Verify with the commands above rather than assuming either way.

Building from source is only needed on older distros whose package predates 0.8.0 (which added --overlay-src), or where the package is installed setuid-root — bubblewrap refuses overlay mounts in setuid mode, so a setuid 0.8.0+ build still fails the overlay check. setup.sh builds from source only when the packaged binary actually fails these checks:

./src/codumentor/plugins/workspace_isolation/setup/setup.sh

AppArmor on Ubuntu 24.04+

Ubuntu 24.04 introduced apparmor_restrict_unprivileged_userns=1, which blocks unprivileged user-namespace creation. bubblewrap needs user namespaces, so the bwrap binary must be granted userns.

The apparmor package already ships this profile/etc/apparmor.d/bwrap-userns-restrict, attached to /usr/bin/bwrap. If you install bubblewrap from apt and leave that profile in place, there is nothing to do. It is also tighter than a hand-written flags=(unconfined) profile, because it stacks a child profile that strips capabilities inside the namespace.

Do not add a second profile named bwrap. The distro profile declares profile bwrap /usr/bin/bwrap. AppArmor profile names are global, so a hand-written /etc/apparmor.d/bwrap that declares profile bwrap /usr/local/bin/bwrap collides with and displaces it, silently leaving /usr/bin/bwrap unable to create user namespaces. Confirm which definition is live with sudo aa-status | grep bwrap.

Never let two profiles claim the same binary. Two loaded profiles with the same attachment path make the attachment ambiguous, and neither grant applies — so "add a second profile under a new name for the same bwrap" does not work around the collision, it breaks the binary that was working. One path, one profile.

Migrating an existing host off a colliding /etc/apparmor.d/bwrap

Upgrade Codumentor before you delete that file. Releases before the functional-probe fix gate isolation on /etc/apparmor.d/bwrap merely existing, without ever parsing it. Delete it first and bwrap keeps working perfectly while every instance decides isolation is unavailable and fails closed — tools disabled, and the only symptom is a Workspace isolation unavailable: AppArmor blocks user namespaces warning at startup.

Order:

  1. Upgrade Codumentor on every instance on the host.
  2. sudo rm /etc/apparmor.d/bwrap && sudo apparmor_parser -r /etc/apparmor.d/bwrap-userns-restrict
  3. If you were running a locally built bwrap on PATH ahead of /usr/bin, retire it (move, don't delete) so PATH falls through to the packaged binary.
  4. Verify — see the warning below about verifying as root.
  5. Restart the instances so no cached agent keeps a stale verdict.

Verify as an unprivileged user. The AppArmor userns restriction does not apply to root, so sudo bwrap --unshare-user … succeeds even when every unprivileged user is blocked. Always check as the service account: sudo -u <svc-user> bwrap --ro-bind / / --unshare-user /bin/true. And prefer check.sh over a bare userns probe — a userns smoke test passes while the full overlay path, or the application's own availability check, still fails.

If you do build bwrap to a non-distro path such as /usr/local/bin/bwrap, give its profile a distinct name, and make sure no other profile attaches to that same path:

abi <abi/4.0>,
include <tunables/global>

profile bwrap-local /usr/local/bin/bwrap flags=(unconfined) {
  userns,
}

3. Enable it

plugins:
  - module: codumentor.plugins.workspace_isolation
    class: WorkspaceIsolationPlugin
    priority: 5
    args:
      enabled: true
      persistent_home: true        # shared $HOME across agents (default)
      subagent_isolation: shared   # "shared" (default) or "isolated"
      snapshots:
        upper_dir_ttl: 2592000     # 30 d — writable overlay layer lifetime
        cleanup_interval: 3600     # sweep cadence (0 = startup only)
        min_free_gb: 5             # disk-pressure backstop
      sandbox:
        unshare_net: true         # block networking inside the sandbox (default)

See Configuration → Workspace Isolation Plugin for the full option table.

Browser sandbox on Ubuntu 24.04+

mermaid_render and webretrieve (JS rendering) drive a headless Chromium that renders agent-controlled content — fetched pages and model-generated diagram source. That browser runs host-direct, outside the workspace_isolation bwrap, so Chrome's own sandbox is its only containment layer. Codumentor always tries to launch with it enabled and falls back to --no-sandbox only after an observed failure, logging a warning.

On Ubuntu 24.04+, apparmor_restrict_unprivileged_userns=1 means that sandbox only starts if the browser binary's AppArmor profile grants userns. Which binary you run therefore decides whether you get a sandbox:

BrowserProfileSandbox
Distro Google Chrome (/opt/google/chrome/chrome)/etc/apparmor.d/chrome, shipped by apparmor✅ works out of the box
Distro Chromium (/usr/lib/chromium/chromium)/etc/apparmor.d/chromium, shipped by apparmor✅ works out of the box
Playwright-managed (~/.cache/ms-playwright/chromium-*/…)none❌ falls back to --no-sandbox

playwright install chromium produces the third row, and find_chrome() prefers the Playwright cache over a system browser — so the default install path is the unsandboxed one. Check what you are getting:

grep "Chrome sandbox" <instance>/logs/codumentor.log   # "retrying with --no-sandbox" = unsandboxed

Two ways to get a sandbox. Option A — point Codumentor at a profiled system browser (simplest; no AppArmor work):

sudo apt install chromium            # or google-chrome-stable
export CODUMENTOR_CHROME_PATH=/usr/bin/chromium

Option B — profile the Playwright binary. Keeps the pinned, known-good Chromium that the plugins are tested against. Model it on the distro's own Chrome profile, which is exactly flags=(unconfined) plus userns:

sudo tee /etc/apparmor.d/codumentor-chrome >/dev/null <<'EOF'
abi <abi/4.0>,
include <tunables/global>

# Grants unprivileged user namespaces to a Playwright-managed Chromium so
# Chrome's own sandbox can start. Same shape as the distro's /etc/apparmor.d/chrome.
#
# The profile NAME must not be `chrome` or `chromium` — those belong to the
# apparmor package, and a duplicate name displaces the distro profile.
# The revision glob is required: the directory changes on every
# `playwright install` (hosts routinely have several side by side).
profile codumentor-chrome /home/*/.cache/ms-playwright/chromium-*/chrome-linux64/chrome flags=(unconfined) {
  userns,
}
EOF
sudo apparmor_parser -r /etc/apparmor.d/codumentor-chrome

Adjust the path if the service account's home is not under /home/, or if you set PLAYWRIGHT_BROWSERS_PATH. Then restart the instance and confirm the warning is gone. To make a misconfiguration loud instead of silent, set CODUMENTOR_CHROME_SANDBOX=1 — the launch then fails hard rather than falling back.

Option B is derived from the distro profile's shape rather than verified end-to-end in our environment (this dev box has no system Chromium to compare against). Confirm with the grep above after applying it, and please report back if the glob needs adjusting.

Speech (read-aloud / voice)

read_aloud (the "🔊 read aloud" message action), Voice Mode (the full-screen voice conversation), and Telegram voice replies share one top-level speech: block pointing at an OpenAI-compatible speech service:

speech:
  enabled: true
  base_url: "https://api.openai.com/v1"
  api_key: "${OPENAI_API_KEY}"
  tts_model: "tts-1"
  tts_voice: "alloy"

The read-aloud action and the Voice Mode entry button light up only when enabled: true and api_key resolves to a real value through an environment variable. A per-user ${secret:...} key will not work here, because those web calls run outside an agent turn (where per-user secrets are resolved). Without a resolvable key the action renders disabled / the voice-conversation button stays hidden.

External coding agents (external_agent)

Each configured backend (claude-code, codex, …) is a separate CLI that the main agent can delegate to. Two things must be true for a backend to work:

  1. The CLI binary is installed and on PATH for the service user (binary: defaults to claude, codex, opencode, gemini).
  2. A credential resolves at spawn time — otherwise the call is rejected, not spawned. How the credential is sourced depends on mode:

``bash
# as the user the Codumentor process runs as:
claude /login # populates ~/.claude
codex login # populates ~/.codex
``

Sandbox tier. default_sandbox_tier (and per-backend overrides) sets isolation: tier 0 runs the CLI as a plain subprocess; tier 1 wraps it in a bubblewrap overlay (repo read-only, copy-on-write) and can enforce memory/wall-clock limits via user-level systemd-run plus a network egress allow-list. Tier 1 needs bubblewrap — see Workspace isolation prerequisites. When workspace_isolation runs with persistent_home: true, that shared $HOME is what external agents see, so a one-time login persists across sessions.

See also