Security¶
Optional per-deployment hardening: response headers, CORS and framing, Content-Security-Policy, rate limiting and the session cookie. Everything on this page is optional — left unset, Boards behaves exactly as it did before these settings existed.
Always-on response headers¶
These are sent on every response and need no configuration. Only Strict-Transport-Security is tunable (see HSTS).
| Header | Value | Sent by | Purpose |
|---|---|---|---|
X-Content-Type-Options |
nosniff |
Both | Stops the browser guessing a response's content type |
Referrer-Policy |
strict-origin-when-cross-origin |
Both | Limits how much of the Boards URL is leaked to third parties |
Strict-Transport-Security |
max-age=15724800; includeSubDomains |
Both | Forces HTTPS |
Cross-Origin-Resource-Policy |
cross-origin |
Frontend | Required so Boards can be framed by a portal on another origin |
X-Powered-By |
(removed) | API | No server technology disclosure |
Both = the API (core) and the frontend (webfront); the two are kept deliberately in step so a proxy in front of them never sees a header on one origin and not the other.
CORS & framing¶
By default the API accepts cross-origin calls from any origin (needed for embedding Boards in unknown parent sites). Set CORS_ALLOWED_ORIGINS to lock Boards down to a known set of origins instead.
| Key | Description | Default |
|---|---|---|
global.env.CORS_ALLOWED_ORIGINS |
Optional: Comma-separated list of origins allowed to embed or cross-origin-call Boards. When set, the API reflects only these origins (Boards' own URL is added automatically) and the frontend restricts framing (CSP frame-ancestors) to them. * matches a single sub-domain label, e.g. https://*.example.com. Blank = open (any origin). |
(open) |
A single origin can be set inline. For a longer list, use a YAML folded block (>-) with one origin per line, each ending in a comma:
global:
env:
# inline
CORS_ALLOWED_ORIGINS: https://connections.company.com
# list
CORS_ALLOWED_ORIGINS: >-
https://teams.microsoft.com,
https://*.teams.microsoft.com,
https://connections.company.com
HSTS¶
Strict-Transport-Security is always sent (default max-age=15724800; includeSubDomains). Use these settings to override the default:
| Key | Description | Default |
|---|---|---|
global.env.HSTS_MAX_AGE |
Optional: max-age in seconds. Non-numeric values fall back to the default. |
15724800 |
global.env.HSTS_INCLUDE_SUBDOMAINS |
Optional: Set false to drop the includeSubDomains directive. |
true |
global.env.HSTS_PRELOAD |
Optional: Set true to add the preload directive. |
false |
For example, a two-year policy with preload:
global:
env:
HSTS_MAX_AGE: 31536000
HSTS_INCLUDE_SUBDOMAINS: 'true'
HSTS_PRELOAD: 'true'
Content-Security-Policy¶
No CSP is enforced by default (aside from frame-ancestors, set via CORS_ALLOWED_ORIGINS above). Validate a policy in report-only mode first, then enforce it.
| Key | Description | Notes |
|---|---|---|
webfront.env.CSP_REPORT_ONLY |
Optional: A candidate policy sent as Content-Security-Policy-Report-Only. The browser reports violations without blocking anything — use it to see what a policy would break before enforcing. |
|
webfront.env.CSP |
Optional: An enforced Content-Security-Policy (same syntax as CSP_REPORT_ONLY). |
Do not set frame-ancestors here — control framing via CORS_ALLOWED_ORIGINS only, or the two policies intersect and can block framers you intended to allow. |
Use a YAML folded block (>-) with one directive per line, each ending in a semicolon. Start in report-only mode, then move the same value to CSP to enforce it.
What Boards needs from a policy¶
In the table below, API host is where API_GATEWAY points, and Connections host is the Connections deployment Boards is configured against. When either shares the Boards domain, 'self' already covers it and the entry can be dropped.
| Directive | Value | Why Boards needs it |
|---|---|---|
default-src |
'self' |
Safe fallback for anything not listed; Boards itself is served from its own origin. |
script-src |
'unsafe-inline' |
Connections/Collab banner only — it injects inline scripts. Boards' own code does not need this; drop it on every other deployment. |
'unsafe-eval' |
Connections/Collab banner only (its Dojo toolkit). Boards' own code does not need this; drop it on every other deployment. | |
| Connections host | Banner scripts (only when Connections is on a different origin). | |
style-src |
'unsafe-inline' |
Always required, on every deployment: rich-text formatting (text colour, alignment) is rendered as inline styles. The Connections banner also injects styles. Do not drop this one alongside the script-src entries above. |
| Connections host | Banner stylesheets. | |
img-src |
data: blob: |
Avatars and file thumbnails are fetched via the API and displayed from memory; placeholders and icons are inline data URLs. |
https: |
User content (descriptions, comments) can reference images on any host. Replace with known hosts for a tighter policy — external images then show as broken. | |
connect-src |
API host + wss: API host |
All API calls, file/preview downloads and live updates (WebSocket). |
| Connections host | Banner data requests and notifications. | |
media-src |
API host | Video/audio file preview. |
frame-src |
API host | HTML file preview renders in an iframe served by the API. |
form-action |
'self' |
Restricts where forms may submit (prevents form-hijacking). Boards' own forms post to its own origin. |
| API host | Form submits to the API (e.g. sign-in). Drop when the API shares the Boards origin. | |
| Connections host | Connections/Collab banner forms (e.g. its search box). | |
font-src |
data: |
Bundled fonts; some are inlined as data URLs. |
| Connections host | Banner icon fonts. | |
https://static2.sharepointonline.com https://spoprod-a.akamaihd.net |
Microsoft 365 deployments only: the Microsoft 365 header loads its fonts and icon font from these Microsoft CDNs. Without them the header falls back to system fonts and icons render blank. | |
worker-src |
'self' blob: |
Offline service worker and the PDF preview worker. |
manifest-src |
'self' |
The web-app manifest (install/pin metadata). |
object-src |
'none' |
Nothing uses plugins — lock it down. |
base-uri |
'self' |
Restricts the page <base> URL. This directive has no default-src fallback, so always set it explicitly. |
frame-ancestors |
do not set | Framing is controlled by CORS_ALLOWED_ORIGINS (see above), which emits its own frame-ancestors policy. Browsers enforce every policy — setting it here too means a parent must be allowed by both, which can block framers you meant to allow. It is also ignored in Report-Only mode, so it cannot be validated via CSP_REPORT_ONLY anyway. |
Which deployments need 'unsafe-inline'?¶
Only the HCL Connections / Collab embedded SSO header forces script-src 'unsafe-inline' 'unsafe-eval'. Boards' own bundle runs under script-src 'self'.
| Deployment | script-src needs |
|---|---|
| HCL Connections or Collab with the embedded SSO header | 'self' 'unsafe-inline' 'unsafe-eval' + the Connections host |
HCL Connections without the header (different domain, no clientHeaderSSO) |
'self' |
| Microsoft 365 | 'self' — the header is bundled with Boards. Its fonts come from Microsoft CDNs, so add those to font-src |
| HCL Domino | 'self' + the Domino host — the navbar loads as a module from the provider, with no inline script |
| HCL DX | 'self' — DX injects nothing into a standalone Boards page |
| Auth0 or standalone | 'self' |
style-src 'unsafe-inline' is not in that list
Every deployment needs style-src 'unsafe-inline', banner or not — Boards renders rich-text formatting (colour, alignment) as inline styles. Dropping it makes formatted card descriptions and comments render unstyled.
Same domain as Connections¶
The usual Component Pack shape — Boards and its API served from the Connections domain (e.g. connections.example.com/boards and connections.example.com/api-boards). Everything is same-origin, so 'self' covers the API and the Connections banner:
webfront:
env:
CSP_REPORT_ONLY: >-
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob: https:;
connect-src 'self';
media-src 'self';
frame-src 'self';
form-action 'self';
font-src 'self' data:;
worker-src 'self' blob:;
manifest-src 'self';
object-src 'none';
base-uri 'self';
Separate host names¶
Boards on its own host names (e.g. boards.example.com with the API at api-boards.example.com, Connections at connections.example.com). https://*.example.com covers the API and Connections hosts in one entry — replace it with the explicit host names for a tighter policy:
webfront:
env:
CSP_REPORT_ONLY: >-
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.example.com;
style-src 'self' 'unsafe-inline' https://*.example.com;
img-src 'self' data: blob: https:;
connect-src 'self' https://*.example.com wss://*.example.com;
media-src 'self' https://api-boards.example.com;
frame-src 'self' https://api-boards.example.com;
form-action 'self' https://*.example.com;
font-src 'self' data: https://*.example.com;
worker-src 'self' blob:;
manifest-src 'self';
object-src 'none';
base-uri 'self';
Standalone (no Connections banner)¶
Without the Connections/Collab banner — Microsoft 365, HCL Domino, HCL DX, Auth0 and standalone deployments — script-src needs no 'unsafe-inline' or 'unsafe-eval' at all, and the Connections host entries go away. Add the provider host where the deployment loads something from it (a Domino navbar module, the Microsoft 365 font CDNs). For a deployment at boards.example.com with the API at api-boards.example.com:
webfront:
env:
CSP_REPORT_ONLY: >-
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data: blob: https:;
connect-src 'self' https://api-boards.example.com wss://api-boards.example.com;
media-src 'self' https://api-boards.example.com;
frame-src 'self' https://api-boards.example.com;
form-action 'self' https://api-boards.example.com;
font-src 'self' data:;
worker-src 'self' blob:;
manifest-src 'self';
object-src 'none';
base-uri 'self';
DX: no extra CSP entries. Embedding Boards inside DX pages is controlled by CORS_ALLOWED_ORIGINS (framing), not by this policy.
Whatever the shape, run report-only first — violations surface anything deployment-specific (integrations, customisations) before a policy blocks it.
Rate limiting¶
Off by default. Once any limit is set, the API rejects requests over it with 429 Too Many Requests. Counters are held in Redis, so the limit is shared across every core replica rather than applied per pod.
Requests are counted per signed-in user, falling back to the client IP for anonymous callers. Image and task-statistic requests are never counted.
| Key | Description | Default |
|---|---|---|
core.env.RATE_LIMIT_MAX_AUTH |
Optional: Requests per window allowed for a signed-in user. Unset = unlimited. | (unlimited) |
core.env.RATE_LIMIT_MAX_PUBLIC |
Optional: Requests per window allowed for an unauthenticated caller, counted per IP. Unset = unlimited. | (unlimited) |
core.env.RATE_LIMIT_WINDOW |
Optional: Length of the rolling window, as a duration string (30s, 10m, 1h). An unparseable value falls back to 15 minutes. |
15m |
core.env.RATE_LIMIT_MAX_BY_PREFIX |
Optional: JSON object overriding the limit for a named endpoint group. Currently inviteUser (sending user invitations), which is capped at 30 per window whenever rate limiting is on. |
{} |
core:
env:
RATE_LIMIT_WINDOW: 10m
RATE_LIMIT_MAX_AUTH: 2000
RATE_LIMIT_MAX_PUBLIC: 800
RATE_LIMIT_MAX_BY_PREFIX: '{"inviteUser": 10}'
Info
Start generously. A single board view issues many API calls, and an office behind one NAT address shares an IP for the public limit — a low RATE_LIMIT_MAX_PUBLIC can lock out a whole site before anyone signs in.
Session cookie¶
API requests authenticate with a bearer token. A cookie is used only where the browser cannot send an Authorization header — image and file requests — so it is issued with a narrow path (/file under the API context root) rather than for the whole site.
The defaults suit a deployment served over HTTPS, including when Boards is embedded in a portal on another domain. Change them only if you have a reason to.
| Key | Description | Default |
|---|---|---|
core.env.COOKIE_SECURE |
Optional: Set false to drop the Secure flag, allowing the cookie over plain HTTP. |
true |
core.env.COOKIE_SAMESITE |
Optional: SameSite value — none, lax or strict. none is required when Boards is embedded in a page on another domain. |
none |
core.env.COOKIE_HTTPONLY |
Optional: Set false to make the cookie readable by JavaScript. |
true |
core.env.COOKIE_PATH |
Optional: Overrides the cookie path. By default the cookie is scoped to the API's /file routes. |
[API_URI_PATH]/file |
SameSite=none requires Secure
Browsers reject a SameSite=none cookie that is not marked Secure. Setting COOKIE_SECURE: 'false' while leaving COOKIE_SAMESITE at none therefore breaks image and file loading. On a plain-HTTP deployment set both — COOKIE_SECURE: 'false' and COOKIE_SAMESITE: 'lax' — and accept that Boards can no longer be embedded cross-domain.