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') }) })