Batch 12 shipped envelope-level gating: /api/access/gate returned a decision JSON and the shell acted on it. Batch 13 adds a stronger path: /api/access/render. The server reads the target HTML file from DAS_DOCS_ROOT, evaluates access state, and returns the actual HTML (full, restricted variant, or stub) — so the response IS the page. A reverse-proxy rule routing all traffic through this endpoint achieves real HTTP-level gating.
Batch 12 ส่ง envelope · Batch 13 ยกระดับเป็น /api/access/render · server อ่านไฟล์เอง · ส่ง HTML จริงกลับไป · reverse-proxy ชี้ไปที่ endpoint นี้ = gating ระดับ HTTP จริง
Server now returns the HTML. When routed through /api/access/render, hidden docs never leave the server. Honest: static URLs are still served by whatever hosts the files unless reverse-proxy rules are in place.
Server ส่ง HTML เอง · ถ้า route ผ่าน /api/access/render · เอกสาร hidden ไม่หลุดจาก server · หน้าเดิมยังเปิดได้ถ้าไม่มี reverse-proxy
The render endpoint is a FastAPI route that: (1) validates doc_id against path traversal, (2) resolves the current session to a profile, (3) computes access state via the same matrix as /api/access/resolve, (4) reads the file if the state is visible/restricted, or returns an inline stub if blocked. Every response carries X-DAS-Render-State and Cache-Control: private, no-store. Security: only .html under DOCS_ROOT · no .. · no absolute paths.
| Aspect | Envelope (Batch 12) | HTTP render (Batch 13) |
|---|---|---|
| Path | /api/access/gate | /api/access/render |
| Returns | JSON decision + optional stub_html | text/html · the actual page |
| Client role | cooperate: show banner or swap body | consume: response IS the page |
| Protection against curl | none — shell JS is the enforcement | full when routed through this endpoint (server never sends the file body for blocked states) |
| Requires routing change | no | yes, for full coverage (reverse-proxy rewrite) |
| Use case | pages already using the shell | proxied routes · team-shared links · fetch-and-display |
GET /api/access/render?doc_id=/planning/document-access-model.html Cookie: ds_session=<body>.<sig> 200 OK X-DAS-Render-State: visible | restricted | hidden-doc | hidden-group | not-granted X-DAS-Render-Profile: <email> Cache-Control: private, no-store Content-Type: text/html <!DOCTYPE html>… (full file OR restricted-wrapped OR stub)
Full spec: render_contract.json
For true route-level protection on a deployment, wire a reverse proxy (nginx / Cloudflare Workers / HAProxy) to rewrite requests for protected paths to the render endpoint. Example nginx snippet (not implemented — platform concern):
location ~ ^/(planning/document-.*|document-groups|runtime/document-.*-service/service)\.html$ {
proxy_pass http://das:8090/api/access/render?doc_id=$request_uri;
proxy_set_header Cookie $http_cookie;
}Without this, users can still request the file at its original path and a permissive static host will serve it. That's the honest remaining gap.
doc_id must not contain .. / absolute path / :.html files are renderedDOCS_ROOTX-DAS-Render-State header surfaces the decision for audit/debugCache-Control: private, no-store prevents shared caching of gated content