@agent-inspect/studio 6.7.2 → 6.7.3

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/index.cjs CHANGED
@@ -862,9 +862,9 @@ var DEFAULT_HTTP_INGEST_BASE_PATH = "/api/ingest";
862
862
  var HTTP_INGEST_BUNDLE_PATH = "/api/ingest/bundle";
863
863
  var HTTP_INGEST_ARTIFACT_PATH = "/api/ingest/artifact";
864
864
  var DEFAULT_MAX_INGEST_BYTES = 52428800;
865
- function resolveHttpIngestConfig(options, registryEnabled) {
866
- const http = options.context?.registry.ingest?.http;
867
- const enabled = options.ingestHttp === true || registryEnabled === true || http?.enabled === true;
865
+ function resolveHttpIngestConfig(options, registryHttp) {
866
+ const http = registryHttp ?? options.context?.registry.ingest?.http;
867
+ const enabled = options.ingestHttp === true || http?.enabled === true;
868
868
  const basePath = (http?.path ?? DEFAULT_HTTP_INGEST_BASE_PATH).trim() || DEFAULT_HTTP_INGEST_BASE_PATH;
869
869
  const tokenEnv = resolveIngestTokenEnv({
870
870
  ...options.ingestTokenEnv !== void 0 ? { tokenEnv: options.ingestTokenEnv } : {},
@@ -897,7 +897,7 @@ function isHttpIngestRoute(pathname, config) {
897
897
  return pathname === HTTP_INGEST_BUNDLE_PATH || pathname === HTTP_INGEST_ARTIFACT_PATH || pathname === `${config.basePath}/bundle` || pathname === `${config.basePath}/artifact`;
898
898
  }
899
899
  async function handleHttpIngestRequest(req, res, ctx, options, pathname) {
900
- const config = resolveHttpIngestConfig(options, ctx.registry.ingest?.http?.enabled);
900
+ const config = resolveHttpIngestConfig(options, ctx.registry.ingest?.http);
901
901
  if (!isHttpIngestRoute(pathname, config)) return false;
902
902
  if (!config.enabled) {
903
903
  sendJson(res, 404, { error: "HTTP ingest is disabled" });
@@ -1344,8 +1344,6 @@ async function loadBundleExportView(ctx, params) {
1344
1344
  note: "Studio does not mutate traces or upload bundles. Run the CLI locally to assemble a share-safe bundle."
1345
1345
  };
1346
1346
  }
1347
-
1348
- // packages/studio/src/auth.ts
1349
1347
  function resolveStudioAuthMode(options) {
1350
1348
  return options.auth === "basic" ? "basic" : "none";
1351
1349
  }
@@ -1365,13 +1363,19 @@ function parseBasicAuth(header) {
1365
1363
  return void 0;
1366
1364
  }
1367
1365
  }
1366
+ function timingSafePasswordEqual(provided, expected) {
1367
+ const providedDigest = crypto.createHash("sha256").update(provided, "utf8").digest();
1368
+ const expectedDigest = crypto.createHash("sha256").update(expected, "utf8").digest();
1369
+ return crypto.timingSafeEqual(providedDigest, expectedDigest);
1370
+ }
1368
1371
  function isStudioRequestAuthorized(req, options) {
1369
1372
  const mode = resolveStudioAuthMode(options);
1370
1373
  if (mode === "none") return true;
1371
1374
  const expected = resolveStudioPassword(options);
1372
1375
  if (!expected) return false;
1373
1376
  const provided = parseBasicAuth(req.headers.authorization);
1374
- return provided === expected;
1377
+ if (provided === void 0) return false;
1378
+ return timingSafePasswordEqual(provided, expected);
1375
1379
  }
1376
1380
  function studioAuthRequiredResponse() {
1377
1381
  return {
@@ -1616,7 +1620,7 @@ function createStudioServer(options = {}) {
1616
1620
  contextPromise = createStudioContext(options);
1617
1621
  }
1618
1622
  const ctx = await contextPromise;
1619
- const httpConfig = resolveHttpIngestConfig(options, ctx.registry.ingest?.http?.enabled);
1623
+ const httpConfig = resolveHttpIngestConfig(options, ctx.registry.ingest?.http);
1620
1624
  if (isHttpIngestRoute(pathname, httpConfig)) {
1621
1625
  const handled2 = await handleHttpIngestRequest(req, res, ctx, options, pathname);
1622
1626
  if (handled2) return;
@@ -1895,6 +1899,28 @@ async function validateBundleDirectory(bundleDir) {
1895
1899
  }
1896
1900
  return errors;
1897
1901
  }
1902
+ async function hashBundleContents(bundleDir) {
1903
+ const hash = crypto.createHash("sha256");
1904
+ const walk = async (dir, relPrefix) => {
1905
+ const entries = (await promises.readdir(dir, { withFileTypes: true })).sort(
1906
+ (a, b) => a.name.localeCompare(b.name)
1907
+ );
1908
+ for (const entry of entries) {
1909
+ const abs = path8__default.default.join(dir, entry.name);
1910
+ const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
1911
+ if (entry.isDirectory()) {
1912
+ await walk(abs, rel);
1913
+ } else if (entry.isFile()) {
1914
+ hash.update(rel);
1915
+ hash.update("\0");
1916
+ hash.update(await promises.readFile(abs));
1917
+ hash.update("\0");
1918
+ }
1919
+ }
1920
+ };
1921
+ await walk(bundleDir, "");
1922
+ return hash.digest("hex");
1923
+ }
1898
1924
  async function copyDirectoryRecursive(sourceDir, destDir) {
1899
1925
  await promises.mkdir(destDir, { recursive: true });
1900
1926
  const entries = await promises.readdir(sourceDir, { withFileTypes: true });
@@ -1949,8 +1975,7 @@ async function importBundleUpload(options) {
1949
1975
  registryImportWarnings
1950
1976
  };
1951
1977
  }
1952
- const metadataRaw = await promises.readFile(path8__default.default.join(bundlePath, "metadata.json"), "utf8");
1953
- const contentHash = crypto.createHash("sha256").update(metadataRaw).digest("hex");
1978
+ const contentHash = await hashBundleContents(bundlePath);
1954
1979
  const sourceKey = `bundle:${bundlePath}`;
1955
1980
  const existing = findIngestFileBySourceKey(options.db, sourceKey);
1956
1981
  if (existing && existing.contentHash === contentHash) {