CodeVetter · Repo Unpack · Risk map
d3/d3
The hottest risks are supply-chain and publish footguns, not application auth. Docs build executes a remote Observable module, the site loads a third-party script, and map examples fetch atlases from jsDelivr at view time. prebuild.sh does not fail fast, the test workflow never checks out d3.github.com, and postpublish force-pushes tags if someone publishes. The barrel, sidebar, API index, and prebuild copy list can drift, and this repo has no tests for the actual visualization code.
- gallery.data.js executes remote code at docs build time: import("https://api.observablehq.com/@d3/gallery.js?v=4") and then runtime.module on its default export. package.json docs scripts enable this with node --experimental-network-imports. A compromised gallery notebook becomes code running inside CI and the deploy job.
- config.ts injects https://static.observablehq.com/assets/components/observable-made-by.js into every page via head script async defer, and Vue is told to treat observable-* tags as custom elements. That is a third-party script on the documentation origin.
- stargazers.data.ts reads process.env.GITHUB_TOKEN and sends it as Authorization: token <value> to api.github.com. The token is not committed. Failure is fatal only when process.env.CI is set, so a rate-limit or auth error fails CI and deploy builds and is swallowed locally as NaN.
- postpublish is git push && git push --tags. It runs after a successful npm publish. There is no GitHub Action that publishes to npm; the script is local. Running npm publish from a checkout with a dirty or unintended tag will push the branch and all tags to the remote named by the local git config.
- prebuild.sh never calls set -e. The first hundreds of commands cp historical files from ./build/d3.github.com, which only deploy.yml checks out. test.yml still runs yarn docs:build. Because the script's exit status is the last command, and the last commands copy from node_modules after a normal install, missing historical CDN files do not by themselves fail CI. (inferred)
- Docs cannot import d3 until dist/d3.mjs exists, because the Vite alias replaces "d3" with ./dist/d3.mjs. docs:dev does not run Rollup. A clean checkout that runs yarn docs:dev without a prior yarn prepublishOnly fails module resolution for every example that imports d3.
- dist/d3.mjs is produced and aliased by the docs site but omitted from package.json files, while exports.default points at src/index.js and exports.umd points at the minified UMD file. Shipping a docs-only ESM bundle that is not the package consumers import is a packaging trap when debugging "works on d3js.org, differs in npm".
- WorldMap and UsMap fetch topojson from cdn.jsdelivr.net at browser runtime (world-atlas@2.0.2 and us-atlas@3.0.1) and parse it with topojson-client. Visitors of those doc pages depend on jsDelivr availability and on those package versions staying compatible. deferRender only delays the work; it does not vendor the data.
- The documented d3.html, d3.svg, and d3.xml helpers fetch a URL and parse it with DOMParser, and d3.image forwards a crossOrigin init. Implementations are not in this repo, but the public API is specified here and is re-exported by the barrel, so any app that passes untrusted URLs into those helpers inherits that behavior.
- PlotRender calls Plot[method] where method is a component prop defaulting to "plot". A markdown page can therefore select any export of @observablehq/plot. The fake DOM used for SSR ignores addEventListener, so interactive plots are not faithful until the client replace hook runs.
- terser reserves only InternMap and InternSet. CHANGES.md says ordinal scales key domains with InternMap and that those names must survive minification. Dropping the reserved list, or minifying a future build that needs additional constructor names, would break the UMD build that jsDelivr and unpkg serve. Nothing in test/ constructs the minified bundle and reads those names back.
- Adding or renaming a module is a multi-file contract with partial enforcement. test/d3-test.js fails if a dependency export is missing from src/index.js, and docs-test.js fails if a new page is not linked with a real anchor, but nothing checks that prebuild.sh, .gitignore, docs/api.md, and themeConfig.sidebar were all updated together. .gitignore already duplicates several generated paths (for example d3-array.v3.js is listed twice).
- Rollup file IO is local and expected: readFileSync("./LICENSE") and an import assertion of ./package.json. volcano.data.js readFileSyncs the watched JSON path VitePress passes in. Neither path is user-controlled. There is no shell-out, no child_process, and no auth middleware anywhere in src/ or docs/.