@nuxtjp/localized-site 0.1.1 → 0.1.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/README.md CHANGED
@@ -64,3 +64,7 @@ pnpm pack
64
64
 
65
65
  Code: [Apache-2.0](LICENSE). [LICENSE-PREVIOUS](LICENSE-PREVIOUS) retains prior grants, and [NOTICE](NOTICE) preserves attribution.
66
66
  [LICENSE-ASSETS](LICENSE-ASSETS) retains separate terms for covered brand/content assets. It does not change the code license or grant rights to third-party or site-owned content.
67
+
68
+ ## Consumer dependency security
69
+
70
+ 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.
@@ -1,7 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { runValidatorCli } from '../validator/content/cli.js'
3
3
 
4
- process.exitCode = await runValidatorCli(process.argv.slice(2), {
4
+ if (process.argv[2] === 'site') {
5
+ try {
6
+ const { runOrganizationCli } = await import('../organization/cli.mjs')
7
+ process.exitCode = await runOrganizationCli(process.argv.slice(3))
8
+ } catch (error) {
9
+ console.error(error.message)
10
+ process.exitCode = 1
11
+ }
12
+ } else process.exitCode = await runValidatorCli(process.argv.slice(2), {
5
13
  out: value => process.stdout.write(`${value}\n`),
6
14
  error: value => process.stderr.write(`${value}\n`)
7
15
  })
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^4.5.1"
6
6
  },
7
- "version": "0.1.1",
7
+ "version": "0.1.3",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "unknown"
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <NuxtLayout>
3
+ <NuxtPage />
4
+ </NuxtLayout>
5
+ </template>
@@ -0,0 +1,58 @@
1
+ import assert from 'node:assert/strict'
2
+ import { spawn } from 'node:child_process'
3
+ import { readFileSync } from 'node:fs'
4
+ import { createRequire } from 'node:module'
5
+ import { join, dirname } from 'node:path'
6
+ import { organizationSites, siteDirectory, checkSitePublication } from './index.mjs'
7
+ import { loadSiteConfig } from '../validator/content/index.js'
8
+ import { localizedHead, siteRoutes } from '../dist/runtime/core/index.js'
9
+
10
+ export async function runOrganizationCli(args, root = process.cwd()) {
11
+ const [command, key, ...extra] = args
12
+ if (extra.length || !['list', 'check', 'dev', 'build', 'typecheck', 'preview'].includes(command)) {
13
+ throw new Error('usage: nuxtjp-localized-site site <list|check|dev|build|typecheck|preview> [SITE_ID]')
14
+ }
15
+ const keys = organizationSites(root)
16
+ if (key !== undefined) siteDirectory(root, key)
17
+ if (command === 'list') { console.log(JSON.stringify(keys)); return 0 }
18
+ const selected = key === undefined ? keys : [key]
19
+ const all = keys.map(id => loadSiteConfig(siteDirectory(root, id)))
20
+ for (const field of ['siteId', 'origin']) {
21
+ assert.equal(new Set(all.map(config => config[field])).size, all.length, `Duplicate ${field}`)
22
+ }
23
+ for (const id of selected) {
24
+ const config = loadSiteConfig(siteDirectory(root, id))
25
+ const routes = siteRoutes(config)
26
+ assert.equal(config.externalActions, false)
27
+ assert.equal(config.defaultLocale, config.audienceScope === 'japan' ? 'ja' : 'en')
28
+ assert.equal(new Set(routes).size, routes.length)
29
+ assert.equal(routes.length, 8)
30
+ for (const route of routes) {
31
+ const head = localizedHead(route, config)
32
+ assert.ok(head.link.some(link => link.rel === 'canonical' && new URL(link.href).origin === new URL(config.origin).origin))
33
+ }
34
+ checkSitePublication(root, id)
35
+ if (command === 'check') console.log(JSON.stringify({ site: id, siteId: config.siteId, routes: routes.length }))
36
+ }
37
+ if (command === 'check') return 0
38
+ if (key === undefined) throw new Error('SITE_ID is required for Nuxt commands')
39
+ // Resolve the installed consumer CLI without a shell or a package-manager download.
40
+ const require = createRequire(join(root, 'package.json'))
41
+ const manifestPath = require.resolve('nuxt/package.json')
42
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
43
+ const bin = typeof manifest.bin === 'string' ? manifest.bin : manifest.bin.nuxt
44
+ if (!bin) throw new Error('Installed Nuxt CLI not found')
45
+ const run = (file, args) => new Promise((resolve, reject) => {
46
+ const child = spawn(process.execPath, [file, ...args], {
47
+ cwd: root, stdio: 'inherit', env: { ...process.env, SITE_ID: key, NUXT_TELEMETRY_DISABLED: '1' }
48
+ })
49
+ child.on('error', reject)
50
+ child.on('exit', (code, signal) => resolve(signal ? 1 : code ?? 1))
51
+ })
52
+ if (command === 'typecheck') {
53
+ const prepared = await run(join(dirname(manifestPath), bin), ['prepare'])
54
+ if (prepared !== 0) return prepared
55
+ return await run(require.resolve('vue-tsc/bin/vue-tsc.js'), ['--noEmit', '--project', join(root, '.nuxt', key, 'tsconfig.json')])
56
+ }
57
+ return await run(join(dirname(manifestPath), bin), [command === 'build' ? 'generate' : command])
58
+ }
@@ -0,0 +1,12 @@
1
+ export function organizationSites(root: string): string[];
2
+ export function siteDirectory(root: string, key: string): string;
3
+ export function organizationSite(configUrl: string, key?: string): {
4
+ srcDir: string;
5
+ devtools: { enabled: boolean };
6
+ buildDir: string;
7
+ dir: { public: string };
8
+ modules: [string, { contentRoot: string }][];
9
+ nitro: { output: { dir: string }; prerender: { failOnError: boolean } };
10
+ };
11
+ export function checkPublicCatalog(policy: unknown, catalog: unknown): { scope: string; entries: number };
12
+ export function checkSitePublication(root: string, key: string): { scope: string; entries: number } | null;
@@ -0,0 +1,77 @@
1
+ import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs'
2
+ import { dirname, join, resolve } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import assert from 'node:assert/strict'
5
+
6
+ const keyPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
7
+
8
+ function directory(path) {
9
+ if (!existsSync(path) || lstatSync(path).isSymbolicLink() || !lstatSync(path).isDirectory()) {
10
+ throw new Error('Expected a real organization site directory')
11
+ }
12
+ return realpathSync(path)
13
+ }
14
+
15
+ export function organizationSites(root) {
16
+ const sites = directory(join(directory(root), 'sites'))
17
+ const keys = readdirSync(sites).sort()
18
+ if (!keys.length) throw new Error('No organization sites configured')
19
+ for (const key of keys) siteDirectory(root, key)
20
+ return keys
21
+ }
22
+
23
+ export function siteDirectory(root, key) {
24
+ if (typeof key !== 'string' || !keyPattern.test(key)) throw new Error('Invalid SITE_ID')
25
+ const sites = directory(join(directory(root), 'sites'))
26
+ const path = directory(join(sites, key))
27
+ if (dirname(path) !== sites) throw new Error('Site must remain within sites/')
28
+ const config = join(path, 'site.config.json')
29
+ if (!existsSync(config) || lstatSync(config).isSymbolicLink() || !lstatSync(config).isFile()) {
30
+ throw new Error('Site configuration must be a regular file')
31
+ }
32
+ for (const name of ['package.json', 'nuxt.config.ts', 'app.vue', 'layouts', 'pages', 'components']) {
33
+ if (existsSync(join(path, name))) throw new Error('Site-specific Nuxt applications/layouts are not permitted')
34
+ }
35
+ return path
36
+ }
37
+
38
+ export function organizationSite(configUrl, key = process.env.SITE_ID) {
39
+ const root = dirname(fileURLToPath(configUrl))
40
+ const contentRoot = siteDirectory(root, key)
41
+ return {
42
+ srcDir: dirname(fileURLToPath(import.meta.url)),
43
+ devtools: { enabled: false },
44
+ buildDir: join(root, '.nuxt', key),
45
+ dir: { public: join(contentRoot, 'public') },
46
+ modules: [['@nuxtjp/localized-site', { contentRoot }]],
47
+ nitro: {
48
+ output: { dir: join(root, '.output', key) },
49
+ prerender: { failOnError: true }
50
+ }
51
+ }
52
+ }
53
+
54
+ export function checkPublicCatalog(policy, catalog) {
55
+ assert.equal(policy.scope, 'public')
56
+ assert.ok(Array.isArray(policy.repository_ids) && policy.repository_ids.length > 0)
57
+ assert.equal(new Set(policy.repository_ids).size, policy.repository_ids.length)
58
+ assert.ok(Array.isArray(catalog.entries))
59
+ assert.deepEqual(catalog.entries.map(entry => entry.repository_id).sort(), [...policy.repository_ids].sort(),
60
+ 'Catalog visibility gate failed: entries must match the reviewed public allowlist exactly')
61
+ return { scope: 'public', entries: catalog.entries.length }
62
+ }
63
+
64
+ export function checkSitePublication(root, key) {
65
+ const path = siteDirectory(root, key)
66
+ const policy = join(path, 'public-package-policy.json')
67
+ const catalog = join(path, 'public/catalog/v2/index.json')
68
+ if (existsSync(policy) || existsSync(catalog)) {
69
+ for (const file of [policy, catalog]) {
70
+ if (!existsSync(file) || lstatSync(file).isSymbolicLink() || !lstatSync(file).isFile()) {
71
+ throw new Error('Public catalog and reviewed policy must both exist as regular files')
72
+ }
73
+ }
74
+ return checkPublicCatalog(JSON.parse(readFileSync(policy, 'utf8')), JSON.parse(readFileSync(catalog, 'utf8')))
75
+ }
76
+ return null
77
+ }
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readdirSync, readFileSync } from 'node:fs'
3
+ import { join, resolve } from 'node:path'
4
+ import { spawnSync } from 'node:child_process'
5
+ import { organizationSites } from './index.mjs'
6
+
7
+ const [command, input] = process.argv.slice(2)
8
+ if (!['module', 'verify'].includes(command) || !input || process.argv.length !== 4) {
9
+ throw new Error('usage: workspace.mjs <module|verify> WORKSPACE_ROOT')
10
+ }
11
+ const root = resolve(input)
12
+ const moduleRoot = join(root, 'organizations/nuxtjp/nuxt-localized-site')
13
+ if (command === 'module') console.log(moduleRoot)
14
+ else {
15
+ for (const organization of readdirSync(join(root, 'organizations'), { withFileTypes: true })) {
16
+ if (!organization.isDirectory() || organization.isSymbolicLink()) continue
17
+ const owner = join(root, 'organizations', organization.name)
18
+ for (const entry of readdirSync(owner, { withFileTypes: true })) {
19
+ if (!entry.isDirectory() || entry.isSymbolicLink()) continue
20
+ const repository = join(owner, entry.name)
21
+ if (!existsSync(join(repository, 'sites')) || !existsSync(join(repository, 'package.json'))) continue
22
+ const manifest = JSON.parse(readFileSync(join(repository, 'package.json'), 'utf8'))
23
+ if (!manifest.dependencies?.['@nuxtjp/localized-site']) continue
24
+ const commands = [['test'], ...organizationSites(repository).flatMap(key => [['site', 'typecheck', key], ['site', 'build', key]])]
25
+ for (const args of commands) {
26
+ const result = spawnSync('pnpm', args, { cwd: repository, stdio: 'inherit' })
27
+ if (result.status !== 0) process.exit(result.status ?? 1)
28
+ }
29
+ }
30
+ }
31
+ }
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@nuxtjp/localized-site",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Contract-first Japanese and English public-site module for Nuxt",
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"
@@ -19,7 +20,10 @@
19
20
  "NOTICE",
20
21
  "LICENSE-ASSETS",
21
22
  "LICENSE-PREVIOUS",
22
- "README.ja.md"
23
+ "README.ja.md",
24
+ "organization",
25
+ "security",
26
+ "ASSET-SCOPE.md"
23
27
  ],
24
28
  "bin": {
25
29
  "nuxtjp-localized-site": "./bin/nuxtjp-localized-site.mjs"
@@ -39,20 +43,36 @@
39
43
  "types": "./validator/content/index.d.ts",
40
44
  "import": "./validator/content/index.js"
41
45
  },
42
- "./schema": "./schemas/localized-site-v1.schema.json"
46
+ "./schema": "./schemas/localized-site-v1.schema.json",
47
+ "./organization": {
48
+ "types": "./organization/index.d.mts",
49
+ "import": "./organization/index.mjs"
50
+ }
51
+ },
52
+ "scripts": {
53
+ "build:module": "nuxt-module-build build",
54
+ "build:validator": "tsc -p tsconfig.validator.json",
55
+ "build": "pnpm run build:validator && pnpm run build:module && nuxt build playground",
56
+ "typecheck": "vue-tsc --noEmit --project tsconfig.json",
57
+ "test": "vitest run && pnpm run test:organization && pnpm run test:dependency-security && pnpm run test:consumer-security",
58
+ "validate:example": "node ./bin/nuxtjp-localized-site.mjs validate --content-root ./examples/content",
59
+ "prepack": "pnpm test && pnpm typecheck && pnpm build && pnpm validate:example",
60
+ "test:organization": "pnpm run build:validator && pnpm run build:module && node --test test/organization-check.mjs",
61
+ "test:dependency-security": "node --test scripts/dependency-security.check.cjs",
62
+ "test:consumer-security": "node --test scripts/consumer-security.check.mjs"
43
63
  },
44
64
  "peerDependencies": {
45
65
  "nuxt": "^4.5.1",
46
66
  "vue": "^3.5.40"
47
67
  },
48
68
  "dependencies": {
49
- "@nuxt/kit": "4.5.1",
69
+ "@nuxt/kit": "4.5.2",
50
70
  "@nuxt/schema": "4.5.1"
51
71
  },
52
72
  "devDependencies": {
53
73
  "@nuxt/module-builder": "1.0.2",
54
74
  "@types/node": "24.12.0",
55
- "nuxt": "4.5.1",
75
+ "nuxt": "4.5.2",
56
76
  "typescript": "5.9.3",
57
77
  "vitest": "4.1.11",
58
78
  "vue": "3.5.43",
@@ -67,12 +87,16 @@
67
87
  "access": "public",
68
88
  "provenance": true
69
89
  },
70
- "scripts": {
71
- "build:module": "nuxt-module-build build",
72
- "build:validator": "tsc -p tsconfig.validator.json",
73
- "build": "pnpm run build:validator && pnpm run build:module && nuxt build playground",
74
- "typecheck": "vue-tsc --noEmit --project tsconfig.json",
75
- "test": "vitest run",
76
- "validate:example": "node ./bin/nuxtjp-localized-site.mjs validate --content-root ./examples/content"
90
+ "pnpm": {
91
+ "overrides": {
92
+ "@nuxt/devtools>simple-git": "4.0.2",
93
+ "simple-git": "4.0.2",
94
+ "@simple-git/argv-parser": "2.0.1",
95
+ "esbuild": "0.28.2"
96
+ },
97
+ "patchedDependencies": {
98
+ "braces@3.0.3": "patches/braces@3.0.3.patch",
99
+ "node-forge@1.4.0": "patches/node-forge@1.4.0.patch"
100
+ }
77
101
  }
78
- }
102
+ }
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014-present, Jon Schlinkert.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,331 @@
1
+ You may use the Forge project under the terms of either the BSD License or the
2
+ GNU General Public License (GPL) Version 2.
3
+
4
+ The BSD License is recommended for most projects. It is simple and easy to
5
+ understand and it places almost no restrictions on what you can do with the
6
+ Forge project.
7
+
8
+ If the GPL suits your project better you are also free to use Forge under
9
+ that license.
10
+
11
+ You don't have to do anything special to choose one license or the other and
12
+ you don't have to notify anyone which license you are using. You are free to
13
+ use this project in commercial projects as long as the copyright header is
14
+ left intact.
15
+
16
+ If you are a commercial entity and use this set of libraries in your
17
+ commercial software then reasonable payment to Digital Bazaar, if you can
18
+ afford it, is not required but is expected and would be appreciated. If this
19
+ library saves you time, then it's saving you money. The cost of developing
20
+ the Forge software was on the order of several hundred hours and tens of
21
+ thousands of dollars. We are attempting to strike a balance between helping
22
+ the development community while not being taken advantage of by lucrative
23
+ commercial entities for our efforts.
24
+
25
+ -------------------------------------------------------------------------------
26
+ New BSD License (3-clause)
27
+ Copyright (c) 2010, Digital Bazaar, Inc.
28
+ All rights reserved.
29
+
30
+ Redistribution and use in source and binary forms, with or without
31
+ modification, are permitted provided that the following conditions are met:
32
+ * Redistributions of source code must retain the above copyright
33
+ notice, this list of conditions and the following disclaimer.
34
+ * Redistributions in binary form must reproduce the above copyright
35
+ notice, this list of conditions and the following disclaimer in the
36
+ documentation and/or other materials provided with the distribution.
37
+ * Neither the name of Digital Bazaar, Inc. nor the
38
+ names of its contributors may be used to endorse or promote products
39
+ derived from this software without specific prior written permission.
40
+
41
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
42
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
43
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
44
+ DISCLAIMED. IN NO EVENT SHALL DIGITAL BAZAAR BE LIABLE FOR ANY
45
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
46
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
47
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
48
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
49
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
50
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
51
+
52
+ -------------------------------------------------------------------------------
53
+ GNU GENERAL PUBLIC LICENSE
54
+ Version 2, June 1991
55
+
56
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
57
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
58
+ Everyone is permitted to copy and distribute verbatim copies
59
+ of this license document, but changing it is not allowed.
60
+
61
+ Preamble
62
+
63
+ The licenses for most software are designed to take away your
64
+ freedom to share and change it. By contrast, the GNU General Public
65
+ License is intended to guarantee your freedom to share and change free
66
+ software--to make sure the software is free for all its users. This
67
+ General Public License applies to most of the Free Software
68
+ Foundation's software and to any other program whose authors commit to
69
+ using it. (Some other Free Software Foundation software is covered by
70
+ the GNU Lesser General Public License instead.) You can apply it to
71
+ your programs, too.
72
+
73
+ When we speak of free software, we are referring to freedom, not
74
+ price. Our General Public Licenses are designed to make sure that you
75
+ have the freedom to distribute copies of free software (and charge for
76
+ this service if you wish), that you receive source code or can get it
77
+ if you want it, that you can change the software or use pieces of it
78
+ in new free programs; and that you know you can do these things.
79
+
80
+ To protect your rights, we need to make restrictions that forbid
81
+ anyone to deny you these rights or to ask you to surrender the rights.
82
+ These restrictions translate to certain responsibilities for you if you
83
+ distribute copies of the software, or if you modify it.
84
+
85
+ For example, if you distribute copies of such a program, whether
86
+ gratis or for a fee, you must give the recipients all the rights that
87
+ you have. You must make sure that they, too, receive or can get the
88
+ source code. And you must show them these terms so they know their
89
+ rights.
90
+
91
+ We protect your rights with two steps: (1) copyright the software, and
92
+ (2) offer you this license which gives you legal permission to copy,
93
+ distribute and/or modify the software.
94
+
95
+ Also, for each author's protection and ours, we want to make certain
96
+ that everyone understands that there is no warranty for this free
97
+ software. If the software is modified by someone else and passed on, we
98
+ want its recipients to know that what they have is not the original, so
99
+ that any problems introduced by others will not reflect on the original
100
+ authors' reputations.
101
+
102
+ Finally, any free program is threatened constantly by software
103
+ patents. We wish to avoid the danger that redistributors of a free
104
+ program will individually obtain patent licenses, in effect making the
105
+ program proprietary. To prevent this, we have made it clear that any
106
+ patent must be licensed for everyone's free use or not licensed at all.
107
+
108
+ The precise terms and conditions for copying, distribution and
109
+ modification follow.
110
+
111
+ GNU GENERAL PUBLIC LICENSE
112
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
113
+
114
+ 0. This License applies to any program or other work which contains
115
+ a notice placed by the copyright holder saying it may be distributed
116
+ under the terms of this General Public License. The "Program", below,
117
+ refers to any such program or work, and a "work based on the Program"
118
+ means either the Program or any derivative work under copyright law:
119
+ that is to say, a work containing the Program or a portion of it,
120
+ either verbatim or with modifications and/or translated into another
121
+ language. (Hereinafter, translation is included without limitation in
122
+ the term "modification".) Each licensee is addressed as "you".
123
+
124
+ Activities other than copying, distribution and modification are not
125
+ covered by this License; they are outside its scope. The act of
126
+ running the Program is not restricted, and the output from the Program
127
+ is covered only if its contents constitute a work based on the
128
+ Program (independent of having been made by running the Program).
129
+ Whether that is true depends on what the Program does.
130
+
131
+ 1. You may copy and distribute verbatim copies of the Program's
132
+ source code as you receive it, in any medium, provided that you
133
+ conspicuously and appropriately publish on each copy an appropriate
134
+ copyright notice and disclaimer of warranty; keep intact all the
135
+ notices that refer to this License and to the absence of any warranty;
136
+ and give any other recipients of the Program a copy of this License
137
+ along with the Program.
138
+
139
+ You may charge a fee for the physical act of transferring a copy, and
140
+ you may at your option offer warranty protection in exchange for a fee.
141
+
142
+ 2. You may modify your copy or copies of the Program or any portion
143
+ of it, thus forming a work based on the Program, and copy and
144
+ distribute such modifications or work under the terms of Section 1
145
+ above, provided that you also meet all of these conditions:
146
+
147
+ a) You must cause the modified files to carry prominent notices
148
+ stating that you changed the files and the date of any change.
149
+
150
+ b) You must cause any work that you distribute or publish, that in
151
+ whole or in part contains or is derived from the Program or any
152
+ part thereof, to be licensed as a whole at no charge to all third
153
+ parties under the terms of this License.
154
+
155
+ c) If the modified program normally reads commands interactively
156
+ when run, you must cause it, when started running for such
157
+ interactive use in the most ordinary way, to print or display an
158
+ announcement including an appropriate copyright notice and a
159
+ notice that there is no warranty (or else, saying that you provide
160
+ a warranty) and that users may redistribute the program under
161
+ these conditions, and telling the user how to view a copy of this
162
+ License. (Exception: if the Program itself is interactive but
163
+ does not normally print such an announcement, your work based on
164
+ the Program is not required to print an announcement.)
165
+
166
+ These requirements apply to the modified work as a whole. If
167
+ identifiable sections of that work are not derived from the Program,
168
+ and can be reasonably considered independent and separate works in
169
+ themselves, then this License, and its terms, do not apply to those
170
+ sections when you distribute them as separate works. But when you
171
+ distribute the same sections as part of a whole which is a work based
172
+ on the Program, the distribution of the whole must be on the terms of
173
+ this License, whose permissions for other licensees extend to the
174
+ entire whole, and thus to each and every part regardless of who wrote it.
175
+
176
+ Thus, it is not the intent of this section to claim rights or contest
177
+ your rights to work written entirely by you; rather, the intent is to
178
+ exercise the right to control the distribution of derivative or
179
+ collective works based on the Program.
180
+
181
+ In addition, mere aggregation of another work not based on the Program
182
+ with the Program (or with a work based on the Program) on a volume of
183
+ a storage or distribution medium does not bring the other work under
184
+ the scope of this License.
185
+
186
+ 3. You may copy and distribute the Program (or a work based on it,
187
+ under Section 2) in object code or executable form under the terms of
188
+ Sections 1 and 2 above provided that you also do one of the following:
189
+
190
+ a) Accompany it with the complete corresponding machine-readable
191
+ source code, which must be distributed under the terms of Sections
192
+ 1 and 2 above on a medium customarily used for software interchange; or,
193
+
194
+ b) Accompany it with a written offer, valid for at least three
195
+ years, to give any third party, for a charge no more than your
196
+ cost of physically performing source distribution, a complete
197
+ machine-readable copy of the corresponding source code, to be
198
+ distributed under the terms of Sections 1 and 2 above on a medium
199
+ customarily used for software interchange; or,
200
+
201
+ c) Accompany it with the information you received as to the offer
202
+ to distribute corresponding source code. (This alternative is
203
+ allowed only for noncommercial distribution and only if you
204
+ received the program in object code or executable form with such
205
+ an offer, in accord with Subsection b above.)
206
+
207
+ The source code for a work means the preferred form of the work for
208
+ making modifications to it. For an executable work, complete source
209
+ code means all the source code for all modules it contains, plus any
210
+ associated interface definition files, plus the scripts used to
211
+ control compilation and installation of the executable. However, as a
212
+ special exception, the source code distributed need not include
213
+ anything that is normally distributed (in either source or binary
214
+ form) with the major components (compiler, kernel, and so on) of the
215
+ operating system on which the executable runs, unless that component
216
+ itself accompanies the executable.
217
+
218
+ If distribution of executable or object code is made by offering
219
+ access to copy from a designated place, then offering equivalent
220
+ access to copy the source code from the same place counts as
221
+ distribution of the source code, even though third parties are not
222
+ compelled to copy the source along with the object code.
223
+
224
+ 4. You may not copy, modify, sublicense, or distribute the Program
225
+ except as expressly provided under this License. Any attempt
226
+ otherwise to copy, modify, sublicense or distribute the Program is
227
+ void, and will automatically terminate your rights under this License.
228
+ However, parties who have received copies, or rights, from you under
229
+ this License will not have their licenses terminated so long as such
230
+ parties remain in full compliance.
231
+
232
+ 5. You are not required to accept this License, since you have not
233
+ signed it. However, nothing else grants you permission to modify or
234
+ distribute the Program or its derivative works. These actions are
235
+ prohibited by law if you do not accept this License. Therefore, by
236
+ modifying or distributing the Program (or any work based on the
237
+ Program), you indicate your acceptance of this License to do so, and
238
+ all its terms and conditions for copying, distributing or modifying
239
+ the Program or works based on it.
240
+
241
+ 6. Each time you redistribute the Program (or any work based on the
242
+ Program), the recipient automatically receives a license from the
243
+ original licensor to copy, distribute or modify the Program subject to
244
+ these terms and conditions. You may not impose any further
245
+ restrictions on the recipients' exercise of the rights granted herein.
246
+ You are not responsible for enforcing compliance by third parties to
247
+ this License.
248
+
249
+ 7. If, as a consequence of a court judgment or allegation of patent
250
+ infringement or for any other reason (not limited to patent issues),
251
+ conditions are imposed on you (whether by court order, agreement or
252
+ otherwise) that contradict the conditions of this License, they do not
253
+ excuse you from the conditions of this License. If you cannot
254
+ distribute so as to satisfy simultaneously your obligations under this
255
+ License and any other pertinent obligations, then as a consequence you
256
+ may not distribute the Program at all. For example, if a patent
257
+ license would not permit royalty-free redistribution of the Program by
258
+ all those who receive copies directly or indirectly through you, then
259
+ the only way you could satisfy both it and this License would be to
260
+ refrain entirely from distribution of the Program.
261
+
262
+ If any portion of this section is held invalid or unenforceable under
263
+ any particular circumstance, the balance of the section is intended to
264
+ apply and the section as a whole is intended to apply in other
265
+ circumstances.
266
+
267
+ It is not the purpose of this section to induce you to infringe any
268
+ patents or other property right claims or to contest validity of any
269
+ such claims; this section has the sole purpose of protecting the
270
+ integrity of the free software distribution system, which is
271
+ implemented by public license practices. Many people have made
272
+ generous contributions to the wide range of software distributed
273
+ through that system in reliance on consistent application of that
274
+ system; it is up to the author/donor to decide if he or she is willing
275
+ to distribute software through any other system and a licensee cannot
276
+ impose that choice.
277
+
278
+ This section is intended to make thoroughly clear what is believed to
279
+ be a consequence of the rest of this License.
280
+
281
+ 8. If the distribution and/or use of the Program is restricted in
282
+ certain countries either by patents or by copyrighted interfaces, the
283
+ original copyright holder who places the Program under this License
284
+ may add an explicit geographical distribution limitation excluding
285
+ those countries, so that distribution is permitted only in or among
286
+ countries not thus excluded. In such case, this License incorporates
287
+ the limitation as if written in the body of this License.
288
+
289
+ 9. The Free Software Foundation may publish revised and/or new versions
290
+ of the General Public License from time to time. Such new versions will
291
+ be similar in spirit to the present version, but may differ in detail to
292
+ address new problems or concerns.
293
+
294
+ Each version is given a distinguishing version number. If the Program
295
+ specifies a version number of this License which applies to it and "any
296
+ later version", you have the option of following the terms and conditions
297
+ either of that version or of any later version published by the Free
298
+ Software Foundation. If the Program does not specify a version number of
299
+ this License, you may choose any version ever published by the Free Software
300
+ Foundation.
301
+
302
+ 10. If you wish to incorporate parts of the Program into other free
303
+ programs whose distribution conditions are different, write to the author
304
+ to ask for permission. For software which is copyrighted by the Free
305
+ Software Foundation, write to the Free Software Foundation; we sometimes
306
+ make exceptions for this. Our decision will be guided by the two goals
307
+ of preserving the free status of all derivatives of our free software and
308
+ of promoting the sharing and reuse of software generally.
309
+
310
+ NO WARRANTY
311
+
312
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
313
+ FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
314
+ OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
315
+ PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
316
+ OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
317
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
318
+ TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
319
+ PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
320
+ REPAIR OR CORRECTION.
321
+
322
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
323
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
324
+ REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
325
+ INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
326
+ OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
327
+ TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
328
+ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
329
+ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
330
+ POSSIBILITY OF SUCH DAMAGES.
331
+
@@ -0,0 +1,21 @@
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 root configuration also requires simple-git 4.0.2, @simple-git/argv-parser 2.0.1 and esbuild 0.28.2 from the public registry to address their upstream advisories. Exact versions are maintained in `dependency-versions.json`. 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/localized-site/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/localized-site/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.
@@ -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,59 @@
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 { installedDependencies } = require('./resolved-dependency.cjs');
9
+ const matches = installedDependencies(process.cwd(), name, version);
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
+ });
@@ -0,0 +1,12 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "overrides": {
4
+ "simple-git": "4.0.2",
5
+ "@simple-git/argv-parser": "2.0.1",
6
+ "esbuild": "0.28.2"
7
+ },
8
+ "backportedAdvisories": [
9
+ "GHSA-vfj7-8cjw-p6xm",
10
+ "GHSA-86w9-cpqp-85rv"
11
+ ]
12
+ }
@@ -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,76 @@
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 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
+ }
@@ -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 (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 };