Client trust boundary: what the browser can prove
The browser is the user's; the server decides every action from its own records.
Predict first
Before you read: predict
ExamHall's proctor console shows a Grade button on each submitted attempt; the candidate app never shows one. The console grades with POST /attempts/77/grade, sending the proctor's session cookie, the header X-Client: proctor-console, a Referer of the console's page and the body {"score": 18}. Mia, a candidate signed in with her own session cookie, writes a script on her laptop that calls POST /attempts/77/grade with the body {"score": 18}.
Which is the simplest grading handler that refuses Mia's request and still accepts the proctor's?
The 30-second version
Whatever reaches a browser, its user controls: scripts can be read, storage edited, and requests sent from any client with any header. So the server learns who is asking from its own records and decides every action itself; a hidden button only spares confusion. Permissions-Policy limits which frames may use features such as the camera, protecting the user.
The browser is the user's; the server decides every action from its own records.
How it works
Step 1 of 13
Analogy: A patent office and the inventors who file with it (intellectual property)
Anyone can read the office's published files, and every inventor keeps her own notebook; the office decides from its own register.
| At the patent office | In the browser and on the server |
|---|---|
| a published application, open to anyone, the examiner's prior-art notes printed with it | the script sent to the browser, all of it readable (step 2) |
| the dates and notes an inventor writes in her own notebook | browser storage: her claims, not the office's entries (step 3) |
| the examiners' fast-track form left out of the public guide, yet taken at the filing desk | a hidden button over an endpoint that still answers (steps 4–5) |
| the filing clerk checking each applicant against the office's own register of attorneys, never the letterhead on the request | the server reading its own session record, not a header the client adds (step 6) |
| the office's post book of every document that reaches it, and nothing of what an inventor does in her workshop | the server's log of requests (step 10) |
In real terms: the client asks; the server, from its own records, answers.
Breaks down when: an examiner can visit a workshop and watch. A server never sees inside the browser, only what is sent to it (step 10).
Examples, run for real
4.1 ExamHall EXH-3450: a button hidden, an endpoint open
ExamHall's candidate app never rendered the Grade button, and POST /attempts/:id/grade checked only that a session existed. MR !3750 looks the session's user up in the server's user table and requires the proctor role; the exam page also gains a camera policy:
4.1 before MR !3750 (any signed-in session): candidate's direct POST /attempts/77/grade -> 200, grade stored · proctor -> 200, grade stored
4.1 after MR !3750 (proctor role from the server's user table): candidate -> 403, no grade · proctor -> 200, grade stored
4.1 after MR !3750, the exam page's response header: permissions-policy: camera=(self)
The candidate app did not change: the endpoint was never hidden, only its button (steps 4–6). The grade now follows the server's record of who is asking. The policy header is a separate job: it limits which frames on the exam page may open the camera, for the candidate's protection (steps 11, 13).
4.2 Ticketing: a fan edits her own storage
A box office's fan app saves {"tier": "member"} in localStorage for members and draws presale seats only for that tier. The hold endpoint checks the server's member list. Ola, not a member, sets the stored tier herself and reloads:
4.2 Ola, not a member: presale seats on her screen 0 of 12 · after she sets her stored tier to "member" and reloads: 12 of 12
4.2 her hold on presale seat A3 -> 403 (the server's member list: not a member) · a member's hold on A4 -> 200
The screen followed her storage; the hold followed the server's list (steps 3, 6). Drawing seats from a stored tier is fine for layout, since the seat map decides nothing. The tier itself stays a claim: the server never reads it.
Try it
Answer first, then check. Nothing is saved.
Question 1 of 7
A box office's MR changes three files and leaves the holds endpoint unchanged. Seat B12 costs €80 in the box office's price table, and B11 is sold.
Which change should the review block?
Question 2 of 7
ExamHall's exam page is served with Permissions-Policy: camera=(self). It holds a proctoring frame from the exam's own origin, a help-chat frame from a vendor's origin with allow="camera", and an analytics script the page loads with <script src> from that vendor's origin. In Chromium, with a camera present and permission granted, each of the four calls getUserMedia({ video: true }).
Which of them get the camera?
Question 3 of 7
In a design round you said ExamHall's grading endpoint checks the proctor role on the server. The interviewer follows up: "The candidate app also hides the Grade button from candidates. Now that the server checks, what should happen to that check in the app?" You give one answer.
Which answer survives the interviewer's next question?
Question 4 of 7
During ECON-201, Mia does four things in her browser: (1) she edits the countdown on her screen from 05:00 to 45:00; (2) two minutes after her deadline she presses Save; (3) from the page's console she calls fetch('/attempts/77/grade', …) with her own session; (4) she opens quiz.js in the Sources panel and reads it. The exam server logs every request it receives, with its own receipt time and its response.
Which of the four appear in the server's log?
Question 5 of 7
The interviewer asks you to "draw the trust boundary for ExamHall's exam page" and to show what runs in the browser, what the server decides, and what you do with the browser's signals.
Write your answer in at most 150 words, as a list of boxes and arrows.
Question 6 of 7
A box office's checkout page is served with Permissions-Policy: camera=(self "https://seats.partner.test"). It frames three pages, each with allow="camera": an ID scanner from the box office's own origin, a seat-view widget from https://seats.partner.test and a reviews widget from https://widgets.fanreviews.test. Each frame calls getUserMedia({ video: true }), in Chromium, with a camera present and permission granted.
Which frames get the camera?
Question 7 of 7
A box office's checkout script compares a code the buyer types with the staff discount code, held in its source as STAFF_DISCOUNT_CODE, and on a match takes 100% off. The checkout page loads that script as checkout.js, minified with terser. Ola, a fan with no code, opens checkout.js in her browser's Sources panel.
What does she find there?
Go deeper
Pro tip
- In a design round, draw the trust boundary as a line on the diagram, and label every arrow that crosses it with what the server re-checks. When: the high-level diagram of any client-heavy design (
fsd-radio-framework's architecture phase). Status: SELF (this lesson's practice target; not run).
Source
Valid as of 2026-10-10.
- OWASP, Authorization Cheat Sheet (the row's first source), https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html (
owasp-authorization.txt): "Developers must never rely on client-side access control checks." and "While such checks may be permissible for improving the user experience, they should never be the decisive factor in granting or denying access to a resource"; "client-side logic is often easy to bypass" and "Access control checks must be performed server-side, at the gateway, or using serverless functions"; "based solely on currently authenticated user's identity and attributes (e.g. through information contained in a securely implemented JSON Web Token (JWT) or server-side session)" (step 6); "Rather than relying on some form of security through obscurity, the focus should be on controlling access to the underlying objects and/or the identifiers themselves." (steps 2, 5). - OWASP, HTML5 Security Cheat Sheet (the row's second source), https://cheatsheetseries.owasp.org/cheatsheets/HTML5_Security_Cheat_Sheet.html (
owasp-html5.txt): "Don't rely only on the Origin header for Access Control checks. Browser always sends this header in CORS requests, but may be spoofed outside the browser." (step 4; t2); "any authentication your application requires can be bypassed by a user with local privileges to the machine on which the data is stored" and "A user or process with access to the browser profile can read or modify stored data" (steps 1, 3); "A single Cross Site Scripting can be used to load malicious data into these objects too, so don't consider objects in these to be trusted." (step 3). - MDN, Permissions-Policy header (the row's third source), https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy (
mdn-permissions-policy.txt): "provides a mechanism to allow and deny the use of browser features in a document or within any <iframe> elements in the document" (step 11); "The feature will be allowed in this document, and in all nested browsing contexts (<iframe>s) in the same origin only. The feature is not allowed in cross-origin documents in nested browsing contexts." (step 11); "For an <iframe> to have a feature enabled its allowed origin must also be in the allowlist for the parent page."; "This value is only used in the <iframe> allow attribute, and is the default allowlist value in <iframe>s."; "Controls whether the current document is allowed to use video input devices." and "The Promise returned by getUserMedia() will reject with a NotAllowedError DOMException if the permission is not allowed.". The page marks the header "Experimental" and "not Baseline"; Chromium supports it, which is what P2 executes. As of 2026-10-10. - OWASP, Input Validation Cheat Sheet, https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html (
owasp-input-validation.txt), read 2026-10-12: "Validate both syntax (the expected type and format) and semantics (whether the value makes sense for the operation)."; "Client-only checks: Validate on the server even when the browser checks the same fields. Client-side validation is bypassable; use it for immediate feedback." (steps 7, 8). As of 2026-10-12.
Related lessons
Checked against the tools and docs as of 2026-10-10.