-
Notifications
You must be signed in to change notification settings - Fork 1
Security and Privacy
The PIMActivation Portal is built around two non-negotiables:
- No backend. Every privileged call goes from your browser directly to Microsoft Graph or Azure Resource Manager.
-
Tokens never leave your browser, and they don't survive the tab. MSAL.js caches them in
sessionStorageonly.
This page lays out what that means in practice, what the threat model is, and what mitigations are in place.
┌──────────────────────────────┐
│ Your browser │
│ │
│ Portal SPA ──────────────► login.microsoftonline.com (auth)
│ ──────────────► graph.microsoft.com (Entra + Group PIM)
│ ──────────────► management.azure.com (Azure Resource PIM)
└──────────────────────────────┘
There is no other arrow. No telemetry, no analytics, no third-party APIs. The Content Security Policy enforces that.
Portal/staticwebapp.config.json sets:
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net;
connect-src 'self' https://login.microsoftonline.com
https://graph.microsoft.com
https://management.azure.com;
img-src 'self' data:;
style-src 'self' 'unsafe-inline';
frame-ancestors 'none';
upgrade-insecure-requests
What this gives you:
-
No inline scripts and no
eval. Any<script>must come from the same origin (or, for MSAL.js, fromcdn.jsdelivr.net). - No unauthorized network destinations. A bug or compromise that tries to exfiltrate data to an attacker-controlled host is blocked by the browser at the CSP layer.
-
No clickjacking.
frame-ancestors 'none'andX-Frame-Options: DENYprevent the portal from being embedded. - HTTPS upgrade. Mixed-content requests are upgraded.
style-src 'unsafe-inline' is required for the theme-switching mechanism (CSS custom properties applied at runtime). It is mitigated by the absence of any inline <script> and by the strict script-src.
| Header | Value | Why |
|---|---|---|
Strict-Transport-Security |
max-age=31536000; includeSubDomains; preload |
Forces HTTPS for the entire eTLD+1 |
X-Content-Type-Options |
nosniff |
Stops MIME sniffing |
X-Frame-Options |
DENY |
Belt-and-braces clickjacking defense |
X-XSS-Protection |
1; mode=block |
Legacy XSS auditor for older browsers |
Referrer-Policy |
strict-origin-when-cross-origin |
Limits Referer leakage |
Permissions-Policy |
camera=(), microphone=(), geolocation=(), payment=() |
Disables sensors the portal never needs |
-
MSAL.js v5 (loaded from the only allowed third-party origin,
cdn.jsdelivr.net). - Authorization code flow with PKCE. No implicit grant.
- Redirect flow for sign-in and sign-out (not popup) — survives strict popup blockers and works on mobile.
-
Token cache:
sessionStorageonly. Tokens disappear when the tab closes. There is no refresh-token persistence.
When a Conditional Access policy issues a claims challenge, the portal:
- Decodes the challenge from a
WWW-Authenticate: insufficient_claimsheader (or a JSON-encoded body). - Persists the in-flight operation state to
sessionStorage. - Calls
acquireTokenRedirectwith the decodedclaimsparameter. - Resumes the operation after sign-in returns.
-
Threads the same
claimsinto every subsequent token acquisition for the rest of the operation, so all Graph and ARM calls satisfy the requirement without prompting again.
For role policies that require an auth context proactively, the same flow runs before submit instead of in response to a 401.
| Store | What | When it's gone |
|---|---|---|
sessionStorage |
MSAL access tokens, MSAL ID tokens, in-flight operation state during step-up, preferred tenant for the session | Tab closes |
localStorage |
Feature flags, theme, role cache (per tenant), saved filter pills | Manual clear |
| IndexedDB | Activation profiles | Manual clear |
| In-memory | Policy cache (30 min), batch state, role render state | Page reloads |
| Cookies | None set by the portal itself | n/a |
| Telemetry | None | n/a |
| Threat | Mitigation |
|---|---|
| Server breach exfiltrating tokens | No server. Tokens never reach a server we run. |
| XSS in the portal stealing tokens | Strict CSP forbids inline / unsafe scripts and limits script origins to self and a single CDN. No eval. Tokens live in sessionStorage and are scoped to the tab. |
| Network exfiltration | CSP connect-src allow-list blocks any network destination other than Microsoft auth, Graph, and ARM. |
| Clickjacking |
frame-ancestors 'none' and X-Frame-Options: DENY. |
| Subdomain takeover of the CDN | MSAL.js is loaded over HTTPS from cdn.jsdelivr.net. SRI pinning is on the roadmap. If that origin were compromised, the CSP still prevents it from talking to anywhere except Microsoft endpoints. |
| Stolen browser session | An attacker with full access to your browser session is already inside your trust boundary. Use OS-level protections (lock screen, FIDO2) and browser profile separation. |
| Compromised app registration (self-hosted) | Use single-tenant registrations for self-hosted deployments and review the redirect URIs and consented scopes regularly. The portal does not require any client secret. |
For sensitive security reports, use GitHub's private security advisory flow rather than a public issue. Public issues are fine for non-sensitive hardening suggestions and CSP / header improvements.
The portal stores no personal data outside your browser. It does not send any data to the maintainer or any third party. There is no telemetry, no analytics, and no logging endpoint. The only network destinations the portal contacts are:
-
login.microsoftonline.com(Microsoft authentication) -
graph.microsoft.com(Microsoft Graph) -
management.azure.com(Azure Resource Manager) -
cdn.jsdelivr.net(MSAL.js bundle, no API surface, no observation)