---
title: "First review: every commit since 4 November, the keys, the sign-in, the inbox, the scheduled tasks, and the three open passes"
by: security@PrompTugal.dinis
at: 2026-11-06T03:50Z
reviewed_to: 2874e72 (site v0.1.93), from the first commit of 4 November (288 commits on dev, 252 of them since 5 November)
issue: WS-177
for: Dinis Cruz
---
# First review, 6 November 2026, 03:50 UTC

## Verdict

**No hold.** Nothing secret is public: no private key, no token, no vault key, no private JWK, no phone number and no person's contact detail in any line added since 4 November. Four medium findings, each one commit or one decision away from something worse, and four low ones. The three open passes may go, with the conditions in their files.

## What I did

- Ran every added line of every commit since 4 November (binary files left out) through the gate's secret shapes (`newsroom/build/secrets.cjs`, the team site's list), plus `sk-or-v1-` keys, assignments of passwords, tokens and keys, email addresses and phone-shaped numbers.
- Read `board/keys.json`, `board/person-keys.json`, `board/inbox.json`, `newsroom/observability/sealed/dinis.json` (its header only; nothing opened), `assets/login/keyring.js`, `assets/login/login.js`, `assets/obs/obs.js`, `newsroom/tools/person-key.mjs`, `.gitignore`, the outreach desk, `data/people/` (the fields each person has).
- Read the stored prompt of every scheduled task on the account (the PrompTugal ones and the team site's).
- Checked that no SVG or HTML anywhere in the repository carries script, an event handler, `javascript:` or `foreignObject`.
- Ran the gate: `OK (v0.1.92: 986 pages, 6284 files, 14 checks)`.

## What holds

| Checked | Result |
|---|---|
| Secrets in commits | none: no private key, sgit private key, vault-key-shaped string, API key or token, private JWK |
| Addresses | only agents' role names (`<role>@web-summit.<person>`, `<role>@PrompTugal.dinis`, not mailboxes) and the promptugal.news role addresses Dinis set up (the publication's agent address on the cards, the editors' addresses and the DMARC reports address in REALITY.md) |
| Phone numbers | none (every phone-shaped match was a date, a session or attendee id in a websummit.com URL, a Brazilian company registry number in a public API URL, or PDF geometry) |
| People | 248 people with name, role, organisation and their summit listings only, each sourced; no social profile, no contact |
| Private halves | outside the repository: `.credentials/`, `.sg-send/`, `.sg_vault/` ignored; `person-key.mjs` refuses to write the sign-in QR code inside the repository; every agent key made with the pinned `pki-agent.mjs` |
| Sign-in | the key arrives in the URL fragment (never sent to a server), leaves the address bar at once (`history.replaceState`) and is imported as non-extractable keys |
| Page views | no cookie, no user-agent string, no IP recorded; Do Not Track, Global Privacy Control and the footer's Pause honoured; each event sealed to the drain key |
| Inbox | each agent's lane token sealed to that agent's key and signed by the conductor's; the public lane is write-only by design |
| Scheduled tasks | no secret and no connector in any prompt; every prompt says only Dinis instructs |
| `board/schedule.json` | true as of `aabb8b4` (15 tasks, the six new agents included; correspondent@ is in CLAUDE.md) |

## Findings

| # | Severity | Finding | Owner | What I ask |
|---|---|---|---|---|
| S1 | Medium | **Any same-origin script can use Dinis's key.** `assets/login/keyring.js` loads on every page and exposes `window.PTKeyring.open()` and `sign()` to any script on promptugal.news; `newsroom/observability/sealed/dinis.json` opens the observability vaults' keys with it. `desks/` is served as is and `.gitignore` re-includes every file type under it, so an HTML or SVG file with script committed by any desk agent (other people's agents write there) would run on this origin and could open that envelope or sign as Dinis. Clean today: no file carries script. This is the team site's WS-164 (High there, where other people's scripts already ran) one commit away. | dev@ | a gate check that refuses script, event handlers and `javascript:` in any served HTML or SVG outside `assets/` and the build's output; and load the keyring only on `/login/` and the pages that open something |
| S2 | Medium | **Three holders can open the observability vaults, not one.** Dinis's sign-in key was made by conductor@PrompTugal.dinis, which holds the private half too (the sign-in page says so). The vault keys were re-sealed to the new key at 03:27, not rotated, and the earlier envelope (to the replaced key of 3 November, held by conductor@web-summit.dinis as well) stays in git history. So Dinis, conductor@PrompTugal.dinis and conductor@web-summit.dinis can all open them. All are Dinis's, which is why this is not higher. | Dinis (decision); observability@ | rotate the observability vault keys (new vaults, sealed once to the current key) when it is convenient, and later a key made in Dinis's own browser (as the team site's WS-117) so no agent holds it |
| S3 | Medium (privacy) | **Every reader's IP goes to two third parties.** `assets/obs/obs.js` asks ipapi.co, then ipwho.is, for country, region and city, once per tab. The site never reads the IP in the answer, but those services receive it with the request; the footer says "no IP". City, the minute, the screen class, the language and the time zone together can single out a reader while traffic is low, and the report is public. Dinis asked for the place (6 November); this is about saying it and about the report. | observability@; Dinis | say on the footer and `/newsroom/observability/` that the browser asks ipapi.co or ipwho.is for its place; publish a city only when at least five readers share it in the period |
| S4 | Medium (rule) | **The team's sales ranking is published on the newsroom.** `desks/outreach/targets.json` and each `thread.json` give the rank, priority and ask (meet) for 50 companies, from Silvio's outreach list; `desks/` is served (https://promptugal.news/desks/outreach/targets.json answers 200). CLAUDE.md: the team's notes (priority, owner, why it matters) are not published. It is already public on the team site (`people/silvio/outreach/messages.json` answers 200), so this is not a leak; it is a newsroom publishing which companies its team most wants to meet or sell to, beside emails to those same companies. | outreach@; Dinis | keep rank, priority and ask outside the served tree (or drop them, keeping the company list), unless Dinis says they may stay |
| S5 | Low | `ws-ecies-1` envelopes are not signed: `by` and `at` in `sealed/dinis.json` are claims anyone with push access could write (the team site's review of 5 November, finding 3). | dev@, observability@ | sign the envelope with the sealer's key and check it on opening |
| S6 | Low | The observability drain's scheduled task runs `pip install -q sgit-ai` unpinned in a fresh container, then handles the vault credentials. | devops@, observability@ | pin a version (and its hash) in the prompt or a requirements file |
| S7 | Low | The synthetic vault (qourr7be) has a published read key, so its 375 files are public, and 23 MB of screenshots removed from the tree remain in git history. I have not opened them. | synthetic@ | confirm no screenshot shows a signed-in view, an opened observability pane or a draft's addresses; say so in the vault's README |
| S8 | Low | The public inbox lane's append token is in `board/inbox.json` by design (write-only). Anyone can fill the lane; a full lane answers 507. | conductor@ | treat a full public lane as noise, not an outage; the agents' lanes are separate |

## The three open passes (pre-review)

All three: **go, with conditions**, written in each pass file. The common condition: no pass adds a script, font or request to another host, and no screenshot or mock-up shows a signed-in view or the observability pane open.

- `2026-11-06-the-top-of-the-page`: go.
- `2026-11-06-the-synthetic-readers-page`: go; every reader stays marked SYNTHETIC, and the themes the historian and ontologist draw publish no team notes (S4).
- `2026-11-06-the-newsroom-search`: go; the index holds rendered page text only, never `board/` or `desks/` JSON (no tokens, no sealed blobs, no outreach ranking); results escaped (no `innerHTML` with page text); queries stay in the browser and are not sent to the page-view lanes.

## What blocks me

Nothing. S2, S3 and S4 wait on Dinis's decisions; S1 and S5 on dev@.
