The forensic record
You found the row that matters — a call to a host you do not recognise, or one you simply want to understand. Now the question is: what exactly does the log know about it, and can I trust that what it shows me has not been quietly edited? This page opens a single row of the audit log and walks every field on the Forensic Record form, in plain language, so you know precisely what was captured, what was deliberately not captured, and how the row proves it has not been tampered
You found the row that matters — a call to a host you do not recognise, or one you simply want to understand. Now the question is: what exactly does the log know about it, and can I trust that what it shows me has not been quietly edited? This page opens a single row of the audit log and walks every field on the Forensic Record form, in plain language, so you know precisely what was captured, what was deliberately not captured, and how the row proves it has not been tampered with.
Open any row from Network Audit → Audit Log to land on this form.
The Forensic Record form: one network event, every field — and the hash chain that seals it.
Metadata only — by design
Before you read another line: this form has no body field, and that is not a setting you can flip. The captured event simply has nowhere to store a request or response body. What you see below is all the log ever keeps about a call. See SIZE & TIMING for why that is the whole point.
WHAT happened — the shape of the call
The first thing you want from any log line is the gist: which way did the data go, over what kind of connection, and did it succeed? The Forensic Record answers that in five fields.
| Field | What it tells you |
|---|---|
| Direction | Whether this was traffic leaving your Odoo (outbound) or arriving at it (inbound). |
| Channel | Which network layer captured the call — for example requests, httpx, urllib, imap, smtp, pop3, the raw TLS socket, or the low-level socket backstop. |
| Protocol | The protocol seen on the wire (for example HTTP/HTTPS for web calls, or the mail protocol for a mailbox sync). |
| Method | The verb of the call — an HTTP method like GET or POST for web traffic, or the equivalent operation for the channel. |
| Status | The outcome — the HTTP status code for web calls, so you can tell a clean 200 from a 403 or a failure. |
Read together, these five tell you the story of the call before you ever look at the
address: "an outbound HTTPS POST over requests that returned 200" is already a
complete sentence.
Why the channel matters
The Channel field is your evidence that the capture went deep. A call can be
seen by a high-level library hook (requests, httpx) and, if it slips past one,
by the raw socket backstop — so even a library the auditor does not specifically
know still produces a row with a destination. A channel of socket is not an error;
it is the safety net doing its job.
WHERE it went — the destination
This is the section most people open the record for: where did my data actually go?
| Field | What it tells you |
|---|---|
| Host | The destination hostname the call was made to. |
| URL path | The path portion of the URL (no body, no secrets in the path beyond what was on the wire). |
| Query keys | The names of any query-string parameters — the keys only, never their values. |
| Destination IP / port | The resolved IP address and port the connection actually reached. |
| Source IP / port | The local address the connection went out from. |
The host is matched against the seeded Egress Components catalogue to decide whether the destination is MIT-bound or third-party — the same classification that drives the verdict on the Trust Report. On a row attributed to one of your own modules talking to your own Gmail, IMAP, or LLM endpoint, you should see your provider's host here — not an MIT one.
Keys, not values
The Query keys field keeps parameter names (so you can see the shape of a
request) but never the values. A ?token=… becomes a record that a token
parameter was present, not the token itself.
WHO made the call — attribution
A destination on its own is only half the answer. The field that makes this module a trust tool rather than a packet sniffer is attribution: every row names the Odoo module responsible for the call.
| Field | What it tells you |
|---|---|
| Addon (technical name) | The technical name of the Odoo module that originated the call (for example maildesk_mail_client). |
| Module | The human-readable module the call belongs to. |
| Author | The author declared by that module's manifest. |
| Call site | Where in the code the call was made — the originating location identified by walking the live call stack. |
This is captured by walking the live call stack at the moment of the network
operation and matching the responsible frame back to an installed ir.module.module.
It is what lets you say, with evidence, "this call to my mail provider came from
MailDesk, authored by Metzler IT" — and, just as importantly, "no call on this list is
attributed to a module I did not install."
The self-egress check
mit_network_audit makes no outbound calls of its own. If you ever see a row
whose Addon is mit_network_audit, the Trust Report
raises an alarm — the auditor catching itself would be the one thing it must never
do silently.
SIZE & TIMING — bytes, no body stored
Here is where the redaction-by-design shows up on the form. You get the measurements of the payload, never the payload.
| Field | What it tells you |
|---|---|
| Request size | How many bytes the request carried. |
| Response size | How many bytes the response carried. |
| Timings | How long the call took. |
That is deliberately everything. The sizes let you reason about volume — a sudden large upload to an unexpected host is visible as a number — without the log ever holding the content of that upload. No request or response body is stored on this record. The captured event has no field for one; the redaction is structural, baked into the data model, not a checkbox an administrator could uncheck.
The one exception, stated plainly
There is a separate, off-by-default, officer-only capability — forensic body capture — that can store bodies for a legal case. It is not part of this record, it is gated to the Network Forensic Officer role, and every read of a captured body is itself logged to this same append-only journal. If you have not turned it on, no bodies exist to read.
PROVENANCE — where the record itself came from
These fields are about the recording, not the call: they let an auditor place the row in time and on a specific machine.
| Field | What it tells you |
|---|---|
| Logged at | When the event was recorded. |
| Worker PID | The process ID of the Odoo worker that captured it. |
| DB name | The database the event was logged in. |
Provenance is what makes the log usable in a real investigation: you can correlate a row to a particular worker process and a particular database, not just to "the system."
TAMPER-EVIDENT CHAIN — the row that seals onto the last
This is the heart of why you can trust the log. Three fields turn an ordinary table into a tamper-evident chain.
| Field | What it tells you |
|---|---|
| Chain Seq | A database-unique serial number that fixes this row's position in the chain. |
| Prev Hash | The hash of the previous row in the chain. |
| Row Hash | This row's own hash — a SHA-256 computed over this row's contents plus the previous row's hash. |
The seal in close-up: Chain Seq, Prev Hash and Row Hash — and below them, headers kept by name but with their values masked.
What the chain means, in plain terms
Think of the log as a stack of wax seals. Each new row is stamped with a seal that is made from its own contents and an imprint of the seal below it (its Prev Hash). Because every row's Row Hash folds in the one before it, you cannot quietly change an old row without breaking the seal on every row stacked on top of it. To forge one line, an attacker would have to re-compute the entire chain from that point forward — and even then, two further defences stand in the way:
- The journal is append-only. The
network.egress_logtable rejectswriteandunlink— even withsudo— for everything except the retention vacuum. There is no supported path to edit a row in place. - A daily job signs the chain head. The current top-of-chain is HMAC-signed into a stored parameter, so even deleting rows is detectable: the signed head outlives the rows it covers. (More on this on Verify & export.)
When you press Verify chain on the Trust Report, the module recomputes the Row Hash of every row from the chain itself and compares. If everything matches you get "Audit chain verified OK — no tampering detected." If a single row was altered, the verifier names the first row where the chain breaks.
What this proves, and what it does not
The chain proves the log has not been edited after the fact — it is tamper-evident. It does not, on its own, prove that every call your system ever made was captured. The capture is deep (down to the socket), but this is a transparency-and-detection tool, not a sealed sandbox. We say so on How it works rather than oversell it.
Masked request and response headers
The record keeps the headers of both the request and the response — but masked. You
see the header names (so you can confirm, for instance, that an Authorization
header was present and what content type was negotiated) while the sensitive values
are masked at the moment of capture. The names tell you the shape of the exchange; the
masked values mean a bearer token or cookie never lands in the log in the clear.
Strict redaction goes further
If Strict redaction is enabled in Settings, capture keeps only host, IP, port and sizes and masks everything else at capture time — so on a strict-mode deployment the header detail above is intentionally minimal. That is a stronger privacy posture, not a malfunction.
From one row to proof
Reading a single record tells you what happened. Turning that into something you can hand to an auditor — a signed, independently checkable export — is the next step.
- Verify & export — run Verify chain and produce a signed JSON proof for a standalone verifier.
- The audit log list — filter and find the rows that matter before you drill in here.
- Reading the Trust Report — the dashboard that summarises all of these rows at a glance.