Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Documented the viewer-token create/revoke UI design and no-account
read-only viewer boundary.
- Added incident-detail wrapped-key delivery revocation for account-owned
wrapped-key metadata.
- Added incident-scoped sharing-grant management for active contact keys,
Expand Down
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,12 +256,12 @@ do not imply production readiness or public `/v1` API readiness.

The server currently confirms bearer session auth, browser-cookie auth routes,
`POST /v1/auth/register`, `POST /v1/auth/email/verify`, `GET /v1/account`,
`POST /v1/account/password`, owner-scoped incident list/detail routes, contact
public-key routes, sharing-grant routes, and wrapped-key routes. Current
`open-proofline/server` documents authenticated
`GET /v1/incidents`, and this client parses that response shape in live mode.
Mock mode uses prototype incident records only and must not be treated as
backend truth.
`POST /v1/account/password`, owner-scoped incident list/detail routes,
viewer-token create/revoke routes, contact public-key routes, sharing-grant
routes, and wrapped-key routes. Current `open-proofline/server` documents
authenticated `GET /v1/incidents`, and this client parses that response shape
in live mode. Mock mode uses prototype incident records only and must not be
treated as backend truth.

Authenticated users can open the account profile route to review safe account
metadata and change their password through `POST /v1/account/password`. The
Expand All @@ -282,6 +282,13 @@ verification request body, and clears the fragment from the address bar. Raw
verification tokens must not be logged, persisted, screenshotted, copied into
issue drafts, or sent to analytics.

Viewer-token create/revoke UI is documented as a design boundary in
[Viewer Token UI Design](docs/viewer-token-ui-design.md). The intended
web-client viewer will replace the current server-rendered incident viewer
while preserving the same viewer-token system for no-account, read-only access
to unencrypted incident data. That path is separate from future notification
delivery and trusted-contact account/key flows.

The client must not log session tokens, Authorization headers, request bodies,
uploaded bytes, plaintext, raw keys, raw media keys, contact private keys,
wrapped-key ciphertext, verification credentials, object keys, stored paths,
Expand Down
6 changes: 3 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
Proofline Web Client is experimental and not production-ready.

Do not report real secrets, raw session tokens, browser session cookies, CSRF
tokens, Authorization headers, request bodies, plaintext, raw keys, wrapped-key
ciphertext, verification credentials, private deployment details, or user
safety data in public issues.
tokens, raw viewer tokens, token-bearing viewer links, Authorization headers,
request bodies, plaintext, raw keys, wrapped-key ciphertext, verification
credentials, private deployment details, or user safety data in public issues.

Backend security issues may belong in
[`open-proofline/server`](https://github.com/open-proofline/server). Web-client
Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ source of truth remains `open-proofline/server`.
client modes.
- [Security model](security-model.md): implemented controls, non-controls, and
browser auth review areas.
- [Viewer token UI design](viewer-token-ui-design.md): owner create/revoke
design and the planned no-account read-only viewer boundary.
- [Browser security headers](browser-security-headers.md): static-host header
guidance and credentialed CORS review notes.
- [Supply chain review](supply-chain.md)
Expand Down
27 changes: 27 additions & 0 deletions docs/api-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ From current `open-proofline/server` docs and route registration:
- `POST /v1/incidents`
- `GET /v1/incidents`
- `GET /v1/incidents/{incident_id}`
- `POST /v1/incidents/{incident_id}/incident-tokens`
- `POST /v1/incident-tokens/{token_id}/revoke`
- `POST /v1/incidents/{incident_id}/deletion`
- `GET /v1/incidents/{incident_id}/deletion`
- `POST /v1/contact-public-keys`
Expand Down Expand Up @@ -77,6 +79,31 @@ The UI maps password-change failures to fixed safe messages and does not log or
persist passwords, request bodies, session tokens, Authorization headers,
browser session cookies, or CSRF token values.

## Viewer Token UI Boundary

Current `open-proofline/server` documents authenticated owner-scoped
viewer-token creation and revocation:

- `POST /v1/incidents/{incident_id}/incident-tokens`
- `POST /v1/incident-tokens/{token_id}/revoke`

The create route returns the raw viewer token only once and stores only a token
hash on the server. The web-client design for this flow is documented in
[Viewer Token UI Design](viewer-token-ui-design.md). Runtime client methods are
not implemented in this repository yet.

The current server also serves a token-scoped read-only incident viewer. The
intended web-client direction is to replace that surface while using the same
viewer-token authority model for no-account notification contacts. That viewer
path is distinct from the future trusted-contact account system and must not
imply browser decryption, key unwrapping, playable export, notification
delivery, emergency dispatch, or public production readiness.

No incident-token list or read route is currently documented for long-term
management UI. Until such a backend contract exists, the web client should not
promise a durable token table or post-reload revocation workflow for tokens
created outside the current browser flow.

## Frontend Metadata Boundary

Incident detail parsing keeps browser state focused on public-safe metadata.
Expand Down
19 changes: 19 additions & 0 deletions docs/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,25 @@ persisted in browser storage, screenshotted, copied into public issue drafts,
included in analytics, or exposed in UI beyond the transient browser URL
fragment needed to complete verification.

## Viewer Token Boundary

Viewer tokens are bearer secrets for no-account, read-only incident access.
The planned web-client viewer should replace the current server-rendered viewer
while preserving the same server token semantics. It may show only unencrypted
incident data that the backend intentionally exposes to token holders, such as
safe status, check-in, current-location, or device-state fields when those
fields are part of the viewer payload.

Viewer-token UI must not persist raw tokens, viewer links, or token-bearing
paths in browser storage. It must not send raw token values to analytics, logs,
public issues, PR text, or error messages. Invalid, expired, missing, and
revoked token states should collapse into generic viewer errors.

Viewer-token access is separate from the future trusted-contact account system.
Trusted contacts require their own accept/decline flow, account identity,
client-side private-key creation, public-key storage, encrypted evidence access
design, and key-custody threat model.

## Browser Cookie Auth And CSRF

The implemented live client supports bearer-token auth and explicit
Expand Down
10 changes: 8 additions & 2 deletions docs/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ in `open-proofline/server`.
- Browser session cookies when cookie auth mode is enabled.
- CSRF tokens for cookie-authenticated unsafe requests.
- Raw email-verification tokens carried in verification URL fragments.
- Raw viewer tokens and token-bearing viewer links returned once after owner
creation.
- Account metadata visible to the authenticated user.
- Incident, stream, chunk, contact public-key, sharing-grant, and wrapped-key
metadata.
Expand All @@ -32,6 +34,9 @@ in `open-proofline/server`.
- Browser local storage can retain credentials after a session should be gone.
- Verification URL fragments can be exposed by screenshots, browser extensions,
debugging tools, copied issue text, or analytics if handled carelessly.
- Viewer tokens or token-bearing links can be exposed through clipboard history,
logs, analytics, referrers, screenshots, issue drafts, or persistent browser
storage if the one-time success state is mishandled.
- Registration UI wording could expose account-existence state if it diverges
from the server's generic verification-required response.
- Cookie-auth mode could accidentally mix bearer and cookie credentials,
Expand All @@ -50,5 +55,6 @@ in `open-proofline/server`.

Recording, decryption, key escrow, break-glass access, trusted-contact
decryption, payment processing, public-production account portal claims,
emergency notifications, and playable media export are out of scope until
explicitly designed and reviewed.
emergency notifications, notification delivery channels, trusted-contact
account/key flows, and playable media export are out of scope until explicitly
designed and reviewed.
185 changes: 185 additions & 0 deletions docs/viewer-token-ui-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# Viewer Token UI Design

This note scopes the planned browser UI for owner-created viewer tokens and
the future web-client read-only viewer. The backend source of truth remains
`open-proofline/server`.

## Goals

- Let an authenticated incident owner create a read-only viewer token for one
incident.
- Show the raw viewer token or token-bearing viewer link exactly once, with
explicit copy and clear controls.
- Let the owner revoke a known viewer token by server-issued token ID.
- Keep no-account viewer access separate from the account-based trusted-contact
system.
- Keep the web-client viewer limited to read-only unencrypted incident data,
such as safe incident status, latest check-in, current location, or device
state when those fields are present in the server viewer contract.

## Current Server Contract

Current `open-proofline/server` docs and route registration confirm:

- `POST /v1/incidents/{incident_id}/incident-tokens`
- `POST /v1/incident-tokens/{token_id}/revoke`

The create route is owner-scoped, returns the raw token only in the creation
response, stores only a SHA-256 token hash in the configured metadata backend,
and sends `Cache-Control: no-store`. `expires_at` is optional. If omitted, the
server applies its configured default token lifetime, currently 24 hours unless
the deployment changes `SAFE_DEFAULT_INCIDENT_TOKEN_TTL`. Sending `null`
requests a token that remains valid until revoked.

The revoke route disables a token by token ID. There is currently no documented
incident-token list or read route for long-term browser management UI. Until
that route exists, the web client should not promise a durable token table or
post-reload revoke workflow for tokens created elsewhere.

The server still provides the current token-scoped `/i/{token}` incident
viewer. The intended product direction is for the web-client viewer to replace
that surface while preserving the same viewer-token authority model.

## Access Models

Viewer-token access is the no-account contact path. A contact may receive an
email, SMS, Messenger, Signal, or other notification that leads to read-only
incident viewer access. Notification delivery is not part of this design note.
The token grants read-only access to the server-defined viewer payload and must
not imply account access, trusted-contact status, emergency dispatch, or
encrypted evidence decryption.

Trusted contacts are a separate account-based system. That future flow requires
a Proofline account, an accept or decline step after the owner adds a contact by
email or username, client-side private key creation, public key storage, and
client support for viewing or downloading encrypted evidence. That flow is not
implemented by viewer-token UI and needs its own design, backend contract, and
key-custody threat model.

## Owner Create UI

The authenticated incident detail view should expose a "Viewer access" section
only after the user is signed in and the incident detail is loaded.

The create form should include:

- a short label for the intended recipient or purpose;
- an expiry control with the server default as the recommended path;
- an explicit option for no expiry until revoked, with warning copy;
- a submit button that names the action without implying notifications are sent.

The form must not accept raw token input. It should not mention or reveal server
storage paths, object keys, private deployment details, contact private keys,
wrapped-key ciphertext, raw media keys, plaintext, or request bodies.

Recommended create copy:

- Button: "Create viewer link"
- Default expiry helper: "Uses the server default expiry unless changed."
- No-expiry warning: "This link remains valid until revoked. Use this only for
a reviewed contact path."
- Success heading: "Viewer link created"
- One-time warning: "Copy it now. Proofline will not show this link again after
you clear it."

## One-Time Token Handling

The raw viewer token is a bearer secret. The browser may hold it only in
component memory for the active success state.

Required handling:

- do not store the raw token in `localStorage`, `sessionStorage`, IndexedDB, URL
query parameters, persisted TanStack Query cache, service-worker cache, logs,
analytics, telemetry, screenshots, issue drafts, or error messages;
- do not send the raw token to any route other than the intended viewer route;
- do not include raw token values in test fixtures, public docs, issue bodies,
PR text, or changelog entries;
- clear the raw token when the user dismisses the success state, navigates away,
logs out, changes auth mode, or resets session state;
- copy only from a user gesture and report generic copy failures;
- prefer copying a token-bearing viewer link over showing the bare token, while
still treating the full link as the same secret.

The success panel should use an accessible status region, keep warning text next
to the copy action, and provide an explicit "Clear link" control. After clear,
the UI may keep non-secret creation metadata such as label, token ID, created
time, and expiry in volatile browser state, but must not reconstruct or reveal
the raw token again.

## Revoke UI

Revocation should be available only for token IDs the browser knows. With the
current server contract, that means tokens created during the current browser
flow. A durable management table requires a future list/read backend route that
returns non-secret token metadata.

The revoke action should:

- require explicit confirmation for active tokens;
- identify tokens by label, created time, and expiry rather than raw token;
- call `POST /v1/incident-tokens/{token_id}/revoke`;
- remove or mark the local row revoked after success;
- keep failures generic, for example "Viewer access could not be revoked.";
- treat missing, already revoked, unauthorized, and ownership-boundary failures
as non-revealing UI errors.

Revoke copy must not claim clawback. A recipient who already copied data or kept
an open page may have seen read-only incident data before revocation.

## Web-Client Viewer Target

The replacement web-client viewer should consume the same viewer-token system
but remain separate from authenticated owner UI.

Viewer route behavior should be designed before implementation:

- accept a token-bearing route without storing the token in browser storage;
- request only the server-defined read-only viewer payload;
- show unencrypted incident status data that the server intentionally exposes to
token holders;
- avoid encrypted evidence decryption, key unwrapping, playable export,
account login, trusted-contact private-key handling, and emergency response
claims;
- use no-store cache behavior and conservative browser headers at deployment;
- collapse invalid, expired, missing, and revoked token states into generic
errors that do not reveal token validity.

If the replacement viewer needs a different route shape or link origin from the
current server `/i/{token}` path, that must be resolved with server and
deployment docs before runtime implementation.

## Error And Empty States

Create errors should be short and generic:

- incident not found or not owned: "Viewer access could not be created.";
- invalid expiry: "Enter a valid expiry time or use the default.";
- auth expired: "Sign in again to create viewer access.";
- network/server failure: "Viewer access could not be created.";

Revoke errors should also stay generic:

- unknown, already revoked, or not owned: "Viewer access could not be revoked.";
- auth expired: "Sign in again to revoke viewer access.";
- network/server failure: "Viewer access could not be revoked.";

The UI should include `role="status"` for success and loading states and
`role="alert"` for blocking errors. Buttons should have stable disabled states
while requests are pending.

## Follow-Up Implementation Questions

Before runtime implementation, decide:

- the exact web-client viewer route and configured public viewer origin;
- whether owner create UI may create a server `/i/{token}` link during the
transition or must wait for the web-client viewer route;
- whether the server should add a non-secret incident-token list/read route for
long-term revocation management;
- which viewer payload fields are intended for no-account notification contacts,
especially current location and device state;
- whether notification delivery channels are server, client, or separate
service responsibilities;
- how deployment will keep token-bearing paths out of access logs, referrers,
analytics, and public issue text.