Skip to content

Commit 7aeffd8

Browse files
committed
Rename site in more places
1 parent d93a7f2 commit 7aeffd8

4 files changed

Lines changed: 69 additions & 36 deletions

File tree

.env.local.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Risk Matrix — local development environment.
1+
# Risk Mapper — local development environment.
22

33
# Copy this file to `.env.local` and edit.
44

THREAT-MODEL.md

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
# Risk Matrix — what cloud sync protects (and what it doesn't)
1+
# Risk Mapper — what cloud sync protects (and what it doesn't)
22

3-
Risk Matrix can save your matrices to a server and let you share them via a
3+
Risk Mapper can save your matrices to a server and let you share them via a
44
link. This page is for users deciding whether the feature fits their needs.
55
It's plain-English on purpose; for the cryptographic implementation details,
66
see the code under `lib/e2ee/` and `app/api/matrix/`.
77

8-
98
## How it works, in one paragraph
109

1110
When you choose to save a matrix to the cloud, the matrix is encrypted on
@@ -14,20 +13,26 @@ encryption key lives in the part of the URL after `#`, which browsers don't
1413
send to servers. Anyone you give the URL to can read and edit the matrix
1514
the same way you can.
1615

17-
1816
## What's protected
1917

2018
**The server can't read your matrices.** Whoever runs the server can't see
2119
your risks, your mitigations, or even the matrix title. They see opaque
22-
encrypted blobs, calendar dates, and a counter — that's it.
20+
encrypted blobs, coarse calendar dates, sequence numbers, and per-writer
21+
labels attached to updates — but not the plaintext inside the blobs.
2322

2423
**The server can't tamper without you noticing.** If the server modifies
2524
the blob, swaps blobs between matrices, or changes their version numbers,
2625
your client's decryption fails loudly. You won't silently see the wrong
2726
content.
2827

29-
**The server can't roll you back.** If it tries to serve you an older
30-
version of a matrix you've already seen, the client refuses.
28+
**Rollbacks and lost history are not the same as decrypt failures.** The
29+
client does not cryptographically verify that every sync is strictly newer
30+
than every version you've ever seen. If a host restores an older database
31+
snapshot or drops some updates, your app may merge older Yjs data into
32+
your document instead of showing a dedicated "rollback detected" error.
33+
Tampering that breaks the authenticated encryption still fails at decrypt
34+
time; **missing or replayed ciphertext** is handled by CRDT merge rules, not
35+
by a hard refusal.
3136

3237
**Network observers can't read your matrices.** Anyone watching the
3338
connection sees only encrypted blobs. The encryption key in the URL
@@ -37,12 +42,20 @@ fragment never travels over the wire.
3742
someone's share URL, the matrix appears in a sandboxed preview. Nothing is
3843
saved locally until you click "Save on this device."
3944

40-
4145
## What's not protected
4246

43-
**Anyone with the link can read and edit.** That's the entire sharing
44-
model. Treat the URL like a password — share it through a private channel
45-
only.
47+
**Anyone with the link can read, edit, or delete the cloud copy.** The URL
48+
is a full capability: same read/write as you, and anyone who has it can
49+
remove the matrix from the server (or use Stop sharing from their session).
50+
There is **no view-only** share link. Treat the URL like a password — share
51+
it through a private channel only.
52+
53+
**Subpoenas and lawful requests can still obtain metadata.** Whoever hosts
54+
the database or HTTP infrastructure can usually be compelled to produce
55+
ciphertext, record ids, coarse dates, access logs (timestamps, IPs, paths),
56+
and similar material. That does **not** let them decrypt matrix **contents**
57+
without the secret in the URL fragment — but it is **not** the case that
58+
there is "nothing" responsive to a subpoena.
4659

4760
**The link itself can leak.** Browser history, browser sync, screenshots,
4861
screen-sharing, smart clipboards, pasting into a chat client — any of
@@ -75,11 +88,11 @@ the server. Stopping sharing deletes it.
7588
server's logs can see when a matrix is being edited and roughly how big
7689
it is. They can't see what changed.
7790

78-
7991
## In plain words
8092

8193
This is "encrypted Pastebin / Cryptpad-style sharing." The server can't
82-
read your matrices. The link is the password. If your device, your
94+
read your matrix contents; the link is the password. Operators and hosts
95+
can still see correlation metadata and ciphertext. If your device, your
8396
browser, or our own hosting is compromised, the encryption doesn't help —
8497
but no browser-based encryption tool can protect against that.
8598

app/not-found.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default function NotFound() {
88
href="/"
99
className="text-sm font-medium text-rm-ink underline underline-offset-4 opacity-80 hover:opacity-100"
1010
>
11-
Back to risk matrix
11+
Back to risk mapper
1212
</Link>
1313
</div>
1414
);

components/risk-matrix/MatrixTopBar.tsx

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ type Props = {
2424
statusIndicator?: React.ReactNode;
2525
};
2626

27-
const SITE_NAME = "Risk Matrix";
27+
const SITE_NAME = "Risk Mapper";
2828

2929
/** Invisible row matching full-label toolbar width — stable “does it fit?” probe (avoids compact/full flicker). */
3030
function MatrixToolbarWidthProbe() {
@@ -56,10 +56,11 @@ export default function MatrixTopBar({
5656
cloudShareControl,
5757
statusIndicator,
5858
}: Props) {
59-
const titleCharWidth = Math.min(44, Math.max(12, ws.activeTitle.length || 0));
6059
const toolbarRef = useRef<HTMLDivElement>(null);
6160
const measureRef = useRef<HTMLDivElement>(null);
61+
const titleMirrorRef = useRef<HTMLSpanElement>(null);
6262
const [iconOnlyToolbar, setIconOnlyToolbar] = useState(false);
63+
const [titleInputWidthPx, setTitleInputWidthPx] = useState(220);
6364

6465
useLayoutEffect(() => {
6566
const toolbar = toolbarRef.current;
@@ -83,6 +84,14 @@ export default function MatrixTopBar({
8384
};
8485
}, []);
8586

87+
useLayoutEffect(() => {
88+
const mirror = titleMirrorRef.current;
89+
if (!mirror) return;
90+
const measured = Math.ceil(mirror.getBoundingClientRect().width);
91+
// Clamp to a readable minimum while allowing the field to grow naturally.
92+
setTitleInputWidthPx(Math.max(140, measured));
93+
}, [ws.activeTitle]);
94+
8695
return (
8796
<TooltipProvider delayDuration={400}>
8897
<div className="mb-3 flex flex-col gap-3">
@@ -104,25 +113,36 @@ export default function MatrixTopBar({
104113
{SITE_NAME}
105114
</TooltipContent>
106115
</Tooltip>
107-
<input
108-
type="text"
109-
value={ws.activeTitle}
110-
onChange={(e) => ws.setActiveTitle(e.target.value)}
111-
onBlur={(e) => {
112-
const t = e.target.value.trim();
113-
ws.setActiveTitle(t.length > 0 ? t : "Untitled");
114-
}}
115-
onKeyDown={(e) => {
116-
if (e.key === "Enter") {
117-
e.preventDefault();
118-
(e.target as HTMLInputElement).blur();
119-
}
120-
}}
121-
placeholder="Matrix title"
122-
aria-label="Matrix title"
123-
size={titleCharWidth}
124-
className="min-w-0 max-w-88 shrink rounded-md border border-transparent bg-transparent px-2 py-1 text-lg font-semibold text-rm-ink outline-none placeholder:opacity-45 hover:border-black/20 hover:bg-black/2 focus-visible:border-rm-primary focus-visible:bg-white focus-visible:ring-2 focus-visible:ring-rm-primary/20 sm:text-xl"
125-
/>
116+
<div
117+
className="relative min-w-0 max-w-88 shrink"
118+
style={{ width: `${titleInputWidthPx}px` }}
119+
>
120+
<span
121+
ref={titleMirrorRef}
122+
className="pointer-events-none invisible absolute left-0 top-0 whitespace-pre rounded-md border border-transparent px-2 py-1 text-lg font-semibold sm:text-xl"
123+
aria-hidden
124+
>
125+
{ws.activeTitle || "Matrix title"}
126+
</span>
127+
<input
128+
type="text"
129+
value={ws.activeTitle}
130+
onChange={(e) => ws.setActiveTitle(e.target.value)}
131+
onBlur={(e) => {
132+
const t = e.target.value.trim();
133+
ws.setActiveTitle(t.length > 0 ? t : "Untitled");
134+
}}
135+
onKeyDown={(e) => {
136+
if (e.key === "Enter") {
137+
e.preventDefault();
138+
(e.target as HTMLInputElement).blur();
139+
}
140+
}}
141+
placeholder="Matrix title"
142+
aria-label="Matrix title"
143+
className="w-full min-w-0 rounded-md border border-transparent bg-transparent px-2 py-1 text-lg font-semibold text-rm-ink outline-none placeholder:opacity-45 hover:border-black/20 hover:bg-black/2 focus-visible:border-rm-primary focus-visible:bg-white focus-visible:ring-2 focus-visible:ring-rm-primary/20 sm:text-xl"
144+
/>
145+
</div>
126146
{statusIndicator ? (
127147
<div className="shrink-0">{statusIndicator}</div>
128148
) : null}

0 commit comments

Comments
 (0)