Log SQL with placeholders, never bound values (closes #207) (#222)
Some checks failed
check / check (push) Superseded by a newer commit; never tested
Some checks failed
check / check (push) Superseded by a newer commit; never tested
This commit was merged in pull request #222.
This commit is contained in:
63
README.md
63
README.md
@@ -256,6 +256,51 @@ On first startup, webhooker creates an `admin` user
|
||||
with a randomly generated password and logs it to stdout. This password
|
||||
is only displayed once.
|
||||
|
||||
#### What `DEBUG=true` exposes
|
||||
|
||||
`DEBUG=true` lowers the log level to `DEBUG`, which turns on every
|
||||
statement GORM runs, the two by-design lookup misses on the
|
||||
unauthenticated routes, and the rate limiter's own rejections. It is
|
||||
meant to be safe to turn on while diagnosing a live service and safe to
|
||||
paste the output of into a bug report.
|
||||
|
||||
What it does **not** put in the log:
|
||||
|
||||
- **Values bound to a SQL statement.** Statements are logged with their
|
||||
placeholders, never with the values substituted into them, at every
|
||||
level. That is what keeps the session encryption key out of the first
|
||||
boot's `INSERT INTO settings` and the `admin` account's Argon2id
|
||||
password hash out of its `INSERT INTO users` — the two statements
|
||||
that made a debug log worth stealing. It applies to every table and
|
||||
every statement rather than to a list of tables known to hold a
|
||||
secret, so a table added later is covered without anyone remembering
|
||||
to add it. The cost is that a failing statement can no longer be
|
||||
replayed from the log alone: the statement, the table, the driver
|
||||
error and the row count are all still there, but its values have to
|
||||
come from the database.
|
||||
`internal/gormlog/firstboot_test.go` boots the real graph with
|
||||
`DEBUG=true` against an empty `DATA_DIR` and asserts that neither
|
||||
secret appears in what that boot wrote to stdout.
|
||||
The one exception is `(*gorm.DB).Scan`, which GORM logs through its
|
||||
own trace recorder rather than through this filter. No production
|
||||
code path calls it, and `internal/gormlog/scan_guard_test.go` fails
|
||||
if a non-test file adds one.
|
||||
- **Session cookies, API keys or target credentials.** None of these is
|
||||
logged at any level.
|
||||
|
||||
What is in the log regardless of `DEBUG`, and is not a debug-logging
|
||||
decision:
|
||||
|
||||
- **The initial `admin` password**, in the clear, once, at `INFO`, on
|
||||
the first boot that creates the account. That line is the only place
|
||||
it is ever shown; the database stores the hash. A first boot's output
|
||||
is not safe to paste anywhere until that account's password has been
|
||||
changed.
|
||||
- **An authenticated operator's own configuration**, echoed back
|
||||
untruncated — webhook names, target hostnames. See the logging
|
||||
section under Security for the full list and for the per-line size
|
||||
bound that covers unauthenticated traffic.
|
||||
|
||||
### Running with Docker
|
||||
|
||||
```bash
|
||||
@@ -1464,6 +1509,24 @@ and the driver error — against a smaller fixed portion than the access
|
||||
log's, and `internal/gormlog/gormlog_test.go` asserts each line against
|
||||
`MaxAccessLogLineBytes` directly rather than leaving it as arithmetic.
|
||||
|
||||
The adapter also logs no bound value at all: it implements
|
||||
`gorm.ParamsFilter` and discards the parameters, so GORM renders the
|
||||
statement with its placeholders intact instead of substituting the
|
||||
values into it. That is a separate property from the size bound and it
|
||||
is what a bound is no substitute for — the session encryption key is 44
|
||||
base64 characters and an Argon2id hash under 100, so both fit inside
|
||||
every budget above and a truncated secret is still a secret. It holds
|
||||
on all three arms of `Trace`, including the routine one an operator
|
||||
reaches at `DEBUG`, which is the only level at which a successful
|
||||
`INSERT` is written at all. One GORM path does not consult the filter —
|
||||
`(*gorm.DB).Scan`, which records the statement through GORM's own trace
|
||||
recorder. No production code path calls it; its one caller is
|
||||
`internal/database/database_test.go:91`, whose `SELECT 1` binds
|
||||
nothing, and `internal/gormlog/scan_guard_test.go` fails if a non-test
|
||||
file calls it. `Pluck`, `Row` and `Raw` all run through the normal
|
||||
callback processor and are filtered.
|
||||
See `#### What DEBUG=true exposes` under Configuration.
|
||||
|
||||
What that ceiling does **not** cover, stated here so the figure is not
|
||||
read as more than it is:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user