This document describes the security model for PAM, the known risks, and the mitigations in place.
PAM is a client-side web application. All encryption and decryption
happens in the browser using the Web Crypto API
(SubtleCrypto). No data is ever sent to a server. The
encrypted file is stored locally by the user.
The primary threat vectors are:
.pam file and attempts to brute-force the
master password offline.html-type field.PAM supports an html field type that allows rich content
(formatted notes, links, instructions). This is an intentional feature
designed for controlled internal deployments where a security team
authors PAM data and distributes it on a read-only volume to a defined
cohort.
Risk: If a user loads a PAM file from an untrusted
source, html fields could contain malicious scripts
(XSS).
Mitigation: - HTML field rendering is
disabled by default. Fields of type html
render as escaped plain text with a </> badge. - HTML
rendering can only be enabled in Preferences → Security → Allow
HTML Field Rendering, which is accessible only after unlocking
preferences with the prefs password. - When HTML rendering is enabled, a
persistent ⚠ HTML ON warning badge is shown in the
toolbar so users always know they are in a mode where HTML fields render
live.
When is it safe to enable? - The PAM file was authored by a trusted party (e.g. your own security team). - The file is distributed on a read-only volume and cannot be modified by end users. - You are not loading PAM files from untrusted URLs.
Residual risk: A PAM file distributed with HTML rendering already enabled in its prefs block carries that setting with it. The mitigation protects against foreign malicious files, not against the trusted file itself being compromised.
The master file password can be cached in browser storage to avoid re-entry on every save/load. The cache strategy is controlled in Preferences → Administration → filePass Cache Strategy.
Options: - session (default) — password
is cached in sessionStorage for the current browser tab
only and cleared when the tab is closed. - local — password
persists in localStorage across sessions and power cycles.
Use with caution on shared devices. A ⚠ PASS:
LOCAL warning badge appears in the toolbar while this is
active. - global — password is held in a JavaScript
variable only (lost on page reload). - none — password is
never cached; must be re-entered on every load/save.
Per-device strategy persistence:
The chosen strategy is stored per-device in localStorage
under the key pamCacheStrategy. On startup, PAM reads this
key and applies it before loading any file, solving the chicken-and-egg
problem where the PAM file (which contains preferences) cannot be opened
without first knowing the cache strategy.
This means the user’s strategy choice persists across page reloads, browser restarts, and power cycles, independently of the PAM file. It can be changed at any time in Preferences and takes effect immediately. When the strategy is changed, the password is cleared from the previous storage location before the new strategy is applied.
History: - v2.0.3 — default changed from
local to session as a security improvement
(SEC-002). - v2.0.5 — default reverted to local due to PWA
reload behaviour causing excessive re-entry friction;
⚠ PASS: LOCAL badge added as compensating control. - v2.0.7
— default changed back to session; per-device
pamCacheStrategy persistence introduced so the user’s
choice survives reloads without being tied to the PAM file default. -
v1.3.1 — BUG-002 fixed: password was stored in the wrong storage bucket
when the loaded file’s filePassCache pref differed from the
startup default; manifested as lost password on iOS Safari PWA
relaunch.
PWA note: When PAM is installed as a Progressive Web
App on iOS Safari, each launch may start a fresh session, wiping
sessionStorage. If your PAM file specifies
filePassCache: local, PAM now re-stores the password in
localStorage after loading the file and persists that
strategy as pamCacheStrategy so subsequent launches find it
there. (BUG-002)
Recommendation: Use session (the
default) for most environments. Use local only on a trusted
personal device where re-entry friction is a genuine concern.
PAM v1.x uses AES-256-CBC with PBKDF2-SHA-256 key derivation. The v1 implementation has two known weaknesses:
TextEncoder.encode() which calls
.toString() first, producing an ASCII string like
"0,34,211,..." instead of raw bytes, dramatically reducing
salt entropy.Both bugs define the key derivation for all existing v1 files. Fixing them requires a new file format (v2).
Migration: v2 format support is planned for PAM
v1.3. The migration is non-breaking — v1 files can always be decrypted;
users choose when to re-save in v2 format. See MIGRATION.md
(published with v2.0).
The preferences lock password (lockPreferencesPassword)
is stored as plaintext in the PAM file’s prefs
block.
This is intentional. The prefs lock is a convenience feature whose sole purpose is to prevent casual or accidental modification of preferences in a shared deployment. It is not a cryptographic boundary.
The entire PAM file — including the prefs block and
lockPreferencesPassword — is encrypted with the master
password (AES-256-CBC). An attacker who has the master password can read
everything. Hashing lockPreferencesPassword would add no
security benefit in this threat model, and would silently break existing
files where the value was stored as plaintext.
Threat model: The prefs lock protects against an authorised user (who has the master password) accidentally changing deployment-managed settings. It does not protect against a determined attacker.
The Load → URL feature only accepts
https:// and http:// URLs.
javascript:, data:, ftp:, and
other URI schemes are rejected to prevent protocol-based injection
attacks.
PAM sets a Content-Security-Policy meta tag in
index.html:
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net;
style-src 'self';
img-src 'self' data:;
font-src 'self'
This prevents loading of scripts, styles, and resources from
untrusted origins. The cdn.jsdelivr.net exception covers
the DOMPurify library used for HTML sanitization.
This is an open-source personal password manager. If you find a security issue please open a GitHub issue at jlinoff/pam.