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.
- For the list of every built-in plugin and the YAML to enable one, see Setup → Plugins.
- For per-plugin configuration and behaviour, see the Plugin Catalog.
- For the trust model (plugins run unsandboxed, with full process access), see Security → Plugin Security.
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 provide | How |
|---|---|---|
workspace_isolation, branch_switching, scm_push, external_agent (sandbox tier ≥ 1), isolated subagents | Linux + bubblewrap with overlay support (apt install bubblewrap on current distros) + overlayfs + (Ubuntu 24.04+) userns allowed for bwrap | Workspace 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 grant | pip install playwright && playwright install chromium, then Browser sandbox on Ubuntu 24.04+ |
ripgrep | The rg binary on PATH | apt install ripgrep (or your distro's package) |
svn | A Subversion client | apt install subversion |
database | DB access. Native pure-Python drivers ship bundled (pg8000, PyMySQL, oracledb, python-tds). Only mode: cli (or mode: auto with no driver) needs an external client | psql / mysql / go-sqlcmd per engine |
read_aloud, voice_mode, Telegram voice | An OpenAI-compatible STT/TTS endpoint + API key via the top-level speech: block | Speech (read-aloud / voice) below |
google_workspace | A Google Cloud OAuth 2.0 client (id + secret) and the Gmail/Calendar/Docs/Sheets/Drive APIs enabled; also oauth + auth.jwt_secret | Google Workspace |
websearch | A search-provider API key (Tavily, Brave, Serper, Google CSE, …) | provider_config.api_key: "${TAVILY_API_KEY}" |
external_agent | The 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_report | A reachable Redmine instance + API key | redmine: each user stores a redmine_api_key secret; bug_report: instance api_key in config |
user_secrets, model_profiles, oauth | auth.jwt_secret set, or these stores are written unencrypted | Authentication → jwt_secret |
goals, scheduler_tools | The 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.
| Requirement | Minimum | Notes |
|---|---|---|
| OS | Linux | Ubuntu 22.04+ / Debian 12+ recommended. (No Windows/macOS host sandbox.) |
| Kernel | 5.11+ | For unprivileged overlayfs |
| bubblewrap | 0.8.0+ | Needs overlay support (--overlay-src) and must not be setuid — see below |
| AppArmor | — | Needs 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 declaresprofile bwrap /usr/bin/bwrap. AppArmor profile names are global, so a hand-written/etc/apparmor.d/bwrapthat declaresprofile bwrap /usr/local/bin/bwrapcollides with and displaces it, silently leaving/usr/bin/bwrapunable to create user namespaces. Confirm which definition is live withsudo 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:
- Upgrade Codumentor on every instance on the host.
sudo rm /etc/apparmor.d/bwrap && sudo apparmor_parser -r /etc/apparmor.d/bwrap-userns-restrict- If you were running a locally built bwrap on
PATHahead of/usr/bin, retire it (move, don't delete) soPATHfalls through to the packaged binary. - Verify — see the warning below about verifying as root.
- 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 prefercheck.shover 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:
| Browser | Profile | Sandbox |
|---|---|---|
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
grepabove 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:
- The CLI binary is installed and on
PATHfor the service user (binary:defaults toclaude,codex,opencode,gemini). - A credential resolves at spawn time — otherwise the call is rejected, not spawned. How the credential is sourced depends on
mode:
technical_user— one deployer credential shared by all callers. Either setapi_key(supports${env:VAR}/${secret:NAME}), or omit it and let the CLI use its own auth store under$HOME. For the omit case you must log in once as the service user so that store exists:
``bash``
# as the user the Codumentor process runs as:
claude /login # populates ~/.claude
codex login # populates ~/.codex
byo_subscription— each end user authenticates their own CLI subscription (OAuth) into a per-user$HOME; no shared deployer credential.
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
- Setup → Plugins — full plugin list and enable syntax
- Plugin Catalog — per-plugin configuration reference
- Security → Plugin Security — the plugin trust model
- Configuration — full
codumentor.yamlreference