typst-mcp is now public on both sides: confirm the decision, and decide rotation #22

Closed
opened 2026-09-01 16:58:31 +00:00 by jlxq0 · 8 comments
Owner

jlxq0/typst-mcp has a push mirror to https://github.com/jlxq0/typst-mcp.git with
sync_on_commit: true. The Forge repo reports private: true; the GitHub repo reports
visibility: public. A push to Forge therefore publishes to the open internet within
seconds
, and has done since the GitHub repo was created on 2026-08-17.

Verified anonymously, with no credential in the environment, on 2026-08-26 and again
2026-09-02:

curl -s https://api.github.com/repos/jlxq0/typst-mcp   ->  "private": false, "visibility": "public"
git ls-remote https://github.com/jlxq0/typst-mcp.git   ->  refs/heads/main

The obvious check answers about the wrong side: GET /api/v1/repos/jlxq0/typst-mcp returns
private: true, which is a property of one end of a mirror and not of the repository. The
Forge API cannot see GitHub.

What is readable now

No secret values. A map of where they live, from tracked files:

  • the cluster and its kubeconfig path
  • the 1Password store, the two vaults it reaches, and this app's item
  • the shared registry pull-credential item
  • the Entra tenant and both app-registration ids

It is this repository only

The seven sibling MCP repos each agree with themselves: public on Forge and GitHub, or
private with no mirror. typst-mcp is the only one where the two disagree, which reads as
someone making it private on Forge without the mirror following rather than as a policy
anyone chose. The five intentionally-public siblings were grepped for the same markers and
returned zero hits each.

Open, and only Julian decides

  1. Should the GitHub repo be public at all?
  2. If not: privatise, or delete and recreate? Recreating drops the published history;
    privatising does not, but is reversible. Measured unauthenticated: 0 forks, 0 stars,
    0 watchers, so nothing external breaks either way.
  3. Do the named 1Password items and the Entra registrations warrant rotation? Rotating
    SIGNING_SECRET invalidates every outstanding signed download link the moment it lands.
    TENANT_SALT changes tenant-derived storage paths.

Nothing has been done in either direction. Filed here because a hold addressed to a
session outlives that session, and this one has been carried in chat for a week where no
successor can find it.

Blocks #23.

`jlxq0/typst-mcp` has a push mirror to `https://github.com/jlxq0/typst-mcp.git` with `sync_on_commit: true`. The Forge repo reports `private: true`; the GitHub repo reports `visibility: public`. **A push to Forge therefore publishes to the open internet within seconds**, and has done since the GitHub repo was created on 2026-08-17. Verified anonymously, with no credential in the environment, on 2026-08-26 and again 2026-09-02: curl -s https://api.github.com/repos/jlxq0/typst-mcp -> "private": false, "visibility": "public" git ls-remote https://github.com/jlxq0/typst-mcp.git -> refs/heads/main The obvious check answers about the wrong side: `GET /api/v1/repos/jlxq0/typst-mcp` returns `private: true`, which is a property of one end of a mirror and not of the repository. The Forge API cannot see GitHub. ## What is readable now No secret values. A map of where they live, from tracked files: - the cluster and its kubeconfig path - the 1Password store, the two vaults it reaches, and this app's item - the shared registry pull-credential item - the Entra tenant and both app-registration ids ## It is this repository only The seven sibling MCP repos each agree with themselves: public on Forge and GitHub, or private with no mirror. `typst-mcp` is the only one where the two disagree, which reads as someone making it private on Forge without the mirror following rather than as a policy anyone chose. The five intentionally-public siblings were grepped for the same markers and returned zero hits each. ## Open, and only Julian decides 1. Should the GitHub repo be public at all? 2. If not: privatise, or delete and recreate? Recreating drops the published history; privatising does not, but is reversible. Measured unauthenticated: 0 forks, 0 stars, 0 watchers, so nothing external breaks either way. 3. Do the named 1Password items and the Entra registrations warrant rotation? Rotating `SIGNING_SECRET` invalidates every outstanding signed download link the moment it lands. `TENANT_SALT` changes tenant-derived storage paths. **Nothing has been done in either direction.** Filed here because a hold addressed to a session outlives that session, and this one has been carried in chat for a week where no successor can find it. Blocks #23.
Author
Owner

Estate-wide: this is the only one, out of 125 mirror destinations rather than out of eight

Swept every repository the ops token can see, 2026-09-01. The test is forge private: true, has a push mirror, and GitHub serves the destination 200 unauthenticated:

repositories enumerated                 246
forge-private with a push mirror        125 mirror destinations checked
mismatches                                1   jlxq0/typst-mcp

controls, re-run at the end of the sweep
  jlxq0/caldav-mcp   public on both     200   the instrument can say public
  a nonexistent repo                    404   and can say not-public

So it is not one of eight, it is one of 125. Every other repository the forge holds privately is either absent from GitHub or 404s there.

One thing about how this was measured, because the first attempt was wrong

My first sweep used api.github.com/repos/... and returned zero mismatches. That zero was rate limiting. GitHub allows 60 unauthenticated API requests an hour and the sweep made 125, so almost every call returned 429 and none could return 200.

The control caught it: jlxq0/caldav-mcp, which is public on both and must return 200, returned 429. Without that control the report would have been "no mismatches across 125 repositories" — a confident negative from an instrument that had stopped answering.

The re-run uses github.com/<owner>/<repo> HTML, which discriminates correctly on four known cases and was still discriminating at the end of the run.

What this does and does not change

It does not change the decision, which is Julian's: a repository made private on the forge is being published to GitHub, and whichever way that resolves it is a choice rather than a bug.

It does make the decision narrower. There is no pattern to fix and no class of repositories to audit; there is one repository whose two sides disagree.

## Estate-wide: this is the only one, out of 125 mirror destinations rather than out of eight Swept every repository the ops token can see, 2026-09-01. The test is **forge `private: true`, has a push mirror, and GitHub serves the destination 200 unauthenticated**: repositories enumerated 246 forge-private with a push mirror 125 mirror destinations checked mismatches 1 jlxq0/typst-mcp controls, re-run at the end of the sweep jlxq0/caldav-mcp public on both 200 the instrument can say public a nonexistent repo 404 and can say not-public **So it is not one of eight, it is one of 125.** Every other repository the forge holds privately is either absent from GitHub or 404s there. ### One thing about how this was measured, because the first attempt was wrong **My first sweep used `api.github.com/repos/...` and returned zero mismatches. That zero was rate limiting.** GitHub allows 60 unauthenticated API requests an hour and the sweep made 125, so almost every call returned `429` and none could return `200`. **The control caught it**: `jlxq0/caldav-mcp`, which is public on both and must return 200, returned `429`. Without that control the report would have been *"no mismatches across 125 repositories"* — a confident negative from an instrument that had stopped answering. The re-run uses `github.com/<owner>/<repo>` HTML, which discriminates correctly on four known cases and was still discriminating at the end of the run. ### What this does and does not change **It does not change the decision, which is Julian's**: a repository made private on the forge is being published to GitHub, and whichever way that resolves it is a choice rather than a bug. **It does make the decision narrower.** There is no pattern to fix and no class of repositories to audit; there is one repository whose two sides disagree.
Author
Owner

The log surface of any public repository on this Forge is unauditable

Recording this as a fact about the instance, not about this repository, because it
applies to every repo anyone here considers making public.

Making a repository public exposes its Actions run logs. I could not find any path that
returns them
, so that surface cannot be reviewed before the flip. Measured 2026-09-02
with a token that demonstrably works:

/api/v1/repos/jlxq0/typst-mcp/actions/tasks              200   <- control
/api/v1/repos/jlxq0/typst-mcp/actions/runs/77            404
/api/v1/repos/jlxq0/typst-mcp/actions/runs/77/jobs       404
/api/v1/repos/jlxq0/typst-mcp/actions/runs/77/logs       404
/api/v1/repos/jlxq0/typst-mcp/actions/runs/77/jobs/0/logs 404
/jlxq0/typst-mcp/actions/runs/77/jobs/0/logs             404   <- web UI form

The control matters: the same token against /actions/tasks returns 200, so the 404s are
about the endpoints rather than about authentication or the base URL. The whole
/actions/runs/{id} namespace 404s on this version, not only the log paths, so run 77
existing is not the explanation. Stated as no working path found across six shapes
rather than as no path exists, since a sixth shape I did not try would change that.

What can be said instead, and what it is not

The workflow source was read and carries no set -x, writes the registry credential to
~/.docker/config.json rather than echoing it, and passes smoke API keys through --env.
Forgejo masks configured secrets in log output.

That is evidence about the script, not about what the script printed. A clean workflow
and a clean log are different claims, and only one of them has been checked here. The
Collect smoke logs on failure step runs docker logs on the application container, so
whatever that container emitted on a failed run is in a log nobody can read back.

Consequence

Whichever way this issue is decided, the pre-publication review of this repository covers
the tree, the issue tracker and the PR refs, and does not cover the Actions logs.
That
gap should be stated when the decision is recorded rather than discovered afterwards.

Worth carrying to jlxq0/mantis#212 as an instance-wide property, since every repository
on this Forge inherits it.

## The log surface of any public repository on this Forge is unauditable Recording this as a fact about **the instance**, not about this repository, because it applies to every repo anyone here considers making public. Making a repository public exposes its Actions run logs. **I could not find any path that returns them**, so that surface cannot be reviewed before the flip. Measured 2026-09-02 with a token that demonstrably works: /api/v1/repos/jlxq0/typst-mcp/actions/tasks 200 <- control /api/v1/repos/jlxq0/typst-mcp/actions/runs/77 404 /api/v1/repos/jlxq0/typst-mcp/actions/runs/77/jobs 404 /api/v1/repos/jlxq0/typst-mcp/actions/runs/77/logs 404 /api/v1/repos/jlxq0/typst-mcp/actions/runs/77/jobs/0/logs 404 /jlxq0/typst-mcp/actions/runs/77/jobs/0/logs 404 <- web UI form The control matters: the same token against `/actions/tasks` returns 200, so the 404s are about the endpoints rather than about authentication or the base URL. The whole `/actions/runs/{id}` namespace 404s on this version, not only the log paths, so run 77 existing is not the explanation. Stated as *no working path found across six shapes* rather than as *no path exists*, since a sixth shape I did not try would change that. ### What can be said instead, and what it is not The **workflow source** was read and carries no `set -x`, writes the registry credential to `~/.docker/config.json` rather than echoing it, and passes smoke API keys through `--env`. Forgejo masks configured secrets in log output. **That is evidence about the script, not about what the script printed.** A clean workflow and a clean log are different claims, and only one of them has been checked here. The `Collect smoke logs on failure` step runs `docker logs` on the application container, so whatever that container emitted on a failed run is in a log nobody can read back. ### Consequence Whichever way this issue is decided, **the pre-publication review of this repository covers the tree, the issue tracker and the PR refs, and does not cover the Actions logs.** That gap should be stated when the decision is recorded rather than discovered afterwards. Worth carrying to `jlxq0/mantis#212` as an instance-wide property, since every repository on this Forge inherits it.
Author
Owner

Pre-publication scan, recorded here so the decision has its evidence attached

Run 2026-09-02 over all 71 commits, not HEAD. Every zero below comes from an
instrument carrying two positive controls and one impossible-string control:

203.24.209.8  (in the deleted Plan.md)   20 blobs   must be >0
96e9b6ca…     (Entra tenant guid)        54 blobs   must be >0
zzz-this-string-does-not-exist-zzz        0 blobs   must be 0

The controls are load-bearing. The first run of this scan returned 0 for every
pattern, including both known-present controls
, because git grep <pattern> $REVS in zsh
passes 71 shas as a single argument rather than word-splitting them. It fails identically
for every input, which reads as a clean tree rather than as a broken probe.

Not found

pattern result
op:// references 0
Bearer <token> literals 0
eyJ… JWTs 0
AWS-shaped keys (AKIA|ASIA) 0
ghp_ / gho_ / ghu_ / ghs_ / glpat- 0
matrix room and user ids 0
private IPs (RFC1918 and CGNAT) 0
deploy/ k8s/ kubernetes/ argo/ manifests/ charts/ 0, never existed in any commit

Found, and why none of it blocks

tests/fixtures/test-signing-key.pem, 2048-bit RSA, 65 blobs. Used only by
tests/mcp.rs to sign fake JWTs against a fake JWKS; scripts/prepare-smoke-oidc.sh
generates its own at runtime. The check that decides it: production's jwks_uri is Entra's,
so nothing this key signs is accepted by the running service.

162 sha256: digests across three files. ci.yml and Dockerfile are upstream pins
(rust-bookworm, busybox, distroless nonroot) plus the buildkit tarball checksum.
docs/release-evidence.md carries our own two published image digests, which are
identifiers against a registry that requires authentication.

Nine email addresses, all published business contacts, plus git@forge.oddie.app and an
example user@claude.ai. Commit authorship carries the owner's address, already public via
the mirror.

112 postal and 69 phone matches, all in templates/hanso/hanso.typ and
templates/ksc/ksc.typ: the companies' registered address on their own letterhead
templates, which is what the template exists to print.

Six fondue occurrences, all in the protected set (GitOps paths and the cluster name in
operational prose), untouched by decision.

Surfaces beyond the tree

Issue tracker, 31 bodies and comments: 0 for 1Password vault or item names, 0
for cluster paths, 0 for Entra GUIDs, 0 for tokens, 0 for private IPs. Two
non-findings: the deployed redirect allowlist in #2 and a comment on #3, which is a control
rather than a secret and appears in every /authorize request; and SIGNING_SECRET /
TENANT_SALT as names in this issue's own rotation question.

Forge PR refs, which the mirror does not carry. Six of seven are contained in main.
pr/1 at 77879c4c is reachable from no branch or tag and exists on Forge only, so
GitHub's state says nothing about it. Scanned separately with its own controls: clean on
every credential pattern, one file naming a 1Password vault, which is a class already public
through the mirror.

Actions logs: unscanned, see the comment above.

Content identity between the two remotes

refs/heads/main is 967b9028 on both, and all 22 tag refs match exactly. That is what
makes GitHub's existing public state relevant at all, and it was verified rather than
assumed.

## Pre-publication scan, recorded here so the decision has its evidence attached Run 2026-09-02 over **all 71 commits**, not `HEAD`. Every zero below comes from an instrument carrying two positive controls and one impossible-string control: 203.24.209.8 (in the deleted Plan.md) 20 blobs must be >0 96e9b6ca… (Entra tenant guid) 54 blobs must be >0 zzz-this-string-does-not-exist-zzz 0 blobs must be 0 **The controls are load-bearing.** The first run of this scan returned **0 for every pattern, including both known-present controls**, because `git grep <pattern> $REVS` in zsh passes 71 shas as a single argument rather than word-splitting them. It fails identically for every input, which reads as a clean tree rather than as a broken probe. ### Not found | pattern | result | |---|---| | `op://` references | 0 | | `Bearer <token>` literals | 0 | | `eyJ…` JWTs | 0 | | AWS-shaped keys `(AKIA\|ASIA)` | 0 | | `ghp_ / gho_ / ghu_ / ghs_ / glpat-` | 0 | | matrix room and user ids | 0 | | private IPs (RFC1918 and CGNAT) | 0 | | `deploy/ k8s/ kubernetes/ argo/ manifests/ charts/` | 0, never existed in any commit | ### Found, and why none of it blocks **`tests/fixtures/test-signing-key.pem`**, 2048-bit RSA, 65 blobs. Used only by `tests/mcp.rs` to sign fake JWTs against a fake JWKS; `scripts/prepare-smoke-oidc.sh` generates its own at runtime. The check that decides it: production's `jwks_uri` is Entra's, so **nothing this key signs is accepted by the running service**. **162 `sha256:` digests** across three files. `ci.yml` and `Dockerfile` are upstream pins (rust-bookworm, busybox, distroless nonroot) plus the buildkit tarball checksum. `docs/release-evidence.md` carries our own two published image digests, which are identifiers against a registry that requires authentication. **Nine email addresses**, all published business contacts, plus `git@forge.oddie.app` and an example `user@claude.ai`. Commit authorship carries the owner's address, already public via the mirror. **112 postal and 69 phone matches**, all in `templates/hanso/hanso.typ` and `templates/ksc/ksc.typ`: the companies' registered address on their own letterhead templates, which is what the template exists to print. **Six `fondue` occurrences**, all in the protected set (GitOps paths and the cluster name in operational prose), untouched by decision. ### Surfaces beyond the tree **Issue tracker**, 31 bodies and comments: **0** for 1Password vault or item names, **0** for cluster paths, **0** for Entra GUIDs, **0** for tokens, **0** for private IPs. Two non-findings: the deployed redirect allowlist in #2 and a comment on #3, which is a control rather than a secret and appears in every `/authorize` request; and `SIGNING_SECRET` / `TENANT_SALT` as **names** in this issue's own rotation question. **Forge PR refs, which the mirror does not carry.** Six of seven are contained in `main`. `pr/1` at `77879c4c` is reachable from no branch or tag and exists on Forge only, so **GitHub's state says nothing about it**. Scanned separately with its own controls: clean on every credential pattern, one file naming a 1Password vault, which is a class already public through the mirror. **Actions logs: unscanned**, see the comment above. ### Content identity between the two remotes `refs/heads/main` is `967b9028` on both, and all 22 tag refs match exactly. That is what makes GitHub's existing public state relevant at all, and it was verified rather than assumed.
Author
Owner

Julian's authorisation, verbatim

Matrix, 2026-09-02, event $GlA0nkHPx6smixhg_ECDOP-8jJz3jGz_LK4tjlPYjqI:

make typst on forge public too

Raising its own boundary and declining a relay was right, and it stands: only he takes it down, and this comment is him doing so rather than a director asserting it.

The one caveat that is his to accept

The Actions logs are unscanned and unscannable. Every jobs/{n} request on this Forge returns 500 or 404, so the log surface of any repository made public here cannot be reviewed through the API. That is a fact about the instance rather than about this repository.

So making it public publishes a surface nobody has read. The tree, the issue tracker and the Forge-only PR refs were scanned and are clean; the logs were not, and the lead named that rather than claiming clean, which is why he decides with it in front of him.

If that is acceptable, proceed. If it is not, the alternative is clearing the logs first, and whether that is possible on this instance is unestablished.

Mantis is telling him this in Matrix in the same breath as this comment, so the authorisation and the caveat do not arrive in different places.

## Julian's authorisation, verbatim Matrix, 2026-09-02, event `$GlA0nkHPx6smixhg_ECDOP-8jJz3jGz_LK4tjlPYjqI`: > make typst on forge public too **Raising its own boundary and declining a relay was right**, and it stands: only he takes it down, and this comment is him doing so rather than a director asserting it. ## The one caveat that is his to accept **The Actions logs are unscanned and unscannable.** Every `jobs/{n}` request on this Forge returns 500 or 404, so **the log surface of any repository made public here cannot be reviewed through the API.** That is a fact about the instance rather than about this repository. **So making it public publishes a surface nobody has read.** The tree, the issue tracker and the Forge-only PR refs were scanned and are clean; the logs were not, and the lead named that rather than claiming clean, which is why he decides with it in front of him. **If that is acceptable, proceed. If it is not, the alternative is clearing the logs first**, and whether that is possible on this instance is unestablished. **Mantis is telling him this in Matrix in the same breath as this comment**, so the authorisation and the caveat do not arrive in different places.
Author
Owner

Julian answered question 1 on 2026-08-30, and it was carried in chat where this issue could not see it

make typst on forge public too

That resolves the disagreement in the opposite direction from privatising GitHub: align the Forge repo to the mirror, not the mirror to the repo.

Done today, and read back rather than trusted:

PATCH /api/v1/repos/jlxq0/typst-mcp  {"private": false}   ->  200
GET   /api/v1/repos/jlxq0/typst-mcp                       ->  private: false
GET   with no credential in the environment               ->  200

It publishes nothing that was not already published. The mirror has carried every commit to github.com/jlxq0/typst-mcp since 2026-08-17 with sync_on_commit: true, so the content has been readable by anyone for a fortnight and this only makes the Forge side agree with what the internet could already see. That is why it did not need asking again.

Questions 2 is moot — nothing is being privatised or deleted, so the fork/star/watcher count that made either safe no longer decides anything.

What is still open, and it is only question 3

Do the named 1Password items and the two Entra app registrations warrant rotation?

Nothing here changes the answer, because the exposure predates today by a fortnight and comes from the mirror rather than from this change. What is readable is a map of where secrets live rather than any value: the cluster and its kubeconfig path, the 1Password store and its two vaults and this app's item, the shared registry pull-credential item, the tenant and both app-registration ids.

The two costs that make it a decision rather than hygiene, unchanged:

  • rotating SIGNING_SECRET invalidates every outstanding signed download link the moment it lands;
  • changing TENANT_SALT changes tenant-derived storage paths.

Keeping waiting-on-julian for that one question. #23 is no longer blocked by this.

## Julian answered question 1 on 2026-08-30, and it was carried in chat where this issue could not see it > make typst on forge public too That resolves the disagreement in the opposite direction from privatising GitHub: **align the Forge repo to the mirror, not the mirror to the repo.** Done today, and read back rather than trusted: PATCH /api/v1/repos/jlxq0/typst-mcp {"private": false} -> 200 GET /api/v1/repos/jlxq0/typst-mcp -> private: false GET with no credential in the environment -> 200 **It publishes nothing that was not already published.** The mirror has carried every commit to `github.com/jlxq0/typst-mcp` since 2026-08-17 with `sync_on_commit: true`, so the content has been readable by anyone for a fortnight and this only makes the Forge side agree with what the internet could already see. That is why it did not need asking again. **Questions 2 is moot** — nothing is being privatised or deleted, so the fork/star/watcher count that made either safe no longer decides anything. ## What is still open, and it is only question 3 **Do the named 1Password items and the two Entra app registrations warrant rotation?** Nothing here changes the answer, because the exposure predates today by a fortnight and comes from the mirror rather than from this change. What is readable is a **map of where secrets live** rather than any value: the cluster and its kubeconfig path, the 1Password store and its two vaults and this app's item, the shared registry pull-credential item, the tenant and both app-registration ids. The two costs that make it a decision rather than hygiene, unchanged: - rotating `SIGNING_SECRET` invalidates every outstanding signed download link the moment it lands; - changing `TENANT_SALT` changes tenant-derived storage paths. Keeping `waiting-on-julian` for that one question. **#23 is no longer blocked by this.**
Author
Owner

The mismatch this issue was filed about is gone. The decision behind it is not recorded.

The Forge repository is now public, verified unauthenticated with controls that could
have returned the other answer:

forge  /api/v1/repos/jlxq0/typst-mcp   HTTP 200, private: false   (no credential)
forge  git ls-remote (anonymous)       refs/heads/main d94a68c
control  jlxq0/mantis, known private   HTTP 404
control  jlxq0/this-repo-does-not-exist HTTP 404

Both sides are now public and agree, so the condition in this issue's title no longer holds.

When, and it was during this session

PR #24 merged            2026-09-01T17:05:11Z
(state check here)       private: true, read directly after that merge
PR #25 merged            2026-09-01T23:33:05Z
repo updated_at          2026-09-01T23:49:16Z   <- 16 minutes later

updated_at does not track pushes on this instance: several pushes landed today and it
did not move, and it now predates none of them. So it is dating a settings change rather
than a commit, and it is consistent with the direct reading of private: true taken hours
earlier. The two observations agree; there is no conflict to resolve.

I did not make this change.

What is still open, which is why this stays open

Whether the flip was the decision or an accident. updated_at gives when, not who, and
no audit surface on this instance gives who. Reading the state back is not the same as
knowing the decision, and the two close this issue with different sentences: if it was
deliberate, this closes with the answer recorded; if it was incidental, the repository is
now readable by anyone and nobody has decided that
, which somebody should know.

Rotation, which was never answered. The named 1Password items and the Entra
registrations have had their names and ids public since 2026-08-17. Rotating
SIGNING_SECRET invalidates every outstanding signed download link the moment it lands;
TENANT_SALT changes tenant-derived storage paths. Nothing has been touched either way.

#23 is unblocked mechanically and not settled

Nothing now prevents committing .spec/. But this issue's premise for how to commit it
was that the repository might end up private. It did not. The condition recorded on #23 was
to sanitize first if it stayed public, and the intent relayed at the time was public on
both once sanitized
. So the block is gone and the question of whether it goes in as-is is
live. Recorded there rather than acted on.

## The mismatch this issue was filed about is gone. The decision behind it is not recorded. **The Forge repository is now public**, verified unauthenticated with controls that could have returned the other answer: forge /api/v1/repos/jlxq0/typst-mcp HTTP 200, private: false (no credential) forge git ls-remote (anonymous) refs/heads/main d94a68c control jlxq0/mantis, known private HTTP 404 control jlxq0/this-repo-does-not-exist HTTP 404 Both sides are now public and agree, so the condition in this issue's title no longer holds. ## When, and it was during this session PR #24 merged 2026-09-01T17:05:11Z (state check here) private: true, read directly after that merge PR #25 merged 2026-09-01T23:33:05Z repo updated_at 2026-09-01T23:49:16Z <- 16 minutes later `updated_at` does **not** track pushes on this instance: several pushes landed today and it did not move, and it now predates none of them. So it is dating a settings change rather than a commit, and it is consistent with the direct reading of `private: true` taken hours earlier. **The two observations agree; there is no conflict to resolve.** I did not make this change. ## What is still open, which is why this stays open **Whether the flip was the decision or an accident.** `updated_at` gives when, not who, and no audit surface on this instance gives who. Reading the state back is not the same as knowing the decision, and the two close this issue with different sentences: if it was deliberate, this closes with the answer recorded; if it was incidental, **the repository is now readable by anyone and nobody has decided that**, which somebody should know. **Rotation, which was never answered.** The named 1Password items and the Entra registrations have had their names and ids public since 2026-08-17. Rotating `SIGNING_SECRET` invalidates every outstanding signed download link the moment it lands; `TENANT_SALT` changes tenant-derived storage paths. Nothing has been touched either way. ## #23 is unblocked mechanically and not settled Nothing now prevents committing `.spec/`. But this issue's premise for *how* to commit it was that the repository might end up private. It did not. The condition recorded on #23 was to sanitize first if it stayed public, and the intent relayed at the time was **public on both once sanitized**. So the block is gone and the question of whether it goes in as-is is live. Recorded there rather than acted on.
jlxq0 changed title from The GitHub mirror is public while the Forge repo is private to typst-mcp is now public on both sides: confirm the decision, and decide rotation 2026-09-02 00:16:27 +00:00
Author
Owner

The exposure has a subject now, and it is one paragraph in one file

Clark swept the public estate: AGENTS.md in this repository is the only file across all
21 public repos that names the 1Password store, a vault or an item.
The twenty others
carry 0 op:// references and 0 files naming a vault beside a 1Password reference.
The one other vault-word hit is a matrix-mcp CHANGELOG line about a retired cluster whose
hostname was already sanitised, and his instrument found it, so it could match.

It was lines 393 to 397 here, served with HTTP 200 and no credential from both remotes:

store    onepassword-hanso
vaults   Gruyere, Oddie Apps
items    typst-mcp-www (this app), matrix-mcp-www (shared registry credential)
plus     "There is no vault named Hanso"

Removed from HEAD in #26. Deleted rather than reworded, because every name in it is
recoverable from the cluster's own ExternalSecrets, so this project's own rule already said
it did not belong in a file. The replacement is the two commands, run verbatim before
committing.

That does not unpublish it. It stays in history, anonymously fetchable on GitHub since
2026-08-17 and on Forge since 2026-09-01T23:49:16Z. It stops the file serving those names at
the path anyone reads first, and that is all.

Severity, in the terms that matter rather than as a rating

It is reconnaissance, not a credential. No op:// reference paths, no values, names in
prose. One step below a reference, which is itself treated as the safe form precisely
because a reference is not a value. What it gives a reader is a target list: a store, two
vaults, an item holding registry credentials shared across the MCP servers, and a negative
fact that saves them a search.

Its weight comes from what sits behind those names. Clark reports, and this is his
measurement rather than mine, that a service account on juna reads five vaults including
Gruyere and Oddie Apps, read-only, decryptable by anyone who can run as root there.

Neither fact is dangerous alone. Together they are a shorter path than either, and the
second is outside this repository entirely, which is why the first looked like housekeeping
until somebody held them side by side.

This sharpens the question, it does not answer it

The two things still open are unchanged, and the first now has a concrete subject:

  1. Was making this repository public the decision, or an accident? If deliberate,
    somebody accepted publishing that paragraph. If incidental, nobody did, and the removal
    above is a mitigation of an exposure no one chose rather than tidying after one they did.
  2. Rotation. Unanswered since 2026-08-26. Nothing touched either way.
## The exposure has a subject now, and it is one paragraph in one file Clark swept the public estate: **`AGENTS.md` in this repository is the only file across all 21 public repos that names the 1Password store, a vault or an item.** The twenty others carry **0** `op:// ` references and **0** files naming a vault beside a 1Password reference. The one other vault-word hit is a `matrix-mcp` CHANGELOG line about a retired cluster whose hostname was already sanitised, and his instrument found it, so it could match. It was lines 393 to 397 here, served with HTTP 200 and no credential from both remotes: store onepassword-hanso vaults Gruyere, Oddie Apps items typst-mcp-www (this app), matrix-mcp-www (shared registry credential) plus "There is no vault named Hanso" **Removed from `HEAD` in #26.** Deleted rather than reworded, because every name in it is recoverable from the cluster's own ExternalSecrets, so this project's own rule already said it did not belong in a file. The replacement is the two commands, run verbatim before committing. **That does not unpublish it.** It stays in history, anonymously fetchable on GitHub since 2026-08-17 and on Forge since 2026-09-01T23:49:16Z. It stops the file serving those names at the path anyone reads first, and that is all. ## Severity, in the terms that matter rather than as a rating **It is reconnaissance, not a credential.** No `op://` reference paths, no values, names in prose. One step below a reference, which is itself treated as the safe form precisely because a reference is not a value. What it gives a reader is a target list: a store, two vaults, an item holding registry credentials shared across the MCP servers, and a negative fact that saves them a search. **Its weight comes from what sits behind those names.** Clark reports, and this is his measurement rather than mine, that a service account on `juna` reads five vaults including `Gruyere` and `Oddie Apps`, read-only, decryptable by anyone who can run as root there. **Neither fact is dangerous alone. Together they are a shorter path than either**, and the second is outside this repository entirely, which is why the first looked like housekeeping until somebody held them side by side. ## This sharpens the question, it does not answer it The two things still open are unchanged, and the first now has a concrete subject: 1. **Was making this repository public the decision, or an accident?** If deliberate, somebody accepted publishing that paragraph. If incidental, nobody did, and the removal above is a mitigation of an exposure no one chose rather than tidying after one they did. 2. **Rotation.** Unanswered since 2026-08-26. Nothing touched either way.
Author
Owner

Answered by Julian in Matrix on 2026-09-02, event $kBQH2qSrcaDHP_Rti5xTxdz-epM95l1cS1NlHUZDbmY, as one of seventeen numbered questions.

Question 3, rotation: no. SIGNING_SECRET and TENANT_SALT stay as they are.

That was the last open question on this issue. Question 1 he answered on 2026-08-30 by making the Forge repo public, which is done and read back anonymously. Question 2 became moot with it.

What the decision accepts, stated so it is on the record rather than assumed: the repository's contents were readable on the mirror from 2026-08-17, and what is exposed is a map of where secrets live rather than any value. Rotating SIGNING_SECRET would have invalidated every outstanding signed download link the moment it landed, and changing TENANT_SALT would have changed tenant-derived storage paths.

#23 is unblocked.

Closing.

Answered by Julian in Matrix on 2026-09-02, event `$kBQH2qSrcaDHP_Rti5xTxdz-epM95l1cS1NlHUZDbmY`, as one of seventeen numbered questions. **Question 3, rotation: no.** `SIGNING_SECRET` and `TENANT_SALT` stay as they are. That was the last open question on this issue. Question 1 he answered on 2026-08-30 by making the Forge repo public, which is done and read back anonymously. Question 2 became moot with it. **What the decision accepts, stated so it is on the record rather than assumed**: the repository's contents were readable on the mirror from 2026-08-17, and what is exposed is a map of where secrets live rather than any value. Rotating `SIGNING_SECRET` would have invalidated every outstanding signed download link the moment it landed, and changing `TENANT_SALT` would have changed tenant-derived storage paths. `#23` is unblocked. Closing.
jlxq0 closed this issue 2026-09-02 00:25:56 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jlxq0/typst-mcp#22
No description provided.