@nuxtjp/local-runtime 0.1.0 → 0.1.2
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 +71 -0
- package/README.md +22 -34
- package/dist/module.json +1 -1
- package/package.json +24 -9
- package/security/README.md +17 -0
- package/security/apply.mjs +19 -0
- package/security/dependency-security.check.cjs +51 -0
- package/security/patches/braces@3.0.3.patch +92 -0
- package/security/patches/node-forge@1.4.0.patch +20 -0
- package/security/project.mjs +61 -0
- package/security/resolved-dependency.cjs +40 -0
package/README.ja.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @nuxtjp/local-runtime
|
|
2
|
+
|
|
3
|
+
[English](README.md)
|
|
4
|
+
|
|
5
|
+
利用者の明示操作で、ブラウザ画面から設定済みのローカルRustランタイムへ接続します。
|
|
6
|
+
|
|
7
|
+
## 導入と使い方
|
|
8
|
+
|
|
9
|
+
`0.1.1`はnpmで公開済みです。Node `^22.19.0`または`^24.11.0`、Nuxt `^4.5.1`、Vue `^3.5.40`を使用してください。
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install @nuxtjp/local-runtime@0.1.0
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
export default defineNuxtConfig({
|
|
17
|
+
modules: ['@nuxtjp/local-runtime'],
|
|
18
|
+
nuxtJpLocalRuntime: {
|
|
19
|
+
endpoint: 'http://127.0.0.1:37843'
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```vue
|
|
25
|
+
<script setup lang="ts">
|
|
26
|
+
const runtime = useLocalRuntime()
|
|
27
|
+
|
|
28
|
+
async function connect(): Promise<void> {
|
|
29
|
+
await runtime.connect(['workflow.issue.read'])
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<NuxtJpLocalRuntimeStatus :state="runtime.state.value" />
|
|
35
|
+
<button type="button" @click="connect">
|
|
36
|
+
ローカルランタイムへ接続
|
|
37
|
+
</button>
|
|
38
|
+
</template>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
接続先のhostは、ページのoriginを別途許可する必要があります。要求するcapabilityは利用アプリが指定します。Browser Local Network Accessの許可は、利用者とブラウザが管理します。
|
|
42
|
+
|
|
43
|
+
## 責務と公開インターフェース
|
|
44
|
+
|
|
45
|
+
- 接続を始めるのは、利用者が明示的に操作した後だけです。
|
|
46
|
+
- 上限付きloopback応答を検証し、接続状態を表示します。
|
|
47
|
+
- Nuxtの初期表示はidleにします。build中に接続せず、`connect()`はブラウザでの明示操作として実行します。
|
|
48
|
+
|
|
49
|
+
許可originとcapabilityは利用アプリが指定します。リソースの自動探索や認証情報の永続保存は行いません。
|
|
50
|
+
`/client`と`/guards`はJavaScriptと型宣言を配布します。`/types`は型専用importのための既存TypeScript定義、`/schema`はJSON schemaを公開します。
|
|
51
|
+
|
|
52
|
+
## 開発と検証
|
|
53
|
+
|
|
54
|
+
`pnpm@10.29.3`と、`package.json`のenginesを満たすNodeを使用してください。
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
pnpm install --frozen-lockfile
|
|
58
|
+
pnpm typecheck
|
|
59
|
+
pnpm test
|
|
60
|
+
pnpm build
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
検証入力はNuxt/kit/schema `4.5.1`、Vue `3.5.43`、Vitest `4.1.11`に固定しています。互換性のある依存修正でadvisoryを減らしましたが、残るadvisoryと未検証環境はリリース前に評価してください。Nodeとhostの最低条件はNuxtに合わせて引き上げています。
|
|
64
|
+
module/client API、loopback endpointの規則、明示接続の動作、ライセンス条件は維持しています。
|
|
65
|
+
ローカルTGZの試験は配布物の動作確認であり、registryの配布確認とは別です。操作・配備・公開には、それぞれの権限と設定が必要です。
|
|
66
|
+
|
|
67
|
+
## 文書とライセンス
|
|
68
|
+
|
|
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
|
+
|
|
71
|
+
コードは[Apache-2.0](LICENSE)です。[NOTICE](NOTICE)と[以前の許諾](LICENSE-PREVIOUS)を保持してください。
|
package/README.md
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
# @nuxtjp/local-runtime
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[日本語](README.ja.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Connect a browser interface to one explicitly configured local Rust runtime, after an explicit user operation.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Install and use
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
以下は現行 API の利用例です。npm 上の配布状況を確認してから、対応版を install してください。ローカル TGZ の試験成功は、registry 配布の確認には含めません。
|
|
9
|
+
Version `0.1.1` is published on npm. Use Node `^22.19.0` or `^24.11.0`, Nuxt `^4.5.1` and Vue `^3.5.40`.
|
|
12
10
|
|
|
13
11
|
```sh
|
|
14
12
|
npm install @nuxtjp/local-runtime@0.1.0
|
|
15
13
|
```
|
|
16
14
|
|
|
17
|
-
Node ^22.19.0 / ^24.11.0、Nuxt ^4.5.1、Vue ^3.5.40 が必要です。
|
|
18
|
-
|
|
19
|
-
## Nuxt usage
|
|
20
|
-
|
|
21
15
|
```ts
|
|
22
16
|
export default defineNuxtConfig({
|
|
23
17
|
modules: ['@nuxtjp/local-runtime'],
|
|
@@ -39,34 +33,25 @@ async function connect(): Promise<void> {
|
|
|
39
33
|
<template>
|
|
40
34
|
<NuxtJpLocalRuntimeStatus :state="runtime.state.value" />
|
|
41
35
|
<button type="button" @click="connect">
|
|
42
|
-
|
|
36
|
+
Connect to local runtime
|
|
43
37
|
</button>
|
|
44
38
|
</template>
|
|
45
39
|
```
|
|
46
40
|
|
|
47
|
-
The host must separately allow the page origin. Browser Local Network Access
|
|
48
|
-
permission remains a user- and browser-controlled gate.
|
|
49
|
-
|
|
50
|
-
## English
|
|
41
|
+
The host must separately allow the page origin. The application supplies the requested capabilities. Browser Local Network Access permission remains controlled by the user and browser.
|
|
51
42
|
|
|
52
|
-
|
|
43
|
+
## Scope and interfaces
|
|
53
44
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
- Start a connection only after an explicit user operation.
|
|
45
|
+
- Start connections only after an explicit user operation.
|
|
57
46
|
- Validate bounded loopback responses and display connection state.
|
|
47
|
+
- Keep the initial Nuxt-rendered state idle. A build must not connect to a runtime; `connect()` remains an explicit browser action.
|
|
58
48
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
The application supplies the allowed origin and capabilities. There is no automatic resource discovery or persistent credential storage.
|
|
49
|
+
The application supplies allowed origins and capabilities. The module does not discover resources automatically or persist credentials.
|
|
50
|
+
The `/client` and `/guards` subpaths ship JavaScript and declarations. `/types` exposes the existing raw TypeScript definitions for type-only imports; `/schema` exposes the JSON schema.
|
|
62
51
|
|
|
63
|
-
|
|
64
|
-
The client and guards subpaths ship JavaScript and declarations; /types exposes the existing raw TypeScript definitions for type-only imports. /schema exposes the JSON schema.
|
|
65
|
-
A Nuxt build must render the initial idle state without connecting to a runtime; connect() remains an explicit browser action.
|
|
52
|
+
## Development and verification
|
|
66
53
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Use `pnpm@10.29.3` and the Node.js version declared in `engines` in `package.json`. Run from this repository:
|
|
54
|
+
Use `pnpm@10.29.3` and a Node version accepted by `package.json`:
|
|
70
55
|
|
|
71
56
|
```sh
|
|
72
57
|
pnpm install --frozen-lockfile
|
|
@@ -75,13 +60,16 @@ pnpm test
|
|
|
75
60
|
pnpm build
|
|
76
61
|
```
|
|
77
62
|
|
|
78
|
-
|
|
63
|
+
The verification inputs pin Nuxt/kit/schema to `4.5.2`, Vue to `3.5.43` and Vitest to `4.1.11`. Compatible dependency fixes remove advisories; assess remaining advisories and untested environments before a release. Node and host compatibility floors were raised to match Nuxt.
|
|
64
|
+
The module/client API, loopback endpoint rules, explicit connection behavior and license terms are retained.
|
|
65
|
+
A local TGZ test proves archive behavior, not registry availability. Operations, deployment and publication require their own permissions and settings.
|
|
66
|
+
|
|
67
|
+
## Documentation and license
|
|
79
68
|
|
|
80
|
-
[Usage guide](https://github.com/nuxtjp/nuxt-local-runtime/blob/main/docs/getting-started.md)
|
|
69
|
+
[Usage guide](https://github.com/nuxtjp/nuxt-local-runtime/blob/main/docs/getting-started.md) · [Schemas](schemas) · [Source](https://github.com/nuxtjp/nuxt-local-runtime/tree/main/src) · [Tests](https://github.com/nuxtjp/nuxt-local-runtime/tree/main/test) · [Contributing](https://github.com/nuxtjp/nuxt-local-runtime/blob/main/CONTRIBUTING.md) · [Security reporting](https://github.com/nuxtjp/nuxt-local-runtime/blob/main/SECURITY.md)
|
|
81
70
|
|
|
82
|
-
[
|
|
71
|
+
Code: [Apache-2.0](LICENSE). Retain [NOTICE](NOTICE) and the prior grants in [LICENSE-PREVIOUS](LICENSE-PREVIOUS).
|
|
83
72
|
|
|
84
|
-
##
|
|
73
|
+
## Consumer dependency security
|
|
85
74
|
|
|
86
|
-
|
|
87
|
-
The module/client API, loopback endpoint rules, explicit connection behavior and license terms are retained. Remaining advisories and untested environments must be assessed before release.
|
|
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.
|
package/dist/module.json
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuxtjp/local-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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",
|
|
7
8
|
"engines": {
|
|
8
9
|
"node": "^22.19.0 || ^24.11.0",
|
|
9
10
|
"pnpm": ">=10 <11"
|
|
@@ -15,7 +16,9 @@
|
|
|
15
16
|
"LICENSE",
|
|
16
17
|
"README.md",
|
|
17
18
|
"NOTICE",
|
|
18
|
-
"LICENSE-PREVIOUS"
|
|
19
|
+
"LICENSE-PREVIOUS",
|
|
20
|
+
"README.ja.md",
|
|
21
|
+
"security"
|
|
19
22
|
],
|
|
20
23
|
"main": "./dist/module.mjs",
|
|
21
24
|
"types": "./dist/types.d.mts",
|
|
@@ -38,19 +41,27 @@
|
|
|
38
41
|
},
|
|
39
42
|
"./schema": "./schemas/local-runtime-v1.schema.json"
|
|
40
43
|
},
|
|
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
|
+
},
|
|
41
52
|
"peerDependencies": {
|
|
42
53
|
"nuxt": "^4.5.1",
|
|
43
54
|
"vue": "^3.5.40"
|
|
44
55
|
},
|
|
45
56
|
"dependencies": {
|
|
46
|
-
"@nuxt/kit": "4.5.
|
|
57
|
+
"@nuxt/kit": "4.5.2",
|
|
47
58
|
"@nuxt/schema": "4.5.1"
|
|
48
59
|
},
|
|
49
60
|
"devDependencies": {
|
|
50
61
|
"@nuxt/module-builder": "1.0.2",
|
|
51
62
|
"@types/node": "24.12.0",
|
|
52
63
|
"ajv": "8.20.0",
|
|
53
|
-
"nuxt": "4.5.
|
|
64
|
+
"nuxt": "4.5.2",
|
|
54
65
|
"typescript": "5.9.3",
|
|
55
66
|
"vitest": "4.1.11",
|
|
56
67
|
"vue": "3.5.43",
|
|
@@ -69,9 +80,13 @@
|
|
|
69
80
|
"registry": "https://registry.npmjs.org",
|
|
70
81
|
"provenance": true
|
|
71
82
|
},
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
"pnpm": {
|
|
84
|
+
"overrides": {
|
|
85
|
+
"@nuxt/devtools>simple-git": "4.0.2"
|
|
86
|
+
},
|
|
87
|
+
"patchedDependencies": {
|
|
88
|
+
"braces@3.0.3": "patches/braces@3.0.3.patch",
|
|
89
|
+
"node-forge@1.4.0": "patches/node-forge@1.4.0.patch"
|
|
90
|
+
}
|
|
76
91
|
}
|
|
77
|
-
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Dependency security backports
|
|
2
|
+
|
|
3
|
+
Two upstream dependencies have no fixed release: braces 3.0.3 (GHSA-vfj7-8cjw-p6xm) and node-forge 1.4.0 (GHSA-86w9-cpqp-85rv). The included patches bound brace AST recursion and reject malformed RSA DigestAlgorithm parameters. Regression tests cover ordinary behavior and the malicious input shapes.
|
|
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.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
node node_modules/@nuxtjp/local-runtime/security/apply.mjs --project-root . --apply
|
|
9
|
+
pnpm install --no-frozen-lockfile
|
|
10
|
+
pnpm install --frozen-lockfile
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Commit the generated `.nuxtjp-security/*.patch`, `package.json`, and lockfile in your application. The command does not run installs, access the network, or replace an existing conflicting patch. It preserves unrelated pnpm settings. Each root application must apply the patches, including CI checkouts.
|
|
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.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Apply reviewed Nuxt dependency backports to an explicitly selected pnpm project. */
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { dirname } from 'node:path'
|
|
5
|
+
import { applySecurityPatches } from './project.mjs'
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2)
|
|
8
|
+
if (args.length !== 3 || args[0] !== '--project-root' || args[2] !== '--apply') {
|
|
9
|
+
console.error('Usage: node apply.mjs --project-root PROJECT --apply')
|
|
10
|
+
process.exitCode = 2
|
|
11
|
+
} else {
|
|
12
|
+
try {
|
|
13
|
+
applySecurityPatches(args[1], dirname(fileURLToPath(import.meta.url)))
|
|
14
|
+
console.log('Reviewed backports configured. Run pnpm install --no-frozen-lockfile to refresh the lockfile.')
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error(error.message)
|
|
17
|
+
process.exitCode = 1
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const assert = require('node:assert/strict');
|
|
2
|
+
const { test } = require('node:test');
|
|
3
|
+
const { createRequire } = require('node:module');
|
|
4
|
+
const { generateKeyPairSync, sign, privateEncrypt, constants } = require('node:crypto');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const fs = require('node:fs');
|
|
7
|
+
function dependency(name, version) {
|
|
8
|
+
const explicit = process.env['SECURITY_TEST_' + name.toUpperCase().replace('-', '_')];
|
|
9
|
+
if (explicit) return require(path.resolve(explicit));
|
|
10
|
+
const { installedDependencies } = require('./resolved-dependency.cjs');
|
|
11
|
+
const matches = installedDependencies(process.cwd(), name, version);
|
|
12
|
+
assert.equal(matches.length, 1, 'Expected one active locked dependency implementation');
|
|
13
|
+
return createRequire(matches[0])(name);
|
|
14
|
+
}
|
|
15
|
+
const braces = dependency('braces', '3.0.3');
|
|
16
|
+
const forge = dependency('node-forge', '1.4.0');
|
|
17
|
+
test('brace compilation, expansion and stringification retain ordinary behavior', () => {
|
|
18
|
+
assert.equal(braces.compile('src/{a,b}.vue'), 'src/(a|b).vue');
|
|
19
|
+
assert.deepEqual(braces.expand('src/{a,b}.vue'), ['src/a.vue', 'src/b.vue']);
|
|
20
|
+
assert.equal(braces.stringify(braces.parse('src/{a,b}.vue')), 'src/{a,b}.vue');
|
|
21
|
+
assert.equal(braces.compile('src/\\{a,b\\}.vue'), 'src/{a,b}.vue');
|
|
22
|
+
});
|
|
23
|
+
test('deep brace, parenthesis and caller AST inputs reject before stack exhaustion', () => {
|
|
24
|
+
for (const pattern of ['{'.repeat(6000) + 'a,b' + '}'.repeat(6000), '('.repeat(6000) + 'x' + ')'.repeat(6000), '{'.repeat(6000) + 'x']) {
|
|
25
|
+
for (const fn of [braces.compile, braces.expand, braces.stringify, braces.parse]) assert.throws(() => fn(pattern), SyntaxError);
|
|
26
|
+
}
|
|
27
|
+
let ast = { type: 'text', value: 'x' };
|
|
28
|
+
for (let i = 0; i < 6000; i++) ast = { type: 'root', nodes: [ast] };
|
|
29
|
+
for (const fn of [braces.compile, braces.expand, braces.stringify]) assert.throws(() => fn(ast), SyntaxError);
|
|
30
|
+
const cyclic = { type: 'root', nodes: [] }; cyclic.nodes.push(cyclic);
|
|
31
|
+
assert.throws(() => braces.compile(cyclic), SyntaxError);
|
|
32
|
+
});
|
|
33
|
+
test('RSA verifier accepts standard signatures and rejects extra or malformed DigestAlgorithm elements', () => {
|
|
34
|
+
const keys = generateKeyPairSync('rsa', { modulusLength: 2048 });
|
|
35
|
+
const publicKey = forge.pki.publicKeyFromPem(keys.publicKey.export({ type: 'spki', format: 'pem' }));
|
|
36
|
+
const data = Buffer.from('never-issued security regression fixture');
|
|
37
|
+
const md = forge.md.sha256.create(); md.update(data.toString('binary'));
|
|
38
|
+
const digest = md.digest().getBytes();
|
|
39
|
+
assert.equal(publicKey.verify(digest, sign('sha256', data, keys.privateKey).toString('binary')), true);
|
|
40
|
+
const a = forge.asn1;
|
|
41
|
+
const oid = () => a.create(a.Class.UNIVERSAL, a.Type.OID, false, a.oidToDer(forge.oids.sha256).getBytes());
|
|
42
|
+
const nil = value => a.create(a.Class.UNIVERSAL, a.Type.NULL, false, value);
|
|
43
|
+
for (const parameters of [[oid(), nil(''), oid()], [oid(), a.create(a.Class.UNIVERSAL, a.Type.SEQUENCE, true, [oid()])], [oid(), nil('x')]]) {
|
|
44
|
+
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)]);
|
|
45
|
+
const encoded = Buffer.from(a.toDer(info).getBytes(), 'binary');
|
|
46
|
+
const signature = privateEncrypt({ key: keys.privateKey, padding: constants.RSA_PKCS1_PADDING }, encoded).toString('binary');
|
|
47
|
+
let accepted = false;
|
|
48
|
+
try { accepted = publicKey.verify(digest, signature); } catch { /* Rejection is the expected result for a malformed signature. */ }
|
|
49
|
+
assert.equal(accepted, false, 'Malformed algorithm must not verify');
|
|
50
|
+
}
|
|
51
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
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;
|
|
@@ -0,0 +1,20 @@
|
|
|
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.');
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Configure caller-owned pnpm backports without executing installs or changing unrelated settings. */
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, mkdirSync, writeFileSync, renameSync, unlinkSync, realpathSync } from 'node:fs'
|
|
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']
|
|
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 dir = join(root, '.nuxtjp-security')
|
|
31
|
+
if (existsSync(dir) && (lstatSync(dir).isSymbolicLink() || !lstatSync(dir).isDirectory())) {
|
|
32
|
+
throw new Error('Security patch directory must be a real directory')
|
|
33
|
+
}
|
|
34
|
+
const inputs = patches.map(name => {
|
|
35
|
+
const relative = `.nuxtjp-security/${name}.patch`
|
|
36
|
+
if (existing[name] !== undefined && existing[name] !== relative) {
|
|
37
|
+
throw new Error('An existing dependency patch conflicts; review it before applying')
|
|
38
|
+
}
|
|
39
|
+
const bytes = regular(join(source, 'patches', `${name}.patch`))
|
|
40
|
+
const target = join(root, relative)
|
|
41
|
+
if (existsSync(target) && !regular(target).equals(bytes)) {
|
|
42
|
+
throw new Error('An existing patch differs; no files were overwritten')
|
|
43
|
+
}
|
|
44
|
+
return { name, relative, bytes, target }
|
|
45
|
+
})
|
|
46
|
+
if (!readFileSync(manifest).equals(original)) throw new Error('Manifest changed during preparation')
|
|
47
|
+
mkdirSync(dir, { recursive: true })
|
|
48
|
+
for (const patch of inputs) {
|
|
49
|
+
if (!existsSync(patch.target)) writeFileSync(patch.target, patch.bytes, { flag: 'wx' })
|
|
50
|
+
existing[patch.name] = patch.relative
|
|
51
|
+
}
|
|
52
|
+
data.pnpm = { ...pnpm, patchedDependencies: existing }
|
|
53
|
+
const temporary = join(root, `.nuxtjp-manifest-${randomUUID()}.tmp`)
|
|
54
|
+
try {
|
|
55
|
+
writeFileSync(temporary, JSON.stringify(data, null, 2) + '\n', { flag: 'wx' })
|
|
56
|
+
if (!readFileSync(manifest).equals(original)) throw new Error('Manifest changed; retry after review')
|
|
57
|
+
renameSync(temporary, manifest)
|
|
58
|
+
} finally {
|
|
59
|
+
if (existsSync(temporary)) unlinkSync(temporary)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Resolve a dependency through the installed application graph, ignoring unused store entries. */
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { createRequire } = require('node:module');
|
|
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 (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 };
|