Skip to content

fix(ui): let the sharing list print the server's key mask - #321

Merged
argszero merged 1 commit into
mainfrom
fix/sharing-list-keeps-the-server-mask
Sep 28, 2026
Merged

argszero merged 1 commit into
mainfrom
fix/sharing-list-keeps-the-server-mask

Conversation

@argszero

Copy link
Copy Markdown
Owner

Summary

GET /api/sharings already returns a finished, masked key per row
(sharing_row decrypts the stored ciphertext and hands it to
mask_upstream_key), so the sharing list's local maskKey was only
re-processing a value that was already masked — a second implementation of one
fact. It also got it wrong: its key.length <= 8 branch returned the same
string as the other branch, it printed a 3-character prefix where the server
prints 2, and on the server's whole-string fallback it printed eleven stars
where the server wrote four.

The list now prints the row's own field, the same rule the settings API-Key
table already follows.

A new lexical gate (state_gate::the_sharing_list_prints_the_mask_the_server_made)
holds four rules, each with its own tooth: the cell prints a bare member
expression; that field is the one the row mapper passes through verbatim; the
server really does write the mask into it (so the rule goes red the day the
server stops masking); and app.js carries no second implementation of the
mask literal.

Related Issue

Changes

  • ui/js/app.js: drop the local maskKey, print s.key (the server value) in #share-body; comment records the server's mask semantics
  • src/state_gate.rs: gate + roster (positive control) + teeth (four variants) + scanner self-tests
  • ui/index.html: bump the app.js cache-bust token
  • ui/README.md: the data-section claim now names the server mask, plus a new section documenting the axis and the gate's scope
  • 涉及配置/数据结构的改动已同步示例文件 — n/a (no config or schema change)

Tests

  • cargo test 全部通过 — 421 passed / 0 failed (baseline 417, +4 new tests)
  • cargo fmt --check 通过 — exit 0
  • 新增/更新了单元测试(如适用) — the gate's four rules, a derived-roster control, four single-rule variants, and scanner self-tests
  • cargo clippy --all-targets -- -D warnings — exit 0
  • node --check ui/js/app.js — clean

A/B on the pre-fix tree (git show HEAD:ui/js/app.js): the gate reports
r1=false r2=true r3=true r4=false — the frontend was double-masking and
carrying a second implementation, while the mapper and the server were never
wrong.

Checklist

  • 分支命名符合约定(fix/)
  • Commit message 使用 Conventional Commits 格式
  • 单一职责,改动最小化

`GET /api/sharings` already returns a finished, masked `key` per row --
`sharing_row` decrypts the stored ciphertext and hands it to
`mask_upstream_key` (first 2 chars + `-` + last 4; `****` when the stored key
is <= 6 chars or decryption fails). The browser never holds the plaintext, so
the sharing list's local `maskKey` could only re-process a value that was
already masked.

It did so badly. `maskKey` was `key.slice(0, 3) + "****" + key.slice(-4)` for
every input: its `key.length <= 8` branch returned the same string as the
other branch (dead since the `d70e032` prototype -- the mock keys there were
all longer than 8 chars), it dropped one character of the server prefix
(3 vs 2), and on the server's whole-string fallback it printed eleven stars
where the server wrote four.

Print the row's own field instead, which is what the settings API-Key table
next door already does (`renderSettings` prints `/api/api-keys`'s masked
`key` as-is). One fact, one implementation.

The new gate `state_gate::the_sharing_list_prints_the_mask_the_server_made`
keeps it that way. Four rules, each with its own tooth:

1. the cell prints a bare member expression (not another call);
2. that field is the one `sharingsToView` passes through verbatim;
3. the server really does write the mask into it -- in the function that
   builds the row, the value's binding reaches the `fn` that returns the
   all-stars literal, and the fallback literal sits in the same body
   (this rule goes red the day the server stops masking, because then
   "print it as-is" becomes a leak);
4. `app.js`'s code carries no second implementation -- not one occurrence of
   the server's mask literal.

The mask function name and the mask literal are both derived from
`sharing.rs`; nothing is snapshotted. Scope is lexical: the gate proves whose
value the cell prints and where that value comes from, not the pixels (CI has
no JS runner), which `ui/README.md` records in the new section.

A/B on the pre-fix tree: the gate reports exactly `r1=false r2=true r3=true
r4=false` -- the frontend was double-masking and carried a second
implementation while the mapper and the server were never wrong.

Verified on the branch tree: `cargo test` 421 passed / 0 failed (baseline
417, +4 new tests), `cargo fmt --check` and `cargo clippy --all-targets -D
warnings` both exit 0, `node --check ui/js/app.js` clean.
@argszero

Copy link
Copy Markdown
Owner Author

Self-review (the allow_self_merge config in this task lifts the own-PR restriction; GitHub does not let an author approve their own PR, so this is a plain comment).

Checked against the merged tree:

  • R1–R4 hold on the branch tree and the A/B on the pre-fix tree reports exactly r1=false r2=true r3=true r4=false — the two red rules are the two that describe the defect (a local re-mask and a second implementation of the mask literal), while the mapper passthrough and the server-side masking were never wrong. That is the shape the axis claims.
  • No snapshot in the gate: the mask function name and the mask literal are derived from sharing.rs (first all-stars literal plus the top-level fn that owns it), and the cell's field is derived from the cell itself.
  • Scope is honest: the gate is lexical (no JS runner in CI), which the new ui/README.md section records rather than implying pixel-level proof.
  • Nothing else touched: ui/js/app.js loses one dead helper and prints the row's field; renderSettings' API-Key cell is the same rule, so one fact now has one implementation.
  • Suite: 421 passed / 0 failed (baseline 417, +4), cargo fmt --check and cargo clippy --all-targets -- -D warnings exit 0, node --check ui/js/app.js clean.

@argszero
argszero merged commit e8a6e46 into main Sep 28, 2026
2 checks passed
@argszero
argszero deleted the fix/sharing-list-keeps-the-server-mask branch September 28, 2026 05:43
@argszero argszero mentioned this pull request Sep 30, 2026
12 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant