@nurix/nustack 0.26.4 → 0.26.5

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.
@@ -161,6 +161,10 @@ export async function readStoredCredential() {
161
161
  credential: { deviceToken: credential.device_token, studioBaseUrl: credential.studio_base_url, deviceId, userEmail: credential.user_email },
162
162
  };
163
163
  }
164
+ const deviceTokenOrigins = new Map();
165
+ export function deviceTokenOrigin(token) {
166
+ return deviceTokenOrigins.get(token) ?? null;
167
+ }
164
168
  export async function resolveAccessToken(origin) {
165
169
  const fromEnv = envToken();
166
170
  if (fromEnv) {
@@ -185,6 +189,8 @@ export async function resolveAccessToken(origin) {
185
189
  const refused = refusedContractOrigin(credential.studioBaseUrl, origin);
186
190
  if (refused)
187
191
  return { kind: "origin_mismatch", error: originMismatchError(refused, credential.studioBaseUrl, "device") };
192
+ if (credential.studioBaseUrl)
193
+ deviceTokenOrigins.set(credential.deviceToken, credential.studioBaseUrl);
188
194
  return { kind: "ok", session: { accessToken: credential.deviceToken, userEmail: credential.userEmail, deviceId } };
189
195
  }
190
196
  export async function ensureAccessToken(origin) {
@@ -4,11 +4,26 @@ import { existsSync, readFileSync } from "node:fs";
4
4
  import { mkdir, readFile, rename, rm, rmdir, writeFile } from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  import { HarnessError, HARNESS_CODES } from "./codes.js";
7
+ import { nustackHomeDir } from "../local_store/paths.js";
7
8
  export const LEGACY_MOUNT_RECORD_PATH = ".claude/state/legacy-mount.json";
8
9
  export const LEGACY_EXCLUDE_START = "# etna:mount:start";
9
10
  export const LEGACY_EXCLUDE_END = "# etna:mount:end";
10
11
  const SHA256 = /^[0-9a-f]{64}$/;
11
12
  const REQUEST_TIMEOUT_MS = 15_000;
13
+ export function harnessObjectCachePath(sha256) {
14
+ return path.join(nustackHomeDir(), "cache", "harness-objects", sha256);
15
+ }
16
+ async function cachedObject(sha256) {
17
+ const held = await readIfPresent(harnessObjectCachePath(sha256));
18
+ return held && sha256Of(held) === sha256 ? held : null;
19
+ }
20
+ async function keepObject(sha256, bytes) {
21
+ try {
22
+ await writeAtomic(harnessObjectCachePath(sha256), bytes);
23
+ }
24
+ catch {
25
+ }
26
+ }
12
27
  const FETCH_CONCURRENCY = 8;
13
28
  const sha256Of = (bytes) => createHash("sha256").update(bytes).digest("hex");
14
29
  export function isMountedPath(file) {
@@ -164,10 +179,16 @@ export async function mountLegacyManifest(root, manifestUrl, origin) {
164
179
  const worker = async () => {
165
180
  while (next < needed.length) {
166
181
  const file = needed[next++];
182
+ const cached = await cachedObject(file.sha256);
183
+ if (cached) {
184
+ fetched.set(file, cached);
185
+ continue;
186
+ }
167
187
  const bytes = await fetchBytes(objectUrlOf(manifestUrl, file.key));
168
188
  if (sha256Of(bytes) !== file.sha256) {
169
189
  throw new HarnessError(HARNESS_CODES.HARNESS_DIGEST_MISMATCH, `the object for ${file.path} does not match its sha256; nothing was written`);
170
190
  }
191
+ await keepObject(file.sha256, bytes);
171
192
  fetched.set(file, bytes);
172
193
  }
173
194
  };
@@ -1,9 +1,9 @@
1
1
  import { assertSafeNuStackUrl, assertSafeOpaqueId } from "./guards.js";
2
- import { deadCredentialCopy, DEVICE_HEADER, envToken, forgetRefusedToken, knownDeviceId, TokenRefusedError, userAgent } from "./auth_grant.js";
2
+ import { deadCredentialCopy, DEVICE_HEADER, deviceTokenOrigin, envToken, forgetRefusedToken, knownDeviceId, TokenRefusedError, userAgent } from "./auth_grant.js";
3
3
  import { getOrCreateDeviceId } from "./device_identity.js";
4
4
  import { readStoredCredential } from "./auth_grant.js";
5
5
  import { readProjectContractSync } from "./project_contract.js";
6
- import { contractOrigins, isContractOrigin, isPlatformDefault, originMismatchError, PLATFORM_DEFAULT_ORIGIN, recordContractOrigin, refusedContractOrigin, } from "./origin_guard.js";
6
+ import { contractOrigins, isContractOrigin, statedOrigins, isPlatformDefault, originMismatchError, originOf, PLATFORM_DEFAULT_ORIGIN, recordContractOrigin, refusedContractOrigin, } from "./origin_guard.js";
7
7
  const DEFAULT_NUSTACK_URL = PLATFORM_DEFAULT_ORIGIN;
8
8
  export { contractOrigins };
9
9
  const REQUEST_TIMEOUT_MS = 10_000;
@@ -11,12 +11,19 @@ export async function authorizedFetch(input, init = {}) {
11
11
  const headers = new Headers(init.headers);
12
12
  if (headers.has("Authorization"))
13
13
  await assertCredentialOrigin(String(input));
14
+ const sent = headers.get("Authorization")?.replace(/^Bearer\s+/i, "");
15
+ const minted = sent ? deviceTokenOrigin(sent) : null;
16
+ const isForeign = minted !== null && originOf(String(input)) !== originOf(minted);
17
+ if (isForeign && !statedOrigins.has(originOf(String(input))))
18
+ throw originMismatchError(originOf(String(input)), minted, "device");
14
19
  headers.set(DEVICE_HEADER, knownDeviceId() ?? (await getOrCreateDeviceId()));
15
20
  headers.set("User-Agent", userAgent());
16
21
  const response = await globalThis.fetch(input, { ...init, headers });
17
22
  const bearer = headers.get("Authorization")?.replace(/^Bearer\s+/i, "");
18
23
  if (response.status !== 401 || !bearer)
19
24
  return response;
25
+ if (isForeign)
26
+ return response;
20
27
  if (envToken())
21
28
  throw new TokenRefusedError();
22
29
  await forgetRefusedToken(bearer).catch(() => undefined);
@@ -39,10 +46,16 @@ export function errorMessage(payload, status, label, plane) {
39
46
  return enveloped ?? payload?.message ?? `${label}: HTTP ${status}`;
40
47
  }
41
48
  export function resolveNuStackOrigin(options, cwd) {
42
- if (options.nustackUrl)
43
- return { url: assertSafeNuStackUrl(options.nustackUrl), source: "flag" };
44
- if (process.env.NUSTACK_URL)
45
- return { url: assertSafeNuStackUrl(process.env.NUSTACK_URL), source: "env" };
49
+ if (options.nustackUrl) {
50
+ const url = assertSafeNuStackUrl(options.nustackUrl);
51
+ statedOrigins.add(originOf(url));
52
+ return { url, source: "flag" };
53
+ }
54
+ if (process.env.NUSTACK_URL) {
55
+ const url = assertSafeNuStackUrl(process.env.NUSTACK_URL);
56
+ statedOrigins.add(originOf(url));
57
+ return { url, source: "env" };
58
+ }
46
59
  const origin = readProjectContractSync(cwd).origin;
47
60
  if (origin) {
48
61
  const url = assertSafeNuStackUrl(origin);
@@ -1,6 +1,7 @@
1
1
  import { HarnessError, HARNESS_CODES } from "./harness/codes.js";
2
2
  export const PLATFORM_DEFAULT_ORIGIN = "https://apps.nustack.ai";
3
3
  export const contractOrigins = new Set();
4
+ export const statedOrigins = new Set();
4
5
  export function originOf(url) {
5
6
  try {
6
7
  return new URL(url).origin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nurix/nustack",
3
- "version": "0.26.4",
3
+ "version": "0.26.5",
4
4
  "description": "The nustack CLI — bootstrap NuStack services into a repo via the NuStack discovery plane.",
5
5
  "type": "module",
6
6
  "private": false,