# Bun Analyzer Design — PREP-SCANNER-BUN-001-DESIGN-DOC Status: Draft (2025-12-06) Owners: Bun Analyzer Guild · Scanner Guild Scope: Bun package manager analyzer for npm-ecosystem vulnerability scanning in container filesystems. ## Overview The Bun analyzer extracts npm-ecosystem package inventory from Bun-managed JavaScript/TypeScript projects. Bun uses npm-compatible package.json and produces packages in `node_modules`, making it similar to the Node analyzer but with distinct lockfile formats and installation structures. ## Supported Artifacts ### Lockfile Formats | Format | Extension | Status | Notes | |--------|-----------|--------|-------| | Text lockfile | `bun.lock` | Supported | JSONC format with package metadata | | Binary lockfile | `bun.lockb` | Unsupported | Undocumented binary format; emit migration guidance | ### Installation Structures | Structure | Discovery Pattern | Notes | |-----------|-------------------|-------| | Default (hoisted) | `node_modules/**/package.json` | Standard flat structure | | Isolated linker | `node_modules/.bun/**/package.json` | Symlink-heavy, requires safe traversal | ## Discovery Heuristics ### Project Root Detection A directory is considered a Bun project root when: 1. `package.json` exists, AND 2. One or more of: - `bun.lock` exists (text lockfile) - `bun.lockb` exists (binary lockfile — triggers unsupported message) - `bunfig.toml` exists (Bun configuration) - `node_modules/.bun/` exists (isolated linker marker) ### Input Classification ``` BunInputNormalizer classifies each root: ├── InstalledPath: node_modules exists → traverse installed packages ├── LockfilePath: bun.lock only (no node_modules) → parse lockfile └── Unsupported: bun.lockb only → emit remediation finding ``` ## Lockfile Schema (`bun.lock`) The `bun.lock` text format is a JSONC variant (JSON with comments and trailing commas): ```jsonc { "lockfileVersion": 1, "workspaces": { "": { "name": "my-app", "dependencies": { "lodash": "^4.17.21" } } }, "packages": { "lodash@4.17.21": { "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57...", "dependencies": {} } } } ``` ### Extracted Fields | Field | Source | Usage | |-------|--------|-------| | name | packages key (before `@version`) | PURL name component | | version | packages key (after `@`) | PURL version component | | resolved | packages[].resolved | Evidence locator | | integrity | packages[].integrity | Evidence hash (sha512/sha256) | ## Evidence Model ### LanguageComponentEvidence Structure ```csharp record BunPackageEvidence( LanguageEvidenceKind Kind, // File | Metadata | Lockfile string Source, // "node_modules" | "bun.lock" string Locator, // File path or registry URL string? Content, // package.json content (if File) string? Sha256); // Content hash ``` ### Evidence Collection Matrix | Source | Kind | Locator | Content | Hash | |--------|------|---------|---------|------| | `node_modules/**/package.json` | File | Relative path | JSON content | sha256 of content | | `bun.lock` | Lockfile | `bun.lock:packages[name@version]` | null | null | | Registry resolution | Metadata | resolved URL | null | integrity value | ## PURL Generation Bun packages are npm packages with `bun` package manager qualifier: ``` pkg:npm/@?package_manager=bun ``` ### Scoped Package Encoding | Raw Name | Encoded PURL | |----------|--------------| | `lodash` | `pkg:npm/lodash@4.17.21?package_manager=bun` | | `@types/node` | `pkg:npm/%40types/node@20.10.0?package_manager=bun` | | `@org/pkg` | `pkg:npm/%40org/pkg@1.0.0?package_manager=bun` | ## Symlink Safety Bun's isolated linker creates symlink-heavy structures. Safety requirements: 1. **Prefix Containment**: Only follow symlinks that resolve within root path 2. **Cycle Detection**: Maintain visited inode/realpath set 3. **Path Recording**: Record both logical path (symlink) and real path (target) 4. **Depth Limit**: Cap symlink depth at 32 levels (configurable) ```csharp record SymlinkSafetyContext( string RootPrefix, HashSet<(long Inode, long Device)> VisitedInodes, int MaxDepth = 32); ``` ## Performance Guards | Guard | Default | Rationale | |-------|---------|-----------| | max-files-per-root | 50,000 | Prevent full image traversal | | max-symlink-depth | 32 | Avoid infinite loops in malformed structures | | prefix-pruning | enabled | Skip paths outside expected locations | | timeout-per-root | 60s | Bound analysis time per project | ## CLI Contract ### `stellaops-cli bun inspect` Display Bun package inventory for local root or scan ID: ```bash # Local analysis stellaops-cli bun inspect /path/to/project # Remote scan lookup stellaops-cli bun inspect --scan-id abc123 ``` Output formats: `--output json|table|ndjson` ### `stellaops-cli bun resolve` Resolve Bun packages by scan ID, digest, or image reference: ```bash stellaops-cli bun resolve --scan-id abc123 --package lodash stellaops-cli bun resolve --digest sha256:abc... --format json ``` ## WebService Contract ### `GET /api/scans/{scanId}/bun-packages` Returns inventory of Bun packages for a completed scan. Query parameters: - `page`, `pageSize`: Pagination - `name`: Filter by package name (prefix match) - `scope`: Filter by npm scope Response schema: ```json { "scanId": "abc123", "analyzer": "bun", "packages": [ { "name": "lodash", "version": "4.17.21", "purl": "pkg:npm/lodash@4.17.21?package_manager=bun", "evidence": [ { "kind": "File", "source": "node_modules", "locator": "node_modules/lodash/package.json", "sha256": "abc..." } ] } ], "total": 150, "page": 1, "pageSize": 50 } ``` ## Unsupported Artifact Handling ### Binary Lockfile (`bun.lockb`) When only `bun.lockb` is present (no `bun.lock` or `node_modules`): 1. Emit remediation finding with severity `Info` 2. Provide migration command: `bun install --save-text-lockfile` 3. Skip package enumeration (no false negatives from partial binary parse) ```csharp record BunLockbUnsupportedFinding( string Path, string RemediationCommand = "bun install --save-text-lockfile", string Reason = "Binary lockfile format is undocumented and unstable"); ``` ## Test Fixtures | Fixture | Purpose | Validation | |---------|---------|------------| | `hoisted-install` | Standard Bun install with `node_modules` + `bun.lock` | Installed inventory path | | `isolated-linker` | `bun install --linker isolated` structure | `.bun/` traversal | | `lockfile-only` | No `node_modules`, only `bun.lock` | Lockfile inventory, dev/prod filtering | | `binary-lockfile-only` | Only `bun.lockb` present | Unsupported remediation message | | `monorepo-workspaces` | Multiple `package.json` under single lock | Workspace member handling | | `symlink-cycles` | Malformed structure with cycles | Cycle detection, no infinite loops | ## Configuration ### Environment Variables | Variable | Default | Description | |----------|---------|-------------| | `STELLAOPS_BUN_MAX_FILES` | 50000 | Max files per root | | `STELLAOPS_BUN_MAX_SYMLINK_DEPTH` | 32 | Max symlink traversal depth | | `STELLAOPS_BUN_INCLUDE_DEV` | true | Include dev dependencies | | `STELLAOPS_BUN_TIMEOUT_SECONDS` | 60 | Per-root analysis timeout | ### appsettings.json ```json { "Scanner": { "Analyzers": { "Bun": { "MaxFilesPerRoot": 50000, "MaxSymlinkDepth": 32, "IncludeDevDependencies": true, "TimeoutSeconds": 60 } } } } ``` ## Determinism Requirements 1. **Sorted Output**: Packages ordered by `(name, version)` tuple 2. **Stable IDs**: Component keys computed as `sha256(analyzerId + purl)` 3. **Reproducible Evidence**: Evidence ordered by `(kind, source, locator)` 4. **No Timestamps**: Evidence does not include file modification times 5. **Canonical Paths**: All paths normalized (forward slashes, no trailing slash) ## Open Decisions 1. **Dev Dependency Default**: Currently `include_dev: true` for lockfile-only scans — confirm with Policy Guild 2. **Workspace Handling**: Whether to emit separate inventory per workspace or merged — await monorepo fixture results 3. **PURL Qualifier**: Using `?package_manager=bun` vs no qualifier — coordinate with Concelier linkset resolution ## Handoff This document serves as the PREP artifact for PREP-SCANNER-BUN-001-DESIGN-DOC. Update upon: - Policy Guild confirmation of dev dependency defaults - Concelier Guild decision on PURL qualifier handling - Fixture suite completion revealing edge cases