From b03b5fb1be1b50336b081e624d961d1ac208e41f Mon Sep 17 00:00:00 2001 From: jdalton Date: Tue, 4 Aug 2026 11:38:00 -0400 Subject: [PATCH] fix(ci): clear the lint, test, and doc gates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main's CI has been red on three repo-owned causes since before today's merges. All three are pre-existing; none came from a recent PR. The lint check reported 18 max-comment-block-lines errors across 14 files, from a newly cascaded 20-line cap on inline comment blocks. Each is restructured on its own merits rather than suppressed: - The ~70 lines of pasted `cdxgen --help` output move to docs/references/repo/cdxgen-flags.md, leaving a one-line pointer above the yargs config it documents. The version in the heading is the point of keeping it at all. - Comment runs holding several independent facts are split at the paragraph boundaries a reader already sees: the bazel exit-code taxonomy, the python directory layouts, and the meow flag-type mapping. - Two docblocks had been shredded by an earlier JSDoc reflow. npm-packages.mts lost its archive tree into a bogus `@cyclonedx` tag; both are rewritten as prose that says the same thing. - vfs-extract.mts had extractExternalTools' docblock stranded above MAX_EXTRACTION_DEPTH instead of above the function; it now sits on the function it documents. - The rest keep their contract and drop @example blocks that only restated the signature, folding any fact unique to the example into @param or @returns. Two unit test files read the options bag at argument index 2, but spawnCoanaDlx is called with two arguments, so every read was undefined and threw on property access. Production is correct; the tests move to index 1. Five assertions across perform-reachability-analysis.test.mts and its -coana sibling. Only one surfaced in CI because the Test job shards. CLAUDE.md's releasing-v1x section had one 230-char bullet against the 200 cap. It stated two rules — the prerelease hint and the CHANGELOG section — so it becomes two bullets. --- CLAUDE.md | 3 +- docs/references/repo/cdxgen-flags.md | 85 +++++++++++++++++++ .../build-infra/lib/checkpoint-manager.mts | 19 ++--- packages/build-infra/lib/platform-targets.mts | 4 - .../cli/scripts/sea-build-utils/builder.mts | 72 ++++------------ .../external-tools-install.mts | 6 +- .../scripts/sea-build-utils/npm-packages.mts | 83 +++++------------- .../manifest/bazel/bazel-repo-discovery.mts | 4 +- .../commands/manifest/cmd-manifest-cdxgen.mts | 72 +--------------- packages/cli/src/meow.mts | 1 + packages/cli/src/util/basics/spawn.mts | 27 ++---- packages/cli/src/util/basics/vfs-extract.mts | 4 +- .../cli/src/util/coana/compress-facts.mts | 33 +++---- packages/cli/src/util/dlx/vfs-extract.mts | 40 ++++----- packages/cli/test/helpers/cli-execution.mts | 51 ++++------- .../cli/test/helpers/workspace-helper.mts | 15 +--- ...rform-reachability-analysis-coana.test.mts | 4 +- .../perform-reachability-analysis.test.mts | 8 +- 18 files changed, 202 insertions(+), 329 deletions(-) create mode 100644 docs/references/repo/cdxgen-flags.md diff --git a/CLAUDE.md b/CLAUDE.md index 50238bc712..f38f8ec0bc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -148,5 +148,6 @@ Advice and critical assessment ONLY — never for making code changes. Consult b - 🚨 A failure AFTER the tag step burns that version; move the hint to the next patch, never re-dispatch the same number. [`releasing-v1x`](docs/agents.md/repo/releasing-v1x.md) - 🚨 Never dispatch a real run (`dry-run=false`) and never approve a stage — both are human actions, and stage approval needs browser 2FA. [`releasing-v1x`](docs/agents.md/repo/releasing-v1x.md) - 🚨 The USER names the release version; prepare the bump commit only after they do. [`releasing-v1x`](docs/agents.md/repo/releasing-v1x.md) -- Between releases `package.json` carries an `X.Y.Z-prerelease` hint and notes accrue under CHANGELOG's `## [Unreleased]`; the bump strips the hint and promotes that section. [`releasing-v1x`](docs/agents.md/repo/releasing-v1x.md) +- Between releases `package.json` carries an `X.Y.Z-prerelease` hint; the bump strips it. [`releasing-v1x`](docs/agents.md/repo/releasing-v1x.md) +- Notes accrue under CHANGELOG's `## [Unreleased]`; the bump promotes that section into the release. [`releasing-v1x`](docs/agents.md/repo/releasing-v1x.md) - 🚨 `v1.x` is the consumable line and owns the `latest` dist-tag; the default branch carries the 2.x PRERELEASE line and is refused `latest`. [`releasing-v1x`](docs/agents.md/repo/releasing-v1x.md) diff --git a/docs/references/repo/cdxgen-flags.md b/docs/references/repo/cdxgen-flags.md new file mode 100644 index 0000000000..a25b3dd95c --- /dev/null +++ b/docs/references/repo/cdxgen-flags.md @@ -0,0 +1,85 @@ +# cdxgen 11.2.7 flag reference + +Verbatim `--help` output of the cdxgen version this repo maps flags for. The +`yargsConfig` in +[`packages/cli/src/commands/manifest/cmd-manifest-cdxgen.mts`](../../../packages/cli/src/commands/manifest/cmd-manifest-cdxgen.mts) +is derived from it: cdxgen parses its own args with yargs, so the Socket CLI +mirrors that config instead of re-declaring every flag in meow. + +This lives in a doc rather than inline because it is a snapshot of one exact +version, and that is what makes it useful — a version that gets unpublished +cannot be fetched again. To move to a newer cdxgen, run the command below and +replace the block with its output. + +```console +$ npx @cyclonedx/cdxgen@11.2.7 --help + +Options: + -o, --output Output file. Default bom.json [default: "bom.json"] + -t, --type Project type. Please refer to https://cyclonedx.github.io/cdxgen/#/PROJECT_TYPES for supp + orted languages/platforms. [array] + --exclude-type Project types to exclude. Please refer to https://cyclonedx.github.io/cdxgen/#/PROJECT_TY + PES for supported languages/platforms. + -r, --recurse Recurse mode suitable for mono-repos. Defaults to true. Pass --no-recurse to disable. + [boolean] [default: true] + -p, --print Print the SBOM as a table with tree. [boolean] + -c, --resolve-class Resolve class names for packages. jars only for now. [boolean] + --deep Perform deep searches for components. Useful while scanning C/C++ apps, live OS and oci i + mages. [boolean] + --server-url Dependency track url. Eg: https://deptrack.cyclonedx.io + --skip-dt-tls-check Skip TLS certificate check when calling Dependency-Track. [boolean] [default: false] + --api-key Dependency track api key + --project-group Dependency track project group + --project-name Dependency track project name. Default use the directory name + --project-version Dependency track project version [string] [default: ""] + --project-id Dependency track project id. Either provide the id or the project name and version togeth + er [string] + --parent-project-id Dependency track parent project id [string] + --required-only Include only the packages with required scope on the SBOM. Would set compositions.aggrega + te to incomplete unless --no-auto-compositions is passed. [boolean] + --fail-on-error Fail if any dependency extractor fails. [boolean] + --no-babel Do not use babel to perform usage analysis for JavaScript/TypeScript projects. [boolean] + --generate-key-and-sign Generate an RSA public/private key pair and then sign the generated SBOM using JSON Web S + ignatures. [boolean] + --server Run cdxgen as a server [boolean] + --server-host Listen address [default: "127.0.0.1"] + --server-port Listen port [default: "9090"] + --install-deps Install dependencies automatically for some projects. Defaults to true but disabled for c + ontainers and oci scans. Use --no-install-deps to disable this feature. + [boolean] [default: true] + --validate Validate the generated SBOM using json schema. Defaults to true. Pass --no-validate to di + sable. [boolean] [default: true] + --evidence Generate SBOM with evidence for supported languages. [boolean] [default: false] + --spec-version CycloneDX Specification version to use. Defaults to 1.6 + [number] [choices: 1.4, 1.5, 1.6, 1.7] [default: 1.6] + --filter Filter components containing this word in purl or component.properties.value. Multiple va + lues allowed. [array] + --only Include components only containing this word in purl. Useful to generate BOM with first p + arty components alone. Multiple values allowed. [array] + --author The person(s) who created the BOM. Set this value if you're intending the modify the BOM + and claim authorship. [array] [default: "OWASP Foundation"] + --profile BOM profile to use for generation. Default generic. + [choices: "appsec", "research", "operational", "threat-modeling", "license-compliance", "generic", "machine-learning", + "ml", "deep-learning", "ml-deep", "ml-tiny"] [default: "generic"] + --exclude Additional glob pattern(s) to ignore [array] + --export-proto Serialize and export BOM as protobuf binary. [boolean] [default: false] + --proto-bin-file Path for the serialized protobuf binary. [default: "bom.cdx"] + --include-formulation Generate formulation section with git metadata and build tools. Defaults to false. + [boolean] [default: false] + --include-crypto Include crypto libraries as components. [boolean] [default: false] + --standard The list of standards which may consist of regulations, industry or organizational-specif + ic standards, maturity models, best practices, or any other requirements which can be eva + luated against or attested to. + [array] [choices: "asvs-5.0", "asvs-4.0.3", "bsimm-v13", "masvs-2.0.0", "nist_ssdf-1.1", "pcissc-secure-slc-1.1", "scv + s-1.0.0", "ssaf-DRAFT-2023-11"] + --json-pretty Pretty-print the generated BOM json. [boolean] [default: false] + --min-confidence Minimum confidence needed for the identity of a component from 0 - 1, where 1 is 100% con + fidence. [number] [default: 0] + --technique Analysis technique to use + [array] [choices: "auto", "source-code-analysis", "binary-analysis", "manifest-analysis", "hash-comparison", "instrume + ntation", "filename"] + --auto-compositions Automatically set compositions when the BOM was filtered. Defaults to true + [boolean] [default: true] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] +``` diff --git a/packages/build-infra/lib/checkpoint-manager.mts b/packages/build-infra/lib/checkpoint-manager.mts index b7f4400c08..8ce6ebbd0e 100644 --- a/packages/build-infra/lib/checkpoint-manager.mts +++ b/packages/build-infra/lib/checkpoint-manager.mts @@ -126,19 +126,12 @@ export function computeCacheHash( * @param {string} name - Checkpoint name (must be a CHECKPOINTS value). * @param {() => Promise} smokeTest - Throws if the stage output is * invalid. - * @param {object} [options] - * @param {string} [options.packageName] - * @param {string} [options.artifactPath] - Informational; recorded in JSON. - * @param {string} [options.binaryPath] - Informational; recorded in JSON. - * @param {string | number} [options.binarySize] - Informational; recorded in - * JSON. - * @param {string[]} [options.sourcePaths] - Inputs hashed into the cache key. - * @param {string} [options.buildMode] - * @param {string} [options.nodeVersion] - * @param {string} [options.platform] - * @param {string} [options.arch] - * @param {string} [options.libc] - * @param {string} [options.packageRoot] + * @param {CreateCheckpointOptions} [options] - Only `sourcePaths` and the + * inherited platform fields (`buildMode`, `nodeVersion`, `platform`, `arch`, + * `libc`) feed the cache key. `packageName` picks which checkpoint file is + * written and `packageRoot` only shortens the logged path; `artifactPath`, + * `binaryPath`, and `binarySize` are recorded in the marker for humans. None + * of those five invalidate the checkpoint when they change. */ export async function createCheckpoint( buildDir: string, diff --git a/packages/build-infra/lib/platform-targets.mts b/packages/build-infra/lib/platform-targets.mts index e2117c5f86..efa2dbfc7b 100644 --- a/packages/build-infra/lib/platform-targets.mts +++ b/packages/build-infra/lib/platform-targets.mts @@ -160,12 +160,8 @@ export interface PlatformTargetInfo { * @example * parsePlatformTarget('darwin-arm64') * // { platform: 'darwin', arch: 'arm64' } - * - * @example * parsePlatformTarget('linux-x64-musl') * // { platform: 'linux', arch: 'x64', libc: 'musl' } - * - * @example * parsePlatformTarget('win-x64') * // { platform: 'win32', arch: 'x64' } * diff --git a/packages/cli/scripts/sea-build-utils/builder.mts b/packages/cli/scripts/sea-build-utils/builder.mts index f5a9b0be45..62af119940 100644 --- a/packages/cli/scripts/sea-build-utils/builder.mts +++ b/packages/cli/scripts/sea-build-utils/builder.mts @@ -27,23 +27,17 @@ import { SOCKET_CLI_SEA_BUILD_DIR } from '../constants/paths.mts' // c8 ignore start /** * Generate SEA configuration file for Node.js single executable application. - * Creates sea-config-{name}.json with blob output path and settings. * - * Configuration includes: - Entry point, main file to bundle. - Output blob - * path. - Code cache enabled for optimization. - Snapshot disabled for - * compatibility. - No bundled assets, minimizes size. - * - * @example - * const configPath = await generateSeaConfig( - * '/path/to/dist/cli.js', - * '/path/to/socket-darwin-arm64', - * ) - * // Returns: /path/to/sea-config-socket-darwin-arm64.json + * Writes `sea-config-.json` naming the entry point to bundle and the blob + * output path, with the code cache on for faster startup, snapshots off (they + * would freeze build-time env values, and the CLI reads env at module load), + * and no bundled assets so the binary stays small. * * @param {string} entryPoint - Absolute path to the entry point file. * @param {string} outputPath - Absolute path to the output binary. * - * @returns Promise resolving to absolute path of generated config file. + * @returns Promise resolving to the absolute config path, which is + * `sea-config-.json` beside that binary. */ export async function generateSeaConfig(entryPoint, outputPath) { const outputName = path.basename(outputPath, path.extname(outputPath)) @@ -105,51 +99,19 @@ export async function generateSeaConfig(entryPoint, outputPath) { /** * Inject SEA blob and optional VFS assets into a Node.js binary using binject. * - * This function performs the core SEA binary build step by: - * - * 1. Invoking binject to inject the SEA blob into the Node.js binary. - * 2. Optionally embedding security tools via VFS compression (binject --vfs). - * - * Config-Based Blob Generation: Instead of pre-generating the SEA blob with - * `node --experimental-sea-config`, binject reads the sea-config.json directly - * and generates the blob automatically. This simplifies the API and reduces - * build steps. - * - * VFS Compression (Optional): If vfsTarGz is provided, binject's --vfs flag - * embeds the compressed tar.gz of security tools into the binary. This achieves - * ~70% compression compared to Node.js SEA assets. If vfsTarGz is omitted, - * --vfs-compat mode is used (no actual VFS bundling). - * - * @example - * await injectSeaBlob( - * 'build-infra/build/downloaded/node-smol/darwin-arm64/node', - * 'dist/sea/sea-config-socket-darwin-arm64.json', - * 'dist/sea/socket-darwin-arm64', - * 'socket-darwin-arm64-abc123', - * 'build-infra/build/external-tools/darwin-arm64.tar.gz', - * ) - * // Creates: dist/sea/socket-darwin-arm64 with CLI + compressed VFS - * - * @example - * await injectSeaBlob( - * 'build-infra/build/downloaded/node-smol/linux-x64/node', - * 'dist/sea/sea-config-socket-linux-x64.json', - * 'dist/sea/socket-linux-x64', - * 'socket-linux-x64-abc123', - * ) - * // Creates: dist/sea/socket-linux-x64 with CLI only (no VFS) + * Binject reads sea-config.json directly and generates the blob itself, so + * there is no separate `node --experimental-sea-config` step. When `vfsTarGz` + * is given, its `--vfs` flag also embeds that compressed tar.gz of security + * tools, roughly 70% smaller than the equivalent Node.js SEA assets; without + * it binject runs in `--vfs-compat` mode and bundles the CLI alone. * * @param {string} nodeBinary - Path to the node-smol binary to inject into. - * @param {string} configPath - Path to the sea-config.json file for - * config-based blob generation. - * @param {string} outputPath - Path to the output SEA binary (may be same as - * nodeBinary). - * @param {string} cacheId - Unique cache identifier for parallel builds - * prevents interference. - * @param {string} [vfsTarGz] - Optional path to tar.gz file containing security - * tools for VFS bundling. If provided, security tools are compressed and - * embedded in the binary. If omitted, only the CLI code is bundled (no - * additional tools). + * @param {string} configPath - Path to the sea-config.json file. + * @param {string} outputPath - Path to the output SEA binary (may be the same + * as nodeBinary). + * @param {string} cacheId - Unique cache identifier, so parallel builds do not + * interfere with each other. + * @param {string} [vfsTarGz] - Optional tar.gz of security tools to embed. * * @returns Promise that resolves when injection completes. */ diff --git a/packages/cli/scripts/sea-build-utils/external-tools-install.mts b/packages/cli/scripts/sea-build-utils/external-tools-install.mts index a9ac1dc157..81e65af4dc 100644 --- a/packages/cli/scripts/sea-build-utils/external-tools-install.mts +++ b/packages/cli/scripts/sea-build-utils/external-tools-install.mts @@ -131,14 +131,14 @@ export async function downloadAndInstallTool( // Unlike other tools, Python requires its entire directory structure (stdlib, lib, // include directories) to function. The python-build-standalone package is a // complete, self-contained Python installation (~19 MB compressed). - // + // Unix directory structure after extraction: // python/ // ├── bin/ # Python executable and symlinks. // ├── lib/ # Standard library and site-packages. // ├── include/ # C headers for extension modules. // └── share/ # Documentation and other resources. - // + // Windows directory structure after extraction: // python/ // ├── python.exe # Python executable at root. @@ -146,7 +146,7 @@ export async function downloadAndInstallTool( // ├── Lib/ # Standard library and site-packages. // ├── libs/ # Import libraries for linking. // └── include/ # C headers for extension modules. - // + // We keep the entire python/ directory in the VFS for socket-basics to use. const pythonBinPath = normalizePath( path.join( diff --git a/packages/cli/scripts/sea-build-utils/npm-packages.mts b/packages/cli/scripts/sea-build-utils/npm-packages.mts index bd94d40b42..00557a5805 100644 --- a/packages/cli/scripts/sea-build-utils/npm-packages.mts +++ b/packages/cli/scripts/sea-build-utils/npm-packages.mts @@ -37,29 +37,11 @@ const externalTools = JSON.parse(readFileSync(externalToolsPath, 'utf8')).tools /** * Combine npm packages and external tools into a single VFS archive. * - * Creates a unified tar.gz containing both: - * - * - Node_modules/ with npm packages and dependencies. - * - External tool binaries (Python, Trivy, TruffleHog, OpenGrep, socket-patch). - * - * The combined archive is used by binject for VFS embedding into SEA binaries. - * - * Directory structure in combined archive: ./node_modules/ # npm packages with - * dependencies ├── @coana-tech/cli/ ├── @cyclonedx/cdxgen/ └── synp/ ./python/ - * - * # Python runtime ./trivy # Trivy binary ./trufflehog # TruffleHog binary - * - * ./opengrep # OpenGrep binary ./socket-patch # Socket Patch Rust binary - * (v2.0.0+) - * - * @example - * const combined = await combineVfsArchives( - * '../build-infra/build/npm-packages/npm-packages.tar.gz', - * '../build-infra/build/external-tools/darwin-arm64.tar.gz', - * 'darwin', - * 'arm64', - * ) - * // Returns: '../build-infra/build/vfs/darwin-arm64.tar.gz' + * Writes one tar.gz holding `node_modules/` — the npm packages plus their + * production dependencies — alongside the external tool payloads at the archive + * root: `python/` (the runtime directory), and the `trivy`, `trufflehog`, + * `opengrep`, and `socket-patch` binaries. binject reads this archive to embed + * the VFS into a SEA binary. * * @param {string} npmPackagesTarGz - Path to npm packages tar.gz. * @param {string} externalToolsTarGz - Path to external tools tar.gz. @@ -67,7 +49,8 @@ const externalTools = JSON.parse(readFileSync(externalToolsPath, 'utf8')).tools * @param {string} arch - Architecture identifier (arm64, x64). * @param {boolean} [isMusl=false] - Whether this is musl libc, Linux only. * - * @returns Promise resolving to path of combined tar.gz. + * @returns Promise resolving to the combined tar.gz path, `/ + * -.tar.gz`. */ async function combineVfsArchives( npmPackagesTarGz, @@ -176,19 +159,10 @@ async function combineVfsArchives( /** * Download a single npm package with full dependency tree using Arborist. * - * Downloads the complete package structure including node_modules/ with all - * production dependencies, ready for VFS bundling. - * - * The install is checked against the tool's `integrity` pin before it is used. - * A missing or mismatched pin throws — see npm-integrity.mts for why both the - * npm-recorded hash and our pin are needed. - * - * @example - * await downloadNpmPackage( - * { integrity: 'sha512-xxx', name: 'synp', version: '1.9.14' }, - * '/tmp/synp', - * ) - * // Creates: /tmp/synp/node_modules/synp/ with full dependency tree + * Installs into `/node_modules//` with all production + * dependencies, ready for VFS bundling, then checks the result against the + * tool's `integrity` pin. A missing or mismatched pin throws — see + * npm-integrity.mts for why both the npm-recorded hash and our pin are needed. * * @param {object} pin - The npm tool as declared in bundle-tools.json. * @param {string} targetDir - Directory to install package into. @@ -254,32 +228,19 @@ async function downloadNpmPackage(pin, targetDir) { /** * Download all npm packages with full dependency trees for VFS bundling. * - * Downloads npm packages specified in bundle-tools.json that have type='npm', - * installs them with full production dependency trees using Arborist, and - * packages them into a compressed tar.gz for VFS embedding. - * - * Npm Packages: - * - * - @coana-tech/cli: Static analysis and reachability detection. - * - @cyclonedx/cdxgen: CycloneDX SBOM generator. - * - Synp: yarn.lock to package-lock.json converter. - * - * Note: socket-patch was migrated from npm to GitHub releases in v2.0.0. It's - * now bundled as a standalone Rust binary via downloads.mts. - * - * Directory Structure: / └── node_modules/ ├── @coana-tech/cli/ │ - * ├── bin/coana │ ├── package.json │ └── node_modules/ # Dependencies ├── - * - * @example - * const tarGzPath = await downloadNpmPackages() - * // Returns: '../build-infra/build/npm-packages/npm-packages.tar.gz' + * Installs every `type='npm'` entry in bundle-tools.json with its full + * production dependency tree via Arborist, then packs the result into one + * tar.gz for VFS embedding. Today that is @coana-tech/cli (static analysis and + * reachability), @cyclonedx/cdxgen (CycloneDX SBOM generation), and synp + * (yarn.lock to package-lock.json). * - * @returns Promise resolving to path of tar.gz archive, or null if no npm - * packages defined. + * Each package keeps its normal layout under `/node_modules/`: its + * own `bin/`, its `package.json`, and a nested `node_modules/` for its + * dependencies. socket-patch is NOT here — it ships as a standalone Rust + * binary from GitHub releases, handled by downloads.mts. * - * @cyclonedx/cdxgen/ │ ├── bin/cdxgen │ ├── package.json │ └── node_modules/ # - * Dependencies └── synp/ ├── bin/synp ├── package.json └── node_modules/ # - * Dependencies. + * @returns Promise resolving to the tar.gz path, `/ + * npm-packages.tar.gz`, or null when no npm packages are defined. */ async function downloadNpmPackages() { const rootPath = getRootPath() diff --git a/packages/cli/src/commands/manifest/bazel/bazel-repo-discovery.mts b/packages/cli/src/commands/manifest/bazel/bazel-repo-discovery.mts index 3eb9bf0eba..654bbdb8b3 100644 --- a/packages/cli/src/commands/manifest/bazel/bazel-repo-discovery.mts +++ b/packages/cli/src/commands/manifest/bazel/bazel-repo-discovery.mts @@ -103,14 +103,14 @@ const FETCHED_HUB_BULLET_RE = // Starlark. This is NOT a failure to analyze; it is a positive, // authoritative "there is no maven extension here". It must map to // `not-defined` so the workspace cleanly contributes no Maven. -// + // (b) The module graph genuinely fails to evaluate: a Starlark eval error, // an unbound name (e.g. a MODULE.bazel referencing `PYTHON_VERSION` / // `pip` before definition), a syntax error, or the bazel binary itself // being missing/spawn-failed (normalized to code -1). Here we have NO // evidence about whether a maven extension exists, so it must map to // `indeterminate` and the run can never be reported complete. -// + // We classify by stderr shape. The exact wording differs across Bazel // versions; the regex families below are intentionally broad and SHOULD be // confirmed against live `bazel mod show_extension` output. diff --git a/packages/cli/src/commands/manifest/cmd-manifest-cdxgen.mts b/packages/cli/src/commands/manifest/cmd-manifest-cdxgen.mts index 20af789f79..94607c89af 100644 --- a/packages/cli/src/commands/manifest/cmd-manifest-cdxgen.mts +++ b/packages/cli/src/commands/manifest/cmd-manifest-cdxgen.mts @@ -46,76 +46,8 @@ export function toLower(arg: string): string { return arg.toLowerCase() } -// npx @cyclonedx/cdxgen@11.2.7 --help -// -// Options: -// -o, --output Output file. Default bom.json [default: "bom.json"] -// -t, --type Project type. Please refer to https://cyclonedx.github.io/cdxgen/#/PROJECT_TYPES for supp -// orted languages/platforms. [array] -// --exclude-type Project types to exclude. Please refer to https://cyclonedx.github.io/cdxgen/#/PROJECT_TY -// PES for supported languages/platforms. -// -r, --recurse Recurse mode suitable for mono-repos. Defaults to true. Pass --no-recurse to disable. -// [boolean] [default: true] -// -p, --print Print the SBOM as a table with tree. [boolean] -// -c, --resolve-class Resolve class names for packages. jars only for now. [boolean] -// --deep Perform deep searches for components. Useful while scanning C/C++ apps, live OS and oci i -// mages. [boolean] -// --server-url Dependency track url. Eg: https://deptrack.cyclonedx.io -// --skip-dt-tls-check Skip TLS certificate check when calling Dependency-Track. [boolean] [default: false] -// --api-key Dependency track api key -// --project-group Dependency track project group -// --project-name Dependency track project name. Default use the directory name -// --project-version Dependency track project version [string] [default: ""] -// --project-id Dependency track project id. Either provide the id or the project name and version togeth -// er [string] -// --parent-project-id Dependency track parent project id [string] -// --required-only Include only the packages with required scope on the SBOM. Would set compositions.aggrega -// te to incomplete unless --no-auto-compositions is passed. [boolean] -// --fail-on-error Fail if any dependency extractor fails. [boolean] -// --no-babel Do not use babel to perform usage analysis for JavaScript/TypeScript projects. [boolean] -// --generate-key-and-sign Generate an RSA public/private key pair and then sign the generated SBOM using JSON Web S -// ignatures. [boolean] -// --server Run cdxgen as a server [boolean] -// --server-host Listen address [default: "127.0.0.1"] -// --server-port Listen port [default: "9090"] -// --install-deps Install dependencies automatically for some projects. Defaults to true but disabled for c -// ontainers and oci scans. Use --no-install-deps to disable this feature. -// [boolean] [default: true] -// --validate Validate the generated SBOM using json schema. Defaults to true. Pass --no-validate to di -// sable. [boolean] [default: true] -// --evidence Generate SBOM with evidence for supported languages. [boolean] [default: false] -// --spec-version CycloneDX Specification version to use. Defaults to 1.6 -// [number] [choices: 1.4, 1.5, 1.6, 1.7] [default: 1.6] -// --filter Filter components containing this word in purl or component.properties.value. Multiple va -// lues allowed. [array] -// --only Include components only containing this word in purl. Useful to generate BOM with first p -// arty components alone. Multiple values allowed. [array] -// --author The person(s) who created the BOM. Set this value if you're intending the modify the BOM -// and claim authorship. [array] [default: "OWASP Foundation"] -// --profile BOM profile to use for generation. Default generic. -// [choices: "appsec", "research", "operational", "threat-modeling", "license-compliance", "generic", "machine-learning", -// "ml", "deep-learning", "ml-deep", "ml-tiny"] [default: "generic"] -// --exclude Additional glob pattern(s) to ignore [array] -// --export-proto Serialize and export BOM as protobuf binary. [boolean] [default: false] -// --proto-bin-file Path for the serialized protobuf binary. [default: "bom.cdx"] -// --include-formulation Generate formulation section with git metadata and build tools. Defaults to false. -// [boolean] [default: false] -// --include-crypto Include crypto libraries as components. [boolean] [default: false] -// --standard The list of standards which may consist of regulations, industry or organizational-specif -// ic standards, maturity models, best practices, or any other requirements which can be eva -// luated against or attested to. -// [array] [choices: "asvs-5.0", "asvs-4.0.3", "bsimm-v13", "masvs-2.0.0", "nist_ssdf-1.1", "pcissc-secure-slc-1.1", "scv -// s-1.0.0", "ssaf-DRAFT-2023-11"] -// --json-pretty Pretty-print the generated BOM json. [boolean] [default: false] -// --min-confidence Minimum confidence needed for the identity of a component from 0 - 1, where 1 is 100% con -// fidence. [number] [default: 0] -// --technique Analysis technique to use -// [array] [choices: "auto", "source-code-analysis", "binary-analysis", "manifest-analysis", "hash-comparison", "instrume -// ntation", "filename"] -// --auto-compositions Automatically set compositions when the BOM was filtered. Defaults to true -// [boolean] [default: true] -// -h, --help Show help [boolean] -// -v, --version Show version number [boolean] +// The upstream flag list this config mirrors, as cdxgen 11.2.7 prints it: +// docs/references/repo/cdxgen-flags.md // isSecureMode defined at: // https://github.com/CycloneDX/cdxgen/blob/v11.2.7/lib/helpers/utils.js#L66 diff --git a/packages/cli/src/meow.mts b/packages/cli/src/meow.mts index c50a6f3056..cd304a4dbc 100644 --- a/packages/cli/src/meow.mts +++ b/packages/cli/src/meow.mts @@ -61,6 +61,7 @@ export function defineFlags(flags: F): F { // `cli.flags.someFlag` from a wide-typed result don't get the wrong // runtime shape narrowed away. Concrete schemas with literal `type` // strings still resolve to the precise primitive. + // 'number' maps to `number | string`, not `number`: the parse layer only // converts a number flag when `Number(raw)` is not NaN, so garbage input // (`--page=invalid`) arrives as the raw STRING. The union keeps that honest diff --git a/packages/cli/src/util/basics/spawn.mts b/packages/cli/src/util/basics/spawn.mts index 76d180ec18..1eb7bcdbe5 100644 --- a/packages/cli/src/util/basics/spawn.mts +++ b/packages/cli/src/util/basics/spawn.mts @@ -146,11 +146,9 @@ export type SocketBasicsResult = { /** * Run socket-basics comprehensive security scanning. * - * Spawns socket-basics, Python tool, to perform: - * - * - SAST, Static Application Security Testing, via OpenGrep - * - Secret detection via TruffleHog - * - Container scanning via Trivy, if images are specified + * Spawns socket-basics, a Python tool, for SAST via OpenGrep, secret detection + * via TruffleHog, and — when images are specified — container scanning via + * Trivy. * * Environment Variables Set: * @@ -158,24 +156,11 @@ export type SocketBasicsResult = { * - SKIP_SOCKET_SUBMISSION=1 - Skip socket-basics submitting to Socket API * - PATH - Updated to include extracted tool directories * - * @example - * const result = await runSocketBasics({ - * cwd: '/path/to/project', - * orgSlug: 'my-org', - * repoName: 'my-repo', - * languages: ['python', 'javascript'], - * scanSecrets: true, - * }) - * - * if (result.ok && result.data.factsPath) { - * logger.log('Socket facts:', result.data.factsPath) - * logger.log('SAST findings:', result.data.findings.sast) - * logger.log('Secrets found:', result.data.findings.secrets) - * } - * * @param config - Socket-basics configuration options. * - * @returns Result with path to .socket.facts.json and finding counts. + * @returns Result whose `data` carries `factsPath`, the path to the written + * `.socket.facts.json`, and a `findings` count per scanner (`sast`, + * `secrets`, `containers`). */ export async function runSocketBasics( config: SocketBasicsOptions, diff --git a/packages/cli/src/util/basics/vfs-extract.mts b/packages/cli/src/util/basics/vfs-extract.mts index 788e04d967..a079128e7b 100644 --- a/packages/cli/src/util/basics/vfs-extract.mts +++ b/packages/cli/src/util/basics/vfs-extract.mts @@ -67,9 +67,7 @@ export function areBasicsToolsAvailable(): boolean { * Extract basics tools from VFS using process.smol.mount(). * * Extracts Python, Trivy, TruffleHog, and OpenGrep binaries from the SEA's VFS. - * process.smol.mount() handles caching, locking, and extraction automatically. - * - * Extraction is managed by node-smol and tools are cached persistently. + * process.smol.mount() handles caching, locking, and persistent extraction. * * @example * const toolsDir = await extractBasicsTools() diff --git a/packages/cli/src/util/coana/compress-facts.mts b/packages/cli/src/util/coana/compress-facts.mts index 0eb6986bfb..10e7ae2aa7 100644 --- a/packages/cli/src/util/coana/compress-facts.mts +++ b/packages/cli/src/util/coana/compress-facts.mts @@ -28,32 +28,21 @@ export type CompressedScanPaths = { /** * For each `.socket.facts.json` in `scanPaths`, stream-brotli-compress a - * sibling `.socket.facts.json.br` next to the original file and swap its path - * in. Other paths pass through unchanged. Missing files also pass through - * unchanged (the upload will fail downstream with the same error it would - * have). + * sibling `.socket.facts.json.br` and swap its path in. Every other path, and + * a missing file, passes through unchanged. * - * Streaming + worker-thread compression keeps the event loop responsive: - * default brotli quality (11) on a 60+MB facts file takes multiple seconds of - * CPU, which would otherwise freeze the spinner / signal handlers / any - * concurrent work. + * Compression is streamed on a worker thread: brotli quality 11 on a 60+MB + * facts file is multiple seconds of CPU, which would otherwise freeze the + * spinner, the signal handlers, and any concurrent work. * - * The `.br` lives next to the source rather than under the OS temp dir because - * depscan's multipart ingest (`addStreamEntry`) rejects entries whose names - * contain `..` traversal segments. The SDK computes the multipart entry name - * via `path.relative(cwd, brPath)`, so an OS-tmpdir temp path turns into - * `../../../var/folders/...` and gets dropped as `unmatchedFiles`. - * Sibling-write keeps the relative path inside cwd, and keeps the directory - * shape symmetric with the plain `.socket.facts.json` upload (depscan strips - * only the `.br` suffix at ingest, so `/.socket.facts.json.br` and - * `/.socket.facts.json` resolve to the same storage path). - * - * Concurrent scans against the same source directory are already racy on - * `.socket.facts.json` itself, coana writes to a single path, so the sibling - * `.br` doesn't introduce a new race. + * The `.br` is a SIBLING of the source, never a temp-dir path. The multipart + * entry name is computed as `path.relative(cwd, brPath)`, so a temp-dir path + * becomes `../../../var/folders/...` and the ingest side drops it as an + * unmatched file; a sibling keeps that name inside cwd. It adds no new race — + * concurrent scans already share the one `.socket.facts.json` coana writes. * * Caller MUST `await cleanup()` (typically in a `finally` block) once the - * upload completes — successful or not — to remove the sibling files. + * upload finishes, successful or not, to remove the siblings. */ export async function compressSocketFactsForUpload( scanPaths: string[], diff --git a/packages/cli/src/util/dlx/vfs-extract.mts b/packages/cli/src/util/dlx/vfs-extract.mts index acc53b0804..124d853f69 100644 --- a/packages/cli/src/util/dlx/vfs-extract.mts +++ b/packages/cli/src/util/dlx/vfs-extract.mts @@ -93,28 +93,6 @@ export { type ExternalTool, } from './vfs-extract-config.mts' -/** - * Extract external tools from VFS to node-smol's dlx directory. - * - * Extracts external tools from the SEA's VFS and writes them to node-smol's - * shared dlx directory (~/.socket/_dlx//). - * - * Tool extraction paths: - * - * - Standalone binaries: ~/.socket/_dlx//{tool} - * - Npm packages: - * ~/.socket/_dlx//node_modules/{packageName}/bin/{binaryName} - * - * @example - * const toolPaths = await extractExternalTools() - * if (toolPaths) { - * const sfwPath = toolPaths.sfw // ~/.socket/_dlx//sfw - * const cdxgenPath = toolPaths.cdxgen // ~/.socket/_dlx//node_modules/@cyclonedx/cdxgen/bin/cdxgen - * } - * - * @returns Record of tool names to their extracted paths, or null if extraction - * failed. - */ // Maximum recursion depth for extraction retries. const MAX_EXTRACTION_DEPTH = 5 @@ -141,6 +119,24 @@ export function areExternalToolsAvailable(): boolean { return false } +/** + * Extract external tools from VFS to node-smol's dlx directory. + * + * Extracts external tools from the SEA's VFS and writes them to node-smol's + * shared dlx directory (~/.socket/_dlx//). A standalone binary + * lands at the directory root; an npm package keeps its + * node_modules/{packageName}/bin/{binaryName} layout under it. + * + * @example + * const toolPaths = await extractExternalTools() + * if (toolPaths) { + * const sfwPath = toolPaths.sfw // ~/.socket/_dlx//sfw + * const cdxgenPath = toolPaths.cdxgen // ~/.socket/_dlx//node_modules/@cyclonedx/cdxgen/bin/cdxgen + * } + * + * @returns Record of tool names to their extracted paths, or null if extraction + * failed. + */ export async function extractExternalTools( depth = 0, ): Promise | undefined> { diff --git a/packages/cli/test/helpers/cli-execution.mts b/packages/cli/test/helpers/cli-execution.mts index d3d1f4f8f2..840e92e5aa 100644 --- a/packages/cli/test/helpers/cli-execution.mts +++ b/packages/cli/test/helpers/cli-execution.mts @@ -248,30 +248,16 @@ interface CliInScratchOptions extends CliExecutionOptions { } /** - * Execute Socket CLI inside a fully isolated scratch directory. Pins - * **everything** the CLI or its spawned subprocesses might read or write - * outside of cwd into the scratch tree, so an e2e run never touches the - * developer's system: + * Execute Socket CLI inside a fully isolated scratch directory, so an e2e run + * never touches the developer's system. `cwd` and `HOME` / `USERPROFILE` each + * get a fresh `os.tmpdir()` tree, and every XDG base-directory var plus the + * npm / pnpm / yarn / pip / cargo / gradle cache and config vars are redirected + * under that scratch HOME — the `env` literal in the body is the exhaustive + * list, grouped by tool. * - * - `cwd` → fresh `os.os.tmpdir()/socket-e2e-/` - * - `HOME` / `USERPROFILE` → fresh `os.os.tmpdir()/socket-e2e-home-/` - * - `XDG_CONFIG_HOME` → `/.config` - * - `XDG_CACHE_HOME` → `/.cache` - * - `XDG_DATA_HOME` → `/.local/share` - * - `XDG_STATE_HOME` → `/.local/state` - * - `NPM_CONFIG_CACHE` / `npm_config_cache` → `/.npm` - * - `NPM_CONFIG_PREFIX` / `npm_config_prefix` → `/.npm-global` - * - `NPM_CONFIG_USERCONFIG` / `npm_config_userconfig` → `/.npmrc` - * - `PNPM_HOME` → `/.pnpm` - * - `YARN_CACHE_FOLDER` → `/.yarn-cache` - * - `PIP_CACHE_DIR` → `/.pip-cache` - * - `CARGO_HOME` → `/.cargo` - * - `GRADLE_USER_HOME` → `/.gradle` - * - * Anything not pinned by the helper (the developer's `SOCKET_API_KEY` env, the - * real OS keychain for credentials) is **read-only** from the CLI's perspective - * — the CLI may read the token but the scratch HOME ensures it can't persist a - * new one back into the dev's config. + * What it does NOT redirect (the developer's `SOCKET_API_KEY`, the real OS + * keychain) stays readable: the CLI may read a token, but the scratch HOME + * stops it persisting a new one into the dev's config. * * Cleans up the scratch trees via `safeDelete()` even on failure. * @@ -352,21 +338,18 @@ export async function executeCliInScratch( * the duration of `fn`. Restores the original env on exit and `safeDelete()`s * the scratch tree. * - * Use this when an e2e test calls socket-cli internals directly (in-process) — - * e.g. `spawnDlx()` — rather than spawning the CLI binary. The - * `executeCliInScratch` helper covers the spawn-the-binary path; this is the - * sibling for the in-process path. + * Use this when an e2e test calls socket-cli internals in-process (say + * `spawnDlx()`) rather than spawning the binary, which `executeCliInScratch` + * covers. * - * Concurrency note: vitest runs tests within a single file serially by default. - * Each worker has its own Node process so env mutation here doesn't race - * against other test files. Don't use this in a file that opts into - * `it.concurrent`. + * Concurrency: vitest runs a single file's tests serially and gives each worker + * its own Node process, so this env mutation races nothing. Do NOT use it in a + * file that opts into `it.concurrent`. * * @example * await withScratchHome(async () => { - * const result = await spawnDlx({ name: 'cowsay', version: '1.6.0' }, [ - * 'moo', - * ]) + * const cowsay = { name: 'cowsay', version: '1.6.0' } + * const result = await spawnDlx(cowsay, ['moo']) * expect((await result.spawnPromise).code).toBe(0) * }) */ diff --git a/packages/cli/test/helpers/workspace-helper.mts b/packages/cli/test/helpers/workspace-helper.mts index 3a1976aa3d..0863f0311a 100644 --- a/packages/cli/test/helpers/workspace-helper.mts +++ b/packages/cli/test/helpers/workspace-helper.mts @@ -110,25 +110,16 @@ export interface Workspace { * Create a temporary test workspace with specified files and configuration. * * @example - * ;```typescript * const workspace = await createTestWorkspace({ - * packageJson: { - * name: 'test-project', - * dependencies: { express: '^4.18.0' }, - * }, + * packageJson: { name: 'test-project' }, * files: [{ path: 'index.js', content: 'console.log("hello")' }], * }) - * - * // Use workspace - * const result = await executeCliCommand(['scan'], { cwd: workspace.path }) - * - * // Cleanup + * await executeCliCommand(['scan'], { cwd: workspace.path }) * await workspace.cleanup() - * ``` * * @param config - Workspace configuration. * - * @returns Workspace instance with cleanup + * @returns Workspace instance whose `cleanup()` removes the temporary tree. */ export async function createTestWorkspace( config?: WorkspaceConfig | undefined, diff --git a/packages/cli/test/unit/commands/scan/perform-reachability-analysis-coana.test.mts b/packages/cli/test/unit/commands/scan/perform-reachability-analysis-coana.test.mts index 1089ce571c..b41b62289f 100644 --- a/packages/cli/test/unit/commands/scan/perform-reachability-analysis-coana.test.mts +++ b/packages/cli/test/unit/commands/scan/perform-reachability-analysis-coana.test.mts @@ -311,7 +311,7 @@ describe('performReachabilityAnalysis — machine-output mode', () => { target: '.', }) const args = mockSpawnCoanaDlx.mock.calls[0][0] as string[] - const opts = mockSpawnCoanaDlx.mock.calls[0][2] + const opts = mockSpawnCoanaDlx.mock.calls[0][1] expect(args[0]).toBe('--silent') // Payload owns stdout; coana progress goes to fd 2 so it stays visible. expect(opts.stdio).toEqual(['inherit', 2, 'inherit']) @@ -323,7 +323,7 @@ describe('performReachabilityAnalysis — machine-output mode', () => { reachabilityOptions: baseReachOpts, target: '.', }) - const opts = mockSpawnCoanaDlx.mock.calls[0][2] + const opts = mockSpawnCoanaDlx.mock.calls[0][1] expect(opts.stdio).toBe('inherit') }) }) diff --git a/packages/cli/test/unit/commands/scan/perform-reachability-analysis.test.mts b/packages/cli/test/unit/commands/scan/perform-reachability-analysis.test.mts index 2a1c0b67e3..dbee2acc71 100644 --- a/packages/cli/test/unit/commands/scan/perform-reachability-analysis.test.mts +++ b/packages/cli/test/unit/commands/scan/perform-reachability-analysis.test.mts @@ -389,7 +389,7 @@ describe('performReachabilityAnalysis — repo and branch env', () => { repoName: 'socket-default-repository', target: '.', }) - const callOpts = mockSpawnCoanaDlx.mock.calls[0][2] + const callOpts = mockSpawnCoanaDlx.mock.calls[0][1] expect(callOpts.env['SOCKET_REPO_NAME']).toBeUndefined() }) @@ -399,7 +399,7 @@ describe('performReachabilityAnalysis — repo and branch env', () => { repoName: 'my-repo', target: '.', }) - const callOpts = mockSpawnCoanaDlx.mock.calls[0][2] + const callOpts = mockSpawnCoanaDlx.mock.calls[0][1] expect(callOpts.env['SOCKET_REPO_NAME']).toBe('my-repo') }) @@ -409,7 +409,7 @@ describe('performReachabilityAnalysis — repo and branch env', () => { reachabilityOptions: baseReachOpts, target: '.', }) - const callOpts = mockSpawnCoanaDlx.mock.calls[0][2] + const callOpts = mockSpawnCoanaDlx.mock.calls[0][1] expect(callOpts.env['SOCKET_BRANCH_NAME']).toBeUndefined() }) @@ -419,7 +419,7 @@ describe('performReachabilityAnalysis — repo and branch env', () => { reachabilityOptions: baseReachOpts, target: '.', }) - const callOpts = mockSpawnCoanaDlx.mock.calls[0][2] + const callOpts = mockSpawnCoanaDlx.mock.calls[0][1] expect(callOpts.env['SOCKET_BRANCH_NAME']).toBe('feat/x') }) })