Skip to content

Security

Omisen edited this page Aug 14, 2026 · 1 revision

The installer's security choices and its known limits, stated honestly.

Odoo master password

  • The admin default is discouraged. Interactively it requires an explicit confirmation; non-interactively with admin the installer stops (set a different password).
  • The password is treated as a secret: it never appears in the logs, the summary or the output. It ends up only in the final config file (/opt/odoo/odoo<N>.conf, permissions 0640 odoo:odoo) — and that file is written through a private temporary (0600), so the password is never readable by other users at any instant.

wkhtmltopdf checksums — TOFU pinning (known limit)

The installer verifies the SHA-256 of the wkhtmltopdf package before installing it. But the official release publishes neither checksums nor signatures for the packages (only the git tag is GPG-signed). Hence manual TOFU pinning (trust-on-first-use): pins generated once from a trusted source (official HTTPS), after which every download is verified against that pin.

The pins are preloaded for the three amd64 .deb files of release 0.12.6.1-3 (jammy, bullseye, bookworm), generated by downloading them over HTTPS from the official release. If a download does not match its pin — or if a variant has no pin — the fail-closed check refuses the installation. That behaviour is never bypassed.

To regenerate them (only needed when changing the pinned version):

for cn in jammy bullseye bookworm; do
  url="https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.${cn}_amd64.deb"
  echo -n "$cn = "; curl -fsSL "$url" | sha256sum | cut -d' ' -f1
done

Put the values into default_checksums() (src/steps/install_wkhtmltopdf.rs). They are TOFU pins, not upstream checksums: update them when you change version. Detail: 1.5 InstallWkhtmltopdf.

Least privilege

  • The odoo system user is created with useradd --system --user-group --shell /bin/false: no interactive shell, a system account with no password.
  • The systemd service runs as odoo (not root), with hardening (NoNewPrivileges, PrivateTmp, ProtectSystem=full, ProtectHome, …). See 1.14 SetupSystemd.
  • The odoo helper command is installed for the installing user only (~/.local/bin), not in /usr/local/bin: less exposure to other users and automations.

The .env configuration is not executed

The .env file is parsed declaratively, never sourced: it is not a code-execution vector, unlike the old Bash version. See .env file reference.

--aggressive-rollback — handle with care

By default the rollback leaves the common utilities installed and does not purge PostgreSQL (too destructive to do automatically). With --aggressive-rollback those are purged as well — but the PostgreSQL purge is still declined if the cluster hosts other databases, so as not to destroy another application's data. Do not use it on machines with a shared cluster unless you are certain.

Temporary files: where they are born matters as much as how

Wherever root writes a file that another user will read, the file is not born in a shared directory. /tmp is world-writable, and a name written in the source is known to anyone: between root's write and the odoo user's read there is a window in which that file can be replaced. For pip's requirements that would mean having arbitrary packages installed into the venv — that is, code execution as the owner of the filestore and the database. The issue is not the symlink (which fs.protected_symlinks mitigates) but the replacement of the contents, and there the kernel does not help.

File Where it is born Why
pip requirements <install_dir>/sandbox it belongs to odoo and is not writable by third parties: the attack's premise disappears. And it sits inside the reversible perimeter
source tarball system temp, random name, created fail-closed there is no expected checksum to hold against replaced contents
wkhtmltopdf package system temp, random name (extension preserved) the contents are already protected by the TOFU pin; here we close the point where the file is born

“Fail-closed” means O_CREAT | O_EXCL | O_NOFOLLOW: if something is already at that path — file, directory or symlink — the operation fails instead of writing over it.

The manifest is a trusted source, and it is verified

/var/lib/invok/state.json drives rm -rf, dropdb and userdel. Before consuming it for a real rollback we check that it belongs to root, is not writable by group or others, and does not live in a directory writable by third parties — where it could be replaced without even being modified. And that it declares the expected perimeter (/opt/odoo, a constant that cannot be overridden): a manifest declaring a different one does not describe an installation made by this program.

Reporting

Security vulnerabilities: see SECURITY.md in the repository.

Clone this wiki locally