@vellumai/credential-executor 0.11.10-dev.202609091224.201657f → 0.11.10-dev.202609091515.2bd0662

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/credential-executor",
3
- "version": "0.11.10-dev.202609091224.201657f",
3
+ "version": "0.11.10-dev.202609091515.2bd0662",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -163,6 +163,10 @@ describe("health probes", () => {
163
163
  expect(src).toMatch(/\/readyz/);
164
164
  // Health server uses Bun.serve on a dedicated port, not the socket
165
165
  expect(src).toMatch(/startHealthServer\(\s*healthPort/);
166
+ // Kubelet httpGet targets the pod IP, so the probe listener must not
167
+ // bind loopback-only. Credential routes are gated with isLoopbackAddress.
168
+ expect(src).not.toMatch(/hostname:\s*"127\.0\.0\.1"/);
169
+ expect(src).toMatch(/isLoopbackAddress\(/);
166
170
  });
167
171
 
168
172
  test("getHealthPort defaults to 8090", () => {
@@ -0,0 +1,35 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ isCesHttpProbePath,
5
+ isLoopbackAddress,
6
+ } from "./loopback-address.js";
7
+
8
+ describe("isLoopbackAddress", () => {
9
+ test("accepts IPv4 loopback", () => {
10
+ expect(isLoopbackAddress("127.0.0.1")).toBe(true);
11
+ expect(isLoopbackAddress("127.0.0.2")).toBe(true);
12
+ });
13
+
14
+ test("accepts IPv6 loopback and IPv4-mapped loopback", () => {
15
+ expect(isLoopbackAddress("::1")).toBe(true);
16
+ expect(isLoopbackAddress("::ffff:127.0.0.1")).toBe(true);
17
+ });
18
+
19
+ test("rejects non-loopback addresses", () => {
20
+ expect(isLoopbackAddress("10.0.0.1")).toBe(false);
21
+ expect(isLoopbackAddress("192.168.1.10")).toBe(false);
22
+ expect(isLoopbackAddress("8.8.8.8")).toBe(false);
23
+ expect(isLoopbackAddress("::ffff:10.0.0.1")).toBe(false);
24
+ expect(isLoopbackAddress("2001:db8::1")).toBe(false);
25
+ });
26
+ });
27
+
28
+ describe("isCesHttpProbePath", () => {
29
+ test("matches kubelet probe paths only", () => {
30
+ expect(isCesHttpProbePath("/healthz")).toBe(true);
31
+ expect(isCesHttpProbePath("/readyz")).toBe(true);
32
+ expect(isCesHttpProbePath("/v1/credentials")).toBe(false);
33
+ expect(isCesHttpProbePath("/v1/logs/export")).toBe(false);
34
+ });
35
+ });
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Strict loopback check for CES HTTP peers: 127.0.0.0/8 and ::1.
3
+ * IPv4-mapped IPv6 (`::ffff:127.0.0.1`) is treated as IPv4.
4
+ */
5
+ export function isLoopbackAddress(addr: string): boolean {
6
+ const v4Mapped = addr.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i);
7
+ const normalized = v4Mapped ? v4Mapped[1] : addr;
8
+
9
+ if (normalized.includes(".")) {
10
+ const parts = normalized.split(".").map(Number);
11
+ if (
12
+ parts.length !== 4 ||
13
+ parts.some((p) => Number.isNaN(p) || p < 0 || p > 255)
14
+ ) {
15
+ return false;
16
+ }
17
+ return parts[0] === 127;
18
+ }
19
+
20
+ return normalized.toLowerCase() === "::1";
21
+ }
22
+
23
+ export function isCesHttpProbePath(pathname: string): boolean {
24
+ return pathname === "/healthz" || pathname === "/readyz";
25
+ }
package/src/main.ts CHANGED
@@ -51,6 +51,7 @@ import {
51
51
  type CredentialRouteDeps,
52
52
  } from "./http/credential-routes.js";
53
53
  import { handleLogExportRoute } from "./http/log-export-routes.js";
54
+ import { isLoopbackAddress } from "./http/loopback-address.js";
54
55
  import { handleMetadataRoute } from "./http/metadata-routes.js";
55
56
  import { CES_MIGRATIONS } from "./migrations/registry.js";
56
57
  import { runCesMigrations } from "./migrations/runner.js";
@@ -246,9 +247,12 @@ function startHealthServer(
246
247
  signal: AbortSignal,
247
248
  credentialDeps: CredentialRouteDeps | null,
248
249
  ): ReturnType<typeof Bun.serve> {
250
+ // Listen on every interface so kubelet httpGet probes can hit the pod IP.
251
+ // Credential CRUD, metadata, and log export stay loopback-only: assistant
252
+ // and gateway in this pod call http://localhost:<CES_HEALTH_PORT>.
249
253
  const server = Bun.serve({
250
254
  port,
251
- async fetch(req) {
255
+ async fetch(req, httpServer) {
252
256
  const url = new URL(req.url);
253
257
  if (url.pathname === "/healthz") {
254
258
  return new Response(JSON.stringify({ status: "ok" }), {
@@ -256,7 +260,7 @@ function startHealthServer(
256
260
  });
257
261
  }
258
262
  if (url.pathname === "/readyz") {
259
- // Always return 200 — pod readiness must not depend on whether the
263
+ // Always return 200: pod readiness must not depend on whether the
260
264
  // assistant has connected. When the CES feature flag is off the
261
265
  // assistant never connects, and a 503 here would block pod
262
266
  // scheduling during dark-launch. The sidecar can't do useful work
@@ -275,6 +279,11 @@ function startHealthServer(
275
279
  );
276
280
  }
277
281
 
282
+ const peer = httpServer.requestIP(req)?.address;
283
+ if (!peer || !isLoopbackAddress(peer)) {
284
+ return new Response("Not Found", { status: 404 });
285
+ }
286
+
278
287
  // Credential CRUD routes (only if service token is configured)
279
288
  if (credentialDeps) {
280
289
  const metadataResponse = await handleMetadataRoute(req);