mirror of
https://github.com/hanzoai/extension.git
synced 2026-10-31 21:57:06 +00:00
28 apps told the user to paste an `hk-` key — input placeholders, settings help text, READMEs, the error thrown when the field is empty — and five of them validated that paste against /^hk-/ before sending it as a bearer. Nothing mints that prefix any more: cloud accepts pk- and sk-, IAM mints sk-. So the guard admitted a credential that authenticates nothing, and the panel failed later at the gateway with nothing on screen to explain it. The three leak detectors keep hk- and gain sk-. Salesforce's source scan and the two Zendesk tests exist to catch a key committed to source or echoed back through a request; retired keys are still in circulation and leak exactly as badly, while a detector that only knew hk- would have gone blind to every key minted from here on. `sk-` used to be the example of the foreign key the guard screened out — an OpenAI key. It cannot mean that now, and shape alone cannot tell one vendor's sk- from another's, so those comments say what the check really proves and point at the /v1/models call that proves the rest. The three tests that pinned "sk- is rejected" now pin "hk- is rejected", which is the invariant that survived. Verified: 2029 tests pass across the 28 packages; typecheck clean on 25 of them (hubspot, jupyter and teams fail identically at HEAD on JSX and ESM type conflicts in files this does not touch); salesforce validate-source green. Co-authored-by: Hanzo Dev <[email protected]>
50 lines
1.7 KiB
TypeScript
50 lines
1.7 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
|
|
// A minimal localStorage stub so the browser-only auth module is testable in
|
|
// node. Installed before importing the module under test.
|
|
class MemStorage {
|
|
private m = new Map<string, string>();
|
|
getItem(k: string) {
|
|
return this.m.has(k) ? this.m.get(k)! : null;
|
|
}
|
|
setItem(k: string, v: string) {
|
|
this.m.set(k, v);
|
|
}
|
|
removeItem(k: string) {
|
|
this.m.delete(k);
|
|
}
|
|
}
|
|
|
|
beforeEach(() => {
|
|
(globalThis as { localStorage?: unknown }).localStorage = new MemStorage();
|
|
vi.resetModules();
|
|
});
|
|
|
|
describe('auth: pasted Hanzo key (NOT the FHIR/PHI token)', () => {
|
|
it('stores, reads, and clears the key', async () => {
|
|
const { getApiKey, setApiKey, hasApiKey, bearer } = await import('../src/auth.js');
|
|
expect(getApiKey()).toBe('');
|
|
expect(hasApiKey()).toBe(false);
|
|
setApiKey(' sk-abc ');
|
|
expect(getApiKey()).toBe('sk-abc'); // trimmed
|
|
expect(hasApiKey()).toBe(true);
|
|
expect(bearer()).toBe('sk-abc');
|
|
setApiKey('');
|
|
expect(getApiKey()).toBe('');
|
|
expect(hasApiKey()).toBe(false);
|
|
});
|
|
|
|
it('validateKey rejects an empty key before any network call', async () => {
|
|
const { validateKey } = await import('../src/auth.js');
|
|
await expect(validateKey(' ')).rejects.toThrow(/Hanzo API key/);
|
|
});
|
|
|
|
it('does not touch the FHIR/PHI storage — only the Hanzo gateway key', async () => {
|
|
const { setApiKey } = await import('../src/auth.js');
|
|
const { APIKEY_STORAGE_KEY } = await import('../src/config.js');
|
|
setApiKey('sk-x');
|
|
// The one and only key it writes is the Hanzo gateway key.
|
|
expect(localStorage.getItem(APIKEY_STORAGE_KEY)).toBe('sk-x');
|
|
});
|
|
});
|