Skip to content
This repository was archived by the owner on Sep 10, 2026. It is now read-only.

Name the search credential for what it has to be - #2

Open
tomj12k wants to merge 2 commits into
mainfrom
security/t118-search-key-must-be-public-scoped
Open

Name the search credential for what it has to be#2
tomj12k wants to merge 2 commits into
mainfrom
security/t118-search-key-must-be-public-scoped

Conversation

@tomj12k

@tomj12k tomj12k commented Sep 10, 2026

Copy link
Copy Markdown

Threat: T118 (Morelitea/security, Boundary 15). Severity Medium. NIST SC-12, IA-5.
Work item: WI-20.

The mechanism, demonstrated rather than asserted

vite.config.ts:17 sets envPrefix: ["VITE_", "TAURI_"], so anything named TAURI_* and read through import.meta.env is inlined into the bundle at build time. Search.tsx:19 read TAURI_MEILI_API_KEY that way, and release.yml fed it from a repository secret.

I built it with a sentinel value:

$ TAURI_MEILI_HOST=https://sentinel-host.example \
  TAURI_MEILI_SEARCH_KEY=SENTINEL_KEY_9f3a1c npx vite build
✓ built in 2.95s

$ grep -ro 'SENTINEL_KEY_9f3a1c' dist/ | wc -l
4                       # dist/assets/index-468969ec.js

A CI secret that ends up in the artifact is not a secret.

Correcting the threat model

T118 says the key "ships inside every published binary and web build … and is extractable by anyone holding an artifact."

That is not true today. release.yml has never run — it triggers only on a v* tag push or workflow_dispatch, and neither has happened — and the only release is a draft with zero assets. Nothing has been distributed, so this key has not leaked.

(An earlier draft of this said "gh run list returns nothing". That is now false: this pull request itself triggered two runs, Release Drafter and verify_frontend.yml. The substantive claim is unchanged — it is release.yml specifically that has never run — but the stated evidence no longer reproduces.)

The finding is latent, not realised: the design guarantees exposure on the first release, and that release has not happened. Worth being precise about, because it changes what merging this costs. Fixing it now is a rename. Fixing it after a release would also mean rotating a key that strangers hold.

I will update the threat model wording separately.

Change

There is no way to hide a credential in a client that queries a search server directly. The Rust binary is no better a hiding place than the JavaScriptstrings reads both. So the only correct answer is that the shipped value must be one the client is allowed to hold.

Meilisearch is designed for exactly this: a search key, scoped read-only to specific indexes, is meant to be public. The master key is not — it can create keys, write documents, and read every index on the instance.

So the variable is now TAURI_MEILI_SEARCH_KEY in both the workflow and the component. The name states the requirement at the point where someone would otherwise paste the wrong value, and comments in both files say the value is inlined and public by construction.

Verification

npx tsc --noEmit          exit 0
npx vite build            succeeds; the value still reaches the client

Before merging — this needs a repository change, not just a merge

  1. Rename the repository secret TAURI_MEILI_API_KEYTAURI_MEILI_SEARCH_KEY. The workflow reads the new name; without the rename, search is disabled at runtime (the component no-ops when the key is empty) rather than breaking the build.
  2. Confirm its contents are a search key, not the master key. I cannot see the secret, so I cannot check this — and it is the part that actually matters.

What this does not do

It does not make the value private. Nothing can, for a desktop app that talks to a search server directly. If search ever needs a credential the client may not hold, the request has to go through something that holds it instead — a proxy, or a server-side endpoint. The comment in Search.tsx says so, so the next person does not reach for the Rust side thinking it is safer.

🤖 Generated with Claude Code

https://claude.ai/code/session_01D5xdKhPXJT4HMWwUyENiu1

T118 / WI-20. vite.config.ts sets envPrefix: ["VITE_", "TAURI_"], so
TAURI_MEILI_API_KEY -- read in Search.tsx through import.meta.env -- is
inlined into the bundle at build time. Demonstrated rather than asserted:
building with a sentinel value puts it in the shipped JavaScript.

  TAURI_MEILI_SEARCH_KEY=SENTINEL_KEY_9f3a1c npx vite build
  grep -ro 'SENTINEL_KEY_9f3a1c' dist/  ->  4 occurrences
                                            dist/assets/index-468969ec.js

CORRECTING THE THREAT MODEL: T118 says the key "ships inside every published
binary and web build". That is not true today. The release workflow has never
run, and the only release is a draft with zero assets, so nothing has been
distributed and this key has not leaked. The finding is latent, not realised
-- the design guarantees exposure on the first release, which has not
happened. Fixing it now costs nothing; after a release it would also cost a
rotation.

There is no way to hide a credential in a client that queries a search server
directly. The Rust binary is no better a hiding place than the JavaScript --
`strings` reads both. So the shipped value must be one the client is ALLOWED
to hold: a Meilisearch search key, scoped read-only to the indexes this app
searches, never the master key, which can create keys, write documents and
read every index on the instance.

The variable is now TAURI_MEILI_SEARCH_KEY in both the workflow and the
component, so the name states the requirement at the point where someone
would otherwise paste the wrong value. Comments in both places say the value
is inlined and public by construction.

Verified: npx tsc --noEmit exits 0; vite build succeeds and the value still
reaches the client.

BEFORE MERGING: the repository secret must be renamed
TAURI_MEILI_API_KEY -> TAURI_MEILI_SEARCH_KEY, and its contents confirmed to
be a search key rather than the master key. The workflow reads the new name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D5xdKhPXJT4HMWwUyENiu1
@tomj12k
tomj12k requested a review from LeeJMorel as a code owner September 10, 2026 13:52
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant