One compromised package. How far does it fall?
Live demo: dependency.toolsmith.online (choose Small example or Real project to load a bundled lockfile)
Dependency Domino is a software supply-chain visualization tool. Import a project's dependency lockfile, explore its dependency graph, and select any package to run a hypothetical compromise simulation. The graph highlights every package that depends on the selection, directly or indirectly, so you can see how far trust in one upstream package reaches.
The app can also query OSV.dev for known advisories against exact package versions. Known advisories and hypothetical simulation results are shown as separate kinds of information.
Dependency Domino is an exposure visualization and educational tool, not a vulnerability scanner. It does not detect exploits, analyze malware, or prove that a dependent package is vulnerable.
| Graph overview | Simulation in progress | Known advisories |
|---|---|---|
![]() |
![]() |
![]() |
Demo video: docs/demo/dependency-domino-demo.mp4 (2:52, narrated by the team). The demo script has the matching narration and timings. Screenshots are
regenerated with node scripts/screenshots.mjs <dev server URL>.
Feature complete for the hackathon scope: lockfile import for npm, pnpm, Yarn, and Bun, the interactive dependency graph, package search, hypothetical compromise simulation with root paths, OSV advisory lookup, and JSON, Markdown, and PNG exports. See the project plan for scope and milestones.
- Open the live demo or start the app locally. Choose Try demo and pick Small example (14 hand-written
packages) or Real project (the yargs repository's lockfile, 562 installed instances), GitHub URL to fetch
a supported lockfile from a public repository's default branch, or Import Lockfile to select
package-lock.json,pnpm-lock.yaml,yarn.lock, orbun.lock. You can also drop the file onto the graph area. - Pan and zoom the graph. Click a package or search by name or version to select an installed instance. The details panel shows its version, lockfile identity, source, and dependency information.
- Choose Simulate Compromise. The animation highlights packages that depend on the selected instance, directly or indirectly. Results include a dependent count and a sample path from the project root when one exists.
- Choose Reset to clear the simulation and selection while keeping the loaded graph, or select another package and simulate again.
- Review Warnings in the footer for unresolved dependencies and unsupported relationships. A rejected file shows an error while keeping any previously loaded graph.
- After running a simulation, choose Export JSON or Export Markdown in the results panel to save a local report. Both include the origin, dependent ancestors, sample paths, metrics, warnings, generation time, advisory lookup status, and hypothetical-exposure disclaimer. Advisory IDs are recorded separately from the simulated exposure.
Package details includes expandable lists of direct dependencies and dependents, with edge kinds and requested ranges where recorded. Click an installed instance to select and focus it. Lists initially show ten relationships.
Open Most dependents in the Overview sidebar to see the ten installed instances with the most direct or indirect dependents. Counts exclude the origin and project root and use every recorded dependency, regardless of canvas filters. Select a ranked row to inspect it or choose Simulate to start a hypothetical compromise trace. Ranking runs in a browser worker and is cached while the graph is loaded.
The Direct and indirect dependencies count includes each reachable installed instance once, even when several paths lead to it. It excludes the selected instance and project root and is based on recorded edges, so unresolved relationships can make it incomplete. Canvas filters do not change this count.
Under View, set Package labels to Name only for shorter canvas labels. Names installed at different versions keep their versions, including when another copy is outside the current view. OSV markers remain visible on advisory labels. Hovering or selecting a package outlines its other visible installed copies in blue. Other installed copies in package details lists their versions and lockfile identities, ten per page, and lets you select and focus each copy. Dropdowns support arrow keys, Home/End, and typing an option's starting letters. Enter selects the highlighted option; Escape closes without changing the selection. Tab selects the highlighted option and moves to the next control.
Choose Explore neighborhood to lay out the packages within 1 to 4 hops of the selection. The View section lets you follow dependencies, dependents, or both; selecting another package moves the neighborhood to that instance. During a simulation, Show only simulated impact lays out just the origins and their dependent instances. Both views respect the development and direct-only filters and include relevant packages beyond the overview's reveal depth. Back to overview restores the previous layout and viewport when its layout direction, label mode, and reveal set are unchanged. Clearing a simulation exits impact-only view; importing another lockfile or resetting exits either view. Searching outside an impact-only view returns to the overview so that package can be focused.
The Simulation section also lists Affected packages by distance, grouped by shortest hop count from any simulation origin. It shows 50 installed instances per page, marks development-only packages, and lets you focus each instance. The project root is reported separately. These canvas views never change simulation counts or exported reports, which continue to use the full dependency graph.
When a simulation has alternative root paths, use Sample path to switch the path highlighted on the canvas. Sampling favors different direct dependencies of the root, with up to five short paths across all selected origins. The direct-branch count is computed independently of the sample limit. Each displayed link includes its requested range and dependency kind when recorded. JSON and Markdown exports include every sampled path and its edge metadata, rather than only the currently highlighted path. Additional routes may exist beyond these samples.
For a single simulation origin, Every path passes through lists strict intermediate packages that occur on every recorded route from root, including longer alternatives. The canvas outlines them in solid blue. This is graph structure, not a recommendation to replace or remove a package. Root and origin are excluded, and disconnected origins have no list.
Choose Preview removing this package to calculate reachability on a copy with that installed instance and its edges removed. The result counts only other packages that were reachable from root before removal and become unreachable after it. The original canvas stays visible for comparison, with the removed instance faded and newly unreachable instances outlined in dashed blue. The preview does not establish that consumers will still work or that removal is safe. Starting a compromise trace clears the removal preview and vice versa. Reset clears both; reports remain compromise reports.
Right-click a node, use Package actions, or press Shift+F10 after selecting a package for quick actions: simulate, focus, copy its identifier, show all recorded dependents, or preview removal. The dependents view respects canvas filters.
The canvas offers replay, pause/play, and one-layer stepping during a compromise trace. The displayed layer includes the origin layer. With reduced motion, a new trace starts fully revealed; replay is an explicit action. Automatic playback groups layer updates on deep chains, while manual stepping always moves one layer. Changing origins cancels old playback.
The minimap opens by default above 300 instances and can be toggled on any graph. Click, drag, or use arrow keys to pan; Enter recenters. It covers displayed packages, including nodes outside the viewport, and respects focused views, filters, and progressive reveal. Hidden or unmounted packages have no position in this overview. Geometry is cached separately from the viewport rectangle so pan and zoom do not redraw the package map.
Report downloads stay local and do not upload the lockfile or report. JSON uses report schema version 1; Markdown formats the same snapshot for sharing in issues or PRs. Reports preserve lockfile identities, including duplicate instances and non-npm resolution IDs. The project root appears among ancestors when reached, but package counts exclude the root and the affected-package count also excludes the simulated origin. A missing root path is reported as unavailable. Parser warning metrics count only parser warnings; the report's warning list can also include advisory lookup warnings.
Choose Export PNG on the canvas for the current viewport or the full displayed graph. Both capture the current styling and playback layer on the canvas background; filters, focused views, and progressive reveal still apply. The full export includes displayed packages outside the viewport and adds space around labels while preserving your camera. Images are bounded to 4096 pixels per side. Filenames include a sanitized input label and simulation origin when present. Canvas controls and sidebars are excluded.
After a compromise simulation, Download summary PNG creates a 1600×900 card with the origin, dependent-instance count, shortest distance to root, and selected sample path. Long paths keep both ends with an omitted-package count. The card uses full-graph simulation metrics and includes the hypothetical-exposure disclaimer on the image. Multiple origins and missing root paths are labeled explicitly. Both image exports are generated in the browser and downloaded locally; imported names are drawn as text.
GitHub imports require a public repository URL and network access. The repository metadata determines the default branch,
then the app checks the repository root on raw.githubusercontent.com in this order: package-lock.json,
npm-shrinkwrap.json, pnpm-lock.yaml, pnpm-lock.yml, yarn.lock, and bun.lock. The first file found is parsed;
an invalid or unsupported file reports its error rather than falling back to another lockfile. If only bun.lockb
is found, the app explains how to generate a supported text lockfile. The lockfile is parsed in browser memory
with the same 10 MiB limit and parser path as a local upload. No repository URL is fetched until you submit the GitHub
URL form.
The bundled graph and simulation work without OSV access. Advisory lookup requires network access; a failed lookup does
not prevent simulation. The footer reports lookup progress and partial or failed lookups. Select a package to distinguish
an unchecked package from a successful empty result or a failed lookup, and use Check advisories again to retry.
The right sidebar lists installed instances with known advisories even before you select a node. Select a list entry to
locate that package on the graph and view its OSV links. Advisory nodes have a purple halo and an OSV: label with their
advisory count, separate from the simulation's colors.
All formats have a 10 MiB input limit and are parsed in browser memory.
| Input | Supported format | Graph identity and caveats |
|---|---|---|
package-lock.json / npm-shrinkwrap.json |
npm v1/v2/v3 | v1 nested instances; v2/v3 use packages and ignore legacy dependencies |
pnpm-lock.yaml |
Lockfile v6/v9 | Snapshot keys retain peer contexts; explicit workspace links connect importers |
yarn.lock |
Classic v1 and Berry metadata v2–v10 | Locked descriptors/resolutions; installed copies and resolved peer contexts are unavailable |
bun.lock |
Text lockfile v0/v1 | Recorded package keys retain nested copies; JSONC comments and trailing commas are accepted |
- For Yarn Classic, root declarations are not present in
yarn.lock. Direct dependency flags and root paths are left unavailable rather than inferred. Berry workspace roots are used when explicitly recorded; development-only flags and resolved peer contexts are not reconstructed. Transitive simulation and advisory matching remain available. - pnpm configuration documents preceding the project document are excluded with a warning. Bun's isolated linker and platform-specific installed layouts are not reconstructed. Unsupported sources and unresolved references produce caveats rather than guessed edges. See parser details and references.
- Binary
bun.lockbis unsupported. Generate text withbun install --save-text-lockfile --frozen-lockfile --lockfile-only. pnpm formats other than v6/v9 are also unsupported. - npm v1 lacks the root manifest, so direct flags and root paths are unavailable. Recorded requirements still connect nested and hoisted instances. Development/optional flags are limited to recorded metadata; peers are not inferred.
- npm nodes represent installed paths, so duplicate copies of the same name and version remain separate. Other formats use their recorded resolution identities, which may group installed copies. Edges point from consumer to dependency; simulations follow them in reverse to find dependents.
- Runtime, optional, and peer dependencies are traced, along with development dependencies declared by the root project. Missing required peers produce warnings; missing peers explicitly marked optional are skipped silently.
- npm dependency resolution follows nested and hoisted installed paths. In-project workspace links use the linked folder's dependencies and keep the installed link path as their identity. Project members have hexagonal nodes. Missing/external links and unlinked local folders retain warnings. Root membership edges are not invented when no dependency declaration connects a member.
- npm HTTP(S) resolved hosts appear in package details, with a summary count for hosts outside
registry.npmjs.org. Only hostnames are retained, without URL credentials or query strings. These hosts are not contacted or assessed. - Aliases (
npm:), workspace, Git, file, link, and other unusual dependency ranges produce caveats. An installed target may still be connected by path, but the graph does not establish complete resolution for these cases or validate whether installed versions satisfy requested ranges. - Development and optional indicators reflect lockfile metadata. They do not establish which packages execute in production. Large graphs can still be slow or crowded; progressive reveal, focused views, and layout caching reduce how much needs to be displayed or laid out at once.
- npm v2/v3
hasInstallScriptmetadata is shown as a package flag because the package runs a script at install time; this does not imply the package is malicious. Other supported lockfile formats do not provide this npm-specific flag, so it remains unset for those nodes. - Simulations show hypothetical dependency exposure, not confirmed vulnerabilities, exploitability, or code execution. OSV advisory matches are separate external records; no returned advisories does not establish that a package is safe.
- SvelteKit + TypeScript
- Vite
- Tailwind CSS
- Cytoscape.js
- Iconify
- Vitest
- Prettier
Requires Node.js 22 or later.
npm install
npm run devThen open http://localhost:5173.
| Command | Description |
|---|---|
npm run dev |
Start the dev server |
npm run build |
Build for production |
npm run preview |
Preview the production build |
npm run check |
Type-check with svelte-check |
npm run lint |
Check formatting with Prettier |
npm run format |
Format the codebase |
npm test |
Run unit tests once |
npm run test:e2e |
Run Chromium smoke tests for the demo and file import |
npm run fixtures |
Download real-world lockfile fixtures used by optional tests |
Install the browser and download the pinned fixtures once while online:
npx playwright install chromium
npm run fixtures
npm run test:e2eThe tests start their own dev server on 127.0.0.1:4173; keep that port free. After setup, they run without external
network access: OSV responses are stubbed and other external requests fail the tests. The yargs import test requires
its fixture and reports a setup error if it is missing. Tests cover demo search, simulation counts, JSON export,
and the real yargs file-picker import. Failure screenshots and traces are saved under test-results/.
Use npx playwright show-trace <trace.zip> to inspect a failed run.
The app is deployed on Vercel with @sveltejs/adapter-vercel on the Node 24 runtime. Every push to
main deploys automatically. No environment variables or API keys are needed.
On Windows, npm run build stops at the final adapter step unless Developer Mode is enabled, because the adapter creates
a symlink. Vercel builds on Linux, so deploys are not affected.
Uploaded lockfiles are parsed in the browser and are not stored. Only package names and versions are sent to the server for advisory lookups.
See LICENSE.



