@vltpkg/graph 1.0.1 → 1.0.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.
@@ -292,7 +292,10 @@ export const load = (options) => {
292
292
  }
293
293
  catch {
294
294
  // Hidden lockfile is cache-only, safe to regenerate on any error
295
- // as it will fall back to filesystem traversal
295
+ // as it will fall back to filesystem traversal. Deliberately also
296
+ // swallows EINVALIDNAME, unlike ideal/build.ts: this cache lives
297
+ // inside node_modules and regenerating it is the right response,
298
+ // whereas failing loud would let it brick installs.
296
299
  // TODO: Warn version mismatch to @vltpkg/output for debugging
297
300
  }
298
301
  }
package/dist/graph.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { getId, joinDepIDTuple, splitExtra } from '@vltpkg/dep-id';
2
2
  import { error } from '@vltpkg/error-cause';
3
3
  import { satisfies } from '@vltpkg/satisfies';
4
- import { getOptions, Spec } from '@vltpkg/spec';
4
+ import { assertPathSafeName, getOptions, Spec } from '@vltpkg/spec';
5
5
  import { inspect } from 'node:util';
6
6
  import { lockfileData } from "./lockfile/save.js";
7
7
  import { Edge } from "./edge.js";
@@ -214,6 +214,8 @@ export class Graph {
214
214
  if (to) {
215
215
  spec.name = to.name /* c8 ignore next */ || '(unknown)';
216
216
  spec.spec = `${to.name}@${spec.bareSpec}`;
217
+ // this alias becomes the symlink segment in add-edge
218
+ assertPathSafeName(spec.name, spec.spec);
217
219
  }
218
220
  else {
219
221
  throw error('Impossible to place a missing, nameless dependency', { spec });
@@ -6,6 +6,7 @@ import { longDependencyTypes, normalizeManifest } from '@vltpkg/types';
6
6
  import { fixupAddedNames } from "../fixup-added-names.js";
7
7
  import { shorten } from "../dependencies.js";
8
8
  import { removeOptionalSubgraph } from "../remove-optional-subgraph.js";
9
+ import { isPathSecurityError } from "../path-security-error.js";
9
10
  import { extractNode } from "../reify/extract-node.js";
10
11
  import { checkPeerEdgesCompatible, endPeerPlacement, forkPeerContext, postPlacementPeerCheck, startPeerPlacement, } from "./peers.js";
11
12
  import { compareByHasPeerDeps } from "./sorting.js";
@@ -203,7 +204,10 @@ const fetchManifestsForDeps = async (packageInfo, graph, fromNode, deps, scurry,
203
204
  .manifest(spec, { from: scurry.resolve(fromNode.location) })
204
205
  .then(manifest => manifest)
205
206
  .catch((er) => {
206
- // optional deps ignored if inaccessible
207
+ // optional deps ignored if inaccessible, but a dep that tried
208
+ // to escape the project is never silently dropped
209
+ if (isPathSecurityError(er))
210
+ throw er;
207
211
  if (edgeOptional || fromNode.optional) {
208
212
  return undefined;
209
213
  }
@@ -294,7 +298,9 @@ const processPlacementTasks = async (graph, options, placementTasks, add, modifi
294
298
  const peerSetHash = peerPlacement.peerSetHash;
295
299
  const queuedEntries = peerPlacement.queuedEntries;
296
300
  // places a new node in the graph representing a newly seen dependency
297
- const node = graph.placePackage(fromNode, type, spec, normalizeManifest(manifest), fileTypeInfo?.id, joinExtra({ peerSetHash, modifier: queryModifier }));
301
+ const node = graph.placePackage(fromNode, type, spec,
302
+ // report broken manifest names against the dependency spec
303
+ normalizeManifest(manifest, String(spec)), fileTypeInfo?.id, joinExtra({ peerSetHash, modifier: queryModifier }));
298
304
  /* c8 ignore start - not possible, already ensured manifest */
299
305
  if (!node) {
300
306
  throw error('failed to place package', {
@@ -3,6 +3,7 @@ import { graphStep } from '@vltpkg/output';
3
3
  import { load as loadActual } from "../actual/load.js";
4
4
  import { load as loadVirtual } from "../lockfile/load.js";
5
5
  import { buildIdealFromStartingGraph } from "./build-ideal-from-starting-graph.js";
6
+ import { isPathSecurityError } from "../path-security-error.js";
6
7
  import { splitDepID } from '@vltpkg/dep-id';
7
8
  const getMap = (m) => m ?? new Map();
8
9
  /**
@@ -40,6 +41,9 @@ export const build = async (options) => {
40
41
  });
41
42
  }
42
43
  catch (err) {
44
+ // a path-safety verdict must not be silently rebuilt away
45
+ if (isPathSecurityError(err))
46
+ throw err;
43
47
  // Check if this is a lockfile version mismatch
44
48
  const cause = err.cause;
45
49
  if (cause?.code === 'ELOCKFILEVERSION') {
@@ -1,5 +1,5 @@
1
1
  import { error } from '@vltpkg/error-cause';
2
- import { splitDepID, splitExtra } from '@vltpkg/dep-id/browser';
2
+ import { asDepID, splitDepID, splitExtra, } from '@vltpkg/dep-id/browser';
3
3
  import { getBooleanFlagsFromNum, getBuildStateFromNum, } from "./types.js";
4
4
  export const loadNodes = (graph, nodes, options, actual, throwOnMissingManifest) => {
5
5
  const entries = Object.entries(nodes);
@@ -8,6 +8,9 @@ export const loadNodes = (graph, nodes, options, actual, throwOnMissingManifest)
8
8
  // Only used for non-trivial graphs
9
9
  const registryVersionCache = nodeCount > 50 ? new Map() : null;
10
10
  for (const [id, lockfileNode] of entries) {
11
+ // lockfile keys have never been through `encode`, so they can hold
12
+ // anything, and the id is the store directory name
13
+ asDepID(id);
11
14
  const [flags, name, integrity, resolved, location, manifest, rawManifest, platform, bins, buildState,] = lockfileNode;
12
15
  // workspace nodes and the project root node are already part of the
13
16
  // graph and it should not create new nodes if an existing one is there
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Is this error a path-safety verdict? Walks the cause chain, because
3
+ * lower layers (eg `PackageJson.read`) re-wrap what they throw.
4
+ */
5
+ export declare const isPathSecurityError: (er: unknown) => boolean;
@@ -0,0 +1,13 @@
1
+ const codes = new Set(['EINVALIDNAME']);
2
+ /**
3
+ * Is this error a path-safety verdict? Walks the cause chain, because
4
+ * lower layers (eg `PackageJson.read`) re-wrap what they throw.
5
+ */
6
+ export const isPathSecurityError = (er) => {
7
+ for (let e = er; e && typeof e === 'object'; e = e.cause) {
8
+ const { code } = e;
9
+ if (typeof code === 'string' && codes.has(code))
10
+ return true;
11
+ }
12
+ return false;
13
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vltpkg/graph",
3
3
  "description": "A library that helps understanding & expressing what happens on an install",
4
- "version": "1.0.1",
4
+ "version": "1.0.2",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/vltpkg/vltpkg.git",
@@ -13,26 +13,26 @@
13
13
  "url": "http://vlt.sh"
14
14
  },
15
15
  "dependencies": {
16
- "@vltpkg/cmd-shim": "1.0.1",
17
- "@vltpkg/dep-id": "1.0.1",
18
- "@vltpkg/dss-breadcrumb": "1.0.1",
19
- "@vltpkg/error-cause": "1.0.1",
20
- "@vltpkg/fast-split": "1.0.1",
21
- "@vltpkg/graph-run": "1.0.1",
22
- "@vltpkg/init": "1.0.1",
23
- "@vltpkg/output": "1.0.1",
24
- "@vltpkg/package-info": "1.0.1",
25
- "@vltpkg/package-json": "1.0.1",
26
- "@vltpkg/pick-manifest": "1.0.1",
27
- "@vltpkg/query": "1.0.1",
28
- "@vltpkg/rollback-remove": "1.0.1",
29
- "@vltpkg/run": "1.0.1",
30
- "@vltpkg/satisfies": "1.0.1",
31
- "@vltpkg/security-archive": "1.0.1",
32
- "@vltpkg/spec": "1.0.1",
33
- "@vltpkg/types": "1.0.1",
34
- "@vltpkg/vlt-json": "1.0.1",
35
- "@vltpkg/workspaces": "1.0.1",
16
+ "@vltpkg/cmd-shim": "1.0.2",
17
+ "@vltpkg/dep-id": "1.0.2",
18
+ "@vltpkg/dss-breadcrumb": "1.0.2",
19
+ "@vltpkg/error-cause": "1.0.2",
20
+ "@vltpkg/fast-split": "1.0.2",
21
+ "@vltpkg/graph-run": "1.0.2",
22
+ "@vltpkg/init": "1.0.2",
23
+ "@vltpkg/output": "1.0.2",
24
+ "@vltpkg/package-info": "1.0.2",
25
+ "@vltpkg/package-json": "1.0.2",
26
+ "@vltpkg/pick-manifest": "1.0.2",
27
+ "@vltpkg/query": "1.0.2",
28
+ "@vltpkg/rollback-remove": "1.0.2",
29
+ "@vltpkg/run": "1.0.2",
30
+ "@vltpkg/satisfies": "1.0.2",
31
+ "@vltpkg/security-archive": "1.0.2",
32
+ "@vltpkg/spec": "1.0.2",
33
+ "@vltpkg/types": "1.0.2",
34
+ "@vltpkg/vlt-json": "1.0.2",
35
+ "@vltpkg/workspaces": "1.0.2",
36
36
  "path-scurry": "^2.0.1",
37
37
  "promise-call-limit": "^3.0.2"
38
38
  },