@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.
package/dist/lib/auth_grant.js
CHANGED
|
@@ -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
|
-
|
|
44
|
-
|
|
45
|
-
return { url
|
|
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);
|
package/dist/lib/origin_guard.js
CHANGED
|
@@ -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;
|