Thursday, August 27, 2026
3 changes · master
Security fixes and vulnerability patches
Users can now preview text-based attachments such as Markdown, JSON, XML, and plain text directly in chatter and Knowledge instead of downloading them first. The change also improves privacy and security by limiting caching of restricted files, blocking external tracking content, and safely isolating untrusted previews.
Original PR description
Currently, (pre)views of text attachments (ex: Markdown) directly in the browser is a feature exclusive to the Documents app. In the standard chatter, these files only display a generic icon and…
Currently, (pre)views of text attachments (ex: Markdown)
directly in the browser is a feature exclusive to the
Documents app. In the standard chatter, these files only display a
generic icon and force the user to download them to see their content.
This commit extracts the text rendering engine from the `documents`
app and integrates it into the core `mail` module to make it globally
available.
What this improves:
- Users can now see live thumbnails of text-based files
(txt, md, json, xml) directly inside chatter attachment cards.
- Clicking a markdown attachment in the chatter now opens the full-screen
preview instead of forcing an immediate file download.
- Clicking a text attachment in the knowledge app now shows a formatted
view of the attachment instead of raw text.
- Implements checksum-based caching for Documents thumbnails and previews.
This cache is set to 'private' to prevent reverse proxies (like NGINX)
from caching and leaking access-restricted documents.
- Implements a split Content-Security-Policy (CSP) to maximize security:
- Raw HTML and JSON files get a strict `default-src 'none'; sandbox;`
policy, preventing all external resource loading and scripting.
- Rendered Markdown, Text, and Email get a tailored policy allowing same-domain
styles and images, preserving formatting but actively blocking external
images and tracking pixels (which were previously allowed in Markdown renders).
- Increased security by visually neutralizing clickable links in Markdown,
preventing deceptive external navigation.
Server performance is maintained by safely reading only the first few
kilobytes of large text files for the thumbnails, preventing memory
exhaustion on the worker. (Note: HTML files are streamed in full even
for thumbnails to prevent markup breakage).
Note: a commit before the main one was inserted to change hoot testing behavior to prevent errors upon inserting sandboxed iframes.
Task-5417637This update tightens how Odoo builds and runs dynamic database queries, reducing the chance of unsafe query construction and making automated checks simpler. The change affects many areas internally, with the main business benefit being stronger protection and more consistent maintenance across modules.
This update prevents sensitive database API keys from being exposed when creating a database from a template. It keeps the database creation process working while adding tests to confirm the key remains hidden from users and systems that should not see it.
Original PR description
This commit is a follow-up to 8741c123997438beb56fa065667b431616c809a5 which hardens the security of the `database_api_key` field on `project.project`. The issue is that a similar field is still accessible on the wizard allowing the creation of a database from a template, `project.template.create.wizard`. With this commit, the field is masked in the same way. A test checks that the key cannot be read in cleartext from the ORM any more, and that the database creation still initialize the correct key. Forward-Port-Of: odoo/enterprise#128810 Forward-Port-Of: odoo/enterprise#128596
Original PR description
`odoo.tools.sql.SQL` was added in Odoo 17, and for the most part it's worked quite well. This PR aims to start the process of making it required for SQL queries, alongside simpler CI (the removal of…
`odoo.tools.sql.SQL` was added in Odoo 17, and for the most part it's worked quite well.
This PR aims to start the process of making it required for SQL queries, alongside simpler CI (the removal of the existing complicated pylint checker and the requirement of a semgrep that's both stricter and dumber).
Most of the changes are straightforward if wide ranging, however a few core changes stand out:
- support for strings in `Query.order` and `Query.add_where` has been removed, as they're essentially the same as concatenating unchecked strings into SQL queries (completely unnecessarily)
- because semgrep has limited support for interprocedural analysis and none for typechecking, it gets confused by things like
```python
def foo(self):
return SQL("SELECT 1")
def bar(self):
self.env.cr.execute(self.foo())
```
Because extracting bits and pieces into their own functions is both reasonable and a commmon pattern when building up queries, a fastpath was added for the case of `SQL("%s", var)`[^1], this essentially passes `var` through unchanged *if it's already an `SQL`*, which means it can be used as a safe *and* cheap way to whitelist the value for semgrep that something is fine.
- `cursor.execute_values(str)` is deprecated, it should receive a `Composable`
- `cursor.execute(str)` is not deprecated yet
- there are >150 extant literal-string queries (which have no safety issues but would likely still need to be migrated)
- `SQL` is not currently available in server actions, and the risk of exposing it has not been studied (though at first glance I don't really see more risks with it than with string queries)
## t-strings
Python 3.14 introduces t-strings, which would make *some* migrations simpler. However a lot of the migration work is from sub-queries needing to be migrated, which t-strings would not affect that materially. Debian Forky won't release until 2027 (if not delayed) and it's currently still on Python 3.13 (so is sid). Likewise Ubuntu 26.04 (the next LTS) although it does have 3.14 available as an optional additional (https://packages.ubuntu.com/search?keywords=python3.14).
While I do believe t-strings will make queries more convenient without loss of safety I don't think they make the migration so much easier that there is a good reason to wait *years* before committing.
## Static analysis
The more local behaviour allows for simpler static analysis (especially backed by dynamic checks): use semgrep to check that `SQL` calls only receive a literal first argument, and that `execute` only receives `SQL` objects[^2][^3]. This is slightly hampered by some limitations of semgrep e.g. semgrep sees
foo = "bar"
thing(foo)
as equivalent to
thing("bar")
but such is not the case for bespoke types / expressions. In theory [symbolic propagation](https://semgrep.dev/docs/writing-rules/experiments/symbolic-propagation) does that but it doesn't seem to always work, and furthermore as noted does not work in all cases (e.g. symbols don't currently propagate past branches).
This PR recommends resolving those issues via `SQL("%s", previously_generated_sql)` which it special cases (via an optimisation).
This PR has little to no effect on pylint: while the SQL linter is complex and brittle, experimentally removing it has limited impact on the pylint run time. However it's a big piece of the *motivation* for using pylint, removing it makes for a strong argument to eventually remove the pylint test entirely, replacing its various bits with other linters (ruff or semgrep rules, bespoke handrolled `ast`-based checkers).
odools might eventually handle this even better through models-aware typechecking, but it's currently far from being there.
## Possible Concerns
- how to make server actions work with this system (just expose `SQL`?)
- how to *transition* server actions (catch the warning and notify the admin?)
- `sql.Identifier(sqlype)` sort-of works but becomes case-sensitive (as with other identifiers), and the quoting of attributed types can be temperamental e.g. `CAST(1 as "numeric"(5, 3))` works but `CAST(1 as "char"(3))` fails with "type modifier is not allowed". After more experimentation, `char` seems to be a specifically non-working type, varchar, bit, time do work although the date/time fields in their standard (expanded) form seem extremely temperamental.
[^1]: well it also allows setting `to_flush` as that seems harmless and there's one case which was [committing a mess](https://github.com/odoo/odoo/blob/26440c28f8a242484959afc3d7993b40e4237e04/odoo/orm/models.py#L3245-L3247) for the sole purpose of updating the flushing
[^2]: the second part is not in this PR, but would be a good idea (if requiring a lot more migration time due to needing to take a decision wrt server actions)
[^3]: and eventually t-strings maybe