@nuxtjp/local-runtime 0.1.4 → 0.1.6
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/README.ja.md +6 -3
- package/README.md +8 -3
- package/dist/module.json +2 -2
- package/dist/runtime/app/composables/useLocalRuntime.d.ts +1 -2
- package/dist/runtime/app/composables/useLocalRuntime.js +5 -46
- package/dist/runtime/app/connection.d.ts +94 -0
- package/dist/runtime/app/connection.js +57 -0
- package/dist/runtime/client/client.d.ts +2 -0
- package/dist/runtime/client/client.js +30 -4
- package/package.json +25 -31
- package/security/README.md +6 -16
- package/security/apply.mjs +15 -16
- package/security/dependency-security.check.cjs +9 -70
- package/security/project.mjs +2 -76
- package/security/resolved-dependency.cjs +4 -40
- package/security/dependency-versions.json +0 -12
- package/security/patches/braces@3.0.3.patch +0 -92
- package/security/patches/node-forge@1.4.0.patch +0 -20
- package/security/patches/simple-git@4.0.2.patch +0 -11
package/README.ja.md
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
## 導入と使い方
|
|
8
8
|
|
|
9
|
-
`
|
|
9
|
+
公開版は `npm view @nuxtjp/local-runtime version` で確認してください。Nuxt `^4.6.2`、Vue `^3.5.40`、Node.js 22.19以降または24.11以降を使用します。
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
npm install @nuxtjp/local-runtime
|
|
12
|
+
npm install @nuxtjp/local-runtime
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
```ts
|
|
@@ -60,7 +60,7 @@ pnpm test
|
|
|
60
60
|
pnpm build
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
検証用の依存と固定版はpackage.json・pnpm-lock.yamlを正本とします。独立consumer CIで実配布物と共通セキュリティ処理を検証し、他の環境の受入れとは区別します。
|
|
64
64
|
module/client API、loopback endpointの規則、明示接続の動作、ライセンス条件は維持しています。
|
|
65
65
|
ローカルTGZの試験は配布物の動作確認であり、registryの配布確認とは別です。操作・配備・公開には、それぞれの権限と設定が必要です。
|
|
66
66
|
|
|
@@ -69,3 +69,6 @@ module/client API、loopback endpointの規則、明示接続の動作、ライ
|
|
|
69
69
|
[使い方](https://github.com/nuxtjp/nuxt-local-runtime/blob/main/docs/getting-started.md) · [Schema](schemas) · [実装](https://github.com/nuxtjp/nuxt-local-runtime/tree/main/src) · [テスト](https://github.com/nuxtjp/nuxt-local-runtime/tree/main/test) · [貢献方法](https://github.com/nuxtjp/nuxt-local-runtime/blob/main/CONTRIBUTING.md) · [セキュリティ報告](https://github.com/nuxtjp/nuxt-local-runtime/blob/main/SECURITY.md)
|
|
70
70
|
|
|
71
71
|
コードは[Apache-2.0](LICENSE)です。[NOTICE](NOTICE)と[以前の許諾](LICENSE-PREVIOUS)を保持してください。
|
|
72
|
+
|
|
73
|
+
Dependency updates: [repository policy](docs/dependency-updates.md).
|
|
74
|
+
Registry releases: [version tags and GitHub OIDC](docs/registry-release.md).
|
package/README.md
CHANGED
|
@@ -6,10 +6,10 @@ Connect a browser interface to one explicitly configured local Rust runtime, aft
|
|
|
6
6
|
|
|
7
7
|
## Install and use
|
|
8
8
|
|
|
9
|
-
Install the
|
|
9
|
+
Install the published version identified by `npm view @nuxtjp/local-runtime version`. Use Nuxt `^4.6.2`, Vue `^3.5.40`, and Node.js 22.19+ or 24.11+.
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
npm install @nuxtjp/local-runtime
|
|
12
|
+
npm install @nuxtjp/local-runtime
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
```ts
|
|
@@ -60,7 +60,7 @@ pnpm test
|
|
|
60
60
|
pnpm build
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
The verification
|
|
63
|
+
The exact verification dependencies are declared in package.json and pnpm-lock.yaml. Consumer CI exercises the reviewed archive and shared security tool; other environments require their own acceptance.
|
|
64
64
|
The module/client API, loopback endpoint rules, explicit connection behavior and license terms are retained.
|
|
65
65
|
A local TGZ test proves archive behavior, not registry availability. Operations, deployment and publication require their own permissions and settings.
|
|
66
66
|
|
|
@@ -73,3 +73,8 @@ Code: [Apache-2.0](LICENSE). Retain [NOTICE](NOTICE) and the prior grants in [LI
|
|
|
73
73
|
## Consumer dependency security
|
|
74
74
|
|
|
75
75
|
See [dependency security backports](security/README.md) before installing this package in a Nuxt application. pnpm consumers must explicitly apply the included backports and verify their locked dependency tree; ordinary npm installation does not apply them.
|
|
76
|
+
|
|
77
|
+
[OSS responsibility and source completion](docs/oss-source-contract.md) defines the supported package boundary and required acceptance. It does not declare unexecuted source, distribution or production checks complete.
|
|
78
|
+
|
|
79
|
+
Dependency updates: [repository policy](docs/dependency-updates.md).
|
|
80
|
+
Registry releases: [version tags and GitHub OIDC](docs/registry-release.md).
|
package/dist/module.json
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ViewEnvelope } from '../../shared/types.js';
|
|
2
1
|
export declare function useLocalRuntime(): {
|
|
3
2
|
state: Readonly<import("vue").Ref<{
|
|
4
3
|
readonly phase: import("../../client/index.js").ConnectionPhase;
|
|
@@ -87,6 +86,6 @@ export declare function useLocalRuntime(): {
|
|
|
87
86
|
}>>;
|
|
88
87
|
connected: import("vue").ComputedRef<boolean>;
|
|
89
88
|
connect: (capabilities?: string[]) => Promise<void>;
|
|
90
|
-
view: (capabilityId: string, viewId: string) => Promise<ViewEnvelope>;
|
|
89
|
+
view: (capabilityId: string, viewId: string) => Promise<import("../../client/index.js").ViewEnvelope>;
|
|
91
90
|
disconnect: () => Promise<void>;
|
|
92
91
|
};
|
|
@@ -1,51 +1,10 @@
|
|
|
1
|
-
import { computed, readonly, ref } from "vue";
|
|
2
1
|
import { useRuntimeConfig } from "#imports";
|
|
3
2
|
import { LocalRuntimeClient } from "../../client/client.js";
|
|
3
|
+
import { createLocalRuntimeConnection } from "../connection.js";
|
|
4
4
|
export function useLocalRuntime() {
|
|
5
5
|
const config = useRuntimeConfig();
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
});
|
|
11
|
-
let client = null;
|
|
12
|
-
async function connect(capabilities = []) {
|
|
13
|
-
state.value = { phase: "connecting", manifest: null, error: null };
|
|
14
|
-
try {
|
|
15
|
-
const endpoint = config.public.nuxtJpLocalRuntime.endpoint;
|
|
16
|
-
client = new LocalRuntimeClient({
|
|
17
|
-
endpoint,
|
|
18
|
-
origin: window.location.origin
|
|
19
|
-
});
|
|
20
|
-
const manifest = await client.manifest();
|
|
21
|
-
await client.connect(capabilities);
|
|
22
|
-
state.value = { phase: "connected", manifest, error: null };
|
|
23
|
-
} catch (error) {
|
|
24
|
-
client = null;
|
|
25
|
-
state.value = {
|
|
26
|
-
phase: error instanceof DOMException && error.name === "NotAllowedError" ? "denied" : "unavailable",
|
|
27
|
-
manifest: null,
|
|
28
|
-
error: error instanceof Error ? error.message : "\u63A5\u7D9A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F"
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
async function view(capabilityId, viewId) {
|
|
33
|
-
if (!client) throw new Error("local runtime is not connected");
|
|
34
|
-
return client.view(capabilityId, viewId);
|
|
35
|
-
}
|
|
36
|
-
async function disconnect() {
|
|
37
|
-
try {
|
|
38
|
-
await client?.disconnect();
|
|
39
|
-
} finally {
|
|
40
|
-
client = null;
|
|
41
|
-
state.value = { phase: "idle", manifest: null, error: null };
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return {
|
|
45
|
-
state: readonly(state),
|
|
46
|
-
connected: computed(() => state.value.phase === "connected"),
|
|
47
|
-
connect,
|
|
48
|
-
view,
|
|
49
|
-
disconnect
|
|
50
|
-
};
|
|
6
|
+
return createLocalRuntimeConnection(() => new LocalRuntimeClient({
|
|
7
|
+
endpoint: config.public.nuxtJpLocalRuntime.endpoint,
|
|
8
|
+
origin: window.location.origin
|
|
9
|
+
}));
|
|
51
10
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { LocalRuntimeClient } from '../client/client.js';
|
|
2
|
+
import type { ViewEnvelope } from '../shared/types.js';
|
|
3
|
+
/** Own each connection attempt separately; only the latest operation publishes state. */
|
|
4
|
+
export declare function createLocalRuntimeConnection(createClient: () => LocalRuntimeClient): {
|
|
5
|
+
state: Readonly<import("vue").Ref<{
|
|
6
|
+
readonly phase: import("../client/index.js").ConnectionPhase;
|
|
7
|
+
readonly manifest: {
|
|
8
|
+
readonly schema: "nuxtjp://local-runtime/manifest/v1";
|
|
9
|
+
readonly runtime_id: string;
|
|
10
|
+
readonly protocol_version: "1";
|
|
11
|
+
readonly mode: "local-simulation" | "local-production";
|
|
12
|
+
readonly external_actions: false;
|
|
13
|
+
readonly engine: {
|
|
14
|
+
readonly kind: "rust-v8";
|
|
15
|
+
readonly status: "disabled" | "simulated" | "ready";
|
|
16
|
+
readonly isolation: "in-process-simulation" | "process";
|
|
17
|
+
readonly arbitrary_network: false;
|
|
18
|
+
readonly arbitrary_filesystem: false;
|
|
19
|
+
};
|
|
20
|
+
readonly security: {
|
|
21
|
+
readonly default_band: "sealed";
|
|
22
|
+
readonly external_egress: "deny";
|
|
23
|
+
readonly allowed_origins: readonly string[];
|
|
24
|
+
readonly limits: {
|
|
25
|
+
readonly max_request_bytes: number;
|
|
26
|
+
readonly max_response_bytes: number;
|
|
27
|
+
readonly max_messages_per_minute: number;
|
|
28
|
+
readonly max_bytes_per_minute: number;
|
|
29
|
+
readonly session_ttl_seconds: number;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
readonly capabilities: readonly {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly service_id: string;
|
|
35
|
+
readonly description: string;
|
|
36
|
+
readonly output_band: "session" | "public";
|
|
37
|
+
readonly payload_schema: {
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly fields: readonly {
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly kind: import("../client/index.js").PayloadValueKind;
|
|
42
|
+
}[];
|
|
43
|
+
};
|
|
44
|
+
}[];
|
|
45
|
+
} | null;
|
|
46
|
+
readonly error: string | null;
|
|
47
|
+
}, {
|
|
48
|
+
readonly phase: import("../client/index.js").ConnectionPhase;
|
|
49
|
+
readonly manifest: {
|
|
50
|
+
readonly schema: "nuxtjp://local-runtime/manifest/v1";
|
|
51
|
+
readonly runtime_id: string;
|
|
52
|
+
readonly protocol_version: "1";
|
|
53
|
+
readonly mode: "local-simulation" | "local-production";
|
|
54
|
+
readonly external_actions: false;
|
|
55
|
+
readonly engine: {
|
|
56
|
+
readonly kind: "rust-v8";
|
|
57
|
+
readonly status: "disabled" | "simulated" | "ready";
|
|
58
|
+
readonly isolation: "in-process-simulation" | "process";
|
|
59
|
+
readonly arbitrary_network: false;
|
|
60
|
+
readonly arbitrary_filesystem: false;
|
|
61
|
+
};
|
|
62
|
+
readonly security: {
|
|
63
|
+
readonly default_band: "sealed";
|
|
64
|
+
readonly external_egress: "deny";
|
|
65
|
+
readonly allowed_origins: readonly string[];
|
|
66
|
+
readonly limits: {
|
|
67
|
+
readonly max_request_bytes: number;
|
|
68
|
+
readonly max_response_bytes: number;
|
|
69
|
+
readonly max_messages_per_minute: number;
|
|
70
|
+
readonly max_bytes_per_minute: number;
|
|
71
|
+
readonly session_ttl_seconds: number;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
readonly capabilities: readonly {
|
|
75
|
+
readonly id: string;
|
|
76
|
+
readonly service_id: string;
|
|
77
|
+
readonly description: string;
|
|
78
|
+
readonly output_band: "session" | "public";
|
|
79
|
+
readonly payload_schema: {
|
|
80
|
+
readonly id: string;
|
|
81
|
+
readonly fields: readonly {
|
|
82
|
+
readonly name: string;
|
|
83
|
+
readonly kind: import("../client/index.js").PayloadValueKind;
|
|
84
|
+
}[];
|
|
85
|
+
};
|
|
86
|
+
}[];
|
|
87
|
+
} | null;
|
|
88
|
+
readonly error: string | null;
|
|
89
|
+
}>>;
|
|
90
|
+
connected: import("vue").ComputedRef<boolean>;
|
|
91
|
+
connect: (capabilities?: string[]) => Promise<void>;
|
|
92
|
+
view: (capabilityId: string, viewId: string) => Promise<ViewEnvelope>;
|
|
93
|
+
disconnect: () => Promise<void>;
|
|
94
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { computed, readonly, ref } from "vue";
|
|
2
|
+
export function createLocalRuntimeConnection(createClient) {
|
|
3
|
+
const state = ref({ phase: "idle", manifest: null, error: null });
|
|
4
|
+
let generation = 0;
|
|
5
|
+
let client = null;
|
|
6
|
+
let pending = null;
|
|
7
|
+
async function connect(capabilities = []) {
|
|
8
|
+
const operation = ++generation;
|
|
9
|
+
const previous = pending || client;
|
|
10
|
+
client = null;
|
|
11
|
+
pending = null;
|
|
12
|
+
state.value = { phase: "connecting", manifest: null, error: null };
|
|
13
|
+
let attempt = null;
|
|
14
|
+
try {
|
|
15
|
+
attempt = createClient();
|
|
16
|
+
pending = attempt;
|
|
17
|
+
await previous?.disconnect();
|
|
18
|
+
if (operation !== generation) return;
|
|
19
|
+
const manifest = await attempt.manifest();
|
|
20
|
+
await attempt.connect(capabilities);
|
|
21
|
+
if (operation !== generation) {
|
|
22
|
+
await attempt.disconnect();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
client = attempt;
|
|
26
|
+
pending = null;
|
|
27
|
+
state.value = { phase: "connected", manifest, error: null };
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (operation !== generation) return;
|
|
30
|
+
try {
|
|
31
|
+
await attempt?.disconnect();
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
if (operation !== generation) return;
|
|
35
|
+
pending = null;
|
|
36
|
+
client = null;
|
|
37
|
+
state.value = {
|
|
38
|
+
phase: error instanceof DOMException && error.name === "NotAllowedError" ? "denied" : "unavailable",
|
|
39
|
+
manifest: null,
|
|
40
|
+
error: error instanceof Error ? error.message : "\u63A5\u7D9A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F"
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function view(capabilityId, viewId) {
|
|
45
|
+
if (!client || state.value.phase !== "connected") throw new Error("local runtime is not connected");
|
|
46
|
+
return client.view(capabilityId, viewId);
|
|
47
|
+
}
|
|
48
|
+
async function disconnect() {
|
|
49
|
+
++generation;
|
|
50
|
+
const previous = pending || client;
|
|
51
|
+
pending = null;
|
|
52
|
+
client = null;
|
|
53
|
+
state.value = { phase: "idle", manifest: null, error: null };
|
|
54
|
+
await previous?.disconnect();
|
|
55
|
+
}
|
|
56
|
+
return { state: readonly(state), connected: computed(() => state.value.phase === "connected"), connect, view, disconnect };
|
|
57
|
+
}
|
|
@@ -12,6 +12,7 @@ export declare class LocalRuntimeClient {
|
|
|
12
12
|
private readonly fetcher;
|
|
13
13
|
private readonly maximumResponseBytes;
|
|
14
14
|
private readonly timeoutMilliseconds;
|
|
15
|
+
private operation;
|
|
15
16
|
private grant;
|
|
16
17
|
private runtimeManifest;
|
|
17
18
|
constructor(options: LocalRuntimeClientOptions);
|
|
@@ -19,4 +20,5 @@ export declare class LocalRuntimeClient {
|
|
|
19
20
|
connect(capabilities: string[]): Promise<SessionGrant>;
|
|
20
21
|
view(capabilityId: string, viewId: string): Promise<ViewEnvelope>;
|
|
21
22
|
disconnect(): Promise<void>;
|
|
23
|
+
private revoke;
|
|
22
24
|
}
|
|
@@ -12,6 +12,7 @@ export class LocalRuntimeClient {
|
|
|
12
12
|
fetcher;
|
|
13
13
|
maximumResponseBytes;
|
|
14
14
|
timeoutMilliseconds;
|
|
15
|
+
operation = 0;
|
|
15
16
|
grant = null;
|
|
16
17
|
runtimeManifest = null;
|
|
17
18
|
constructor(options) {
|
|
@@ -28,6 +29,7 @@ export class LocalRuntimeClient {
|
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
async manifest() {
|
|
32
|
+
const operation = this.operation;
|
|
31
33
|
const value = await localJson(
|
|
32
34
|
this.fetcher,
|
|
33
35
|
runtimeUrl(this.endpoint, "/v1/manifest"),
|
|
@@ -38,6 +40,7 @@ export class LocalRuntimeClient {
|
|
|
38
40
|
if (!isLocalRuntimeManifest(value) || !value.security.allowed_origins.includes(this.origin)) {
|
|
39
41
|
throw new Error("invalid runtime manifest");
|
|
40
42
|
}
|
|
43
|
+
if (operation !== this.operation) throw new Error("local runtime operation was superseded");
|
|
41
44
|
this.runtimeManifest = value;
|
|
42
45
|
return value;
|
|
43
46
|
}
|
|
@@ -52,6 +55,12 @@ export class LocalRuntimeClient {
|
|
|
52
55
|
if (capabilities.some((capability) => !available.has(capability))) {
|
|
53
56
|
throw new Error("requested capability is not declared");
|
|
54
57
|
}
|
|
58
|
+
const runtimeManifest = this.runtimeManifest;
|
|
59
|
+
const operation = ++this.operation;
|
|
60
|
+
const previous = this.grant;
|
|
61
|
+
this.grant = null;
|
|
62
|
+
if (previous) await this.revoke(previous.token);
|
|
63
|
+
if (operation !== this.operation) throw new Error("local runtime operation was superseded");
|
|
55
64
|
const request = {
|
|
56
65
|
schema: "nuxtjp://local-runtime/session-request/v1",
|
|
57
66
|
origin: this.origin,
|
|
@@ -69,10 +78,18 @@ export class LocalRuntimeClient {
|
|
|
69
78
|
const expected = [...new Set(capabilities)].sort();
|
|
70
79
|
const observed = isSessionGrant(value) ? [...value.granted_capabilities].sort() : [];
|
|
71
80
|
const now = Math.floor(Date.now() / 1e3);
|
|
72
|
-
const limits =
|
|
81
|
+
const limits = runtimeManifest.security.limits;
|
|
73
82
|
if (!isSessionGrant(value) || value.audience !== this.origin || value.expires_at_unix_seconds <= now || value.expires_at_unix_seconds > now + limits.session_ttl_seconds + 5 || JSON.stringify(observed) !== JSON.stringify(expected) || value.budget.max_messages > limits.max_messages_per_minute || value.budget.max_bytes > limits.max_bytes_per_minute) {
|
|
74
83
|
throw new Error("invalid runtime session grant");
|
|
75
84
|
}
|
|
85
|
+
if (operation !== this.operation) {
|
|
86
|
+
try {
|
|
87
|
+
await this.revoke(value.token);
|
|
88
|
+
} catch {
|
|
89
|
+
throw new Error("superseded runtime session cleanup failed");
|
|
90
|
+
}
|
|
91
|
+
throw new Error("local runtime operation was superseded");
|
|
92
|
+
}
|
|
76
93
|
this.grant = value;
|
|
77
94
|
return value;
|
|
78
95
|
}
|
|
@@ -83,6 +100,9 @@ export class LocalRuntimeClient {
|
|
|
83
100
|
if (!this.grant.granted_capabilities.includes(capabilityId)) {
|
|
84
101
|
throw new Error("capability was not granted");
|
|
85
102
|
}
|
|
103
|
+
const operation = this.operation;
|
|
104
|
+
const grant = this.grant;
|
|
105
|
+
const runtimeManifest = this.runtimeManifest;
|
|
86
106
|
const request = {
|
|
87
107
|
schema: "nuxtjp://local-runtime/view-request/v1",
|
|
88
108
|
capability_id: capabilityId,
|
|
@@ -92,20 +112,26 @@ export class LocalRuntimeClient {
|
|
|
92
112
|
const value = await localJson(
|
|
93
113
|
this.fetcher,
|
|
94
114
|
runtimeUrl(this.endpoint, "/v1/view"),
|
|
95
|
-
localRequest("POST", { body: request, token:
|
|
115
|
+
localRequest("POST", { body: request, token: grant.token }),
|
|
96
116
|
this.maximumResponseBytes,
|
|
97
117
|
this.timeoutMilliseconds
|
|
98
118
|
);
|
|
99
|
-
|
|
119
|
+
if (operation !== this.operation || this.grant !== grant) {
|
|
120
|
+
throw new Error("local runtime operation was superseded");
|
|
121
|
+
}
|
|
122
|
+
const capability = runtimeManifest.capabilities.find((item) => item.id === capabilityId);
|
|
100
123
|
if (!capability || !isViewEnvelope(value, capability.payload_schema) || value.capability_id !== capabilityId || value.view_id !== viewId) {
|
|
101
124
|
throw new Error("invalid runtime view");
|
|
102
125
|
}
|
|
103
126
|
return value;
|
|
104
127
|
}
|
|
105
128
|
async disconnect() {
|
|
129
|
+
++this.operation;
|
|
106
130
|
const token = this.grant?.token;
|
|
107
131
|
this.grant = null;
|
|
108
|
-
if (
|
|
132
|
+
if (token) await this.revoke(token);
|
|
133
|
+
}
|
|
134
|
+
async revoke(token) {
|
|
109
135
|
await localJson(
|
|
110
136
|
this.fetcher,
|
|
111
137
|
runtimeUrl(this.endpoint, "/v1/session"),
|
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuxtjp/local-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Fail-closed Nuxt client for a loopback local runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
|
-
"packageManager": "pnpm@10.29.3",
|
|
8
7
|
"engines": {
|
|
9
8
|
"node": "^22.19.0 || ^24.11.0",
|
|
10
9
|
"pnpm": ">=10 <11"
|
|
@@ -41,31 +40,24 @@
|
|
|
41
40
|
},
|
|
42
41
|
"./schema": "./schemas/local-runtime-v1.schema.json"
|
|
43
42
|
},
|
|
44
|
-
"scripts": {
|
|
45
|
-
"build": "nuxt-module-build build && nuxt build playground",
|
|
46
|
-
"typecheck": "vue-tsc --noEmit --project tsconfig.json",
|
|
47
|
-
"test": "vitest run && pnpm run test:dependency-security && pnpm run test:consumer-security",
|
|
48
|
-
"prepack": "pnpm run test && pnpm run typecheck && pnpm run build",
|
|
49
|
-
"test:dependency-security": "node --test scripts/dependency-security.check.cjs",
|
|
50
|
-
"test:consumer-security": "node --test scripts/consumer-security.check.mjs"
|
|
51
|
-
},
|
|
52
43
|
"peerDependencies": {
|
|
53
|
-
"nuxt": "^4.
|
|
44
|
+
"nuxt": "^4.6.2",
|
|
54
45
|
"vue": "^3.5.40"
|
|
55
46
|
},
|
|
56
47
|
"dependencies": {
|
|
57
|
-
"@nuxt/kit": "4.
|
|
58
|
-
"@nuxt/schema": "4.
|
|
48
|
+
"@nuxt/kit": "4.6.2",
|
|
49
|
+
"@nuxt/schema": "4.6.2",
|
|
50
|
+
"@nuxtjp/dependency-security": "0.1.2"
|
|
59
51
|
},
|
|
60
52
|
"devDependencies": {
|
|
61
|
-
"@nuxt/module-builder": "1.0.
|
|
53
|
+
"@nuxt/module-builder": "1.0.3",
|
|
62
54
|
"@types/node": "24.12.0",
|
|
63
55
|
"ajv": "8.20.0",
|
|
64
|
-
"nuxt": "4.
|
|
56
|
+
"nuxt": "4.6.2",
|
|
65
57
|
"typescript": "5.9.3",
|
|
66
|
-
"vitest": "
|
|
67
|
-
"vue": "3.5.
|
|
68
|
-
"vue-tsc": "3.
|
|
58
|
+
"vitest": "5.0.5",
|
|
59
|
+
"vue": "3.5.44",
|
|
60
|
+
"vue-tsc": "3.3.13"
|
|
69
61
|
},
|
|
70
62
|
"repository": {
|
|
71
63
|
"type": "git",
|
|
@@ -80,17 +72,19 @@
|
|
|
80
72
|
"registry": "https://registry.npmjs.org",
|
|
81
73
|
"provenance": true
|
|
82
74
|
},
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
75
|
+
"keywords": [
|
|
76
|
+
"nuxt",
|
|
77
|
+
"rust",
|
|
78
|
+
"local-first",
|
|
79
|
+
"ipc",
|
|
80
|
+
"developer-tools"
|
|
81
|
+
],
|
|
82
|
+
"scripts": {
|
|
83
|
+
"build": "pnpm run build:security && nuxt-module-build build && nuxt build playground",
|
|
84
|
+
"typecheck": "vue-tsc --noEmit --project tsconfig.json",
|
|
85
|
+
"test": "vitest run && pnpm run test:dependency-security && pnpm run test:consumer-security",
|
|
86
|
+
"test:dependency-security": "node security/dependency-security.check.cjs",
|
|
87
|
+
"test:consumer-security": "node --test scripts/consumer-security.check.mts",
|
|
88
|
+
"build:security": "tsc -p tsconfig.security.json"
|
|
95
89
|
}
|
|
96
|
-
}
|
|
90
|
+
}
|
package/security/README.md
CHANGED
|
@@ -1,23 +1,13 @@
|
|
|
1
|
-
# Dependency security
|
|
1
|
+
# Dependency security maintenance
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
The package is distributed through npm. A consumer must use pnpm 10 and explicitly configure the reviewed backports at its project root. Dependency packages cannot impose root-level npm overrides or pnpm patches on a consuming application. Installing this module alone does not apply the patches.
|
|
3
|
+
`@nuxtjp/dependency-security` owns the reviewed policy, patches, installed dependency resolver and regression checks. This module depends on that peer-free package; its `security/apply.mjs` and `security/dependency-security.check.cjs` paths are generated compatibility entry points and contain no independent policy.
|
|
6
4
|
|
|
7
5
|
```sh
|
|
8
|
-
|
|
6
|
+
pnpm exec nuxtjp-security-config --project-root . --apply
|
|
9
7
|
pnpm install --no-frozen-lockfile
|
|
10
8
|
pnpm install --frozen-lockfile
|
|
9
|
+
pnpm exec nuxtjp-security-check --project-root .
|
|
10
|
+
pnpm audit --json
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Run `node node_modules/@nuxtjp/local-runtime/security/dependency-security.check.cjs` after installing the locked project to verify that the actual resolved dependencies reject the affected inputs. These are backports, not upstream fixed versions. Version-only advisory tools still report the two vulnerable upstream versions; retain their findings and link the lockfile/patch hashes and regression results. Reapply and reverify when dependency versions change. npm-only consumers require a separately reviewed override distribution; they are not certified by this pnpm procedure.
|
|
16
|
-
|
|
17
|
-
The RSA test creates an ephemeral key in memory and never reads a user key. Patches contain code only and do not include credentials, customer content, or private endpoints.
|
|
18
|
-
|
|
19
|
-
Run `pnpm audit --json` on the installed application and retain the report. Only the two backported braces/node-forge advisories may remain; any other advisory, including simple-git, its argument parser, or esbuild, blocks validation. Run the dependency regression command on each install to reject missing or older active versions.
|
|
20
|
-
|
|
21
|
-
The code patch inputs retain third-party notices in `LICENSE-braces` (MIT) and `LICENSE-node-forge`. The original dependencies remain registry packages; the shipped files configure and test the consumer rather than silently replacing its package manager.
|
|
22
|
-
|
|
23
|
-
The simple-git 4.0.2 compatibility patch restores only the legacy ESM default factory used by Nuxt devtools. It aliases the existing, fixed `simpleGit` factory and does not change argument validation, executable selection, or Git execution. The named factory remains identical. `LICENSE-simple-git` retains its upstream MIT notice. A fresh consumer build and type check with devtools enabled are mandatory release checks.
|
|
13
|
+
Installation does not apply patches. Select the application explicitly and commit its generated patches, manifest and lockfile. Configuration rejects conflicts; only the shared policy's tested backport advisories may remain visible. An application build and type check with devtools enabled are required separately. Existing LICENSE-braces, LICENSE-node-forge and LICENSE-simple-git attribution is retained. Root `patches/` files are this repository's own build inputs, not another distributed security implementation.
|
package/security/apply.mjs
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
import { applySecurityPatches } from './project.mjs'
|
|
6
|
-
|
|
7
|
-
const args = process.argv.slice(2)
|
|
2
|
+
// Compatibility CLI: the shared tool owns policy, patches and validation.
|
|
3
|
+
import { applySecurityPatches } from '@nuxtjp/dependency-security';
|
|
4
|
+
const args = process.argv.slice(2);
|
|
8
5
|
if (args.length !== 3 || args[0] !== '--project-root' || args[2] !== '--apply') {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
console.error('Usage: node apply.mjs --project-root PROJECT --apply');
|
|
7
|
+
process.exitCode = 2;
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
try {
|
|
11
|
+
applySecurityPatches(args[1]);
|
|
12
|
+
console.log('Reviewed backports configured. Run pnpm install --no-frozen-lockfile to refresh the lockfile.');
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
console.error('Selected project configuration could not be applied; review manifest and patch conflicts.');
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
}
|
|
19
18
|
}
|
|
@@ -1,70 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
assert.equal(matches.length, 1, 'Expected one active locked dependency implementation');
|
|
11
|
-
return createRequire(matches[0])(name);
|
|
12
|
-
}
|
|
13
|
-
const braces = dependency('braces', '3.0.3');
|
|
14
|
-
const forge = dependency('node-forge', '1.4.0');
|
|
15
|
-
test('brace compilation, expansion and stringification retain ordinary behavior', () => {
|
|
16
|
-
assert.equal(braces.compile('src/{a,b}.vue'), 'src/(a|b).vue');
|
|
17
|
-
assert.deepEqual(braces.expand('src/{a,b}.vue'), ['src/a.vue', 'src/b.vue']);
|
|
18
|
-
assert.equal(braces.stringify(braces.parse('src/{a,b}.vue')), 'src/{a,b}.vue');
|
|
19
|
-
assert.equal(braces.compile('src/\\{a,b\\}.vue'), 'src/{a,b}.vue');
|
|
20
|
-
});
|
|
21
|
-
test('deep brace, parenthesis and caller AST inputs reject before stack exhaustion', () => {
|
|
22
|
-
for (const pattern of ['{'.repeat(6000) + 'a,b' + '}'.repeat(6000), '('.repeat(6000) + 'x' + ')'.repeat(6000), '{'.repeat(6000) + 'x']) {
|
|
23
|
-
for (const fn of [braces.compile, braces.expand, braces.stringify, braces.parse]) assert.throws(() => fn(pattern), SyntaxError);
|
|
24
|
-
}
|
|
25
|
-
let ast = { type: 'text', value: 'x' };
|
|
26
|
-
for (let i = 0; i < 6000; i++) ast = { type: 'root', nodes: [ast] };
|
|
27
|
-
for (const fn of [braces.compile, braces.expand, braces.stringify]) assert.throws(() => fn(ast), SyntaxError);
|
|
28
|
-
const cyclic = { type: 'root', nodes: [] }; cyclic.nodes.push(cyclic);
|
|
29
|
-
assert.throws(() => braces.compile(cyclic), SyntaxError);
|
|
30
|
-
});
|
|
31
|
-
test('RSA verifier accepts standard signatures and rejects extra or malformed DigestAlgorithm elements', () => {
|
|
32
|
-
const keys = generateKeyPairSync('rsa', { modulusLength: 2048 });
|
|
33
|
-
const publicKey = forge.pki.publicKeyFromPem(keys.publicKey.export({ type: 'spki', format: 'pem' }));
|
|
34
|
-
const data = Buffer.from('never-issued security regression fixture');
|
|
35
|
-
const md = forge.md.sha256.create(); md.update(data.toString('binary'));
|
|
36
|
-
const digest = md.digest().getBytes();
|
|
37
|
-
assert.equal(publicKey.verify(digest, sign('sha256', data, keys.privateKey).toString('binary')), true);
|
|
38
|
-
const a = forge.asn1;
|
|
39
|
-
const oid = () => a.create(a.Class.UNIVERSAL, a.Type.OID, false, a.oidToDer(forge.oids.sha256).getBytes());
|
|
40
|
-
const nil = value => a.create(a.Class.UNIVERSAL, a.Type.NULL, false, value);
|
|
41
|
-
for (const parameters of [[oid(), nil(''), oid()], [oid(), a.create(a.Class.UNIVERSAL, a.Type.SEQUENCE, true, [oid()])], [oid(), nil('x')]]) {
|
|
42
|
-
const info = a.create(a.Class.UNIVERSAL, a.Type.SEQUENCE, true, [a.create(a.Class.UNIVERSAL, a.Type.SEQUENCE, true, parameters), a.create(a.Class.UNIVERSAL, a.Type.OCTETSTRING, false, digest)]);
|
|
43
|
-
const encoded = Buffer.from(a.toDer(info).getBytes(), 'binary');
|
|
44
|
-
const signature = privateEncrypt({ key: keys.privateKey, padding: constants.RSA_PKCS1_PADDING }, encoded).toString('binary');
|
|
45
|
-
let accepted = false;
|
|
46
|
-
try { accepted = publicKey.verify(digest, signature); } catch { /* Rejection is the expected result for a malformed signature. */ }
|
|
47
|
-
assert.equal(accepted, false, 'Malformed algorithm must not verify');
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('active dependency graph uses every required upstream security version', () => {
|
|
52
|
-
const policy = JSON.parse(fs.readFileSync(path.join(__dirname, 'dependency-versions.json'), 'utf8'));
|
|
53
|
-
const { installedDependencies } = require('./resolved-dependency.cjs');
|
|
54
|
-
for (const [name, version] of Object.entries(policy.overrides)) {
|
|
55
|
-
const matches = installedDependencies(process.cwd(), name);
|
|
56
|
-
assert.ok(matches.length > 0, 'Required dependency is absent: ' + name);
|
|
57
|
-
for (const manifest of matches) assert.equal(JSON.parse(fs.readFileSync(manifest, 'utf8')).version, version, name);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// Preserve the ESM entry point expected by current Nuxt devtools without downgrading Git protections.
|
|
62
|
-
test('fixed simple-git retains named and legacy ESM factory exports', async () => {
|
|
63
|
-
const { pathToFileURL } = require('node:url');
|
|
64
|
-
const { installedDependencies } = require('./resolved-dependency.cjs');
|
|
65
|
-
for (const manifest of installedDependencies(process.cwd(), 'simple-git', '4.0.2')) {
|
|
66
|
-
const module = await import(pathToFileURL(path.join(path.dirname(manifest), 'dist/index.mjs')).href);
|
|
67
|
-
assert.equal(typeof module.simpleGit, 'function');
|
|
68
|
-
assert.equal(module.default, module.simpleGit);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
// Compatibility entry point; regressions execute only after explicit invocation.
|
|
4
|
+
void import('@nuxtjp/dependency-security').then(({ verifyProject }) => {
|
|
5
|
+
const result = verifyProject(process.cwd());
|
|
6
|
+
process.stdout.write(result.stdout);
|
|
7
|
+
process.stderr.write(result.stderr);
|
|
8
|
+
process.exitCode = result.exitCode;
|
|
9
|
+
}).catch(() => { console.error('Selected dependency graph cannot be verified'); process.exitCode = 2; });
|
package/security/project.mjs
CHANGED
|
@@ -1,76 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { join, resolve } from 'node:path'
|
|
4
|
-
import { randomUUID } from 'node:crypto'
|
|
5
|
-
|
|
6
|
-
const patches = ['braces@3.0.3', 'node-forge@1.4.0', 'simple-git@4.0.2']
|
|
7
|
-
function regular(path) {
|
|
8
|
-
const stat = lstatSync(path)
|
|
9
|
-
if (stat.isSymbolicLink() || !stat.isFile() || stat.size > 1024 * 1024) {
|
|
10
|
-
throw new Error('Expected a bounded regular project or patch file')
|
|
11
|
-
}
|
|
12
|
-
return readFileSync(path)
|
|
13
|
-
}
|
|
14
|
-
function object(value) {
|
|
15
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Reject conflicting patches and links before mutating a project. No network access occurs. */
|
|
19
|
-
export function applySecurityPatches(project, source) {
|
|
20
|
-
const root = realpathSync(resolve(project))
|
|
21
|
-
const manifest = join(root, 'package.json')
|
|
22
|
-
const original = regular(manifest)
|
|
23
|
-
const data = JSON.parse(original)
|
|
24
|
-
if (!object(data) || (data.pnpm !== undefined && !object(data.pnpm))) {
|
|
25
|
-
throw new Error('Expected a package manifest with object-valued pnpm settings')
|
|
26
|
-
}
|
|
27
|
-
const pnpm = data.pnpm ?? {}
|
|
28
|
-
const existing = pnpm.patchedDependencies ?? {}
|
|
29
|
-
if (!object(existing)) throw new Error('Expected object-valued patchedDependencies')
|
|
30
|
-
const pinned = JSON.parse(regular(join(source, 'dependency-versions.json')))
|
|
31
|
-
if (pinned.schemaVersion !== 1 || !object(pinned.overrides)) throw new Error('Invalid dependency version policy')
|
|
32
|
-
const overrides = pnpm.overrides ?? {}
|
|
33
|
-
if (!object(overrides)) throw new Error('Expected object-valued dependency overrides')
|
|
34
|
-
for (const [name, version] of Object.entries(pinned.overrides)) {
|
|
35
|
-
if (!/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/.test(name) || !/^\d+\.\d+\.\d+$/.test(version)) {
|
|
36
|
-
throw new Error('Exact registry package versions required')
|
|
37
|
-
}
|
|
38
|
-
for (const [selector, target] of Object.entries(overrides)) {
|
|
39
|
-
const dependency = selector.split('>').at(-1)
|
|
40
|
-
if ((dependency === name || dependency.startsWith(name + '@')) && target !== version) {
|
|
41
|
-
throw new Error('A dependency override conflicts; review it before applying')
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
const dir = join(root, '.nuxtjp-security')
|
|
46
|
-
if (existsSync(dir) && (lstatSync(dir).isSymbolicLink() || !lstatSync(dir).isDirectory())) {
|
|
47
|
-
throw new Error('Security patch directory must be a real directory')
|
|
48
|
-
}
|
|
49
|
-
const inputs = patches.map(name => {
|
|
50
|
-
const relative = `.nuxtjp-security/${name}.patch`
|
|
51
|
-
if (existing[name] !== undefined && existing[name] !== relative) {
|
|
52
|
-
throw new Error('An existing dependency patch conflicts; review it before applying')
|
|
53
|
-
}
|
|
54
|
-
const bytes = regular(join(source, 'patches', `${name}.patch`))
|
|
55
|
-
const target = join(root, relative)
|
|
56
|
-
if (existsSync(target) && !regular(target).equals(bytes)) {
|
|
57
|
-
throw new Error('An existing patch differs; no files were overwritten')
|
|
58
|
-
}
|
|
59
|
-
return { name, relative, bytes, target }
|
|
60
|
-
})
|
|
61
|
-
if (!readFileSync(manifest).equals(original)) throw new Error('Manifest changed during preparation')
|
|
62
|
-
mkdirSync(dir, { recursive: true })
|
|
63
|
-
for (const patch of inputs) {
|
|
64
|
-
if (!existsSync(patch.target)) writeFileSync(patch.target, patch.bytes, { flag: 'wx' })
|
|
65
|
-
existing[patch.name] = patch.relative
|
|
66
|
-
}
|
|
67
|
-
data.pnpm = { ...pnpm, patchedDependencies: existing, overrides: { ...overrides, ...pinned.overrides } }
|
|
68
|
-
const temporary = join(root, `.nuxtjp-manifest-${randomUUID()}.tmp`)
|
|
69
|
-
try {
|
|
70
|
-
writeFileSync(temporary, JSON.stringify(data, null, 2) + '\n', { flag: 'wx' })
|
|
71
|
-
if (!readFileSync(manifest).equals(original)) throw new Error('Manifest changed; retry after review')
|
|
72
|
-
renameSync(temporary, manifest)
|
|
73
|
-
} finally {
|
|
74
|
-
if (existsSync(temporary)) unlinkSync(temporary)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
1
|
+
// Generated compatibility outputs delegate to the single versioned security owner.
|
|
2
|
+
export { applySecurityPatches } from "@nuxtjp/dependency-security";
|
|
@@ -1,40 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
function installedDependencies(root, target, version) {
|
|
7
|
-
root = fs.realpathSync(root);
|
|
8
|
-
const pending = [path.join(root, 'package.json')];
|
|
9
|
-
const seen = new Set();
|
|
10
|
-
const found = new Set();
|
|
11
|
-
while (pending.length) {
|
|
12
|
-
if (seen.size > 5000) throw new Error('Installed dependency graph exceeds verification budget');
|
|
13
|
-
const file = pending.pop();
|
|
14
|
-
if (seen.has(file)) continue;
|
|
15
|
-
seen.add(file);
|
|
16
|
-
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
17
|
-
if (data.name === target) {
|
|
18
|
-
if (version !== undefined && data.version !== version) throw new Error('Dependency version changed; review its security patch');
|
|
19
|
-
found.add(file);
|
|
20
|
-
}
|
|
21
|
-
const request = createRequire(file);
|
|
22
|
-
const names = Object.keys({ ...data.dependencies, ...data.optionalDependencies, ...data.peerDependencies,
|
|
23
|
-
...(file === path.join(root, 'package.json') ? data.devDependencies : {}) });
|
|
24
|
-
for (const name of names) {
|
|
25
|
-
for (const location of request.resolve.paths(name) ?? []) {
|
|
26
|
-
const candidate = path.join(location, name, 'package.json');
|
|
27
|
-
if (!fs.existsSync(candidate)) continue;
|
|
28
|
-
const real = fs.realpathSync(candidate);
|
|
29
|
-
if (!real.startsWith(path.join(root, 'node_modules') + path.sep)) {
|
|
30
|
-
throw new Error('Dependency resolves outside the selected project; select the workspace root');
|
|
31
|
-
}
|
|
32
|
-
pending.push(real);
|
|
33
|
-
break;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
if (found.size === 0) throw new Error('Dependency not present in the installed application graph');
|
|
38
|
-
return [...found].sort();
|
|
39
|
-
}
|
|
40
|
-
module.exports = { installedDependencies };
|
|
1
|
+
"use strict";
|
|
2
|
+
// The shared maintenance tool owns active dependency resolution.
|
|
3
|
+
const resolver = require("@nuxtjp/dependency-security/installed-dependencies");
|
|
4
|
+
module.exports = resolver;
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
diff --git a/lib/depth-guard.js b/lib/depth-guard.js
|
|
2
|
-
new file mode 100644
|
|
3
|
-
--- /dev/null
|
|
4
|
-
+++ b/lib/depth-guard.js
|
|
5
|
-
@@ -0,0 +1,19 @@
|
|
6
|
-
+'use strict';
|
|
7
|
-
+
|
|
8
|
-
+// Wonderland security backport: bound recursive walkers, including caller ASTs.
|
|
9
|
-
+module.exports = ast => {
|
|
10
|
-
+ const pending = [[ast, 0]];
|
|
11
|
-
+ const seen = new Set();
|
|
12
|
-
+ let count = 0;
|
|
13
|
-
+ while (pending.length) {
|
|
14
|
-
+ const [node, depth] = pending.pop();
|
|
15
|
-
+ if (depth > 64 || ++count > 100000 || seen.has(node)) {
|
|
16
|
-
+ throw new SyntaxError('Brace pattern exceeds safe AST bounds');
|
|
17
|
-
+ }
|
|
18
|
-
+ if (!node || typeof node !== 'object') continue;
|
|
19
|
-
+ seen.add(node);
|
|
20
|
-
+ if (Array.isArray(node.nodes)) {
|
|
21
|
-
+ for (const child of node.nodes) pending.push([child, depth + 1]);
|
|
22
|
-
+ }
|
|
23
|
-
+ }
|
|
24
|
-
+};
|
|
25
|
-
diff --git a/lib/parse.js b/lib/parse.js
|
|
26
|
-
--- a/lib/parse.js
|
|
27
|
-
+++ b/lib/parse.js
|
|
28
|
-
@@ -144,6 +144,7 @@
|
|
29
|
-
|
|
30
|
-
if (value === CHAR_LEFT_PARENTHESES) {
|
|
31
|
-
block = push({ type: 'paren', nodes: [] });
|
|
32
|
-
+ if (stack.length >= 64) throw new SyntaxError('Brace pattern exceeds safe nesting depth');
|
|
33
|
-
stack.push(block);
|
|
34
|
-
push({ type: 'text', value });
|
|
35
|
-
continue;
|
|
36
|
-
@@ -210,6 +211,7 @@
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
block = push(brace);
|
|
40
|
-
+ if (stack.length >= 64) throw new SyntaxError('Brace pattern exceeds safe nesting depth');
|
|
41
|
-
stack.push(block);
|
|
42
|
-
push({ type: 'open', value });
|
|
43
|
-
continue;
|
|
44
|
-
diff --git a/lib/compile.js b/lib/compile.js
|
|
45
|
-
--- a/lib/compile.js
|
|
46
|
-
+++ b/lib/compile.js
|
|
47
|
-
@@ -1,9 +1,12 @@
|
|
48
|
-
'use strict';
|
|
49
|
-
+
|
|
50
|
-
+const assertDepth = require('./depth-guard');
|
|
51
|
-
|
|
52
|
-
const fill = require('fill-range');
|
|
53
|
-
const utils = require('./utils');
|
|
54
|
-
|
|
55
|
-
const compile = (ast, options = {}) => {
|
|
56
|
-
+ assertDepth(ast);
|
|
57
|
-
const walk = (node, parent = {}) => {
|
|
58
|
-
const invalidBlock = utils.isInvalidBrace(parent);
|
|
59
|
-
const invalidNode = node.invalid === true && options.escapeInvalid === true;
|
|
60
|
-
diff --git a/lib/expand.js b/lib/expand.js
|
|
61
|
-
--- a/lib/expand.js
|
|
62
|
-
+++ b/lib/expand.js
|
|
63
|
-
@@ -1,4 +1,6 @@
|
|
64
|
-
'use strict';
|
|
65
|
-
+
|
|
66
|
-
+const assertDepth = require('./depth-guard');
|
|
67
|
-
|
|
68
|
-
const fill = require('fill-range');
|
|
69
|
-
const stringify = require('./stringify');
|
|
70
|
-
@@ -31,6 +33,7 @@
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const expand = (ast, options = {}) => {
|
|
74
|
-
+ assertDepth(ast);
|
|
75
|
-
const rangeLimit = options.rangeLimit === undefined ? 1000 : options.rangeLimit;
|
|
76
|
-
|
|
77
|
-
const walk = (node, parent = {}) => {
|
|
78
|
-
diff --git a/lib/stringify.js b/lib/stringify.js
|
|
79
|
-
--- a/lib/stringify.js
|
|
80
|
-
+++ b/lib/stringify.js
|
|
81
|
-
@@ -1,8 +1,11 @@
|
|
82
|
-
'use strict';
|
|
83
|
-
+
|
|
84
|
-
+const assertDepth = require('./depth-guard');
|
|
85
|
-
|
|
86
|
-
const utils = require('./utils');
|
|
87
|
-
|
|
88
|
-
module.exports = (ast, options = {}) => {
|
|
89
|
-
+ assertDepth(ast);
|
|
90
|
-
const stringify = (node, parent = {}) => {
|
|
91
|
-
const invalidBlock = options.escapeInvalid && utils.isInvalidBrace(parent);
|
|
92
|
-
const invalidNode = node.invalid === true && options.escapeInvalid === true;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
diff --git a/lib/rsa.js b/lib/rsa.js
|
|
2
|
-
--- a/lib/rsa.js
|
|
3
|
-
+++ b/lib/rsa.js
|
|
4
|
-
@@ -1172,7 +1172,15 @@
|
|
5
|
-
var capture = {};
|
|
6
|
-
var errors = [];
|
|
7
|
-
if(!asn1.validate(obj, digestInfoValidator, capture, errors) ||
|
|
8
|
-
- obj.value.length !== 2) {
|
|
9
|
-
+ obj.value.length !== 2 ||
|
|
10
|
-
+ // Wonderland security backport: AlgorithmIdentifier has one OID
|
|
11
|
-
+ // and, optionally, exactly one empty primitive NULL parameter.
|
|
12
|
-
+ obj.value[0].value.length < 1 || obj.value[0].value.length > 2 ||
|
|
13
|
-
+ (obj.value[0].value.length === 2 &&
|
|
14
|
-
+ (obj.value[0].value[1].tagClass !== asn1.Class.UNIVERSAL ||
|
|
15
|
-
+ obj.value[0].value[1].type !== asn1.Type.NULL ||
|
|
16
|
-
+ obj.value[0].value[1].constructed !== false ||
|
|
17
|
-
+ obj.value[0].value[1].value !== ''))) {
|
|
18
|
-
var error = new Error(
|
|
19
|
-
'ASN.1 object does not contain a valid RSASSA-PKCS1-v1_5 ' +
|
|
20
|
-
'DigestInfo value.');
|