Files
zeekay 15a3c4c1dc a.hanzo.ai: harden loader key-guard + kill checkout price-forgery (hand red-review fixes)
RED (manual, agent hit the session limit): (1) secret-key guard /^(hk-|sk-)/ was
case-SENSITIVE and missed fw_ — HK-/SK-/fw_ secrets slipped past into the browser.
Now /^(hk-|sk-|fw_)/i across chat/analytics/checkout (keys already .trim()'d, so no
space bypass). (2) checkout sent a client data-price as unitPrice even for a slug'd
item → a forged data-price='0.01' could set the charge. Now unitPrice is sent ONLY
for a slug-LESS ad-hoc amount; any slug'd item is repriced server-side from the
catalog (forgery-proof). XSS verified PASS separately (ask.js renders model output
via textContent; the sole innerHTML is a static glyph).
2026-07-24 23:05:05 -07:00

56 lines
2.0 KiB
JavaScript

/*
* a.hanzo.ai/chat — <script src=".../chat" data-key="hz_..."></script>
* Loads the Ask-AI bubble (/ask.js) and mounts a floating, Shadow-DOM chat that
* streams from api.hanzo.ai/v1/chat/completions. data-key is an hz_ WIDGET key
* (client-safe, bills the owner org) — never a secret. Idempotent; never throws.
* Optional: data-model (default "enso"), data-title, data-greeting, data-accent,
* data-endpoint.
*/
(function () {
'use strict';
if (window.__hanzoChat) return;
var self = document.currentScript;
if (!self) return;
window.__hanzoChat = true;
var origin = new URL(self.src, location.href).origin;
var d = self.dataset || {};
var key = (d.key || '').trim();
if (!key) {
console.warn('[hanzo/chat] missing data-key — add data-key="hz_…" (a client-safe widget key). Not loaded.');
return;
}
if (/^(hk-|sk-|fw_)/i.test(key)) {
console.error('[hanzo/chat] data-key looks like a SECRET key — never expose hk-/sk-/fw_ keys in a browser. Use an hz_ widget key. Aborted.');
return;
}
if (key.indexOf('hz_') !== 0) {
console.warn('[hanzo/chat] data-key does not start with "hz_" — the chat bubble expects a widget key. Continuing; the server is the source of truth.');
}
var cfg = {
key: key,
origin: origin,
model: d.model || 'enso',
title: d.title || 'Ask AI',
greeting: d.greeting || '',
accent: d.accent || '#000',
endpoint: (d.endpoint || 'https://api.hanzo.ai/v1/chat/completions').trim()
};
var s = document.createElement('script');
s.async = true;
s.src = origin + '/ask.js';
s.onload = function () {
try {
if (window.HanzoAsk && window.HanzoAsk.mount) window.HanzoAsk.mount(cfg);
else console.warn('[hanzo/chat] Ask bubble loaded without HanzoAsk.mount.');
} catch (e) {
console.warn('[hanzo/chat] mount failed:', e && e.message);
}
};
s.onerror = function () { console.warn('[hanzo/chat] failed to load Ask bubble from ' + origin + '/ask.js'); };
(document.head || document.documentElement).appendChild(s);
})();