offrouter-core 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit.d.ts +137 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +320 -0
- package/dist/audit.js.map +1 -0
- package/dist/auth/credential-chain.d.ts +84 -0
- package/dist/auth/credential-chain.d.ts.map +1 -0
- package/dist/auth/credential-chain.js +150 -0
- package/dist/auth/credential-chain.js.map +1 -0
- package/dist/auth/index.d.ts +10 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +7 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/keychain.d.ts +60 -0
- package/dist/auth/keychain.d.ts.map +1 -0
- package/dist/auth/keychain.js +104 -0
- package/dist/auth/keychain.js.map +1 -0
- package/dist/auth/oauth-pkce.d.ts +138 -0
- package/dist/auth/oauth-pkce.d.ts.map +1 -0
- package/dist/auth/oauth-pkce.js +375 -0
- package/dist/auth/oauth-pkce.js.map +1 -0
- package/dist/auth/oauth-server.d.ts +36 -0
- package/dist/auth/oauth-server.d.ts.map +1 -0
- package/dist/auth/oauth-server.js +210 -0
- package/dist/auth/oauth-server.js.map +1 -0
- package/dist/config.d.ts +76 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +355 -0
- package/dist/config.js.map +1 -0
- package/dist/delegation.d.ts +123 -0
- package/dist/delegation.d.ts.map +1 -0
- package/dist/delegation.js +455 -0
- package/dist/delegation.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/policy.d.ts +45 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +318 -0
- package/dist/policy.js.map +1 -0
- package/{src/protocol.ts → dist/protocol.d.ts} +2 -2
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +6 -0
- package/dist/protocol.js.map +1 -0
- package/dist/providers/adapter.d.ts +137 -0
- package/dist/providers/adapter.d.ts.map +1 -0
- package/dist/providers/adapter.js +163 -0
- package/dist/providers/adapter.js.map +1 -0
- package/dist/providers/catalog-data.d.ts +128 -0
- package/dist/providers/catalog-data.d.ts.map +1 -0
- package/dist/providers/catalog-data.js +397 -0
- package/dist/providers/catalog-data.js.map +1 -0
- package/dist/providers/catalog.d.ts +63 -0
- package/dist/providers/catalog.d.ts.map +1 -0
- package/dist/providers/catalog.js +394 -0
- package/dist/providers/catalog.js.map +1 -0
- package/dist/providers/fake.d.ts +46 -0
- package/dist/providers/fake.d.ts.map +1 -0
- package/dist/providers/fake.js +234 -0
- package/dist/providers/fake.js.map +1 -0
- package/dist/providers/openai-compatible.d.ts +65 -0
- package/dist/providers/openai-compatible.d.ts.map +1 -0
- package/dist/providers/openai-compatible.js +434 -0
- package/dist/providers/openai-compatible.js.map +1 -0
- package/dist/proxy/responses-mapper.d.ts +366 -0
- package/dist/proxy/responses-mapper.d.ts.map +1 -0
- package/dist/proxy/responses-mapper.js +517 -0
- package/dist/proxy/responses-mapper.js.map +1 -0
- package/dist/proxy/responses-server.d.ts +29 -0
- package/dist/proxy/responses-server.d.ts.map +1 -0
- package/dist/proxy/responses-server.js +360 -0
- package/dist/proxy/responses-server.js.map +1 -0
- package/dist/router.d.ts +18 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +296 -0
- package/dist/router.js.map +1 -0
- package/dist/schemas.d.ts +560 -0
- package/dist/schemas.d.ts.map +1 -0
- package/{src/schemas.ts → dist/schemas.js} +83 -103
- package/dist/schemas.js.map +1 -0
- package/dist/secrets.d.ts +112 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +326 -0
- package/dist/secrets.js.map +1 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/usage-file.d.ts +59 -0
- package/dist/usage-file.d.ts.map +1 -0
- package/dist/usage-file.js +316 -0
- package/dist/usage-file.js.map +1 -0
- package/dist/usage.d.ts +235 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +517 -0
- package/dist/usage.js.map +1 -0
- package/package.json +7 -2
- package/src/audit.test.ts +0 -302
- package/src/audit.ts +0 -553
- package/src/auth/credential-chain.test.ts +0 -326
- package/src/auth/credential-chain.ts +0 -235
- package/src/auth/index.ts +0 -45
- package/src/auth/keychain.test.ts +0 -265
- package/src/auth/keychain.ts +0 -168
- package/src/auth/oauth-pkce.test.ts +0 -329
- package/src/auth/oauth-pkce.ts +0 -571
- package/src/auth/oauth-server.test.ts +0 -83
- package/src/auth/oauth-server.ts +0 -296
- package/src/config.test.ts +0 -479
- package/src/config.ts +0 -505
- package/src/delegation.test.ts +0 -368
- package/src/delegation.ts +0 -605
- package/src/index.ts +0 -280
- package/src/policy.test.ts +0 -634
- package/src/policy.ts +0 -420
- package/src/providers/adapter.test.ts +0 -293
- package/src/providers/adapter.ts +0 -328
- package/src/providers/catalog-data.test.ts +0 -258
- package/src/providers/catalog-data.ts +0 -498
- package/src/providers/catalog.test.ts +0 -84
- package/src/providers/catalog.ts +0 -483
- package/src/providers/fake.ts +0 -312
- package/src/providers/openai-compatible.test.ts +0 -366
- package/src/providers/openai-compatible.ts +0 -554
- package/src/proxy/responses-mapper.test.ts +0 -290
- package/src/proxy/responses-mapper.ts +0 -736
- package/src/proxy/responses-server.test.ts +0 -322
- package/src/proxy/responses-server.ts +0 -469
- package/src/router.test.ts +0 -699
- package/src/router.ts +0 -352
- package/src/schemas.test.ts +0 -291
- package/src/secrets.test.ts +0 -271
- package/src/secrets.ts +0 -461
- package/src/types.ts +0 -173
- package/src/usage-file.test.ts +0 -243
- package/src/usage-file.ts +0 -435
- package/src/usage.test.ts +0 -335
- package/src/usage.ts +0 -859
- package/tsconfig.json +0 -9
package/src/secrets.test.ts
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
chmod,
|
|
4
|
-
mkdtemp,
|
|
5
|
-
readFile,
|
|
6
|
-
readdir,
|
|
7
|
-
rm,
|
|
8
|
-
stat,
|
|
9
|
-
writeFile,
|
|
10
|
-
} from "node:fs/promises";
|
|
11
|
-
import { tmpdir } from "node:os";
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
-
import {
|
|
14
|
-
FileSecretStore,
|
|
15
|
-
KeychainSecretStore,
|
|
16
|
-
KeychainUnavailableError,
|
|
17
|
-
redact,
|
|
18
|
-
type SecretStore,
|
|
19
|
-
} from "./secrets.js";
|
|
20
|
-
|
|
21
|
-
const tempDirs: string[] = [];
|
|
22
|
-
|
|
23
|
-
afterEach(async () => {
|
|
24
|
-
await Promise.all(
|
|
25
|
-
tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })),
|
|
26
|
-
);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
async function makeTempDir(): Promise<string> {
|
|
30
|
-
const dir = await mkdtemp(join(tmpdir(), "offrouter-secrets-"));
|
|
31
|
-
tempDirs.push(dir);
|
|
32
|
-
return dir;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function makeFileStore(): Promise<{
|
|
36
|
-
dir: string;
|
|
37
|
-
path: string;
|
|
38
|
-
store: FileSecretStore;
|
|
39
|
-
}> {
|
|
40
|
-
const dir = await makeTempDir();
|
|
41
|
-
const path = join(dir, "secrets.json");
|
|
42
|
-
const store = new FileSecretStore({ filePath: path });
|
|
43
|
-
return { dir, path, store };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
describe("FileSecretStore", () => {
|
|
47
|
-
it("stores and reads provider keys by provider id", async () => {
|
|
48
|
-
const { store } = await makeFileStore();
|
|
49
|
-
|
|
50
|
-
await store.set("openai", "sk-live-test-openai-key-001");
|
|
51
|
-
await store.set("anthropic", "sk-ant-test-anthropic-key-002");
|
|
52
|
-
|
|
53
|
-
await expect(store.get("openai")).resolves.toBe(
|
|
54
|
-
"sk-live-test-openai-key-001",
|
|
55
|
-
);
|
|
56
|
-
await expect(store.get("anthropic")).resolves.toBe(
|
|
57
|
-
"sk-ant-test-anthropic-key-002",
|
|
58
|
-
);
|
|
59
|
-
await expect(store.get("missing")).resolves.toBeUndefined();
|
|
60
|
-
|
|
61
|
-
const providers = await store.listProviders();
|
|
62
|
-
expect(providers.sort()).toEqual(["anthropic", "openai"]);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("deletes provider keys by provider id", async () => {
|
|
66
|
-
const { store } = await makeFileStore();
|
|
67
|
-
await store.set("openai", "sk-delete-me");
|
|
68
|
-
await store.delete("openai");
|
|
69
|
-
await expect(store.get("openai")).resolves.toBeUndefined();
|
|
70
|
-
await expect(store.listProviders()).resolves.toEqual([]);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("persists across store instances sharing the same file", async () => {
|
|
74
|
-
const { path, store } = await makeFileStore();
|
|
75
|
-
await store.set("gemini", "AIzaSyTestpersistedkey1234567890");
|
|
76
|
-
|
|
77
|
-
const reloaded = new FileSecretStore({ filePath: path });
|
|
78
|
-
await expect(reloaded.get("gemini")).resolves.toBe(
|
|
79
|
-
"AIzaSyTestpersistedkey1234567890",
|
|
80
|
-
);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("never returns secrets from toJSON", async () => {
|
|
84
|
-
const secret = "sk-super-secret-value-never-json";
|
|
85
|
-
const { store } = await makeFileStore();
|
|
86
|
-
await store.set("openai", secret);
|
|
87
|
-
|
|
88
|
-
const json = store.toJSON();
|
|
89
|
-
const serialized = JSON.stringify(json);
|
|
90
|
-
|
|
91
|
-
expect(serialized).not.toContain(secret);
|
|
92
|
-
expect(serialized).not.toMatch(/sk-super-secret/);
|
|
93
|
-
// Metadata about presence is fine; values are not.
|
|
94
|
-
expect(json).toMatchObject({
|
|
95
|
-
kind: "file",
|
|
96
|
-
providers: ["openai"],
|
|
97
|
-
});
|
|
98
|
-
expect("secrets" in json).toBe(false);
|
|
99
|
-
expect("values" in json).toBe(false);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("JSON.stringify on the store itself never leaks secrets", async () => {
|
|
103
|
-
const secret = "sk-json-stringify-leak-check-xyz";
|
|
104
|
-
const { store } = await makeFileStore();
|
|
105
|
-
await store.set("openai", secret);
|
|
106
|
-
|
|
107
|
-
const out = JSON.stringify(store);
|
|
108
|
-
expect(out).not.toContain(secret);
|
|
109
|
-
expect(out).not.toMatch(/sk-json-stringify/);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it("writes secrets file with mode 0600 on POSIX", async () => {
|
|
113
|
-
if (process.platform === "win32") {
|
|
114
|
-
// Windows does not use POSIX permission bits the same way.
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const { path, store } = await makeFileStore();
|
|
119
|
-
await store.set("openai", "sk-mode-check-value");
|
|
120
|
-
|
|
121
|
-
const info = await stat(path);
|
|
122
|
-
expect(info.mode & 0o777).toBe(0o600);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it("preserves / reasserts 0600 even if file previously had looser perms", async () => {
|
|
126
|
-
if (process.platform === "win32") {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const dir = await makeTempDir();
|
|
131
|
-
const path = join(dir, "secrets.json");
|
|
132
|
-
await writeFile(
|
|
133
|
-
path,
|
|
134
|
-
JSON.stringify({ version: 1, secrets: {} }),
|
|
135
|
-
"utf8",
|
|
136
|
-
);
|
|
137
|
-
await chmod(path, 0o644);
|
|
138
|
-
|
|
139
|
-
const store = new FileSecretStore({ filePath: path });
|
|
140
|
-
await store.set("openai", "sk-tightened-perms");
|
|
141
|
-
|
|
142
|
-
const info = await stat(path);
|
|
143
|
-
expect(info.mode & 0o777).toBe(0o600);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("does not write secrets as plain object dump keys other than provider map", async () => {
|
|
147
|
-
const { path, store } = await makeFileStore();
|
|
148
|
-
await store.set("openai", "sk-file-format-check");
|
|
149
|
-
|
|
150
|
-
const raw = JSON.parse(await readFile(path, "utf8")) as {
|
|
151
|
-
version: number;
|
|
152
|
-
secrets: Record<string, string>;
|
|
153
|
-
};
|
|
154
|
-
expect(raw.version).toBe(1);
|
|
155
|
-
expect(raw.secrets.openai).toBe("sk-file-format-check");
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
describe("redaction", () => {
|
|
160
|
-
it("replaces API key patterns in log strings via standalone redact()", () => {
|
|
161
|
-
const log =
|
|
162
|
-
'provider error: invalid key sk-ant-api03-abcdefghijklmnopqrstuv and sk-live-abcdefghijklmnopqrstuv';
|
|
163
|
-
const redacted = redact(log);
|
|
164
|
-
expect(redacted).not.toContain("sk-ant-api03-abcdefghijklmnopqrstuv");
|
|
165
|
-
expect(redacted).not.toContain("sk-live-abcdefghijklmnopqrstuv");
|
|
166
|
-
expect(redacted).toMatch(/\[REDACTED\]/);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it("replaces stored provider secrets appearing in logs via SecretStore.redact", async () => {
|
|
170
|
-
const { store } = await makeFileStore();
|
|
171
|
-
const secret = "lr-custom-token-not-matching-prefix-patterns-zzzz";
|
|
172
|
-
await store.set("local-provider", secret);
|
|
173
|
-
|
|
174
|
-
const log = `downstream failed auth with ${secret} mid-message`;
|
|
175
|
-
const redacted = store.redact(log);
|
|
176
|
-
|
|
177
|
-
expect(redacted).not.toContain(secret);
|
|
178
|
-
expect(redacted).toContain("[REDACTED]");
|
|
179
|
-
expect(redacted).toContain("downstream failed auth with");
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it("redacts file-backed secrets before the async cache is warm", async () => {
|
|
183
|
-
const { path, store } = await makeFileStore();
|
|
184
|
-
const secret = "lr-cold-cache-token-not-matching-prefix-patterns-zzzz";
|
|
185
|
-
await store.set("local-provider", secret);
|
|
186
|
-
|
|
187
|
-
const coldStore = new FileSecretStore({ filePath: path });
|
|
188
|
-
const redacted = coldStore.redact(`downstream failed with ${secret}`);
|
|
189
|
-
|
|
190
|
-
expect(redacted).not.toContain(secret);
|
|
191
|
-
expect(redacted).toContain("[REDACTED]");
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("removes temporary files when persisting fails after writing the temp secret", async () => {
|
|
195
|
-
const dir = await makeTempDir();
|
|
196
|
-
const path = join(dir, "secrets.json");
|
|
197
|
-
const store = new FileSecretStore({
|
|
198
|
-
filePath: path,
|
|
199
|
-
fileSystem: {
|
|
200
|
-
async rename() {
|
|
201
|
-
throw new Error("simulated rename failure");
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
await expect(store.set("openai", "sk-temp-cleanup-value")).rejects.toThrow();
|
|
207
|
-
|
|
208
|
-
const entries = await readdir(dir);
|
|
209
|
-
expect(entries.filter((entry) => entry.includes(".tmp"))).toEqual([]);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it("redacts bearer tokens and google-style api keys", () => {
|
|
213
|
-
const log =
|
|
214
|
-
"Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.abc.def and key AIzaSyA-abcdefghijklmnopqrstuvwxyz012345";
|
|
215
|
-
const redacted = redact(log);
|
|
216
|
-
expect(redacted).not.toContain("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9");
|
|
217
|
-
expect(redacted).not.toContain("AIzaSyA-abcdefghijklmnopqrstuvwxyz012345");
|
|
218
|
-
expect(redacted).toMatch(/\[REDACTED\]/);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it("is idempotent on already-redacted text", () => {
|
|
222
|
-
const once = redact("using sk-abcdefghijklmnopqrstuvwxyz0123 now");
|
|
223
|
-
const twice = redact(once);
|
|
224
|
-
expect(twice).toBe(once);
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
describe("KeychainSecretStore", () => {
|
|
229
|
-
it("exposes the SecretStore boundary without scraping CLI credentials", async () => {
|
|
230
|
-
const store: SecretStore = new KeychainSecretStore({
|
|
231
|
-
service: "offrouter-test",
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
// Stub must refuse get/set until a real OS keychain backend is wired.
|
|
235
|
-
// It must never read foreign CLI credential stores.
|
|
236
|
-
await expect(store.get("openai")).rejects.toBeInstanceOf(
|
|
237
|
-
KeychainUnavailableError,
|
|
238
|
-
);
|
|
239
|
-
await expect(store.set("openai", "sk-should-not-store")).rejects.toBeInstanceOf(
|
|
240
|
-
KeychainUnavailableError,
|
|
241
|
-
);
|
|
242
|
-
await expect(store.delete("openai")).rejects.toBeInstanceOf(
|
|
243
|
-
KeychainUnavailableError,
|
|
244
|
-
);
|
|
245
|
-
await expect(store.listProviders()).rejects.toBeInstanceOf(
|
|
246
|
-
KeychainUnavailableError,
|
|
247
|
-
);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it("never returns secrets from toJSON", () => {
|
|
251
|
-
const store = new KeychainSecretStore({ service: "offrouter-test" });
|
|
252
|
-
const json = store.toJSON();
|
|
253
|
-
const serialized = JSON.stringify(store);
|
|
254
|
-
|
|
255
|
-
expect(json).toMatchObject({
|
|
256
|
-
kind: "keychain",
|
|
257
|
-
service: "offrouter-test",
|
|
258
|
-
available: false,
|
|
259
|
-
});
|
|
260
|
-
expect(serialized).not.toMatch(/sk-/);
|
|
261
|
-
expect("secrets" in json).toBe(false);
|
|
262
|
-
expect("values" in json).toBe(false);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it("still redacts common API key patterns without stored secrets", () => {
|
|
266
|
-
const store = new KeychainSecretStore({ service: "offrouter-test" });
|
|
267
|
-
const log = "leak sk-abcdefghijklmnopqrstuvwxyz0123 here";
|
|
268
|
-
expect(store.redact(log)).not.toContain("sk-abcdefghijklmnopqrstuvwxyz0123");
|
|
269
|
-
expect(store.redact(log)).toContain("[REDACTED]");
|
|
270
|
-
});
|
|
271
|
-
});
|