jcoffey-dev is traveling from Thursday 1 October through Sunday 4 October. Issues and pull requests are welcome, and will get an answer after that. Thanks for your patience.
Phase 1 of inbuxa-drafts/specs/audit-hold-lock.md: the audit log (AU-1 to AU-12). Console side: inbuxa-admin feature/audit-log.
What gets recorded
Every registry change and every change to the fork's own settings (masked email, deleted accounts, AI limits, protocol policies, audit settings), with each field's before and after. Secrets appear only as "changed".
Administrator sign-ins, failed sign-ins to administrator accounts, and master-user and recovery-admin sign-ins, at most once an hour per account, method and address.
Access to another account's data through impersonation or FetchAnyBlob, once an hour.
Exports and tamper checks.
Registry writes the server makes on its own, named by subsystem (system:AcmeRenewal, system:auto-ban, system:directory-sync, system:oauth-registration). A spam rules update is one summary record.
Not recorded: spam training samples, and a user's own masked addresses and archive.
No change without its record (AU-3). A pending record per requested create, update and destroy is written before anything changes. If it can't be written, the method is refused with serverFail. The outcome follows afterwards, so a change cut short by a crash shows as unfinished.
Tamper-evident (AU-6). One SHA-256 chain per node under L in the fork's subspace. The head is stored rather than cached, and each append asserts it (compare-and-set). Nothing can edit or delete a record. The daily purge trims the oldest records past retention (default 730 days, minimum 90) and records the chain's new start. inbuxa:AuditVerification rechecks every chain.
JMAP (urn:inbuxa:jmap)
inbuxa:AuditEvent/get and /query
inbuxa:AuditSettings (keepForDays)
inbuxa:AuditExport (CSV or JSON Lines, built server-side, each line with its hash, ending in a manifest; returns blobId and sha256)
inbuxa:AuditVerification
Permissions. New sysAuditGet, sysAuditExport and sysAuditSettingsUpdate, granted once to the default roles on existing installs:
Administrator: all three.
Tenant Administrator: read and export only. Tenant admins see their own tenant's records, including a server admin's changes there.
Sign-in method (AU-5). Sessions remember how they signed in. New OAuth access tokens carry their client id in the sealed claims; tokens issued before this show as client unknown until they expire.
Events.security.audit-recorded (647) and security.audit-write-failed (648). The schema gains the permissions, the events and the Management > Compliance > Audit Log link.
Stack. The request layer boxes each inner future where it's made. An earlier version overflowed the default 2 MB worker stack on a registry set in a debug build. Measured with the same request, this branch and main now overflow at the same size (1856–1920 KiB, debug). Note that main itself has only about 130–190 KiB of headroom there.
Tests
Unit tests in inbuxa-features and jmap.
system::audit::audit_log_tests (run with --ignored) passes on RocksDB, SQLite, PostgreSQL 16, PostgreSQL with a read replica, MySQL 8, MySQL with a replica, and FoundationDB 7.4.
The system, jmap and scim suites pass. system_tests is timing-sensitive under load: it failed in three different places across runs, including once on untouched main, and passes with the machine quiet.
Checked by hand in the console against a copy of the demo data (list, filters, detail, export hash, tamper check, retention).
Divergence log: server V29.
Phase 1 of `inbuxa-drafts/specs/audit-hold-lock.md`: the audit log (AU-1 to AU-12). Console side: inbuxa-admin `feature/audit-log`.
**What gets recorded**
- Every registry change and every change to the fork's own settings (masked email, deleted accounts, AI limits, protocol policies, audit settings), with each field's before and after. Secrets appear only as "changed".
- Administrator sign-ins, failed sign-ins to administrator accounts, and master-user and recovery-admin sign-ins, at most once an hour per account, method and address.
- Access to another account's data through impersonation or `FetchAnyBlob`, once an hour.
- Exports and tamper checks.
- Registry writes the server makes on its own, named by subsystem (`system:AcmeRenewal`, `system:auto-ban`, `system:directory-sync`, `system:oauth-registration`). A spam rules update is one summary record.
- Not recorded: spam training samples, and a user's own masked addresses and archive.
**No change without its record (AU-3).** A pending record per requested create, update and destroy is written before anything changes. If it can't be written, the method is refused with `serverFail`. The outcome follows afterwards, so a change cut short by a crash shows as unfinished.
**Tamper-evident (AU-6).** One SHA-256 chain per node under `L` in the fork's subspace. The head is stored rather than cached, and each append asserts it (compare-and-set). Nothing can edit or delete a record. The daily purge trims the oldest records past retention (default 730 days, minimum 90) and records the chain's new start. `inbuxa:AuditVerification` rechecks every chain.
**JMAP (`urn:inbuxa:jmap`)**
- `inbuxa:AuditEvent/get` and `/query`
- `inbuxa:AuditSettings` (`keepForDays`)
- `inbuxa:AuditExport` (CSV or JSON Lines, built server-side, each line with its hash, ending in a manifest; returns `blobId` and `sha256`)
- `inbuxa:AuditVerification`
**Permissions.** New `sysAuditGet`, `sysAuditExport` and `sysAuditSettingsUpdate`, granted once to the default roles on existing installs:
- Administrator: all three.
- Tenant Administrator: read and export only. Tenant admins see their own tenant's records, including a server admin's changes there.
**Sign-in method (AU-5).** Sessions remember how they signed in. New OAuth access tokens carry their client id in the sealed claims; tokens issued before this show as client `unknown` until they expire.
**Events.** `security.audit-recorded` (647) and `security.audit-write-failed` (648). The schema gains the permissions, the events and the Management > Compliance > Audit Log link.
**Stack.** The request layer boxes each inner future where it's made. An earlier version overflowed the default 2 MB worker stack on a registry set in a debug build. Measured with the same request, this branch and `main` now overflow at the same size (1856–1920 KiB, debug). Note that `main` itself has only about 130–190 KiB of headroom there.
**Tests**
- Unit tests in `inbuxa-features` and `jmap`.
- `system::audit::audit_log_tests` (run with `--ignored`) passes on RocksDB, SQLite, PostgreSQL 16, PostgreSQL with a read replica, MySQL 8, MySQL with a replica, and FoundationDB 7.4.
- The `system`, `jmap` and `scim` suites pass. `system_tests` is timing-sensitive under load: it failed in three different places across runs, including once on untouched `main`, and passes with the machine quiet.
- Checked by hand in the console against a copy of the demo data (list, filters, detail, export hash, tamper check, retention).
Divergence log: server V29.
What administrators and the server itself do to the control plane is now
recorded, from inbuxa-drafts/specs/audit-hold-lock.md (AU-1 to AU-12):
settings, accounts, domains, roles and every other registry change, with
each field's before and after (secrets only as "changed"); the fork's own
settings objects; administrator sign-ins (and failed ones to administrator
accounts), master-user and recovery-admin sign-ins, once an hour per
account, method and address; access to another account's data through
impersonation or FetchAnyBlob, once an hour; exports and tamper checks;
and registry writes the server makes on its own, named by subsystem
(system:AcmeRenewal, system:auto-ban, system:directory-sync, ...), with a
spam rules update as one summary record.
No change without its record (AU-3): before a set method changes anything,
a pending record per requested create, update and destroy is written; if
that fails, the method is refused with serverFail. Its outcome follows as
a later entry. A change interrupted by a crash stays "unfinished".
Records live in the fork's subspace under L, as one SHA-256 hash chain per
node. The chain's head is stored, never cached, and every append asserts
it, so two writers can't take the same place. Nothing can edit or delete
a record; the daily purge removes the oldest past the retention (default
730 days, minimum 90) and records where the chain now starts, so
verification still passes. security.audit-recorded (647) copies each
record to webhooks, OpenTelemetry and the log; security.audit-write-failed
(648) reports a failed write.
New JMAP objects under urn:inbuxa:jmap: inbuxa:AuditEvent/get and /query
(filters: time, actor, action, target, account, tenant, outcome, address,
text), inbuxa:AuditSettings, inbuxa:AuditExport (CSV or JSON Lines built
on the server, each line with its chain hash, ending in a manifest; the
created object names the blob and its SHA-256) and
inbuxa:AuditVerification. New permissions sysAuditGet, sysAuditExport and
sysAuditSettingsUpdate: the Administrator role gets all three, the Tenant
Administrator role gets read and export, once, on existing installs too.
A tenant administrator sees records whose actor or target is in its
tenant, including a server administrator's changes there.
Sign-in method on the session: access tokens now remember how they signed
in (password, app password, API key, OAuth client, directory, master user,
recovery admin), including across the HTTP credential cache. New OAuth
access tokens carry their client id in the sealed claims; older ones show
as client "unknown" until they expire.
The schema gains the permissions, the two events and a Management >
Compliance > Audit Log link.
Stack: the request layer boxes every inner future where it's made. Without
that, a debug build overflowed the default 2 MB worker stack on a registry
set; measured with the same request, the branch and main now overflow at
the same stack size (between 1856 and 1920 KiB, debug), so the layer adds
nothing measurable.
Tests: unit tests in inbuxa-features and jmap; system::audit::audit_log_tests
(run with --ignored) passes on RocksDB, SQLite, PostgreSQL, PostgreSQL with a
read replica, MySQL, MySQL with a replica and FoundationDB. The system, JMAP
and SCIM suites pass. authorization.rs skipped fork permissions that guard
no registry object; the audit suite checks a plain user is refused instead.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Phase 1 of
inbuxa-drafts/specs/audit-hold-lock.md: the audit log (AU-1 to AU-12). Console side: inbuxa-adminfeature/audit-log.What gets recorded
FetchAnyBlob, once an hour.system:AcmeRenewal,system:auto-ban,system:directory-sync,system:oauth-registration). A spam rules update is one summary record.No change without its record (AU-3). A pending record per requested create, update and destroy is written before anything changes. If it can't be written, the method is refused with
serverFail. The outcome follows afterwards, so a change cut short by a crash shows as unfinished.Tamper-evident (AU-6). One SHA-256 chain per node under
Lin the fork's subspace. The head is stored rather than cached, and each append asserts it (compare-and-set). Nothing can edit or delete a record. The daily purge trims the oldest records past retention (default 730 days, minimum 90) and records the chain's new start.inbuxa:AuditVerificationrechecks every chain.JMAP (
urn:inbuxa:jmap)inbuxa:AuditEvent/getand/queryinbuxa:AuditSettings(keepForDays)inbuxa:AuditExport(CSV or JSON Lines, built server-side, each line with its hash, ending in a manifest; returnsblobIdandsha256)inbuxa:AuditVerificationPermissions. New
sysAuditGet,sysAuditExportandsysAuditSettingsUpdate, granted once to the default roles on existing installs:Sign-in method (AU-5). Sessions remember how they signed in. New OAuth access tokens carry their client id in the sealed claims; tokens issued before this show as client
unknownuntil they expire.Events.
security.audit-recorded(647) andsecurity.audit-write-failed(648). The schema gains the permissions, the events and the Management > Compliance > Audit Log link.Stack. The request layer boxes each inner future where it's made. An earlier version overflowed the default 2 MB worker stack on a registry set in a debug build. Measured with the same request, this branch and
mainnow overflow at the same size (1856–1920 KiB, debug). Note thatmainitself has only about 130–190 KiB of headroom there.Tests
inbuxa-featuresandjmap.system::audit::audit_log_tests(run with--ignored) passes on RocksDB, SQLite, PostgreSQL 16, PostgreSQL with a read replica, MySQL 8, MySQL with a replica, and FoundationDB 7.4.system,jmapandscimsuites pass.system_testsis timing-sensitive under load: it failed in three different places across runs, including once on untouchedmain, and passes with the machine quiet.Divergence log: server V29.