Skip to content

Accept vendored packages and subpackages under their host's identity, recording those reached - #70

Open
ChristopherTuring wants to merge 4 commits into
mainfrom
claude/vibrant-darwin-9o1exv
Open

ChristopherTuring wants to merge 4 commits into
mainfrom
claude/vibrant-darwin-9o1exv

Conversation

@ChristopherTuring

@ChristopherTuring ChristopherTuring commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Two common layouts failed #locateModule's nested package.json check, which exists to ask where a package.json nothing expects came from:

Inconsistent data between node_modules/next/dist/compiled/ua-parser-js/package.json and node_modules/next/package.json
Inconsistent data between node_modules/@hookform/resolvers/zod/package.json and node_modules/@hookform/resolvers/package.json

Both now bundle. In both cases the files keep their host's identity, the accepted exception is kept narrow, and every manifest let through is recorded in the bundle, so nothing passes silently.

Vendored packages (Next.js dist/compiled)

Next.js compiles about 140 dependencies into dist/compiled/<dir>, each beside a trimmed package.json that names the dependency (ua-parser-js, usually with no version).

  • A maintained list of vendor dirs (VENDOR_DIRS in bundle-util.js, keyed by the host package's name) marks next's dist/compiled. Elsewhere in next, a nested package.json is still held to next's identity.
  • The dependency record gets a vendored field. It maps each vendored copy's directory to the name and version from that copy's package.json, and lists only the copies the bundle carries a file of.
  • stasis sbom lists each reached copy as a package contained in its host. In CycloneDX it is a nested component; in SPDX the host CONTAINS it. A purl is emitted only when a version is recorded.

Subpackages (@hookform/resolvers/zod)

Microbundle-style subpath entry points have their own package.json, named in the package's namespace and carrying a placeholder version (1.0.0).

  • The version of such a manifest is held to nothing only when its name is the package's name for the directory it is actually in (@hookform/resolvers/zod at zod/). The directory is compared by real path, because findPackageJSON resolves pnpm symlinks. A namespaced name at another directory still fails.
  • The dependency record gets a subpackages field (directory → name and version) listing the reached subpackages. They are the package's own code, so the SBOM doesn't list them.

Shared behavior

  • vendored and subpackages are metadata, like repo. They are written to bundles only, never to lockfiles, are not attested, and merge by union.
  • When a bundle is written, each record keeps only the entries holding a file it carries, so each half of a split bundle lists its own. Parse rejects an entry that holds no file, is misnamed, or sits on first-party code.
  • The --mainFields resolver and stasis add follow the same rules. Before this change they bucketed next/dist/compiled/react-is and @hookform/resolvers/zod as packages of their own.

Testing

  • node --run test: 2645 tests, 0 failures. node --run lint is clean.
  • New tests cover State, bundle parse and serialize, merges, bundle=add, both stasis bundle paths, a pnpm symlinked install, and the SBOM output. Without the fix, the new tests reproduce both errors.
  • Checked by hand:
    • Real Next.js 16.4.0, normal and pnpm layouts, via stasis bundle and stasis run: only the 3 reached copies out of about 140 are listed.
    • Real @hookform/resolvers 5.9.1: one record at 5.9.1, listing only the reached subpackages.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NJvPNoXzbzBGVBNknz3hdE

claude added 2 commits October 8, 2026 02:31
…ched as `vendored`

Next.js compiles ~140 dependencies into `dist/compiled/<dir>`, each beside a
trimmed package.json naming it (`ua-parser-js`, mostly with no version), so
bundling any of them failed #locateModule's nested package.json check:

  Inconsistent data between node_modules/next/dist/compiled/ua-parser-js/package.json
  and node_modules/next/package.json

A maintained list of vendor dirs, keyed by the host package's name (so pnpm
store paths and aliases match), now marks `next`'s `dist/compiled` as holding
copies of other packages. A file there stays next's, and the consistency
check is skipped only where a package.json below the vendor dir covers it; a
nested package.json anywhere else in next is still held to next's identity.

For transparency, a dependency's bundle record lists the vendored packages its
files are in: `vendored`, by the directory of each one's package.json, with
that package.json's `name` and (where given) `version`. Only those actually
reached are listed: State records one when it captures a file in it, and
serialization keeps the entries holding a file the artifact carries, so each
half of a split bundle lists its own and parse refuses an entry holding none.
Like `repo`, it is metadata: bundle-only, never in a lockfile, never attested,
and merged by union. The --mainFields resolver and `stasis add`, which bucket
through findPackageMetadata, follow the same rule; there a vendored
package.json with a version used to become a bucket of its own inside next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJvPNoXzbzBGVBNknz3hdE
A bundle's `vendored` list becomes part of the SBOM: in CycloneDX, nested in
the host's `components`, each bom-ref the host's with the directory as a purl
subpath and the directory as its `evidence.occurrences` location; in SPDX,
packages after the rest that the host CONTAINS, the directory as
`packageFileName`. A purl is minted only beside a version: Next.js's copies
mostly record none, and a bare name may not be a registry package at all
(its React copy is `react-builtin`). The summary line counts them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJvPNoXzbzBGVBNknz3hdE
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-09T06:45:20.309071Z 567c3c8 Manual request
🔒 Security Review ✅ Completed 2026-10-08T04:15:09.260457Z cb43717 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

…ng, listing those reached as `subpackages`

@hookform/resolvers lays its subpath entry points out as microbundle does:
zod/package.json, named `@hookform/resolvers/zod`, with a placeholder version
of its own (1.0.0; arktype's 2.0.0). The name was already allowed (the
package owns its namespace), but the version was held to the package's:

  Inconsistent data between node_modules/@hookform/resolvers/zod/package.json
  and node_modules/@hookform/resolvers/package.json

The check is there to ask where a package.json nothing expects came from, so
the answer accepted instead is narrow and recorded. A nested package.json is a
subpackage only where its name is the package's for the very directory it is
in (isSubpackage), compared by real path since findPackageJSON resolves a
symlinked install; one named in the namespace for another directory is still
held to the package's version. Its files stay under the package's identity,
and a bundle lists each subpackage it carries a file of in `subpackages`
(directory -> its package.json's name and version), metadata like `vendored`:
bundle-only, never in a lockfile, merged by union, refused on parse where an
entry is misnamed or holds none of the record's files. `stasis sbom` lists
none: they are the package's own code.

The --mainFields resolver and `stasis add` walk past a subpackage's
package.json the same way; before, it took zod/ for a package of its own,
`@hookform/resolvers/zod@1.0.0`. The per-directory metadata of `vendored` and
`subpackages` now shares one implementation in artifact-util.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJvPNoXzbzBGVBNknz3hdE
@ChristopherTuring ChristopherTuring changed the title Add support for vendored packages in bundles Accept vendored packages and subpackages under their host's identity, recording those reached Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

@codex security review

The last review covered cb43717. Since then, f76c4e7 lets microbundle-style subpackages (@hookform/resolvers/zod) keep their own version and lists them as subpackages.


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f76c4e7d28

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread stasis-core/src/bundle-util.js
…json

A subpackage with a type-only marker below its own package.json (say
zod/dist/package.json, `{"type":"module"}`) went unlisted for the files under
the marker: State looked at the closest package.json alone, and
findPackageMetadata stopped at the first one of any kind. Both now take the
nearest package.json with a name (namedManifestOf), as vendoredPackageOf
already does. State walks it by the file's own path, which also replaces the
real-path comparison of the subpackage's directory.

The version is the subpackage's own only where the closest package.json is
the subpackage's itself: a marker below it that gives a version is still held
to the package's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJvPNoXzbzBGVBNknz3hdE

Copy link
Copy Markdown
Collaborator Author

@codex security review


Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 567c3c8493

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +86 to +89
if (below) {
const repo = packageRepo(pkg)
const { dir: sub, ...identity } = vendored ?? {}
return { pkgDir: nm.dir, name: pkg.name, version: pkg.version, ecosystem: 'npm', ...(repo === undefined ? {} : { repo }), ...(vendored === undefined ? {} : { vendored: { [sub]: identity } }) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep versioned vendored dependencies visible to audit

When a reached Next.js copy has a version (for example the recorded react-is fixture), folding it into the host bucket here removes the dependency bucket that stasis audit can inspect. collectPackagesFromFile in stasis/src/audit.js still considers only each module's top-level name/version, while companion lockfiles explicitly discard vendored, so known advisories for versioned compiled dependencies are now silently missed for both bundle and lockfile audit inputs. Please either teach audit/reason collection to emit code-bearing versioned vendored entries and retain enough lockfile metadata, or preserve an auditable dependency representation.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants