Build Report · Week 37 · the week strangers could vouch for you, and the whole stack was taken apart looking for ways in

Vetted by people who already know you — and a security review that went through every project before we take it to Linux Plumbers

463 changes this week, against 88 last week — the biggest week of the year by a wide margin, and five and a bit distinct efforts running at once. The headline is vetting: a community can now name who is allowed to vouch for newcomers, a newcomer can be vouched for by people who already know them, and the joining decision counts those vouches — the whole path, end to end, with a Linux-style PGP web of trust as the way a community bootstraps its first vetters. Running beside it, a security review across every project in the stack — 46 hardening changes in seven of them, ahead of taking this to Linux Plumbers. Data rooms reached the browser and the community app, personas became something a person can actually arrange and inspect end to end, and the interfaces got a lot of care — the terminal app has themes now. The one nobody asked for but should read: the agent's audit log became a tamper-evident chain, and clients stopped believing replies that are not signed.

📅 September 6 → September 13, 2026 · 10 projects in scope · 9 of them shipped
463
changes shipped across nine of the ten projects — the busiest week of the year, more than five times last week
46
security-hardening changes across seven projects — a deliberate stack-wide review, not incidental fixes
+1,079
new automated checks (11,708 → 12,787), like-for-like on the same eight projects as last week
105
changes on the busiest day — Monday September 7
The Big Picture

Six things changed this week

Last week built two features to a design. This week ran five efforts at once and then walked the whole stack looking for ways in. The through-line is the same one as the last month: decide what the system must never be able to do, then make that structural. A vouch that cannot be forged because the community named who may give one. A reply that is not acted on because it was not signed. An address a wallet will not dial because it is not a public one.

🛡️

Every project taken apart, deliberately

A stack-wide security review ahead of Linux Plumbers — 46 hardening changes across seven projects, in one pass rather than as scattered fixes. The largest strand runs everywhere at once: anything that takes a web address it did not choose — from an identity document, a service advertisement, a push registration — now refuses addresses that are not genuinely public, and refuses to follow a redirect somewhere else. That closes a class of attack where a document you resolve talks your own server into fetching something on the attacker's behalf. Alongside it: backup files written readable only by their owner, an admin credential checked for who sealed it before it is installed, a sign-in challenge that stopped revealing whether an account exists, a plugin icon rendered as an image rather than as markup, and every automation step in every build pipeline pinned to an exact version and dropped to least privilege.

✅

Vouched for by people who know you

The week's headline, and new end to end. A community can publish what it requires to join, name who may vouch for a newcomer with a role credential it can revoke, list those vetters in a directory with profiles, and count the vouches in the joining decision. A vetter can withdraw a statement they later regret. A newcomer gets a guided join that says in plain words what this community wants, tracks their application, and can carry a QR ticket from an in-person meeting. For a Linux-style community that already runs on a PGP web of trust, there is a bootstrap that turns existing key-signing relationships into that first set of vetters — so the community does not start from zero.

🚪

Data rooms, now where people actually are

Last week data rooms existed in the engine. This week they reached the surfaces people use. A browser can be a room member — not a viewer of someone else's session, an actual member that verifies a room's invitation itself. The community app gained a rooms console for operators, and the browser wallet gained a create-a-room flow that mints the room's identity as part of the flow rather than asking someone to prepare one first. Underneath, the parts that make a room trustworthy over time: a record commitment so a member can check the room's history has not been quietly edited, an epoch key chain so a joining member can read what came before, and read mirrors — a host that serves a copy and can write nothing.

🎭

Your identity, arranged the way you think about it

Personas went from a mechanism to something a person can hold in their head. The organising idea is worlds — the parts of a life — with faces in them, so the app can say which links cross a part of your life rather than showing an undifferentiated list. Values can be marked sensitive and stay hidden behind a per-value Show, even from a listing that did not ask for them. A deployment can declare its own claim types rather than living with a fixed vocabulary. And a holder can mark a value as needing a second confirmation before it is ever disclosed — which now actually stops the disclosure, on the phone, rather than failing at the last hop.

🎨

Interfaces that explain themselves

A great deal of care went into the surfaces, in both apps. The terminal app got themes — built in, bring your own, light/dark that follows the system, accessible palettes, NO_COLOR, and import from Neovim and Omarchy. The browser wallet's identity panel was rebuilt as a picture rather than a form: guided first, then a map of where you are known — showing where you are known, not everywhere you could be. Forms open where you clicked. The setup guide steps backwards. And a fair amount of it was vocabulary: the screens stopped saying "disclosure" and "fact" and started saying what a person would say.

📜

Nothing is believed unless it is signed

The one not on anyone's list, and arguably the most consequential. Two halves. First, the agent's audit log became a hash chain with an audit key of its own, and a way to verify it — so an entry cannot be removed or altered after the fact without the chain saying so, and use of the signing oracle is itself recorded. Second, and bluntly: the specification requires successful replies to be signed, 265 requirements say so, and neither service was sending one. Both now sign, and — the half that makes it mean anything — the clients, the wallet and the browser now check. A reply that is not signed is no longer acted on.

Why It Matters

What this means, in plain terms

🤝

Identity you can actually earn, without a company deciding

Most systems answer "is this person real?" by making you prove it to a corporation — upload a passport, pass a face scan, trust that the result is stored well. Communities have always had a better answer: the people already in the room know. A maintainer vouches for a contributor they have worked with for years; a key-signing party is exactly this, done with paper and pens. What shipped this week turns that into something a system can act on. The community decides what it needs, names whose word it takes, and the decision to admit someone counts those words — with no central verifier anywhere in the picture, and a vetter's authority revocable the moment it should be. For a community that already has a web of trust, the existing relationships carry straight over.

🔍

A signature nobody checks is decoration

The stack has been signing things for a long time. This week it found that one whole direction was not: successful replies were unsigned, by both services, against a specification that requires it in 265 separate places. It is the most ordinary kind of gap — everything worked, every test was green, and nothing anywhere would have told you. That matters because a reply that is not signed can be replaced by whoever is carrying it, and a client that does not check could not tell. Both halves were fixed together, and that ordering is the point: signing without checking would have been a bigger lie than not signing at all, because it would have looked finished. The same pattern turned up in the audit trail, which is now a chain — an entry that is quietly altered later breaks the chain and says so.

🚧

The dangerous address is the one you were handed

The single biggest strand of the security review runs through every project, and it is one idea. Software here constantly follows addresses it did not choose: an identity document names where to fetch its history, a service advertisement names where to deliver a message, a device registers where to send its notifications. Each one is an instruction from somewhere else, and a server that follows it without judgement can be talked into fetching things on an attacker's behalf — including from inside its own network, where nothing is exposed to the internet and much is unguarded. Every one of those points now refuses addresses that are not genuinely public, refuses to be redirected elsewhere afterwards, and — the part that is easy to miss — checks every address a name resolves to, not just the name, because a name can be made to point inwards a moment after it was checked.

463
changes across 9 of 10 projects — busiest day: 105 on Monday September 7
+1,079
new automated checks (11,708 → 12,787) — like-for-like, same eight Rust projects as last week
+180k
lines added (−25.0k removed) across 988 files
46
security-hardening changes across 7 projects, in one deliberate review pass
★ Big outcome #1 · The week's headline

Vetting — a community decides whose word it takes, and a newcomer earns their way in

Think of joining a climbing club. You could prove yourself to a national body: send your documents, pay a fee, wait for a certificate, and trust that the body stored your passport properly. Or two members who have climbed with you say so, and the club — which decided in advance that two members' word is what it wants — lets you in. The second is faster, involves no central database of anyone's documents, and is how almost every real community has always worked. It has simply never been something software could act on. That is what shipped this week.

What was built. The whole path, in both the engine and the app. A community publishes its requirements in its join manifest, so a newcomer can see what is wanted before applying. It names its vetters by issuing each of them a role credential — which it can revoke, so "who may vouch" is a live answer rather than a list someone maintains. A vetter directory and profiles let a newcomer find the right person. A vetter writes a vetting statement; they can also withdraw one later, which matters, because a vouch nobody can take back is a vouch nobody should give. The joining decision counts the statements against the requirements. Around it, the practical parts: automatic grants when an application meets the bar, grant resend for when a message is lost, revocation checks on the grant itself, community branding so a newcomer can tell one community from another, and QR tickets so a vouch begun at a physical meeting carries into the app.

The Linux part. A community with no vetters yet has a chicken-and-egg problem: vetters are named by the community, and there is nobody to name them. For communities that already run on a PGP web of trust — which is to say most of the Linux world — the admin tool now bootstraps the first vetters from those existing key-signing relationships. The web of trust that already exists becomes the seed, so a community starts from where it actually is rather than from nothing.

📋
The community says what it wants
Requirements are published in the join manifest, in plain words, before anyone applies. No guessing what the bar is.
🎖️
It names who may vouch
Vetters hold a role credential the community issued and can revoke. "Whose word counts" is live, not a list to maintain.
✍️
People who know you vouch
A vetting statement, findable through a directory, begun at a meeting via QR if you like — and withdrawable later.
🚪
The count decides
Statements are counted against the requirements; a passing application is granted automatically. No central verifier anywhere.
★ Big outcome #2 · The security review, and the one idea running through most of it

Forty-six hardening changes, in seven projects, before this goes on a conference stage

Taking a trust stack to Linux Plumbers means people who break things for a living will look at it. So the week included a deliberate pass across every project rather than the usual incidental fixes. The largest strand is a single idea applied everywhere; the rest are the specific, unglamorous things a review turns up.

🌐 Never dial an address you were handed

Identity documents name where to fetch their history. Service advertisements name where to deliver a message. Devices register where to send notifications. Each is an instruction from elsewhere, and following it blindly lets a document talk your server into fetching things for an attacker — often from inside your own network. Every such point across the engine, the developer kit, the browser wallet, the JavaScript library, the community app, the identity-log host and the phone app now refuses non-public addresses, refuses redirects, and checks every address a name resolves to — because a name can be pointed inwards a moment after it was checked.

🏗️ The build pipeline as an attack surface

Every automation step in every pipeline is now pinned to an exact version by fingerprint rather than a moving label, and the permissions each one runs with dropped to the minimum. Workflow inputs reach their scripts through a controlled channel and are validated, so text someone supplies cannot become a command. In the git-signing project these rules are now enforced by tests that read the pipeline files structurally, so the protection cannot quietly be undone by a later edit, and the paths that matter got named owners.

🔐 Custody, and who sealed what

Backups are written readable only by their owner. An admin credential arriving sealed is checked for who sealed it before installation. The signing oracle is domain-separated, so a signature made for one purpose cannot be replayed as another, and an operator can mark a key as never leaving the agent. On the phone, the holder's key is stored bound to the device and never deleted in order to be rewritten, the notification token stopped being logged, and nothing is approved in the background — it asks the person, every time.

🤫 Things that answered questions they shouldn't

The sign-in challenge revealed whether an account existed. A demo relying party answered cross-origin requests by reflecting whoever asked instead of checking a list. A plugin icon supplied by a third party was rendered as markup rather than as an image. The enclave tool resolved its helper through the search path instead of by absolute location. A page-facing message listener accepted messages without checking their origin. Individually small; collectively the list a review exists to produce.

The Quality Delta · last report → now

How much healthier is it than a week ago?

Counted the same way as the previous eight weeks, from the exact commit each project had reached in the Week 36 report. One project was added this week — the browser wallet — and it is written in a different language, so it contributes nothing to these four measures either before or after: the comparison below stays a clean like-for-like across the same eight projects as last week. Its 182 changes are included in the headline change, line and file counts, which is stated wherever those appear.

Automated checks (run on every change; same eight projects)
11,708→12,787
▲ +1,079 checks (+9.2%) — a ninth consecutive rise, and the largest single-week rise recorded here
Test stand-ins (fakes that let a component be tested without the rest of the system)
778→807
▲ +29 (+3.7%) — mostly around rooms and the signed-reply checks
Future-proofed interfaces (surfaces that can gain a field without breaking everyone downstream)
283→349
▲ +66 — by far the largest rise recorded here, and deliberate: the agent's published interfaces were sealed ahead of outside users arriving
Unused-code markers (lower is better — and this one moved the wrong way)
126→131
▲ +5 — worse. Last week's single-point fall did not hold; five new suppressions came in with the week's volume

In one line: the busiest week of the year by a factor of five, with the largest test rise and the largest interface-sealing rise ever recorded here — and one measure that got worse. Four honest notes. First, the 463 is not comparable to last week's 88 without saying why: a tenth project joined the count this week and contributed 182 of them, so the like-for-like figure against last week's nine projects is 281 — still more than three times last week, but not five. Second, unused-code markers rose by five and that is reported as a regression, not buried: it is the measure that moves the wrong way when a week is fast, which is exactly when it is worth watching. Third, the "265 requirements" finding is not a win, it is a miss being corrected — the specification has required signed replies all along, neither service sent one, and no test anywhere went red; the fix is good news, the gap having existed for that long is not. Fourth, the test stand-in figure reads 778 where the Week-36 page published 774 as its ending value: the earlier number was read from a local copy a few commits behind the published line, and the base here is that same scan point re-read on the published main line.

The Detail · 10 Projects in Scope

What changed, project by project

The published report tracks around eleven projects. This update is drawn from the ten repositories present in this workspace — the nine from last week plus the browser wallet, added this week because it shipped 182 changes and sits at the centre of three of the week's themes. Other parts, such as the personal-AI agent and the data-room demonstration site, aren't included. Figures are read from each project's published main line, not a local copy, and each project is measured from the exact commit it had reached in the Week 36 report.

The trust backbone ★

ENGINE
The engine room — identities, communities, shared rooms, and the agent that acts on your behalf. (verifiable-trust-infrastructure)
  • Vetting, the whole path — a vetter registry with directory and profiles, vetters named by a revocable role credential the community issues, vetting statements that can be withdrawn, statements counted in the joining decision, requirements advertised in the join manifest, automatic grants for an application that meets the bar, grant resend, and community branding. The admin surface and command-line tool landed with it, including a bootstrap that seeds a community's first vetters from an existing PGP web of trust — the answer to "who names the first vetter" for a community that already has one.
  • Data rooms grew the parts that make them trustworthy over time — a record commitment (a Merkle tree over the room's records) so a member can check the history has not been quietly edited; an epoch key chain a joining member can read backwards through; read mirrors, a host that serves a copy and writes nothing; a room that stays readable across a membership change; and a browser that can verify a room's invitation on its own. A community can now decide whose rooms it hosts, and an operator can see the rooms their community hosts.
  • The audit log became a chain, with a key of its own — the agent's audit trail is now hash-chained and independently verifiable, with a dedicated audit key, a single construction point, support for a keyspace that predates chaining, and the use of the signing oracle itself recorded. A failed audit write is now reported rather than swallowed, which is the difference between an audit trail and a log.
  • Successful replies are signed — and checked — the specification requires it in 265 places and neither service was doing it. Both now sign; the client verifies every reply it receives; the verifier moved down to where every consumer reaches it; and the test doubles had to learn to sign too, because the real thing now does. This is the enabling work behind verified reads in data rooms, which turned out not to be a rooms problem at all.
  • Personas, end to end — the claim-type registry a deployment can extend with its own types, a facet service describing the holder's own arrangement of their identity, sensitive values withheld from listings that did not ask, a holder's release override honoured at disclosure, and step-up disclosure enforced — a value the holder marked as needing a second confirmation now genuinely stops there, with an approval the phone can render and a bound approval recorded without elevating the whole session.
  • Access-control entries that hold less than their role — an entry can now be created already narrowed rather than narrowed afterwards, its capabilities are enforced rather than advisory, and an operator has a way to set them. Plus authority over the holder's own identity can be granted without granting every context.
  • Security pass — ten hardening changes including non-public-host refusal on identity resolution, sealed-credential sender checks, owner-only backups, a sign-in challenge that stopped disclosing enrolment, domain separation on the signing oracle, and every pipeline action pinned and least-privileged.
Vetting end to endAudit chainSigned + verified repliesPersona step-up 173 changes · +81.1k

The browser wallet ★ NEW TO THIS REPORT

APP
The wallet that lives in the browser — where a person actually manages faces, rooms and what each site is told. (pnm-browser-plugin)
  • Worlds — the parts of a life, and the faces in them — the organising idea that made the identity panel comprehensible. Attributes live in a world, values show which world they are in, worlds carry a colour, and the panel can finally say which links cross a part of your life. With it came a list view over everything held, with multi-select and a delete preview that counts what a face still references before you bulk-delete it.
  • Data rooms, created from the wallet — a rooms pane, a rooms API, and a create flow that mints the room's identity as part of the flow rather than requiring one prepared in advance; a three-step explained form; a host you finish setting up; issuing in a room's name (invitation, membership, authority); and browsing and reading a room's records through this wallet's own agent, saying what was checked.
  • Disclosure the holder actually controls — the holder decides what happens to a value and "not decided" is a real answer; sensitive values hide behind a per-value Show; the claim-type table is read from the agent; and a step-up disclosure now completes rather than failing at the last hop. The wallet also verifies the replies it receives, matching the engine.
  • The panel became a picture — guided first, then a map showing where you are known rather than everywhere you could be; forms open where you clicked; the setup guide steps backwards; the guide returns when the last face is deleted; and the screens stopped saying "disclosure" and "fact".
  • Seven security changes — endpoints checked before they are dialled, a page-facing listener that checks its origin, a bounded parser on pasted key material, a demo relying party answering cross-origin from an allow-list, a pluggable key-custody seam that is guarded and verified — and a claim of enclave support for one key type removed, because it was not true.
Worlds & facesRooms from the walletPanel as a picture7 hardening changes 182 changes · +33.8k

OpenVTC — the community app ★

APP
The app a person uses to hold identities, join communities and manage what each one sees. (openvtc)
  • The vetting experience — a Vetting page for applicants and vetters, a guided join that reads what the community requires and says it in plain words, a vetter directory and profile, QR tickets so a vouch begun in person carries over, grant revocation checks, resend, and community branding so "this community does not vet" is told apart from "we never asked". The vetter's card is read from the persona's own face.
  • Themes — built-in themes, bring your own, live switching, automatic light/dark, accessible palettes, NO_COLOR, export, and import from Neovim and Omarchy. Small in engineering terms and large in whether anyone enjoys using it.
  • One community, one context — each community can now live in a VTA context of its own, with per-community device access, the ability to choose a persona's context, and a way to delete a community's context outright. That is the isolation boundary made real in the app rather than only in the engine.
  • The identity pane — one pane for the holder's own identity, with worlds and families and the claim-type table the agent serves, a cursor so masked values can be read deliberately, sensitive values masked by default, and an explicit "worn one context up" rather than a bare "nothing". Plus a vocabulary pass so the copy outside the pane speaks the same words.
  • Six security changes — a debug log only its owner can read, a passphrase taken from a file rather than the command line, no literal PIN, health probes restricted to public transport endpoints with no redirects followed, and release builds that ignore environment overrides.
Guided vetted joinThemesPer-community contextIdentity pane 42 changes · +44.6k

Git signing & the identity-log host

TOOL · SERVICE
Proves who wrote each line of code; publishes identity histories on the web. (verifiable-git-infrastructure, affinidi-webvh-service)
  • A signing-policy bypass compiled out, and the gate described honestly — the sharper of the two findings here. A bypass path is now removed at build time rather than guarded at runtime, and the documentation stopped overstating what the gate enforces. Action inputs and commit ranges were also moved out of the code they were being interpolated into.
  • The pipeline rules are now tested structurally — actions pinned, tokens narrowed, dependency builds kept away from publishing — with tests that read the workflow files themselves, so the protections cannot be quietly undone by a later edit. Named owners were added for the signing-gate and verifier paths.
  • Both projects took the public-host resolution policy and the published engine releases, retiring the last git pins in favour of crates.io releases.
Bypass compiled outPipeline rules testedGit pins retired 38 changes · +9.8k

The developer kit

SDK
The shared toolkit every project here builds on. (affinidi-tdk-rs)
  • One guard for every address the stack was handed — the strongest protection in the stack was locked inside one identity-method component; it is now a standalone piece every project can use, with a shared list of worked examples so the JavaScript and Swift versions can be held to the same standard. Identity-history resolution now refuses non-public hosts by default.
  • Relayed messages are only "delivered" when the far side says so — relaying between message hubs over a live connection previously counted a successful send as delivery, so a message the far side refused could vanish with the rejection left as a log line on the other end. Each frame is now acknowledged by content, and anything else falls back to the path that always carried this traffic.
  • A deployment that speaks only the newer transport can manage its own account — it previously could not set its own access rules over any transport, which a permissive default hid entirely.
  • Also on a branch, not counted here — the secure-messaging revision-3 work described in previous reports reached a milestone this week: the specification it targets cleared review, and the reference implementation shipped its first release, giving 19 of 19 interoperability cases passing in both directions including post-quantum. Still unmerged, still shipping nothing, still excluded from every figure on this page.
Shared address guardAcknowledged relayingRev 3 cleared review 14 changes · +7.2k

The phone approver — moving again ★

APP
The device a person uses to approve what their agent proposes. (vta-mobile-agent-ios)
  • Six quiet weeks ended. Named as the standing risk in each of the last five reports, because the approval flow on the engine side moved every one of those weeks while this did not. It shipped nine changes this week, all of them custody and consent.
  • Nothing is approved in the background — it asks for the device owner before every approval, asks before approving a sign-in, and reviews and confirms a pairing before applying it. An approval that happens without a person is not an approval.
  • The holder's key is device-bound, and is never deleted in order to be rewritten — the sequence that can leave a person with no key at all. The notification token stopped being logged, the push gateway address is validated and redirects refused, and the documentation now describes the custody this app actually has rather than the one it aspires to.
Six-week gap closedConsent before every approvalDevice-bound key 9 changes · +2.1k

The supporting services

SERVICE
The trust registry and the chat bridge — both tracking the engine's releases. (affinidi-trust-registry-rs, vti-message-bridge)
  • The trust registry ended a month of quiet: it took the engine's published releases, dropped a legacy HTTP client along with every security-advisory exemption it was carrying, and fixed a case where a lost status reply from a message hub was reported as an empty queue — a failure that reads as "nothing to do" and so is never investigated.
  • The chat bridge shipped one change, aligning with the engine's current releases after last week's Teams work.
Advisory exemptions droppedLost reply ≠ empty queue 5 changes · +1.4k

The quiet project

1 SHIPPED NOTHING
Scanned and unchanged — recorded so "unchanged" can be stated rather than assumed. (affinidi-keri-rs)
  • The KERI library — a third consecutive quiet week, sitting where it was left on August 23. It is a stable dependency rather than an active front, so this is not the same kind of gap the phone approver represented; it is recorded because a scanned zero and an unscanned project are different things.
  • For the first time in six weeks, it is the only one. Every other project in scope shipped something.
ScannedStable dependency 0 changes
★ What's next · Take the vetting path outside, and finish what the review started

A vetting framework proves nothing until a real community uses it on a real stranger

Run the vetted join with an actual Linux community. Everything above works end to end — but end to end internally, with people who already trust each other. The claim that matters is that a newcomer nobody here knows can be vouched for by people who do, and admitted on that basis. Linux Plumbers is the natural place to find out, and the PGP bootstrap exists precisely so a real community can start from the web of trust it already has rather than from an empty registry. Finish the address review where it is hardest. The shared guard is built and adopted in the Rust projects; the JavaScript and Swift versions have the worked examples to be held to but have not yet been held to them. A guard that is strong in one language and approximate in two others is as strong as the weakest one. Watch the measure that got worse. Unused-code suppressions rose by five — small, but it is the one that drifts when a week moves this fast, and the fast weeks are now the normal ones. And land the messaging branch. Its specification cleared review this week and the reference implementation shipped; the reasons to keep it on a branch have largely gone, and every week it sits apart is a week the rest of the stack moves underneath it.

🐧
Vet a real stranger
A genuine Linux community, bootstrapped from its own web of trust, admitting someone nobody here knows. That is the only test that settles it.
🌍
Same guard in all three languages
The worked examples exist so the JavaScript and Swift guards can be held to the Rust one. Until they are, the weakest sets the standard.
🧹
Reverse the one that slipped
Five new unused-code suppressions. The measure that drifts on fast weeks, and fast is now normal.
🔀
Land the branch
Spec reviewed, reference released, 19/19 interoperating. The reasons for it to stay on a branch have mostly gone.