@nuxtjp/dependency-security 0.2.0 → 0.2.1

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
@@ -88,3 +88,25 @@ result before mutation; its owner must project the reviewed rules into that YAML
88
88
  rather than create competing configuration in package.json. Graph verification
89
89
  still supports an explicitly selected workspace root. No workspace YAML parser or
90
90
  second package-manager authority is introduced.
91
+
92
+ ### Explicit workspace graph verification
93
+
94
+ `verifyProject(workspaceRoot, { projects })` checks the aggregate active dependency
95
+ graphs of explicitly selected projects, including each project's development
96
+ dependencies. Supply the actual owner package manager inventory (for example,
97
+ paths returned by `pnpm list -r --depth -1 --json`), including the workspace root
98
+ exactly once. The caller owns inventory completeness; this API does not parse
99
+ workspace YAML or infer that unselected projects were checked. More than 256
100
+ projects, duplicates, missing root, linked manifests, node_modules selections
101
+ and paths outside the workspace are refused. Dependencies shared within the
102
+ workspace are allowed; external resolutions and missing required dependencies
103
+ remain errors. Unreferenced store entries remain excluded.
104
+
105
+ The configuration CLI still refuses workspace YAML. Its owner updates that native
106
+ policy using the public policy export and reviewed patch assets. Graph inspection
107
+ does not install dependencies, invoke package lifecycles or grant runtime authority.
108
+
109
+ The reviewed policy retains fast-uri 3.1.9 for 3.x consumers and selects 4.2.3
110
+ for 4.x consumers; it never forces a cross-major SDK update. The 4.x correction
111
+ follows the upstream release: https://github.com/fastify/fast-uri/releases/tag/v4.2.3 .
112
+ A passing policy check is not an exhaustive advisory audit of unrelated packages.
package/checks.mjs CHANGED
@@ -2,12 +2,17 @@
2
2
  import { spawnSync } from 'node:child_process'
3
3
  import { fileURLToPath } from 'node:url'
4
4
  import { realpathSync } from 'node:fs'
5
- export function verifyProject(project) {
5
+ import { dirname } from 'node:path'
6
+ import resolver from './resolved-dependency.cjs'
7
+ export function verifyProject(project, options = {}) {
8
+ const root = realpathSync(project)
9
+ const projects = resolver.projectManifests(root, options.projects).map(dirname)
6
10
  const env = { ...process.env }
7
11
  // A parent's node:test context would silently skip a child suite.
8
- for (const name of ['NODE_TEST_CONTEXT', 'NODE_OPTIONS', 'NODE_PATH']) delete env[name]
12
+ for (const name of ['NODE_TEST_CONTEXT', 'NODE_OPTIONS', 'NODE_PATH', 'NUXTJP_SECURITY_PROJECTS']) delete env[name]
13
+ env.NUXTJP_SECURITY_PROJECTS = JSON.stringify(projects)
9
14
  const result = spawnSync(process.execPath,
10
15
  ['--test', fileURLToPath(new URL('./dependency-security.check.cjs', import.meta.url))],
11
- { cwd: realpathSync(project), env, encoding: 'utf8', timeout: 120000, maxBuffer: 4 * 1024 * 1024 })
16
+ { cwd: root, env, encoding: 'utf8', timeout: 120000, maxBuffer: 4 * 1024 * 1024 })
12
17
  return { exitCode: result.status ?? 2, stdout: result.stdout ?? '', stderr: result.stderr ?? '' }
13
18
  }
@@ -4,9 +4,11 @@ const { createRequire } = require('node:module');
4
4
  const { generateKeyPairSync, sign, privateEncrypt, constants } = require('node:crypto');
5
5
  const path = require('node:path');
6
6
  const fs = require('node:fs');
7
+ const graphOptions = process.env.NUXTJP_SECURITY_PROJECTS
8
+ ? { projects: JSON.parse(process.env.NUXTJP_SECURITY_PROJECTS) } : {};
7
9
  function dependency(name, version) {
8
10
  const { installedDependencies } = require('./resolved-dependency.cjs');
9
- const matches = installedDependencies(process.cwd(), name, version);
11
+ const matches = installedDependencies(process.cwd(), name, version, graphOptions);
10
12
  assert.equal(matches.length, 1, 'Expected one active locked dependency implementation');
11
13
  return createRequire(matches[0])(name);
12
14
  }
@@ -50,14 +52,14 @@ test('RSA verifier accepts standard signatures and rejects extra or malformed Di
50
52
 
51
53
  test('active dependency graph uses every required upstream security version', () => {
52
54
  const policy = JSON.parse(fs.readFileSync(path.join(__dirname, 'dependency-versions.json'), 'utf8'));
53
- require('./version-policy.cjs').verifyInstalledPolicy(process.cwd(), policy);
55
+ require('./version-policy.cjs').verifyInstalledPolicy(process.cwd(), policy, graphOptions);
54
56
  });
55
57
 
56
58
  // Preserve the ESM entry point expected by current Nuxt devtools without downgrading Git protections.
57
59
  test('fixed simple-git retains named and legacy ESM factory exports', async context => {
58
60
  const { pathToFileURL } = require('node:url');
59
61
  const { installedDependencies } = require('./resolved-dependency.cjs');
60
- const manifests = installedDependencies(process.cwd(), 'simple-git', '4.0.2', { allowMissing: true });
62
+ const manifests = installedDependencies(process.cwd(), 'simple-git', '4.0.2', { ...graphOptions, allowMissing: true });
61
63
  if (!manifests.length) { context.skip('No active simple-git: compatibility check is not applicable'); return; }
62
64
  for (const manifest of manifests) {
63
65
  const module = await import(pathToFileURL(path.join(path.dirname(manifest), 'dist/index.mjs')).href);
@@ -8,7 +8,8 @@
8
8
  "undici@7": "7.30.1",
9
9
  "undici@8": "8.12.2",
10
10
  "serialize-javascript@7": "7.1.3",
11
- "fast-uri@3": "3.1.9"
11
+ "fast-uri@3": "3.1.9",
12
+ "fast-uri@4": "4.2.3"
12
13
  },
13
14
  "backportedAdvisories": [
14
15
  "GHSA-vfj7-8cjw-p6xm",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxtjp/dependency-security",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Explicit pnpm backport configuration and active dependency regression checks without Nuxt peers",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -3,9 +3,28 @@ const fs = require('node:fs');
3
3
  const path = require('node:path');
4
4
  const { createRequire } = require('node:module');
5
5
 
6
+ function projectManifests(root, projects = [root]) {
7
+ root = fs.realpathSync(root);
8
+ if (!Array.isArray(projects) || projects.length === 0 || projects.length > 256) throw new Error('Invalid selected project inventory');
9
+ const manifests = projects.map(project => {
10
+ if (typeof project !== 'string') throw new Error('Invalid selected project');
11
+ const selected = fs.realpathSync(path.resolve(root, project));
12
+ const relative = path.relative(root, selected);
13
+ if (relative === '..' || relative.startsWith('..' + path.sep) || path.isAbsolute(relative)
14
+ || relative.split(path.sep).includes('node_modules')) throw new Error('Selected project escapes workspace boundary');
15
+ const manifest = path.join(selected, 'package.json');
16
+ if (fs.realpathSync(manifest) !== manifest) throw new Error('Linked project manifest refused');
17
+ return manifest;
18
+ });
19
+ if (new Set(manifests).size !== manifests.length || !manifests.includes(path.join(root, 'package.json'))) {
20
+ throw new Error('Project inventory must include the workspace root exactly once');
21
+ }
22
+ return manifests;
23
+ }
6
24
  function installedDependencies(root, target, version, options = {}) {
7
25
  root = fs.realpathSync(root);
8
- const pending = [path.join(root, 'package.json')];
26
+ const entries = new Set(projectManifests(root, options.projects));
27
+ const pending = [...entries];
9
28
  const seen = new Set();
10
29
  const found = new Set();
11
30
  while (pending.length) {
@@ -23,7 +42,7 @@ function installedDependencies(root, target, version, options = {}) {
23
42
  }
24
43
  const request = createRequire(file);
25
44
  const names = Object.keys({ ...data.dependencies, ...data.optionalDependencies, ...data.peerDependencies,
26
- ...(file === path.join(root, 'package.json') ? data.devDependencies : {}) });
45
+ ...(entries.has(file) ? data.devDependencies : {}) });
27
46
  for (const name of names) {
28
47
  let resolved = false;
29
48
  for (const location of request.resolve.paths(name + '/package.json') ?? []) {
@@ -37,7 +56,7 @@ function installedDependencies(root, target, version, options = {}) {
37
56
  resolved = true;
38
57
  break;
39
58
  }
40
- if (!resolved && Object.hasOwn(data.dependencies ?? {}, name)
59
+ if (!resolved && Object.hasOwn({ ...data.dependencies, ...(entries.has(file) ? data.devDependencies : {}) }, name)
41
60
  && !Object.hasOwn(data.optionalDependencies ?? {}, name)) {
42
61
  throw new Error('Incomplete required dependency graph');
43
62
  }
@@ -46,4 +65,4 @@ function installedDependencies(root, target, version, options = {}) {
46
65
  if (found.size === 0 && options.allowMissing !== true) throw new Error('Dependency not present in the installed application graph');
47
66
  return [...found].sort();
48
67
  }
49
- module.exports = { installedDependencies };
68
+ module.exports = { installedDependencies, projectManifests };
@@ -1,2 +1,2 @@
1
- /** Traverse only the selected installed application graph, with a finite budget. */
2
- export function installedDependencies(root: string, target: string, version?: string, options?: { allowMissing?: boolean }): string[];
1
+ /** Traverse active graphs within the selected workspace boundary. Projects must include its root once. */
2
+ export function installedDependencies(root: string, target: string, version?: string, options?: { allowMissing?: boolean; projects?: readonly string[] }): string[];
package/types/index.d.mts CHANGED
@@ -1,6 +1,7 @@
1
+ export interface GraphOptions { projects?: readonly string[] }
1
2
  /** Configure reviewed pnpm backports. No installation or network access occurs. */
2
3
  export function applySecurityPatches(project: string): void;
3
4
  /** Resolve exact active packages; reject external-workspace resolution and missing targets. */
4
- export function installedDependencies(root: string, target: string, version?: string): string[];
5
+ export function installedDependencies(root: string, target: string, version?: string, options?: GraphOptions & { allowMissing?: boolean }): string[];
5
6
  /** Run the known dependency regressions, returning bounded local output and exit status. */
6
- export function verifyProject(project: string): { exitCode: number; stdout: string; stderr: string };
7
+ export function verifyProject(project: string, options?: GraphOptions): { exitCode: number; stdout: string; stderr: string };
@@ -47,10 +47,10 @@ function checkVersions(policy, observed) {
47
47
  }
48
48
  }
49
49
  }
50
- function verifyInstalledPolicy(root, policy) {
50
+ function verifyInstalledPolicy(root, policy, options = {}) {
51
51
  const names = new Set(requirements(policy).map(rule => rule.name)), observed = {};
52
52
  for (const name of names) {
53
- observed[name] = installedDependencies(root, name, undefined, { allowMissing: true })
53
+ observed[name] = installedDependencies(root, name, undefined, { ...options, allowMissing: true })
54
54
  .map(file => JSON.parse(fs.readFileSync(file, 'utf8')).version);
55
55
  }
56
56
  checkVersions(policy, observed);