Repository navigation
Releases
v1.0.2
This release adds browser security headers to the ICE page. On securityheaders.com it scores A+ (previously F). The page looks and works the same as before.
What's new
- Content Security Policy. The browser only runs the scripts and styles that ICE itself puts on the page, using a one-time code that changes on every page load. Injected scripts and inline event handlers are blocked. The page can't be embedded in another site's frame, and the unlock form can only submit back to your own site.
- HTTPS enforcement (HSTS). Once a browser has visited your page over HTTPS, it will refuse to load it over plain HTTP for the next year. This is only sent when the page is served over HTTPS.
- Other headers:
X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy: no-referrer(links out of your profile don't tell the other site where the visitor came from), aPermissions-Policythat turns off camera, microphone and location, and theCross-Origin-Opener/Resource/Embedder-Policyheaders. - PHP version hidden. The
X-Powered-Byheader is no longer sent. - Stricter session cookie. Over HTTPS the cookie is named
__Host-ICESESSID, and it'sSameSite=Stricteverywhere.
Upgrading
Replace index.php with the new one. Your config.php, profile-data.php, sensitive-data.php and photo.png are not affected.
- Anyone currently unlocked will need to enter the unlock word again, because the session cookie has a new name.
- If you changed
index.phpyourself and added inline<script>,<style>,style="…"oronclick="…"code, the new policy will block it. Give each<script>and<style>tag the attributenonce="<?= $cspNonce ?>", and move inline styles and handlers into those tags.
Full changelog: v1.0.1...v1.0.2
v1.0.1
ICE v1.0.1
Added
- Distinguishing marks section — a new collapsible section for hair colour, eye colour, fillings/dental work, tattoos, piercings, and other marks (scars, birthmarks, etc.). Each row only shows if filled in.
- Gender / sex fields — optional
gender,sexAssignedAtBirth,birthSex, andnatalSexfields in the identity header. Disabled by default (commented out inprofile-data.php); uncomment and fill in whichever term you prefer. - Age display — DOB in the identity header now shows calculated age alongside the date, e.g.
DOB: 14 March 1990 (age 36). Falls back to just the date if it can't be parsed. - Update banner — once unlocked, the page checks GitHub for a newer release and shows a banner with a download link if one exists. Cached for 24 hours, times out after 3 seconds, and fails silently if offline — never runs on a locked page load, so an emergency viewer never triggers or waits on it. Add
?testUpdateBanner=1to the URL while unlocked to preview it without a real release.
Changed
- Clarified the README's "Why" section on how ICE contact info is accessed (URL or QR code) and that sensitive fields are locked behind a unique word.
Notes
- No changes to existing field names or behavior — upgrading from v1.0 requires no changes to
profile-data.php,sensitive-data.php, orconfig.php.
v1.0
ICE v1.0
A single-page, self-hosted "In Case of Emergency" medical info page — meant to be linked from a QR code on a wristband, phone case, or wallet card. First responders get critical medical info instantly, with no login; your address, phone numbers, and NHS/policy number stay hidden until unlocked.
Highlights
Open by default, locked where it matters — conditions, allergies, medications, blood type, implants, MRI flags, and resuscitation status render with no gate at all. Home address, phone numbers, and NHS/policy number are masked until the correct unlock word is submitted.
Server-side unlock gate — the check happens entirely in PHP (index.php) using hash_equals(); sensitive data is never loaded into memory, let alone sent to the browser, until the word is verified. Viewing source or the network tab while locked reveals nothing extra.
Post/Redirect/Get unlock flow — submitting the word redirects immediately, so a page refresh never resubmits it.
Client-side QR code generator — a footer button renders a QR code for the page's own URL entirely in-browser via a vendored copy of qrcode-generator (qrcode.lib.js); no third party ever sees the URL.
Printable wallet card — generates a true credit-card-sized (85.6 × 53.98mm) card with the page URL, QR code, and an optional, opt-in field to write in the unlock word before printing.
Content/config separated from the template — all personal data lives in three gitignored files (profile-data.php, sensitive-data.php, config.php) loaded by index.php, so index.php never needs editing and future template updates won't clobber your data. Each ships with a committed *.example.php counterpart.
No-cache, no-index by design — Cache-Control/Pragma/Expires headers stop the rendered (possibly unlocked) HTML from being cached anywhere, and X-Robots-Tag plus plus robots.txt keep the page out of search engines.
Light/dark theme with a pre-paint inline script (no flash of the wrong theme) and a mobile-first, safe-area-aware layout.
Zero dependencies — no database, no build step, no PHP extensions beyond built-in session support. One index.php plus a vendored JS file.
Requirements
PHP 7.4+ on any PHP-enabled web server, deployed over HTTPS.
Security notes
The unlock word has no rate limiting — it's a privacy screen against a casual glance, not a security boundary. Don't reuse a password you care about.
Printing the unlock word onto the wallet card is optional and off by default, since anyone holding that card (or a saved PDF of it) could then unlock full details.
Credits
QR generation uses qrcode-generator by Kazuhiko Arase (MIT), vendored client-side.
License
GPL-3.0