Files
zooqueenandHanzo Dev f717dd89ad keys: the key a user pastes is sk-, not the retired hk-
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]>
2026-08-13 18:07:16 -07:00

92 lines
2.9 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
loadConfig,
normalizePrivateKey,
HANZO_API_BASE_URL,
DEFAULT_MODEL,
DEFAULT_MAX_DIFF_CHARS,
GITHUB_APP_PERMISSIONS,
GITHUB_APP_EVENTS,
} from '../src/config.js';
const base = {
GITHUB_APP_ID: '12345',
GITHUB_PRIVATE_KEY: '-----BEGIN RSA PRIVATE KEY-----\nabc\n-----END RSA PRIVATE KEY-----',
GITHUB_WEBHOOK_SECRET: 'whsec',
};
describe('loadConfig', () => {
it('loads required GitHub credentials and applies Hanzo defaults', () => {
const c = loadConfig({ ...base } as NodeJS.ProcessEnv);
expect(c.github.appId).toBe('12345');
expect(c.github.webhookSecret).toBe('whsec');
expect(c.hanzo.baseURL).toBe(HANZO_API_BASE_URL);
expect(c.hanzo.apiKey).toBe('');
expect(c.model).toBe(DEFAULT_MODEL);
expect(c.maxDiffChars).toBe(DEFAULT_MAX_DIFF_CHARS);
expect(c.port).toBe(3000);
});
it.each(['GITHUB_APP_ID', 'GITHUB_PRIVATE_KEY', 'GITHUB_WEBHOOK_SECRET'])(
'throws when %s is missing',
(key) => {
const env = { ...base } as Record<string, string>;
delete env[key];
expect(() => loadConfig(env as NodeJS.ProcessEnv)).toThrow(new RegExp(key));
},
);
it('reads overrides for model, base url, api key, and diff budget', () => {
const c = loadConfig({
...base,
HANZO_API_BASE_URL: 'https://gw.example.com',
HANZO_API_KEY: 'sk-abc',
HANZO_MODEL: 'zen5-coder',
HANZO_MAX_DIFF_CHARS: '500',
PORT: '8080',
} as NodeJS.ProcessEnv);
expect(c.hanzo.baseURL).toBe('https://gw.example.com');
expect(c.hanzo.apiKey).toBe('sk-abc');
expect(c.model).toBe('zen5-coder');
expect(c.maxDiffChars).toBe(500);
expect(c.port).toBe(8080);
});
it('rejects an invalid PORT', () => {
expect(() => loadConfig({ ...base, PORT: 'nope' } as NodeJS.ProcessEnv)).toThrow(/PORT/);
expect(() => loadConfig({ ...base, PORT: '0' } as NodeJS.ProcessEnv)).toThrow(/PORT/);
});
it('rejects an invalid HANZO_MAX_DIFF_CHARS', () => {
expect(() =>
loadConfig({ ...base, HANZO_MAX_DIFF_CHARS: '-1' } as NodeJS.ProcessEnv),
).toThrow(/MAX_DIFF_CHARS/);
});
});
describe('normalizePrivateKey', () => {
it('restores escaped newlines from a one-line env var', () => {
expect(normalizePrivateKey('a\\nb\\nc')).toBe('a\nb\nc');
});
it('leaves a real multi-line PEM untouched', () => {
const pem = '-----BEGIN-----\nkey\n-----END-----';
expect(normalizePrivateKey(pem)).toBe(pem);
});
});
describe('exported app manifest', () => {
it('declares the exact permissions the README documents', () => {
expect(GITHUB_APP_PERMISSIONS).toEqual({
pull_requests: 'write',
issues: 'write',
contents: 'read',
metadata: 'read',
});
});
it('subscribes to pull_request, issues, and issue_comment', () => {
expect(GITHUB_APP_EVENTS).toEqual(['pull_request', 'issues', 'issue_comment']);
});
});