7f6aaf2e646e478216eb9d7e76967ab84b5425c1
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dd3db50460 |
Secure Send hands over a message, not a bare URL
A link on its own invites being pasted onto text that is already in the chat box, and every chat client turns the selection into the link's caption. The secret then travels as the visible message with the secure link wrapped around it, protecting nothing. This is not hypothetical. Six LAPS passwords went out that way in production - machine names and passwords as the caption, the correct /s/<token>#<key> URL underneath, doing nothing at all. Copy message puts a whole sentence on the clipboard: what the link is, how many opens it has, when it expires, and then the URL. A sentence has nowhere to attach itself - pasted into an empty box it is a message, pasted over a selection it replaces it rather than decorating it. Plain text on purpose; a rich-text flavour is exactly what would let a secret ride along as markup. The success card now says to delete the password from the chat box before pasting. The existing hint explains that the key cannot be recovered, which is true and does not stop anybody pasting the password itself. Nothing about the link was wrong and nothing about it changed: the URL carries the token, the key rides in the fragment the browser never sends, the plaintext is dropped the moment it is sealed, and redeeming stays a POST so a preview cannot spend it. |
||
|
|
92cf1d71f0 |
Stop making people sign in to reach tools that need no account
/tools, /tools/codes and /tools/secure-send were all behind requiresAuth. None of them needed to be, and one of them actively contradicted a setting added an hour earlier: securesend_allow_guests lets somebody with no account create a link, and the page they would create it on was behind a login wall. The label generator runs entirely in the browser - no API, no data, nothing to protect - so gating it only ever cost a guest the use of it. THE SERVER STILL DECIDES who may create a secret. A signed-in user needs tools.secrets; a guest needs the site to have opened guest creation. Making the page reachable is not the same as making the action allowed, and the two were conflated. The page now reads the setting on load, so a guest at a site that has NOT opened guest creation is told to sign in instead of being handed a form that was always going to be refused - with a line saying that receiving a link never needs an account, because somebody who lands there confused usually has a link they should be opening instead. A signed-in user always sees the form, even without the permission: the server's "you do not have permission" is a different answer from "sign in", and collapsing them would send somebody to a login page they are already past. |
||
|
|
8750ba2e74 |
Let a site open secure send to guests, and choose how many times a link opens
Two asks, and the first is a real security decision so it is a SETTING that defaults to the safe answer rather than a code change somebody inherits. securesend_allow_guests, OFF by default. With it off the model is unchanged: a login plus tools.secrets. With it on, somebody with no account - at a shared bay terminal, a contractor - can create a link too. The cost is named in the setting's own description and pinned by a test: a guest secret has NO CREATOR, so the audit trail can say when and from where but never who. That is the whole reason it is a switch a person turns on deliberately. IT FAILS CLOSED. A missing row, a half-migrated site, an unhappy database - the answer is no. An error must never be the thing that opens an unauthenticated write endpoint. A SIGNED-IN USER STILL NEEDS THE PERMISSION even when guests are allowed, which is surprising enough that a test pins it: opening the door to guests is a decision about the door, not about revoking what a role holds. Guest creation is rate limited per address, same shape as the login limiter. Not a security boundary - a determined person has more addresses - but an open endpoint should not be an easy place to park data or fill a disk. VIEWS ARE NOW A NUMBER, 1 to 100, instead of a four-item dropdown. How many people need a password is the sender's business: a shift handover is not one colleague on a call. The floor is what matters, not the ceiling - unlimited views would just be a password on a web page. Also folded in, from the retention question: nothing removed a secret that EXPIRED WITHOUT BEING OPENED. The recipient goes on holiday, the link is never clicked, and the ciphertext sat there for good - not readable, but it made "nothing lingers" untrue and grew the table in exactly the case where the feature failed. Any create now sweeps expired rows and a late click tidies its own, so it stays bounded without a scheduler an air-gapped site would never add. Two gates caught mistakes on the way: current_user is a LocalProxy and is never None, so `current_user is None` silently took the signed-in branch for a guest; and cache was imported straight from shopdb.extensions when it is already on the shopdb.api contract surface. 1836 tests green. |
||
|
|
e8920b2cf3 |
Do not add the mount to a site URL that already has it
|
||
|
|
a3811740bb |
Build a secret's link from the site's URL, not the browser's
Reported from production: clicking a secret link gave an IIS 404, and the link read /s/<token> where the site lives at /shopdb/s/<token>. The link was assembled from window.location.origin, which is scheme and host only. site_base_url already exists for this and says so in its own description - "used for QR codes and any absolute link the app emits" - with getSiteBaseUrl() as the helper the QR pages use. This reached past all of it. Both halves were wrong, and the second is worse because it fails silently: MOUNT PATH. A site served under /shopdb or /ops carries that in front of every route. Leaving it off is the reported 404. HOST. Whoever creates a secret may have reached the app by an IP, a short hostname, or a name that resolves in one VLAN, and the recipient opens the link from somewhere else entirely. The configured site URL is the one address known to work for both of them. The shape of this failure is what makes it worth spelling out: the secret was created SUCCESSFULLY and the sender was handed a link that could never work, so the failure landed on the recipient, the sender had no reason to doubt it, and the secret expired unread. A 500 would have been kinder. Falls back to the browsing origin when a site has not set site_base_url, which is what every other absolute link here does. BASE_URL is injected at build time, so the subpath half of this cannot show up in dev and only appears on a subpath deployment. |
||
|
|
7fc109d91a |
Give secure send a card, a form, and a page for the person receiving it
The backend has been there since
|