Security: ZekStack/curier

Security

docs/security.md

Security

Secret handling

The VAPID private key authorizes sends for the application server identity. Provision it as a secret, avoid logging it, and do not commit production values.

Curier validates the public/private relationship during initialization. Temporary private-key, ECDH, HKDF, AES, nonce, and signature buffers are explicitly zeroed before release. Standard-library capacity and allocator behavior can still leave copies outside Curier's direct control, so the ESP32 heap and crash dumps remain part of the device trust boundary.

The deterministic encryption test accepts explicit ephemeral inputs only through an internal API. Production sends still generate a fresh sender key and salt from the Mbed TLS DRBG.

Subscription validation

Subscription endpoints and keys arrive from an external browser boundary. Curier requires:

  • an HTTPS endpoint within maxEndpointBytes;
  • no URL user information;
  • a valid DNS, IPv4, or bracketed IPv6 host and optional port;
  • no fragments, backslashes, malformed brackets, or ambiguous port separators;
  • a 65-byte uncompressed P-256 p256dh key;
  • a 16-byte auth secret;
  • valid base64url input.

The endpoint origin used for VAPID is built only after this validation. This prevents the HTTP client and VAPID signer from interpreting malformed authorities differently.

This validation does not establish whether the endpoint is still subscribed. HTTP 404 or 410 should cause the application to retire the stored subscription.

Cryptographic qualification

The host suite injects the published RFC 8291 Appendix A sender private key and salt into Curier's production encryption path and checks the complete encrypted body byte-for-byte. VAPID tests decode the generated claims and independently verify the raw ES256 signature with the configured public key.

These tests protect the wire format and derivation logic, but they do not replace Mbed TLS maintenance, secure provisioning, or production-device validation.

TLS

Certificate and host-name verification are enabled by default when the ESP-IDF certificate bundle is available. A custom CA or a prepared global CA store can be selected instead.

Avoid skipTlsCommonNameCheck in production. It allows a valid certificate for the wrong host to authenticate the connection.

Clock

VAPID depends on trustworthy wall-clock time. The custom provider is a trust boundary: Curier accepts its Unix timestamp and signs JWTs from it. A clock rollback does not reuse a JWT after provider replacement because that operation clears the cache.

Logging

Curier result messages do not include endpoints, subscription keys, payload contents, VAPID tokens, or private keys. Applications should preserve that boundary in their own logs.

There aren't any published security advisories

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Security: ZekStack/curier

Security

docs/security.md

Security

Secret handling

The VAPID private key authorizes sends for the application server identity. Provision it as a secret, avoid logging it, and do not commit production values.

Curier validates the public/private relationship during initialization. Temporary private-key, ECDH, HKDF, AES, nonce, and signature buffers are explicitly zeroed before release. Standard-library capacity and allocator behavior can still leave copies outside Curier's direct control, so the ESP32 heap and crash dumps remain part of the device trust boundary.

The deterministic encryption test accepts explicit ephemeral inputs only through an internal API. Production sends still generate a fresh sender key and salt from the Mbed TLS DRBG.

Subscription validation

Subscription endpoints and keys arrive from an external browser boundary. Curier requires:

  • an HTTPS endpoint within maxEndpointBytes;
  • no URL user information;
  • a valid DNS, IPv4, or bracketed IPv6 host and optional port;
  • no fragments, backslashes, malformed brackets, or ambiguous port separators;
  • a 65-byte uncompressed P-256 p256dh key;
  • a 16-byte auth secret;
  • valid base64url input.

The endpoint origin used for VAPID is built only after this validation. This prevents the HTTP client and VAPID signer from interpreting malformed authorities differently.

This validation does not establish whether the endpoint is still subscribed. HTTP 404 or 410 should cause the application to retire the stored subscription.

Cryptographic qualification

The host suite injects the published RFC 8291 Appendix A sender private key and salt into Curier's production encryption path and checks the complete encrypted body byte-for-byte. VAPID tests decode the generated claims and independently verify the raw ES256 signature with the configured public key.

These tests protect the wire format and derivation logic, but they do not replace Mbed TLS maintenance, secure provisioning, or production-device validation.

TLS

Certificate and host-name verification are enabled by default when the ESP-IDF certificate bundle is available. A custom CA or a prepared global CA store can be selected instead.

Avoid skipTlsCommonNameCheck in production. It allows a valid certificate for the wrong host to authenticate the connection.

Clock

VAPID depends on trustworthy wall-clock time. The custom provider is a trust boundary: Curier accepts its Unix timestamp and signs JWTs from it. A clock rollback does not reuse a JWT after provider replacement because that operation clears the cache.

Logging

Curier result messages do not include endpoints, subscription keys, payload contents, VAPID tokens, or private keys. Applications should preserve that boundary in their own logs.

There aren't any published security advisories

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Security: ZekStack/curier

Security

docs/security.md

Security

Secret handling

The VAPID private key authorizes sends for the application server identity. Provision it as a secret, avoid logging it, and do not commit production values.

Curier validates the public/private relationship during initialization. Temporary private-key, ECDH, HKDF, AES, nonce, and signature buffers are explicitly zeroed before release. Standard-library capacity and allocator behavior can still leave copies outside Curier's direct control, so the ESP32 heap and crash dumps remain part of the device trust boundary.

The deterministic encryption test accepts explicit ephemeral inputs only through an internal API. Production sends still generate a fresh sender key and salt from the Mbed TLS DRBG.

Subscription validation

Subscription endpoints and keys arrive from an external browser boundary. Curier requires:

  • an HTTPS endpoint within maxEndpointBytes;
  • no URL user information;
  • a valid DNS, IPv4, or bracketed IPv6 host and optional port;
  • no fragments, backslashes, malformed brackets, or ambiguous port separators;
  • a 65-byte uncompressed P-256 p256dh key;
  • a 16-byte auth secret;
  • valid base64url input.

The endpoint origin used for VAPID is built only after this validation. This prevents the HTTP client and VAPID signer from interpreting malformed authorities differently.

This validation does not establish whether the endpoint is still subscribed. HTTP 404 or 410 should cause the application to retire the stored subscription.

Cryptographic qualification

The host suite injects the published RFC 8291 Appendix A sender private key and salt into Curier's production encryption path and checks the complete encrypted body byte-for-byte. VAPID tests decode the generated claims and independently verify the raw ES256 signature with the configured public key.

These tests protect the wire format and derivation logic, but they do not replace Mbed TLS maintenance, secure provisioning, or production-device validation.

TLS

Certificate and host-name verification are enabled by default when the ESP-IDF certificate bundle is available. A custom CA or a prepared global CA store can be selected instead.

Avoid skipTlsCommonNameCheck in production. It allows a valid certificate for the wrong host to authenticate the connection.

Clock

VAPID depends on trustworthy wall-clock time. The custom provider is a trust boundary: Curier accepts its Unix timestamp and signs JWTs from it. A clock rollback does not reuse a JWT after provider replacement because that operation clears the cache.

Logging

Curier result messages do not include endpoints, subscription keys, payload contents, VAPID tokens, or private keys. Applications should preserve that boundary in their own logs.

There aren't any published security advisories

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Security: ZekStack/curier

Security

docs/security.md

Security

Secret handling

The VAPID private key authorizes sends for the application server identity. Provision it as a secret, avoid logging it, and do not commit production values.

Curier validates the public/private relationship during initialization. Temporary private-key, ECDH, HKDF, AES, nonce, and signature buffers are explicitly zeroed before release. Standard-library capacity and allocator behavior can still leave copies outside Curier's direct control, so the ESP32 heap and crash dumps remain part of the device trust boundary.

The deterministic encryption test accepts explicit ephemeral inputs only through an internal API. Production sends still generate a fresh sender key and salt from the Mbed TLS DRBG.

Subscription validation

Subscription endpoints and keys arrive from an external browser boundary. Curier requires:

  • an HTTPS endpoint within maxEndpointBytes;
  • no URL user information;
  • a valid DNS, IPv4, or bracketed IPv6 host and optional port;
  • no fragments, backslashes, malformed brackets, or ambiguous port separators;
  • a 65-byte uncompressed P-256 p256dh key;
  • a 16-byte auth secret;
  • valid base64url input.

The endpoint origin used for VAPID is built only after this validation. This prevents the HTTP client and VAPID signer from interpreting malformed authorities differently.

This validation does not establish whether the endpoint is still subscribed. HTTP 404 or 410 should cause the application to retire the stored subscription.

Cryptographic qualification

The host suite injects the published RFC 8291 Appendix A sender private key and salt into Curier's production encryption path and checks the complete encrypted body byte-for-byte. VAPID tests decode the generated claims and independently verify the raw ES256 signature with the configured public key.

These tests protect the wire format and derivation logic, but they do not replace Mbed TLS maintenance, secure provisioning, or production-device validation.

TLS

Certificate and host-name verification are enabled by default when the ESP-IDF certificate bundle is available. A custom CA or a prepared global CA store can be selected instead.

Avoid skipTlsCommonNameCheck in production. It allows a valid certificate for the wrong host to authenticate the connection.

Clock

VAPID depends on trustworthy wall-clock time. The custom provider is a trust boundary: Curier accepts its Unix timestamp and signs JWTs from it. A clock rollback does not reuse a JWT after provider replacement because that operation clears the cache.

Logging

Curier result messages do not include endpoints, subscription keys, payload contents, VAPID tokens, or private keys. Applications should preserve that boundary in their own logs.

There aren't any published security advisories

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Security: ZekStack/curier

Security

docs/security.md

Security

Secret handling

The VAPID private key authorizes sends for the application server identity. Provision it as a secret, avoid logging it, and do not commit production values.

Curier validates the public/private relationship during initialization. Temporary private-key, ECDH, HKDF, AES, nonce, and signature buffers are explicitly zeroed before release. Standard-library capacity and allocator behavior can still leave copies outside Curier's direct control, so the ESP32 heap and crash dumps remain part of the device trust boundary.

The deterministic encryption test accepts explicit ephemeral inputs only through an internal API. Production sends still generate a fresh sender key and salt from the Mbed TLS DRBG.

Subscription validation

Subscription endpoints and keys arrive from an external browser boundary. Curier requires:

  • an HTTPS endpoint within maxEndpointBytes;
  • no URL user information;
  • a valid DNS, IPv4, or bracketed IPv6 host and optional port;
  • no fragments, backslashes, malformed brackets, or ambiguous port separators;
  • a 65-byte uncompressed P-256 p256dh key;
  • a 16-byte auth secret;
  • valid base64url input.

The endpoint origin used for VAPID is built only after this validation. This prevents the HTTP client and VAPID signer from interpreting malformed authorities differently.

This validation does not establish whether the endpoint is still subscribed. HTTP 404 or 410 should cause the application to retire the stored subscription.

Cryptographic qualification

The host suite injects the published RFC 8291 Appendix A sender private key and salt into Curier's production encryption path and checks the complete encrypted body byte-for-byte. VAPID tests decode the generated claims and independently verify the raw ES256 signature with the configured public key.

These tests protect the wire format and derivation logic, but they do not replace Mbed TLS maintenance, secure provisioning, or production-device validation.

TLS

Certificate and host-name verification are enabled by default when the ESP-IDF certificate bundle is available. A custom CA or a prepared global CA store can be selected instead.

Avoid skipTlsCommonNameCheck in production. It allows a valid certificate for the wrong host to authenticate the connection.

Clock

VAPID depends on trustworthy wall-clock time. The custom provider is a trust boundary: Curier accepts its Unix timestamp and signs JWTs from it. A clock rollback does not reuse a JWT after provider replacement because that operation clears the cache.

Logging

Curier result messages do not include endpoints, subscription keys, payload contents, VAPID tokens, or private keys. Applications should preserve that boundary in their own logs.

There aren't any published security advisories

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Security: ZekStack/curier

Security

docs/security.md

Security

Secret handling

The VAPID private key authorizes sends for the application server identity. Provision it as a secret, avoid logging it, and do not commit production values.

Curier validates the public/private relationship during initialization. Temporary private-key, ECDH, HKDF, AES, nonce, and signature buffers are explicitly zeroed before release. Standard-library capacity and allocator behavior can still leave copies outside Curier's direct control, so the ESP32 heap and crash dumps remain part of the device trust boundary.

The deterministic encryption test accepts explicit ephemeral inputs only through an internal API. Production sends still generate a fresh sender key and salt from the Mbed TLS DRBG.

Subscription validation

Subscription endpoints and keys arrive from an external browser boundary. Curier requires:

  • an HTTPS endpoint within maxEndpointBytes;
  • no URL user information;
  • a valid DNS, IPv4, or bracketed IPv6 host and optional port;
  • no fragments, backslashes, malformed brackets, or ambiguous port separators;
  • a 65-byte uncompressed P-256 p256dh key;
  • a 16-byte auth secret;
  • valid base64url input.

The endpoint origin used for VAPID is built only after this validation. This prevents the HTTP client and VAPID signer from interpreting malformed authorities differently.

This validation does not establish whether the endpoint is still subscribed. HTTP 404 or 410 should cause the application to retire the stored subscription.

Cryptographic qualification

The host suite injects the published RFC 8291 Appendix A sender private key and salt into Curier's production encryption path and checks the complete encrypted body byte-for-byte. VAPID tests decode the generated claims and independently verify the raw ES256 signature with the configured public key.

These tests protect the wire format and derivation logic, but they do not replace Mbed TLS maintenance, secure provisioning, or production-device validation.

TLS

Certificate and host-name verification are enabled by default when the ESP-IDF certificate bundle is available. A custom CA or a prepared global CA store can be selected instead.

Avoid skipTlsCommonNameCheck in production. It allows a valid certificate for the wrong host to authenticate the connection.

Clock

VAPID depends on trustworthy wall-clock time. The custom provider is a trust boundary: Curier accepts its Unix timestamp and signs JWTs from it. A clock rollback does not reuse a JWT after provider replacement because that operation clears the cache.

Logging

Curier result messages do not include endpoints, subscription keys, payload contents, VAPID tokens, or private keys. Applications should preserve that boundary in their own logs.

There aren't any published security advisories

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Security: ZekStack/curier

Security

docs/security.md

Security

Secret handling

The VAPID private key authorizes sends for the application server identity. Provision it as a secret, avoid logging it, and do not commit production values.

Curier validates the public/private relationship during initialization. Temporary private-key, ECDH, HKDF, AES, nonce, and signature buffers are explicitly zeroed before release. Standard-library capacity and allocator behavior can still leave copies outside Curier's direct control, so the ESP32 heap and crash dumps remain part of the device trust boundary.

The deterministic encryption test accepts explicit ephemeral inputs only through an internal API. Production sends still generate a fresh sender key and salt from the Mbed TLS DRBG.

Subscription validation

Subscription endpoints and keys arrive from an external browser boundary. Curier requires:

  • an HTTPS endpoint within maxEndpointBytes;
  • no URL user information;
  • a valid DNS, IPv4, or bracketed IPv6 host and optional port;
  • no fragments, backslashes, malformed brackets, or ambiguous port separators;
  • a 65-byte uncompressed P-256 p256dh key;
  • a 16-byte auth secret;
  • valid base64url input.

The endpoint origin used for VAPID is built only after this validation. This prevents the HTTP client and VAPID signer from interpreting malformed authorities differently.

This validation does not establish whether the endpoint is still subscribed. HTTP 404 or 410 should cause the application to retire the stored subscription.

Cryptographic qualification

The host suite injects the published RFC 8291 Appendix A sender private key and salt into Curier's production encryption path and checks the complete encrypted body byte-for-byte. VAPID tests decode the generated claims and independently verify the raw ES256 signature with the configured public key.

These tests protect the wire format and derivation logic, but they do not replace Mbed TLS maintenance, secure provisioning, or production-device validation.

TLS

Certificate and host-name verification are enabled by default when the ESP-IDF certificate bundle is available. A custom CA or a prepared global CA store can be selected instead.

Avoid skipTlsCommonNameCheck in production. It allows a valid certificate for the wrong host to authenticate the connection.

Clock

VAPID depends on trustworthy wall-clock time. The custom provider is a trust boundary: Curier accepts its Unix timestamp and signs JWTs from it. A clock rollback does not reuse a JWT after provider replacement because that operation clears the cache.

Logging

Curier result messages do not include endpoints, subscription keys, payload contents, VAPID tokens, or private keys. Applications should preserve that boundary in their own logs.

There aren't any published security advisories

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Security: ZekStack/curier

Security

docs/security.md

Security

Secret handling

The VAPID private key authorizes sends for the application server identity. Provision it as a secret, avoid logging it, and do not commit production values.

Curier validates the public/private relationship during initialization. Temporary private-key, ECDH, HKDF, AES, nonce, and signature buffers are explicitly zeroed before release. Standard-library capacity and allocator behavior can still leave copies outside Curier's direct control, so the ESP32 heap and crash dumps remain part of the device trust boundary.

The deterministic encryption test accepts explicit ephemeral inputs only through an internal API. Production sends still generate a fresh sender key and salt from the Mbed TLS DRBG.

Subscription validation

Subscription endpoints and keys arrive from an external browser boundary. Curier requires:

  • an HTTPS endpoint within maxEndpointBytes;
  • no URL user information;
  • a valid DNS, IPv4, or bracketed IPv6 host and optional port;
  • no fragments, backslashes, malformed brackets, or ambiguous port separators;
  • a 65-byte uncompressed P-256 p256dh key;
  • a 16-byte auth secret;
  • valid base64url input.

The endpoint origin used for VAPID is built only after this validation. This prevents the HTTP client and VAPID signer from interpreting malformed authorities differently.

This validation does not establish whether the endpoint is still subscribed. HTTP 404 or 410 should cause the application to retire the stored subscription.

Cryptographic qualification

The host suite injects the published RFC 8291 Appendix A sender private key and salt into Curier's production encryption path and checks the complete encrypted body byte-for-byte. VAPID tests decode the generated claims and independently verify the raw ES256 signature with the configured public key.

These tests protect the wire format and derivation logic, but they do not replace Mbed TLS maintenance, secure provisioning, or production-device validation.

TLS

Certificate and host-name verification are enabled by default when the ESP-IDF certificate bundle is available. A custom CA or a prepared global CA store can be selected instead.

Avoid skipTlsCommonNameCheck in production. It allows a valid certificate for the wrong host to authenticate the connection.

Clock

VAPID depends on trustworthy wall-clock time. The custom provider is a trust boundary: Curier accepts its Unix timestamp and signs JWTs from it. A clock rollback does not reuse a JWT after provider replacement because that operation clears the cache.

Logging

Curier result messages do not include endpoints, subscription keys, payload contents, VAPID tokens, or private keys. Applications should preserve that boundary in their own logs.

There aren't any published security advisories