├── .config ├── babel.config.js ├── rollup.base.config.mjs ├── rollup.dist.config.mjs └── tsconfig.base.json ├── .editorconfig ├── .env.dist ├── .env.external ├── .env.local ├── .env.test ├── .env.testu ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── lint.yml │ ├── provenance.yml │ ├── test.yml │ └── types.yml ├── .gitignore ├── .husky └── pre-commit ├── .ncurc.json ├── .oxlintignore ├── .oxlintrc.json ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── bin ├── cli.js ├── npm-cli.js └── npx-cli.js ├── biome.json ├── eslint.config.js ├── knip.json ├── package-lock.json ├── package.json ├── patches ├── @npmcli+arborist#9.1.1.patch ├── @rollup+plugin-commonjs#28.0.3.patch ├── ansi-term#0.0.2.patch ├── blessed#0.1.81.patch ├── blessed-contrib#4.11.0.patch ├── brace-expansion#2.0.1.patch ├── bresenham#0.0.3.patch ├── drawille-blessed-contrib#1.0.0.patch ├── drawille-canvas-blessed-contrib#0.1.3.patch ├── graceful-fs#4.2.11.patch ├── lodash#4.17.21.patch ├── string_decoder#0.10.31.patch └── tinyglobby#0.2.14.patch ├── scripts ├── babel │ ├── transform-set-proto-plugin.js │ └── transform-url-parse-plugin.js ├── constants.js ├── rollup │ └── socket-modify-plugin.js └── utils │ ├── fs.js │ └── packages.js ├── sd ├── shadow-bin ├── npm └── npx ├── src ├── cli.mts ├── commands │ ├── analytics │ │ ├── analytics-fixture.json │ │ ├── cmd-analytics.mts │ │ ├── cmd-analytics.test.mts │ │ ├── fetch-org-analytics.mts │ │ ├── fetch-repo-analytics.mts │ │ ├── handle-analytics.mts │ │ ├── output-analytics.mts │ │ └── output-analytics.test.mts │ ├── audit-log │ │ ├── audit-fixture.json │ │ ├── cmd-audit-log.mts │ │ ├── cmd-audit-log.test.mts │ │ ├── fetch-audit-log.mts │ │ ├── handle-audit-log.mts │ │ ├── output-audit-log.mts │ │ └── output-audit-log.test.mts │ ├── cdxgen │ │ ├── cmd-cdxgen.mts │ │ ├── cmd-cdxgen.test.mts │ │ └── handle-cdxgen.mts │ ├── ci │ │ ├── cmd-ci.mts │ │ ├── cmd-ci.test.mts │ │ ├── fetch-default-org-slug.mts │ │ └── handle-ci.mts │ ├── cli.test.mts │ ├── config │ │ ├── cmd-config-auto.mts │ │ ├── cmd-config-get.mts │ │ ├── cmd-config-get.test.mts │ │ ├── cmd-config-list.mts │ │ ├── cmd-config-list.test.mts │ │ ├── cmd-config-set.mts │ │ ├── cmd-config-set.test.mts │ │ ├── cmd-config-unset.mts │ │ ├── cmd-config-unset.test.mts │ │ ├── cmd-config.mts │ │ ├── cmd-config.test.mts │ │ ├── discover-config-value.mts │ │ ├── handle-config-auto.mts │ │ ├── handle-config-get.mts │ │ ├── handle-config-set.mts │ │ ├── handle-config-unset.mts │ │ ├── output-config-auto.mts │ │ ├── output-config-get.mts │ │ ├── output-config-list.mts │ │ ├── output-config-set.mts │ │ └── output-config-unset.mts │ ├── dependencies │ │ ├── cmd-dependencies.mts │ │ ├── cmd-dependencies.test.mts │ │ ├── fetch-dependencies.mts │ │ ├── handle-dependencies.mts │ │ └── output-dependencies.mts │ ├── diff-scan │ │ ├── cmd-diff-scan-get.mts │ │ ├── cmd-diff-scan-get.test.mts │ │ ├── cmd-diff-scan.mts │ │ ├── cmd-diff-scan.test.mts │ │ ├── fetch-diff-scan.mts │ │ ├── handle-diff-scan.mts │ │ └── output-diff-scan.mts │ ├── fix │ │ ├── cmd-fix.mts │ │ ├── cmd-fix.test.mts │ │ ├── git.mts │ │ ├── handle-fix.mts │ │ ├── npm-fix.mts │ │ ├── open-pr.mts │ │ ├── output-fix-result.mts │ │ ├── pnpm-fix.mts │ │ ├── run-fix.mts │ │ └── shared.mts │ ├── info │ │ ├── cmd-info.mts │ │ ├── cmd-info.test.mts │ │ ├── fetch-package-info.mts │ │ ├── handle-package-info.mts │ │ └── output-package-info.mts │ ├── install │ │ ├── cmd-install-completion.mts │ │ ├── cmd-install-completion.test.mts │ │ ├── cmd-install.mts │ │ ├── cmd-install.test.mts │ │ ├── handle-install-completion.mts │ │ ├── output-install-completion.mts │ │ ├── setup-tab-completion.mts │ │ └── socket-completion.bash │ ├── json │ │ ├── cmd-json.mts │ │ ├── cmd-json.test.mts │ │ ├── handle-cmd-json.mts │ │ └── output-cmd-json.mts │ ├── login │ │ ├── apply-login.mts │ │ ├── attempt-login.mts │ │ ├── cmd-login.mts │ │ └── cmd-login.test.mts │ ├── logout │ │ ├── apply-logout.mts │ │ ├── attempt-logout.mts │ │ ├── cmd-logout.mts │ │ └── cmd-logout.test.mts │ ├── manifest │ │ ├── README.md │ │ ├── cmd-manifest-auto.mts │ │ ├── cmd-manifest-auto.test.mts │ │ ├── cmd-manifest-cdxgen.mts │ │ ├── cmd-manifest-cdxgen.test.mts │ │ ├── cmd-manifest-conda.mts │ │ ├── cmd-manifest-conda.test.mts │ │ ├── cmd-manifest-gradle.mts │ │ ├── cmd-manifest-gradle.test.mts │ │ ├── cmd-manifest-kotlin.mts │ │ ├── cmd-manifest-kotlin.test.mts │ │ ├── cmd-manifest-scala.mts │ │ ├── cmd-manifest-scala.test.mts │ │ ├── cmd-manifest-setup.mts │ │ ├── cmd-manifest-setup.test.mts │ │ ├── cmd-manifest.mts │ │ ├── cmd-manifest.test.mts │ │ ├── convert-conda-to-requirements.mts │ │ ├── convert-conda-to-requirements.test.mts │ │ ├── convert_gradle_to_maven.mts │ │ ├── convert_sbt_to_maven.mts │ │ ├── detect-manifest-actions.mts │ │ ├── generate_auto_manifest.mts │ │ ├── handle-manifest-conda.mts │ │ ├── handle-manifest-setup.mts │ │ ├── init.gradle │ │ ├── output-manifest-setup.mts │ │ ├── output-requirements.mts │ │ ├── run-cdxgen.mts │ │ └── setup-manifest-config.mts │ ├── npm │ │ ├── cmd-npm.mts │ │ └── cmd-npm.test.mts │ ├── npx │ │ ├── cmd-npx.mts │ │ └── cmd-npx.test.mts │ ├── oops │ │ ├── cmd-oops.mts │ │ └── cmd-oops.test.mts │ ├── optimize │ │ ├── add-overrides.mts │ │ ├── apply-optimization.mts │ │ ├── cmd-optimize.mts │ │ ├── cmd-optimize.test.mts │ │ ├── deps-includes-by-agent.mts │ │ ├── get-dependency-entries.mts │ │ ├── get-overrides-by-agent.mts │ │ ├── handle-optimize.mts │ │ ├── lockfile-includes-by-agent.mts │ │ ├── ls-by-agent.mts │ │ ├── output-optimize-result.mts │ │ ├── shared.mts │ │ ├── types.mts │ │ ├── update-lockfile.mts │ │ └── update-manifest-by-agent.mts │ ├── organization │ │ ├── cmd-organization-list.mts │ │ ├── cmd-organization-list.test.mts │ │ ├── cmd-organization-policy-license.mts │ │ ├── cmd-organization-policy-license.test.mts │ │ ├── cmd-organization-policy-security.mts │ │ ├── cmd-organization-policy-security.test.mts │ │ ├── cmd-organization-policy.mts │ │ ├── cmd-organization-policy.test.mts │ │ ├── cmd-organization-quota.mts │ │ ├── cmd-organization-quota.test.mts │ │ ├── cmd-organization.mts │ │ ├── cmd-organization.test.mts │ │ ├── fetch-license-policy.mts │ │ ├── fetch-organization-list.mts │ │ ├── fetch-quota.mts │ │ ├── fetch-security-policy.mts │ │ ├── handle-license-policy.mts │ │ ├── handle-organization-list.mts │ │ ├── handle-quota.mts │ │ ├── handle-security-policy.mts │ │ ├── output-license-policy.mts │ │ ├── output-organization-list.mts │ │ ├── output-quota.mts │ │ └── output-security-policy.mts │ ├── package │ │ ├── cmd-package-score.mts │ │ ├── cmd-package-score.test.mts │ │ ├── cmd-package-shallow.mts │ │ ├── cmd-package-shallow.test.mts │ │ ├── cmd-package.mts │ │ ├── cmd-package.test.mts │ │ ├── fetch-purl-deep-score.mts │ │ ├── fetch-purls-shallow-score.mts │ │ ├── fixtures │ │ │ ├── go_deep.json │ │ │ ├── go_shallow.json │ │ │ ├── maven_deep.json │ │ │ ├── maven_shallow.json │ │ │ ├── npm_deep.json │ │ │ ├── npm_shallow.json │ │ │ ├── nuget_deep.json │ │ │ ├── nuget_shallow.json │ │ │ ├── python_deep.json │ │ │ ├── python_dupes.json │ │ │ ├── python_shallow.json │ │ │ ├── ruby_deep.json │ │ │ └── ruby_shallow.json │ │ ├── handle-purl-deep-score.mts │ │ ├── handle-purls-shallow-score.mts │ │ ├── output-purls-deep-score.mts │ │ ├── output-purls-deep-score.test.mts │ │ ├── output-purls-shallow-score.mts │ │ ├── output-purls-shallow-score.test.mts │ │ ├── parse-package-specifiers.mts │ │ └── parse-package-specifiers.test.mts │ ├── raw-npm │ │ ├── cmd-raw-npm.mts │ │ ├── cmd-raw-npm.test.mts │ │ └── run-raw-npm.mts │ ├── raw-npx │ │ ├── cmd-raw-npx.mts │ │ ├── cmd-raw-npx.test.mts │ │ └── run-raw-npx.mts │ ├── report │ │ ├── cmd-report-create.mts │ │ ├── cmd-report-create.test.mts │ │ ├── cmd-report-view.mts │ │ ├── cmd-report-view.test.mts │ │ ├── cmd-report.mts │ │ └── cmd-report.test.mts │ ├── repos │ │ ├── cmd-repos-create.mts │ │ ├── cmd-repos-create.test.mts │ │ ├── cmd-repos-del.mts │ │ ├── cmd-repos-del.test.mts │ │ ├── cmd-repos-list.mts │ │ ├── cmd-repos-list.test.mts │ │ ├── cmd-repos-update.mts │ │ ├── cmd-repos-update.test.mts │ │ ├── cmd-repos-view.mts │ │ ├── cmd-repos-view.test.mts │ │ ├── cmd-repos.mts │ │ ├── cmd-repos.test.mts │ │ ├── fetch-create-repo.mts │ │ ├── fetch-delete-repo.mts │ │ ├── fetch-list-all-repos.mts │ │ ├── fetch-list-repos.mts │ │ ├── fetch-update-repo.mts │ │ ├── fetch-view-repo.mts │ │ ├── handle-create-repo.mts │ │ ├── handle-delete-repo.mts │ │ ├── handle-list-repos.mts │ │ ├── handle-update-repo.mts │ │ ├── handle-view-repo.mts │ │ ├── output-create-repo.mts │ │ ├── output-delete-repo.mts │ │ ├── output-list-repos.mts │ │ ├── output-update-repo.mts │ │ └── output-view-repo.mts │ ├── scan │ │ ├── cmd-scan-create.mts │ │ ├── cmd-scan-create.test.mts │ │ ├── cmd-scan-del.mts │ │ ├── cmd-scan-del.test.mts │ │ ├── cmd-scan-diff.mts │ │ ├── cmd-scan-diff.test.mts │ │ ├── cmd-scan-github.mts │ │ ├── cmd-scan-github.test.mts │ │ ├── cmd-scan-list.mts │ │ ├── cmd-scan-list.test.mts │ │ ├── cmd-scan-metadata.mts │ │ ├── cmd-scan-metadata.test.mts │ │ ├── cmd-scan-reach.mts │ │ ├── cmd-scan-reach.test.mts │ │ ├── cmd-scan-report.mts │ │ ├── cmd-scan-report.test.mts │ │ ├── cmd-scan-setup.mts │ │ ├── cmd-scan-setup.test.mts │ │ ├── cmd-scan-view.mts │ │ ├── cmd-scan-view.test.mts │ │ ├── cmd-scan.mts │ │ ├── cmd-scan.test.mts │ │ ├── create-scan-from-github.mts │ │ ├── fetch-create-org-full-scan.mts │ │ ├── fetch-delete-org-full-scan.mts │ │ ├── fetch-diff-scan.mts │ │ ├── fetch-list-scans.mts │ │ ├── fetch-report-data.mts │ │ ├── fetch-scan-metadata.mts │ │ ├── fetch-scan.mts │ │ ├── fetch-supported-scan-file-names.mts │ │ ├── generate-report.mts │ │ ├── generate-report.test.mts │ │ ├── handle-create-github-scan.mts │ │ ├── handle-create-new-scan.mts │ │ ├── handle-delete-scan.mts │ │ ├── handle-diff-scan.mts │ │ ├── handle-list-scans.mts │ │ ├── handle-reach-scan.mts │ │ ├── handle-scan-config.mts │ │ ├── handle-scan-metadata.mts │ │ ├── handle-scan-report.mts │ │ ├── handle-scan-view.mts │ │ ├── output-create-new-scan.mts │ │ ├── output-delete-scan.mts │ │ ├── output-diff-scan.mts │ │ ├── output-list-scans.mts │ │ ├── output-scan-config-result.mts │ │ ├── output-scan-metadata.mts │ │ ├── output-scan-reach.mts │ │ ├── output-scan-report.mts │ │ ├── output-scan-report.test.mts │ │ ├── output-scan-view.mts │ │ ├── scan-reachability.mts │ │ ├── setup-scan-config.mts │ │ ├── stream-scan.mts │ │ ├── suggest-org-slug.mts │ │ ├── suggest-repo-slug.mts │ │ ├── suggest_branch_slug.mts │ │ └── suggest_target.mts │ ├── threat-feed │ │ ├── cmd-threat-feed.mts │ │ ├── cmd-threat-feed.test.mts │ │ ├── fetch-threat-feed.mts │ │ ├── handle-threat-feed.mts │ │ ├── output-threat-feed.mts │ │ └── types.mts │ ├── uninstall │ │ ├── cmd-uninstall-completion.mts │ │ ├── cmd-uninstall-completion.test.mts │ │ ├── cmd-uninstall.mts │ │ ├── cmd-uninstall.test.mts │ │ ├── handle-uninstall-completion.mts │ │ ├── output-uninstall-completion.mts │ │ └── teardown-tab-completion.mts │ └── wrapper │ │ ├── add-socket-wrapper.mts │ │ ├── check-socket-wrapper-setup.mts │ │ ├── cmd-wrapper.mts │ │ ├── cmd-wrapper.test.mts │ │ ├── postinstall-wrapper.mts │ │ └── remove-socket-wrapper.mts ├── constants.mts ├── external │ └── blessed-contrib │ │ └── lib │ │ ├── layout │ │ └── grid.mjs │ │ └── widget │ │ ├── charts │ │ ├── bar.mjs │ │ └── line.mjs │ │ └── table.mjs ├── flags.mts ├── instrument-with-sentry.mts ├── shadow │ └── npm │ │ ├── arborist-helpers.mts │ │ ├── arborist │ │ ├── index.mts │ │ ├── lib │ │ │ └── arborist │ │ │ │ └── index.mts │ │ └── types.mts │ │ ├── bin.mts │ │ ├── inject.mts │ │ ├── install.mts │ │ ├── link.mts │ │ ├── paths.mts │ │ └── proc-log │ │ └── index.mts ├── types.mts └── utils │ ├── agent.mts │ ├── alert │ ├── artifact.mts │ ├── fix.mts │ └── severity.mts │ ├── alerts-map.mts │ ├── api.mts │ ├── check-input.mts │ ├── cmd.mts │ ├── color-or-markdown.mts │ ├── completion.mts │ ├── config.mts │ ├── config.test.mts │ ├── determine-org-slug.mts │ ├── errors.mts │ ├── fail-msg-with-badge.mts │ ├── fs.mts │ ├── get-output-kind.mts │ ├── glob.mts │ ├── map-to-object.mts │ ├── map-to-object.test.mts │ ├── markdown.mts │ ├── markdown.test.mts │ ├── meow-with-subcommands.mts │ ├── npm-package-arg.mts │ ├── npm-paths.mts │ ├── objects.mts │ ├── output-formatting.mts │ ├── package-environment.mts │ ├── path-resolve.mts │ ├── pnpm.mts │ ├── purl.mts │ ├── sdk.mts │ ├── semver.mts │ ├── serialize-result-json.mts │ ├── socket-package-alert.mts │ ├── socket-url.mts │ ├── socketjson.mts │ ├── spec.mts │ ├── strings.mts │ ├── tildify.mts │ ├── translations.mts │ ├── walk-nested-map.mts │ └── walk-nested-map.test.mts ├── test ├── errors.test.mts ├── path-resolve.test.mts ├── smoke.sh ├── socket-cdxgen.test.mts ├── socket-npm-fixtures │ ├── lacking-typosquat │ │ └── package.json │ ├── manifest-conda │ │ └── environment.yml │ ├── npm10 │ │ ├── package-lock.json │ │ └── package.json │ ├── npm11 │ │ ├── package-lock.json │ │ └── package.json │ ├── npm9 │ │ ├── package-lock.json │ │ └── package.json │ └── sjtest │ │ └── socket.json ├── socket-npm.test.mts └── utils.mts ├── translations.json ├── tsconfig.dts.json ├── tsconfig.json └── vitest.config.mts /.config/babel.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('node:path') 4 | 5 | const rootPath = path.join(__dirname, '..') 6 | const scriptsPath = path.join(rootPath, 'scripts') 7 | const babelPluginsPath = path.join(scriptsPath, 'babel') 8 | 9 | module.exports = { 10 | presets: ['@babel/preset-typescript'], 11 | plugins: [ 12 | '@babel/plugin-proposal-export-default-from', 13 | '@babel/plugin-transform-export-namespace-from', 14 | [ 15 | '@babel/plugin-transform-runtime', 16 | { 17 | absoluteRuntime: false, 18 | corejs: false, 19 | helpers: true, 20 | regenerator: false, 21 | version: '^7.27.1', 22 | }, 23 | ], 24 | path.join(babelPluginsPath, 'transform-set-proto-plugin.js'), 25 | path.join(babelPluginsPath, 'transform-url-parse-plugin.js'), 26 | ], 27 | } 28 | -------------------------------------------------------------------------------- /.config/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // The following options are not supported by @typescript/native-preview. 4 | // They are either ignored or throw an unknown option error: 5 | //"importsNotUsedAsValues": "remove", 6 | //"incremental": true, 7 | "allowImportingTsExtensions": true, 8 | "allowJs": false, 9 | "composite": true, 10 | "declaration": true, 11 | "declarationMap": true, 12 | "erasableSyntaxOnly": true, 13 | "esModuleInterop": true, 14 | "exactOptionalPropertyTypes": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "isolatedModules": true, 17 | "lib": ["esnext"], 18 | "module": "nodenext", 19 | "noEmit": true, 20 | "noEmitOnError": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "noImplicitOverride": true, 23 | "noPropertyAccessFromIndexSignature": true, 24 | "noUncheckedIndexedAccess": true, 25 | "noUnusedLocals": true, 26 | "noUnusedParameters": true, 27 | "resolveJsonModule": true, 28 | "rewriteRelativeImportExtensions": true, 29 | "skipLibCheck": true, 30 | "sourceMap": true, 31 | "strict": true, 32 | "strictNullChecks": true, 33 | "target": "esnext", 34 | "useUnknownInCatchVariables": true, 35 | "verbatimModuleSyntax": true 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | max_line_length = 80 10 | trim_trailing_whitespace = true 11 | -------------------------------------------------------------------------------- /.env.dist: -------------------------------------------------------------------------------- 1 | LINT_DIST=1 2 | NODE_COMPILE_CACHE="$(pwd)/.cache" 3 | -------------------------------------------------------------------------------- /.env.external: -------------------------------------------------------------------------------- 1 | LINT_EXTERNAL=1 2 | NODE_COMPILE_CACHE="$(pwd)/.cache" 3 | -------------------------------------------------------------------------------- /.env.local: -------------------------------------------------------------------------------- 1 | NODE_COMPILE_CACHE="$(pwd)/.cache" 2 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | NODE_COMPILE_CACHE="$(pwd)/.cache" 2 | VITEST=1 3 | -------------------------------------------------------------------------------- /.env.testu: -------------------------------------------------------------------------------- 1 | NODE_COMPILE_CACHE="$(pwd)/.cache" 2 | SOCKET_CLI_NO_API_TOKEN=1 3 | VITEST=1 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lfs 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | day: 'monday' 8 | - package-ecosystem: 'npm' 9 | directory: '/' 10 | schedule: 11 | interval: 'weekly' 12 | day: 'monday' 13 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - '*' 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | permissions: 14 | contents: read 15 | 16 | concurrency: 17 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | linting: 22 | name: 'Linting' 23 | uses: SocketDev/workflows/.github/workflows/reusable-base.yml@master 24 | with: 25 | no-lockfile: true 26 | npm-test-script: 'check-ci' 27 | -------------------------------------------------------------------------------- /.github/workflows/provenance.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package to npm 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | debug: 7 | description: 'Enable debug output' 8 | required: false 9 | default: '0' 10 | type: string 11 | options: 12 | - '0' 13 | - '1' 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | permissions: 19 | contents: read 20 | id-token: write 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: actions/setup-node@v4 25 | with: 26 | node-version: '22' 27 | registry-url: 'https://registry.npmjs.org' 28 | cache: npm 29 | scope: '@socketsecurity' 30 | - run: npm install -g npm@latest 31 | - run: npm ci 32 | - run: INLINED_SOCKET_CLI_PUBLISHED_BUILD=1 npm run build:dist 33 | - run: npm publish --provenance --access public 34 | env: 35 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 36 | SOCKET_CLI_DEBUG: ${{ inputs.debug }} 37 | - run: INLINED_SOCKET_CLI_PUBLISHED_BUILD=1 INLINED_SOCKET_CLI_LEGACY_BUILD=1 npm run build:dist 38 | env: 39 | SOCKET_CLI_DEBUG: ${{ inputs.debug }} 40 | - run: npm publish --provenance --access public 41 | env: 42 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 43 | SOCKET_CLI_DEBUG: ${{ inputs.debug }} 44 | - run: INLINED_SOCKET_CLI_PUBLISHED_BUILD=1 INLINED_SOCKET_CLI_SENTRY_BUILD=1 npm run build:dist 45 | env: 46 | SOCKET_CLI_DEBUG: ${{ inputs.debug }} 47 | - run: npm publish --provenance --access public 48 | env: 49 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 50 | SOCKET_CLI_DEBUG: ${{ inputs.debug }} 51 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - '*' 9 | pull_request: 10 | branches: 11 | - main 12 | workflow_dispatch: 13 | 14 | permissions: 15 | contents: read 16 | 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | test: 23 | name: 'Tests' 24 | uses: SocketDev/workflows/.github/workflows/reusable-base.yml@master 25 | with: 26 | no-lockfile: true 27 | npm-test-script: 'test-ci' 28 | node-versions: '22,23' 29 | os: 'ubuntu-latest,windows-latest' 30 | -------------------------------------------------------------------------------- /.github/workflows/types.yml: -------------------------------------------------------------------------------- 1 | name: Type Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - '*' 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | type-check: 18 | uses: SocketDev/workflows/.github/workflows/type-check.yml@master 19 | with: 20 | no-lockfile: true 21 | ts-versions: '5.8' 22 | ts-libs: 'esnext' 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | ._.DS_Store 3 | Thumbs.db 4 | /.cache 5 | /.env 6 | /.nvm 7 | /.rollup.cache 8 | /.type-coverage 9 | /.vscode 10 | /coverage 11 | /external 12 | /npm-debug.log 13 | **/dist 14 | **/node_modules 15 | *.d.ts 16 | *.d.ts.map 17 | *.tsbuildinfo 18 | 19 | !/.vscode/extensions.json 20 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | if [ -z "${DISABLE_PRECOMMIT_LINT}" ]; then 2 | npm run lint-staged 3 | else 4 | echo "Skipping lint due to DISABLE_PRECOMMIT_LINT env var" 5 | fi 6 | 7 | if [ -z "${DISABLE_PRECOMMIT_TEST}" ]; then 8 | npm test 9 | else 10 | echo "Skipping testing due to DISABLE_PRECOMMIT_TEST env var" 11 | fi 12 | -------------------------------------------------------------------------------- /.ncurc.json: -------------------------------------------------------------------------------- 1 | { 2 | "loglevel": "minimal", 3 | "reject": ["eslint-plugin-unicorn", "terminal-link"], 4 | "upgrade": true 5 | } 6 | -------------------------------------------------------------------------------- /.oxlintignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | -------------------------------------------------------------------------------- /.oxlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/oxlint/configuration_schema.json", 3 | "plugins": ["import", "promise", "typescript", "unicorn"], 4 | "categories": { 5 | "correctness": "warn", 6 | "perf": "warn", 7 | "suspicious": "warn" 8 | }, 9 | "settings": {}, 10 | "rules": { 11 | "@typescript-eslint/array-type": ["error", { "default": "array-simple" }], 12 | "@typescript-eslint/no-misused-new": "error", 13 | "@typescript-eslint/no-this-alias": [ 14 | "error", 15 | { "allowDestructuring": true } 16 | ], 17 | "@typescript-eslint/return-await": ["error", "always"], 18 | "curly": "error", 19 | "no-control-regex": "off", 20 | "no-new": "off", 21 | "no-self-assign": "off", 22 | "no-undef": "off", 23 | "no-unused-vars": "off", 24 | "no-var": "error", 25 | "unicorn/no-empty-file": "off", 26 | "unicorn/no-new-array": "off", 27 | "unicorn/prefer-string-starts-ends-with": "off" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ryanluker.vscode-coverage-gutters", 4 | "hbenl.vscode-test-explorer", 5 | "hbenl.vscode-mocha-test-adapter", 6 | "dbaeumer.vscode-eslint", 7 | "gruntfuggly.todo-tree", 8 | "editorconfig.editorconfig" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Socket Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const Module = require('node:module') 5 | const path = require('node:path') 6 | const rootPath = path.join(__dirname, '..') 7 | Module.enableCompileCache?.(path.join(rootPath, '.cache')) 8 | const process = require('node:process') 9 | 10 | const constants = require(path.join(rootPath, 'dist/constants.js')) 11 | const { spawn } = require( 12 | path.join(rootPath, 'external/@socketsecurity/registry/lib/spawn.js'), 13 | ) 14 | 15 | const { NODE_COMPILE_CACHE } = constants 16 | 17 | process.exitCode = 1 18 | 19 | spawn( 20 | // Lazily access constants.execPath. 21 | constants.execPath, 22 | [ 23 | // Lazily access constants.nodeHardenFlags. 24 | ...constants.nodeHardenFlags, 25 | // Lazily access constants.nodeNoWarningsFlags. 26 | ...constants.nodeNoWarningsFlags, 27 | // Lazily access constants.ENV.INLINED_SOCKET_CLI_SENTRY_BUILD. 28 | ...(constants.ENV.INLINED_SOCKET_CLI_SENTRY_BUILD 29 | ? [ 30 | '--require', 31 | // Lazily access constants.instrumentWithSentryPath. 32 | constants.instrumentWithSentryPath, 33 | ] 34 | : []), 35 | // Lazily access constants.distCliPath. 36 | constants.distCliPath, 37 | ...process.argv.slice(2), 38 | ], 39 | { 40 | env: { 41 | ...process.env, 42 | ...(NODE_COMPILE_CACHE ? { NODE_COMPILE_CACHE } : undefined), 43 | }, 44 | stdio: 'inherit', 45 | }, 46 | ) 47 | // See https://nodejs.org/api/all.html#all_child_process_event-exit. 48 | .process.on('exit', (code, signalName) => { 49 | if (signalName) { 50 | process.kill(process.pid, signalName) 51 | } else if (code !== null) { 52 | // eslint-disable-next-line n/no-process-exit 53 | process.exit(code) 54 | } 55 | }) 56 | -------------------------------------------------------------------------------- /bin/npm-cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const Module = require('node:module') 5 | const path = require('node:path') 6 | const rootPath = path.join(__dirname, '..') 7 | Module.enableCompileCache?.(path.join(rootPath, '.cache')) 8 | 9 | const shadowBin = require(path.join(rootPath, 'dist/shadow-bin.js')) 10 | shadowBin('npm') 11 | -------------------------------------------------------------------------------- /bin/npx-cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const Module = require('node:module') 5 | const path = require('node:path') 6 | const rootPath = path.join(__dirname, '..') 7 | Module.enableCompileCache?.(path.join(rootPath, '.cache')) 8 | 9 | const shadowBin = require(path.join(rootPath, 'dist/shadow-bin.js')) 10 | shadowBin('npx') 11 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", 3 | "files": { 4 | "ignore": [ 5 | "**/.DS_Store", 6 | "**/._.DS_Store", 7 | "**/.env", 8 | "**/.git", 9 | "**/.github", 10 | "**/.husky", 11 | "**/.nvm", 12 | "**/.rollup.cache", 13 | "**/.type-coverage", 14 | "**/.vscode", 15 | "**/coverage", 16 | "**/package.json", 17 | "**/package-lock.json", 18 | "external/@coana-tech" 19 | ], 20 | "maxSize": 8388608 21 | }, 22 | "formatter": { 23 | "enabled": true, 24 | "attributePosition": "auto", 25 | "bracketSpacing": true, 26 | "formatWithErrors": false, 27 | "indentStyle": "space", 28 | "indentWidth": 2, 29 | "lineEnding": "lf", 30 | "lineWidth": 80, 31 | "useEditorconfig": true 32 | }, 33 | "javascript": { 34 | "formatter": { 35 | "arrowParentheses": "asNeeded", 36 | "attributePosition": "auto", 37 | "bracketSameLine": false, 38 | "bracketSpacing": true, 39 | "jsxQuoteStyle": "double", 40 | "quoteProperties": "asNeeded", 41 | "quoteStyle": "single", 42 | "semicolons": "asNeeded", 43 | "trailingCommas": "all" 44 | } 45 | }, 46 | "json": { 47 | "formatter": { 48 | "enabled": true, 49 | "trailingCommas": "none" 50 | }, 51 | "parser": { 52 | "allowComments": true, 53 | "allowTrailingCommas": true 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /knip.json: -------------------------------------------------------------------------------- 1 | { 2 | "entry": [ 3 | ".config/*.{js,mjs}", 4 | "bin/*.js", 5 | "scripts/**/*.js", 6 | "shadow-bin/**", 7 | "src/**/*.mts", 8 | "test/**/*.test.mts", 9 | "*.js" 10 | ], 11 | "project": [ 12 | ".config/**", 13 | "bin/**", 14 | "scripts/**", 15 | "shadow-bin/**", 16 | "src/**", 17 | "test/**" 18 | ], 19 | "ignore": ["dist/**"] 20 | } 21 | -------------------------------------------------------------------------------- /patches/@npmcli+arborist#9.1.1.patch: -------------------------------------------------------------------------------- 1 | Index: /@npmcli/arborist/lib/node.js 2 | =================================================================== 3 | --- /@npmcli/arborist/lib/node.js 4 | +++ /@npmcli/arborist/lib/node.js 5 | @@ -1156,9 +1156,15 @@ 6 | } 7 | 8 | // if they're links, they match if the targets match 9 | if (this.isLink) { 10 | - return node.isLink && this.target.matches(node.target) 11 | + if (node.isLink) { 12 | + if (this.target && node.target) { 13 | + return this.target.matches(node.target) 14 | + } 15 | + } else { 16 | + return false 17 | + } 18 | } 19 | 20 | // if they're two project root nodes, they're different if the paths differ 21 | if (this.isProjectRoot && node.isProjectRoot) { 22 | -------------------------------------------------------------------------------- /patches/@rollup+plugin-commonjs#28.0.3.patch: -------------------------------------------------------------------------------- 1 | Index: /@rollup/plugin-commonjs/dist/cjs/index.js 2 | =================================================================== 3 | --- /@rollup/plugin-commonjs/dist/cjs/index.js 4 | +++ /@rollup/plugin-commonjs/dist/cjs/index.js 5 | @@ -377,10 +377,11 @@ 6 | 7 | export function getAugmentedNamespace(n) { 8 | if (Object.prototype.hasOwnProperty.call(n, '__esModule')) return n; 9 | var f = n.default; 10 | + var a 11 | if (typeof f == "function") { 12 | - var a = function a () { 13 | + a = function a () { 14 | if (this instanceof a) { 15 | return Reflect.construct(f, arguments, this.constructor); 16 | } 17 | return f.apply(this, arguments); 18 | Index: /@rollup/plugin-commonjs/dist/es/index.js 19 | =================================================================== 20 | --- /@rollup/plugin-commonjs/dist/es/index.js 21 | +++ /@rollup/plugin-commonjs/dist/es/index.js 22 | @@ -373,10 +373,11 @@ 23 | 24 | export function getAugmentedNamespace(n) { 25 | if (Object.prototype.hasOwnProperty.call(n, '__esModule')) return n; 26 | var f = n.default; 27 | + var a 28 | if (typeof f == "function") { 29 | - var a = function a () { 30 | + a = function a () { 31 | if (this instanceof a) { 32 | return Reflect.construct(f, arguments, this.constructor); 33 | } 34 | return f.apply(this, arguments); 35 | -------------------------------------------------------------------------------- /patches/brace-expansion#2.0.1.patch: -------------------------------------------------------------------------------- 1 | Index: /brace-expansion/index.js 2 | =================================================================== 3 | --- /brace-expansion/index.js 4 | +++ /brace-expansion/index.js 5 | @@ -103,9 +103,9 @@ 6 | var post = m.post.length 7 | ? expand(m.post, false) 8 | : ['']; 9 | 10 | - if (/\$$/.test(m.pre)) { 11 | + if (m.pre.endsWith('\u0024' /*'$'*/)) { 12 | for (var k = 0; k < post.length; k++) { 13 | var expansion = pre+ '{' + m.body + '}' + post[k]; 14 | expansions.push(expansion); 15 | } 16 | -------------------------------------------------------------------------------- /patches/bresenham#0.0.3.patch: -------------------------------------------------------------------------------- 1 | Index: /bresenham/index.js 2 | =================================================================== 3 | --- /bresenham/index.js 4 | +++ /bresenham/index.js 5 | @@ -1,7 +1,7 @@ 6 | module.exports = function(x0, y0, x1, y1, fn) { 7 | + var arr = []; 8 | if(!fn) { 9 | - var arr = []; 10 | fn = function(x, y) { arr.push({ x: x, y: y }); }; 11 | } 12 | var dx = x1 - x0; 13 | var dy = y1 - y0; 14 | -------------------------------------------------------------------------------- /patches/graceful-fs#4.2.11.patch: -------------------------------------------------------------------------------- 1 | Index: /graceful-fs/clone.js 2 | =================================================================== 3 | --- /graceful-fs/clone.js 4 | +++ /graceful-fs/clone.js 5 | @@ -9,12 +9,11 @@ 6 | function clone (obj) { 7 | if (obj === null || typeof obj !== 'object') 8 | return obj 9 | 10 | - if (obj instanceof Object) 11 | - var copy = { __proto__: getPrototypeOf(obj) } 12 | - else 13 | - var copy = Object.create(null) 14 | + var copy = obj instanceof Object 15 | + ? { __proto__: getPrototypeOf(obj) } 16 | + : Object.create(null) 17 | 18 | Object.getOwnPropertyNames(obj).forEach(function (key) { 19 | Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) 20 | }) 21 | -------------------------------------------------------------------------------- /patches/string_decoder#0.10.31.patch: -------------------------------------------------------------------------------- 1 | Index: /string_decoder/index.js 2 | =================================================================== 3 | --- /string_decoder/index.js 4 | +++ /string_decoder/index.js 5 | @@ -138,9 +138,9 @@ 6 | } 7 | 8 | charStr += buffer.toString(this.encoding, 0, end); 9 | 10 | - var end = charStr.length - 1; 11 | + end = charStr.length - 1; 12 | var charCode = charStr.charCodeAt(end); 13 | // CESU-8: lead surrogate (D800-DBFF) is also the incomplete character 14 | if (charCode >= 0xD800 && charCode <= 0xDBFF) { 15 | var size = this.surrogateSize; 16 | -------------------------------------------------------------------------------- /patches/tinyglobby#0.2.14.patch: -------------------------------------------------------------------------------- 1 | Index: /tinyglobby/package.json 2 | =================================================================== 3 | --- /tinyglobby/package.json 4 | +++ /tinyglobby/package.json 5 | @@ -2,14 +2,9 @@ 6 | "name": "tinyglobby", 7 | "version": "0.2.14", 8 | "description": "A fast and minimal alternative to globby and fast-glob", 9 | "main": "dist/index.js", 10 | - "module": "dist/index.mjs", 11 | "types": "dist/index.d.ts", 12 | - "exports": { 13 | - "import": "./dist/index.mjs", 14 | - "require": "./dist/index.js" 15 | - }, 16 | "sideEffects": false, 17 | "files": [ 18 | "dist" 19 | ], 20 | @@ -61,5 +56,5 @@ 21 | "test:coverage": "node --experimental-transform-types --test --experimental-test-coverage", 22 | "test:only": "node --experimental-transform-types --test --test-only", 23 | "typecheck": "tsc --noEmit" 24 | } 25 | -} 26 | \ No newline at end of file 27 | +} 28 | -------------------------------------------------------------------------------- /scripts/babel/transform-set-proto-plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Helper to check if something is a .__proto__ access. 4 | function isProtoAccess(node, t) { 5 | return ( 6 | t.isMemberExpression(node) && 7 | t.isIdentifier(node.property, { name: '__proto__' }) 8 | ) 9 | } 10 | 11 | // Unwraps A.__proto__ or A.prototype.__proto__. 12 | function unwrapProto(node, t) { 13 | const { object } = node 14 | return { 15 | object, 16 | isPrototype: 17 | t.isMemberExpression(object) && 18 | t.isIdentifier(object.property, { name: 'prototype' }), 19 | } 20 | } 21 | 22 | module.exports = function ({ types: t }) { 23 | return { 24 | name: 'transform-set-proto', 25 | visitor: { 26 | ExpressionStatement(path) { 27 | const { expression: expr } = path.node 28 | // Handle: Xyz.prototype.__proto__ = foo 29 | if (t.isAssignmentExpression(expr) && isProtoAccess(expr.left, t)) { 30 | const { object } = unwrapProto(expr.left, t) 31 | const { right } = expr 32 | path.replaceWith( 33 | t.expressionStatement( 34 | t.callExpression( 35 | t.memberExpression( 36 | t.identifier('Object'), 37 | t.identifier('setPrototypeOf'), 38 | ), 39 | [object, right], 40 | ), 41 | ), 42 | ) 43 | } 44 | }, 45 | }, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /scripts/babel/transform-url-parse-plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function ({ types: t }) { 4 | return { 5 | name: 'transform-url-parse', 6 | visitor: { 7 | CallExpression(path) { 8 | const { node } = path 9 | // Match `url.parse(...)` calls with exactly one argument. 10 | if ( 11 | node.callee.type === 'MemberExpression' && 12 | node.callee.object.name === 'url' && 13 | node.callee.property.name === 'parse' && 14 | node.arguments.length === 1 15 | ) { 16 | const { parent } = path 17 | // Create an AST node for `new URL()`. 18 | const newUrl = t.newExpression(t.identifier('URL'), [ 19 | node.arguments[0], 20 | ]) 21 | // Check if the result of `url.parse()` is immediately accessed, e.g. 22 | // `url.parse(x).protocol`. 23 | if (parent.type === 'MemberExpression' && parent.object === node) { 24 | // Replace the full `url.parse(x).protocol` with `(new URL(x)).protocol`. 25 | path.parentPath.replaceWith( 26 | t.memberExpression( 27 | newUrl, 28 | parent.property, 29 | // Handle dynamic props like `['protocol']`. 30 | parent.computed, 31 | ), 32 | ) 33 | } else { 34 | // Otherwise, replace `url.parse(x)` with `new URL(x)`. 35 | path.replaceWith(newUrl) 36 | } 37 | } 38 | }, 39 | }, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /scripts/rollup/socket-modify-plugin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { createFilter } = require('@rollup/pluginutils') 4 | const MagicString = require('magic-string') 5 | 6 | function socketModifyPlugin({ 7 | exclude, 8 | find, 9 | include, 10 | replace, 11 | sourcemap = true, 12 | }) { 13 | const filter = createFilter(include, exclude) 14 | return { 15 | name: 'socket-modify', 16 | renderChunk(code, { fileName }) { 17 | if (!filter(fileName)) { 18 | return null 19 | } 20 | const s = new MagicString(code) 21 | const { global } = find 22 | find.lastIndex = 0 23 | let match 24 | while ((match = find.exec(code)) !== null) { 25 | s.overwrite( 26 | match.index, 27 | match.index + match[0].length, 28 | typeof replace === 'function' 29 | ? Reflect.apply(replace, match, match) 30 | : String(replace), 31 | ) 32 | // Exit early if not a global regexp. 33 | if (!global) { 34 | break 35 | } 36 | } 37 | return { 38 | code: s.toString(), 39 | map: sourcemap ? s.generateMap() : null, 40 | } 41 | }, 42 | } 43 | } 44 | 45 | module.exports = socketModifyPlugin 46 | -------------------------------------------------------------------------------- /scripts/utils/fs.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { statSync } = require('node:fs') 4 | const path = require('node:path') 5 | 6 | function findUpSync(name, { cwd = process.cwd() }) { 7 | let dir = path.resolve(cwd) 8 | const { root } = path.parse(dir) 9 | const names = [name].flat() 10 | while (dir && dir !== root) { 11 | for (const name of names) { 12 | const filePath = path.join(dir, name) 13 | try { 14 | const stats = statSync(filePath) 15 | if (stats.isFile()) { 16 | return filePath 17 | } 18 | } catch {} 19 | } 20 | dir = path.dirname(dir) 21 | } 22 | return undefined 23 | } 24 | 25 | module.exports = { 26 | findUpSync, 27 | } 28 | -------------------------------------------------------------------------------- /sd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # node 20 does not support strip types (yet) so we have go the slow path 4 | # Note that we don't rebuild here... this will be annoying later but I 5 | # think this is only for dev where we don't need to run node 20. 6 | # Should we emit a warning anyways? Maybe. 7 | if [ "$(node -v | cut -d'v' -f2 | cut -d'.' -f1)" -lt 22 ]; then 8 | npm run s -- "$@" 9 | else 10 | node --experimental-strip-types --no-warnings src/cli.mts "$@" 11 | fi 12 | -------------------------------------------------------------------------------- /shadow-bin/npm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const Module = require('node:module') 5 | const path = require('node:path') 6 | const rootPath = path.join(__dirname, '..') 7 | Module.enableCompileCache?.(path.join(rootPath, '.cache')) 8 | 9 | const shadowBin = require(path.join(rootPath, 'dist/shadow-bin.js')) 10 | shadowBin('npm') 11 | -------------------------------------------------------------------------------- /shadow-bin/npx: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const Module = require('node:module') 5 | const path = require('node:path') 6 | const rootPath = path.join(__dirname, '..') 7 | Module.enableCompileCache?.(path.join(rootPath, '.cache')) 8 | 9 | const shadowBin = require(path.join(rootPath, 'dist/shadow-bin.js')) 10 | shadowBin('npx') 11 | -------------------------------------------------------------------------------- /src/commands/analytics/fetch-org-analytics.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchOrgAnalyticsData( 8 | time: number, 9 | ): Promise['data']>> { 10 | const sockSdkResult = await setupSdk() 11 | if (!sockSdkResult.ok) { 12 | return sockSdkResult 13 | } 14 | const sockSdk = sockSdkResult.data 15 | 16 | return await handleApiCall( 17 | sockSdk.getOrgAnalytics(time.toString()), 18 | 'analytics data', 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/analytics/fetch-repo-analytics.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchRepoAnalyticsData( 8 | repo: string, 9 | time: number, 10 | ): Promise['data']>> { 11 | const sockSdkResult = await setupSdk() 12 | if (!sockSdkResult.ok) { 13 | return sockSdkResult 14 | } 15 | const sockSdk = sockSdkResult.data 16 | 17 | return await handleApiCall( 18 | sockSdk.getRepoAnalytics(repo, time.toString()), 19 | 'analytics data', 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/commands/analytics/handle-analytics.mts: -------------------------------------------------------------------------------- 1 | import { fetchOrgAnalyticsData } from './fetch-org-analytics.mts' 2 | import { fetchRepoAnalyticsData } from './fetch-repo-analytics.mts' 3 | import { outputAnalytics } from './output-analytics.mts' 4 | 5 | import type { CResult, OutputKind } from '../../types.mts' 6 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 7 | 8 | export async function handleAnalytics({ 9 | filePath, 10 | outputKind, 11 | repo, 12 | scope, 13 | time, 14 | }: { 15 | scope: string 16 | time: number 17 | repo: string 18 | outputKind: OutputKind 19 | filePath: string 20 | }) { 21 | let result: CResult< 22 | | SocketSdkReturnType<'getOrgAnalytics'>['data'] 23 | | SocketSdkReturnType<'getRepoAnalytics'>['data'] 24 | > 25 | if (scope === 'org') { 26 | result = await fetchOrgAnalyticsData(time) 27 | } else if (repo) { 28 | result = await fetchRepoAnalyticsData(repo, time) 29 | } else { 30 | result = { 31 | ok: false, 32 | message: 'Missing repository name in command', 33 | } 34 | } 35 | if (result.ok && !result.data.length) { 36 | result = { 37 | ok: true, 38 | message: `The analytics data for this ${scope === 'org' ? 'organization' : 'repository'} is not yet available.`, 39 | data: [], 40 | } 41 | } 42 | 43 | await outputAnalytics(result, { 44 | filePath, 45 | outputKind, 46 | repo, 47 | scope, 48 | time, 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /src/commands/audit-log/fetch-audit-log.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult, OutputKind } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchAuditLog({ 8 | logType, 9 | orgSlug, 10 | outputKind, 11 | page, 12 | perPage, 13 | }: { 14 | outputKind: OutputKind 15 | orgSlug: string 16 | page: number 17 | perPage: number 18 | logType: string 19 | }): Promise['data']>> { 20 | const sockSdkResult = await setupSdk() 21 | if (!sockSdkResult.ok) { 22 | return sockSdkResult 23 | } 24 | const sockSdk = sockSdkResult.data 25 | 26 | return await handleApiCall( 27 | sockSdk.getAuditLogEvents(orgSlug, { 28 | // I'm not sure this is used at all. 29 | outputJson: String(outputKind === 'json'), 30 | // I'm not sure this is used at all. 31 | outputMarkdown: String(outputKind === 'markdown'), 32 | orgSlug, 33 | type: logType, 34 | page: String(page), 35 | per_page: String(perPage), 36 | }), 37 | `audit log for ${orgSlug}`, 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/commands/audit-log/handle-audit-log.mts: -------------------------------------------------------------------------------- 1 | import { fetchAuditLog } from './fetch-audit-log.mts' 2 | import { outputAuditLog } from './output-audit-log.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleAuditLog({ 7 | logType, 8 | orgSlug, 9 | outputKind, 10 | page, 11 | perPage, 12 | }: { 13 | outputKind: OutputKind 14 | orgSlug: string 15 | page: number 16 | perPage: number 17 | logType: string 18 | }): Promise { 19 | const auditLogs = await fetchAuditLog({ 20 | orgSlug, 21 | outputKind, 22 | page, 23 | perPage, 24 | logType, 25 | }) 26 | 27 | await outputAuditLog(auditLogs, { 28 | logType, 29 | orgSlug, 30 | outputKind, 31 | page, 32 | perPage, 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /src/commands/cdxgen/cmd-cdxgen.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { handleCdxgen } from './handle-cdxgen.mts' 4 | import { commonFlags } from '../../flags.mts' 5 | 6 | import type { CliCommandConfig } from '../../utils/meow-with-subcommands.mts' 7 | 8 | const config: CliCommandConfig = { 9 | commandName: 'cdxgen', 10 | description: 'Create an SBOM with CycloneDX generator (cdxgen)', 11 | hidden: true, 12 | flags: { 13 | ...commonFlags, 14 | }, 15 | help: (parentName, _config) => ` 16 | Usage 17 | $ ${parentName} 18 | `, 19 | } 20 | 21 | export const cmdCdxgen = { 22 | description: config.description, 23 | hidden: config.hidden, 24 | run, 25 | } 26 | 27 | async function run( 28 | argv: string[] | readonly string[], 29 | importMeta: ImportMeta, 30 | { parentName }: { parentName: string }, 31 | ): Promise { 32 | logger.warn( 33 | 'Warning: The `socket cdxgen` command moved to `socket manifest cdxgen` and will be removed as a toplevel command in the next major bump.', 34 | ) 35 | 36 | await handleCdxgen(argv, importMeta, { parentName }) 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/cdxgen/handle-cdxgen.mts: -------------------------------------------------------------------------------- 1 | import { cmdManifestCdxgen } from '../manifest/cmd-manifest-cdxgen.mts' 2 | 3 | export async function handleCdxgen( 4 | argv: string[] | readonly string[], 5 | importMeta: ImportMeta, 6 | { parentName }: { parentName: string }, 7 | ): Promise { 8 | await cmdManifestCdxgen.run(argv, importMeta, { parentName }) 9 | } 10 | -------------------------------------------------------------------------------- /src/commands/ci/fetch-default-org-slug.mts: -------------------------------------------------------------------------------- 1 | import { debugFn } from '@socketsecurity/registry/lib/debug' 2 | 3 | import { handleApiCall } from '../../utils/api.mts' 4 | import { getConfigValueOrUndef } from '../../utils/config.mts' 5 | import { setupSdk } from '../../utils/sdk.mts' 6 | 7 | import type { CResult } from '../../types.mts' 8 | 9 | // Use the config defaultOrg when set, otherwise discover from remote 10 | export async function getDefaultOrgSlug(): Promise> { 11 | const defaultOrgResult = getConfigValueOrUndef('defaultOrg') 12 | 13 | if (defaultOrgResult) { 14 | debugFn('use: default org', defaultOrgResult) 15 | return { ok: true, data: defaultOrgResult } 16 | } 17 | 18 | const sockSdkResult = await setupSdk() 19 | if (!sockSdkResult.ok) { 20 | return sockSdkResult 21 | } 22 | const sockSdk = sockSdkResult.data 23 | 24 | const result = await handleApiCall( 25 | sockSdk.getOrganizations(), 26 | 'list of organizations', 27 | ) 28 | 29 | if (!result.ok) { 30 | return result 31 | } 32 | 33 | const orgs = result.data.organizations 34 | const keys = Object.keys(orgs) 35 | 36 | if (!keys[0]) { 37 | return { 38 | ok: false, 39 | message: 'Failed to establish identity', 40 | data: `API did not return any organization associated with the current API token. Unable to continue.`, 41 | } 42 | } 43 | 44 | const slug = (keys[0] in orgs && orgs?.[keys[0]]?.name) ?? undefined 45 | 46 | if (!slug) { 47 | return { 48 | ok: false, 49 | message: 'Failed to establish identity', 50 | data: `Was unable to determine the default organization for the current API token. Unable to continue.`, 51 | } 52 | } 53 | 54 | debugFn('resolve: org', slug) 55 | 56 | return { 57 | ok: true, 58 | message: 'Retrieved default org from server', 59 | data: slug, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/commands/ci/handle-ci.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { getDefaultOrgSlug } from './fetch-default-org-slug.mts' 4 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 5 | import { handleCreateNewScan } from '../scan/handle-create-new-scan.mts' 6 | 7 | export async function handleCI(autoManifest: boolean): Promise { 8 | // ci: { 9 | // description: 'Alias for "report create --view --strict"', 10 | // argv: ['report', 'create', '--view', '--strict'] 11 | // } 12 | const result = await getDefaultOrgSlug() 13 | if (!result.ok) { 14 | process.exitCode = result.code ?? 1 15 | // Always assume json mode 16 | logger.log(serializeResultJson(result)) 17 | return 18 | } 19 | 20 | // TODO: does it make sense to discover the commit details from local git? 21 | // TODO: does it makes sense to use custom branch/repo names here? probably socket.yml, right 22 | await handleCreateNewScan({ 23 | autoManifest, 24 | branchName: 'socket-default-branch', 25 | commitMessage: '', 26 | commitHash: '', 27 | committers: '', 28 | cwd: process.cwd(), 29 | defaultBranch: false, 30 | interactive: false, 31 | orgSlug: result.data, 32 | outputKind: 'json', 33 | pendingHead: true, // when true, requires branch name set, tmp false 34 | pullRequest: 0, 35 | repoName: 'socket-default-repository', 36 | readOnly: false, 37 | report: true, 38 | targets: ['.'], 39 | tmp: false, // don't set when pendingHead is true 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /src/commands/config/cmd-config.mts: -------------------------------------------------------------------------------- 1 | import { cmdConfigAuto } from './cmd-config-auto.mts' 2 | import { cmdConfigGet } from './cmd-config-get.mts' 3 | import { cmdConfigList } from './cmd-config-list.mts' 4 | import { cmdConfigSet } from './cmd-config-set.mts' 5 | import { cmdConfigUnset } from './cmd-config-unset.mts' 6 | import { meowWithSubcommands } from '../../utils/meow-with-subcommands.mts' 7 | 8 | import type { CliSubcommand } from '../../utils/meow-with-subcommands.mts' 9 | 10 | const description = 'Commands related to the local CLI configuration' 11 | 12 | export const cmdConfig: CliSubcommand = { 13 | description, 14 | hidden: true, // [beta]; isTestingV1 15 | async run(argv, importMeta, { parentName }) { 16 | await meowWithSubcommands( 17 | { 18 | auto: cmdConfigAuto, 19 | get: cmdConfigGet, 20 | list: cmdConfigList, 21 | set: cmdConfigSet, 22 | unset: cmdConfigUnset, 23 | }, 24 | { 25 | argv, 26 | description, 27 | importMeta, 28 | name: `${parentName} config`, 29 | }, 30 | ) 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/config/handle-config-auto.mts: -------------------------------------------------------------------------------- 1 | import { discoverConfigValue } from './discover-config-value.mts' 2 | import { outputConfigAuto } from './output-config-auto.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | import type { LocalConfig } from '../../utils/config.mts' 6 | 7 | export async function handleConfigAuto({ 8 | key, 9 | outputKind, 10 | }: { 11 | key: keyof LocalConfig 12 | outputKind: OutputKind 13 | }) { 14 | const result = await discoverConfigValue(key) 15 | 16 | await outputConfigAuto(key, result, outputKind) 17 | } 18 | -------------------------------------------------------------------------------- /src/commands/config/handle-config-get.mts: -------------------------------------------------------------------------------- 1 | import { outputConfigGet } from './output-config-get.mts' 2 | import { getConfigValue } from '../../utils/config.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | import type { LocalConfig } from '../../utils/config.mts' 6 | 7 | export async function handleConfigGet({ 8 | key, 9 | outputKind, 10 | }: { 11 | key: keyof LocalConfig 12 | outputKind: OutputKind 13 | }) { 14 | const result = getConfigValue(key) 15 | 16 | await outputConfigGet(key, result, outputKind) 17 | } 18 | -------------------------------------------------------------------------------- /src/commands/config/handle-config-set.mts: -------------------------------------------------------------------------------- 1 | import { outputConfigSet } from './output-config-set.mts' 2 | import { updateConfigValue } from '../../utils/config.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | import type { LocalConfig } from '../../utils/config.mts' 6 | 7 | export async function handleConfigSet({ 8 | key, 9 | outputKind, 10 | value, 11 | }: { 12 | key: keyof LocalConfig 13 | outputKind: OutputKind 14 | value: string 15 | }) { 16 | const result = updateConfigValue(key, value) 17 | 18 | await outputConfigSet(result, outputKind) 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/config/handle-config-unset.mts: -------------------------------------------------------------------------------- 1 | import { outputConfigUnset } from './output-config-unset.mts' 2 | import { updateConfigValue } from '../../utils/config.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | import type { LocalConfig } from '../../utils/config.mts' 6 | 7 | export async function handleConfigUnset({ 8 | key, 9 | outputKind, 10 | }: { 11 | key: keyof LocalConfig 12 | outputKind: OutputKind 13 | }) { 14 | const updateResult = updateConfigValue(key, undefined) 15 | 16 | await outputConfigUnset(updateResult, outputKind) 17 | } 18 | -------------------------------------------------------------------------------- /src/commands/config/output-config-get.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { isReadOnlyConfig } from '../../utils/config.mts' 4 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 5 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 6 | 7 | import type { CResult, OutputKind } from '../../types.mts' 8 | import type { LocalConfig } from '../../utils/config.mts' 9 | 10 | export async function outputConfigGet( 11 | key: keyof LocalConfig, 12 | result: CResult, 13 | outputKind: OutputKind, 14 | ) { 15 | if (!result.ok) { 16 | process.exitCode = result.code ?? 1 17 | } 18 | 19 | if (outputKind === 'json') { 20 | logger.log(serializeResultJson(result)) 21 | return 22 | } 23 | if (!result.ok) { 24 | logger.fail(failMsgWithBadge(result.message, result.cause)) 25 | return 26 | } 27 | 28 | const readOnly = isReadOnlyConfig() 29 | 30 | if (outputKind === 'markdown') { 31 | logger.log(`# Config Value`) 32 | logger.log('') 33 | logger.log(`Config key '${key}' has value '${result.data}`) 34 | if (readOnly) { 35 | logger.log('') 36 | logger.log( 37 | 'Note: the config is in read-only mode, meaning at least one key was temporarily\n overridden from an env var or command flag.', 38 | ) 39 | } 40 | } else { 41 | logger.log(`${key}: ${result.data}`) 42 | if (readOnly) { 43 | logger.log('') 44 | logger.log( 45 | 'Note: the config is in read-only mode, meaning at least one key was temporarily overridden from an env var or command flag.', 46 | ) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/commands/config/output-config-set.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 5 | 6 | import type { CResult, OutputKind } from '../../types.mts' 7 | 8 | export async function outputConfigSet( 9 | result: CResult, 10 | outputKind: OutputKind, 11 | ) { 12 | if (!result.ok) { 13 | process.exitCode = result.code ?? 1 14 | } 15 | 16 | if (outputKind === 'json') { 17 | logger.log(serializeResultJson(result)) 18 | return 19 | } 20 | if (!result.ok) { 21 | logger.fail(failMsgWithBadge(result.message, result.cause)) 22 | return 23 | } 24 | 25 | if (outputKind === 'markdown') { 26 | logger.log(`# Update config`) 27 | logger.log('') 28 | logger.log(result.message) 29 | if (result.data) { 30 | logger.log('') 31 | logger.log(result.data) 32 | } 33 | } else { 34 | logger.log(`OK`) 35 | logger.log(result.message) 36 | if (result.data) { 37 | logger.log('') 38 | logger.log(result.data) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/commands/config/output-config-unset.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 5 | 6 | import type { CResult, OutputKind } from '../../types.mts' 7 | 8 | export async function outputConfigUnset( 9 | updateResult: CResult, 10 | outputKind: OutputKind, 11 | ) { 12 | if (!updateResult.ok) { 13 | process.exitCode = updateResult.code ?? 1 14 | } 15 | 16 | if (outputKind === 'json') { 17 | logger.log(serializeResultJson(updateResult)) 18 | return 19 | } 20 | if (!updateResult.ok) { 21 | logger.fail(failMsgWithBadge(updateResult.message, updateResult.cause)) 22 | return 23 | } 24 | 25 | if (outputKind === 'markdown') { 26 | logger.log(`# Update config`) 27 | logger.log('') 28 | logger.log(updateResult.message) 29 | if (updateResult.data) { 30 | logger.log('') 31 | logger.log(updateResult.data) 32 | } 33 | } else { 34 | logger.log(`OK`) 35 | logger.log(updateResult.message) 36 | if (updateResult.data) { 37 | logger.log('') 38 | logger.log(updateResult.data) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/commands/dependencies/fetch-dependencies.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchDependencies({ 8 | limit, 9 | offset, 10 | }: { 11 | limit: number 12 | offset: number 13 | }): Promise['data']>> { 14 | const sockSdkResult = await setupSdk() 15 | if (!sockSdkResult.ok) { 16 | return sockSdkResult 17 | } 18 | const sockSdk = sockSdkResult.data 19 | 20 | return await handleApiCall( 21 | sockSdk.searchDependencies({ limit, offset }), 22 | 'organization dependencies', 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/dependencies/handle-dependencies.mts: -------------------------------------------------------------------------------- 1 | import { fetchDependencies } from './fetch-dependencies.mts' 2 | import { outputDependencies } from './output-dependencies.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleDependencies({ 7 | limit, 8 | offset, 9 | outputKind, 10 | }: { 11 | limit: number 12 | offset: number 13 | outputKind: OutputKind 14 | }): Promise { 15 | const result = await fetchDependencies({ limit, offset }) 16 | 17 | await outputDependencies(result, { limit, offset, outputKind }) 18 | } 19 | -------------------------------------------------------------------------------- /src/commands/dependencies/output-dependencies.mts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import chalkTable from 'chalk-table' 3 | import colors from 'yoctocolors-cjs' 4 | 5 | import { logger } from '@socketsecurity/registry/lib/logger' 6 | 7 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 8 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 9 | 10 | import type { CResult, OutputKind } from '../../types.mts' 11 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 12 | 13 | export async function outputDependencies( 14 | result: CResult['data']>, 15 | { 16 | limit, 17 | offset, 18 | outputKind, 19 | }: { 20 | limit: number 21 | offset: number 22 | outputKind: OutputKind 23 | }, 24 | ): Promise { 25 | if (!result.ok) { 26 | process.exitCode = result.code ?? 1 27 | } 28 | 29 | if (outputKind === 'json') { 30 | logger.log(serializeResultJson(result)) 31 | return 32 | } 33 | if (!result.ok) { 34 | logger.fail(failMsgWithBadge(result.message, result.cause)) 35 | return 36 | } 37 | 38 | logger.log( 39 | 'Request details: Offset:', 40 | offset, 41 | ', limit:', 42 | limit, 43 | ', is there more data after this?', 44 | result.data.end ? 'no' : 'yes', 45 | ) 46 | 47 | const options = { 48 | columns: [ 49 | { field: 'namespace', name: colors.cyan('Namespace') }, 50 | { field: 'name', name: colors.cyan('Name') }, 51 | { field: 'version', name: colors.cyan('Version') }, 52 | { field: 'repository', name: colors.cyan('Repository') }, 53 | { field: 'branch', name: colors.cyan('Branch') }, 54 | { field: 'type', name: colors.cyan('Type') }, 55 | { field: 'direct', name: colors.cyan('Direct') }, 56 | ], 57 | } 58 | 59 | logger.log(chalkTable(options, result.data.rows)) 60 | } 61 | -------------------------------------------------------------------------------- /src/commands/diff-scan/cmd-diff-scan.mts: -------------------------------------------------------------------------------- 1 | import { cmdDiffScanGet } from './cmd-diff-scan-get.mts' 2 | import { meowWithSubcommands } from '../../utils/meow-with-subcommands.mts' 3 | 4 | import type { CliSubcommand } from '../../utils/meow-with-subcommands.mts' 5 | 6 | const description = 'Diff scans related commands' 7 | 8 | export const cmdDiffScan: CliSubcommand = { 9 | description, 10 | // Hidden because it was broken all this time (nobody could be using it) 11 | // and we're not sure if it's useful to anyone in its current state. 12 | // Until we do, we'll hide this to keep the help tidier. 13 | // And later, we may simply move this under `scan`, anyways. 14 | hidden: true, 15 | async run(argv, importMeta, { parentName }) { 16 | await meowWithSubcommands( 17 | { 18 | get: cmdDiffScanGet, 19 | }, 20 | { 21 | argv, 22 | description, 23 | importMeta, 24 | name: parentName + ' diff-scan', 25 | }, 26 | ) 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /src/commands/diff-scan/fetch-diff-scan.mts: -------------------------------------------------------------------------------- 1 | import { queryApiSafeJson } from '../../utils/api.mts' 2 | 3 | import type { CResult } from '../../types.mts' 4 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 5 | 6 | export async function fetchDiffScan({ 7 | after, 8 | before, 9 | orgSlug, 10 | }: { 11 | after: string 12 | before: string 13 | orgSlug: string 14 | }): Promise['data']>> { 15 | return await queryApiSafeJson['data']>( 16 | `orgs/${orgSlug}/full-scans/diff?before=${encodeURIComponent(before)}&after=${encodeURIComponent(after)}`, 17 | 'a scan diff', 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/diff-scan/handle-diff-scan.mts: -------------------------------------------------------------------------------- 1 | import { fetchDiffScan } from './fetch-diff-scan.mts' 2 | import { outputDiffScan } from './output-diff-scan.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleDiffScan({ 7 | after, 8 | before, 9 | depth, 10 | file, 11 | orgSlug, 12 | outputKind, 13 | }: { 14 | after: string 15 | before: string 16 | depth: number 17 | file: string 18 | orgSlug: string 19 | outputKind: OutputKind 20 | }): Promise { 21 | const data = await fetchDiffScan({ 22 | after, 23 | before, 24 | orgSlug, 25 | }) 26 | 27 | await outputDiffScan(data, { 28 | depth, 29 | file, 30 | outputKind, 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/fix/handle-fix.mts: -------------------------------------------------------------------------------- 1 | import { outputFixResult } from './output-fix-result.mts' 2 | import { runFix } from './run-fix.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | import type { RangeStyle } from '../../utils/semver.mts' 6 | 7 | export async function handleFix({ 8 | autoMerge, 9 | cwd, 10 | limit, 11 | outputKind, 12 | purls, 13 | rangeStyle, 14 | test, 15 | testScript, 16 | }: { 17 | autoMerge: boolean 18 | cwd: string 19 | limit: number 20 | outputKind: OutputKind 21 | purls: string[] 22 | rangeStyle: RangeStyle 23 | test: boolean 24 | testScript: string 25 | }) { 26 | const result = await runFix({ 27 | autoMerge, 28 | cwd, 29 | limit, 30 | purls, 31 | rangeStyle, 32 | test, 33 | testScript, 34 | }) 35 | 36 | await outputFixResult(result, outputKind) 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/fix/output-fix-result.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 5 | 6 | import type { CResult, OutputKind } from '../../types.mts' 7 | 8 | export async function outputFixResult( 9 | result: CResult, 10 | outputKind: OutputKind, 11 | ) { 12 | if (!result.ok) { 13 | process.exitCode = result.code ?? 1 14 | } 15 | 16 | if (outputKind === 'json') { 17 | logger.log(serializeResultJson(result)) 18 | return 19 | } 20 | if (!result.ok) { 21 | logger.fail(failMsgWithBadge(result.message, result.cause)) 22 | return 23 | } 24 | 25 | logger.log('') 26 | logger.success('Finished!') 27 | } 28 | -------------------------------------------------------------------------------- /src/commands/fix/run-fix.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { npmFix } from './npm-fix.mts' 4 | import { pnpmFix } from './pnpm-fix.mts' 5 | import { CMD_NAME } from './shared.mts' 6 | import constants from '../../constants.mts' 7 | import { detectAndValidatePackageEnvironment } from '../../utils/package-environment.mts' 8 | 9 | import type { CResult } from '../../types.mts' 10 | import type { RangeStyle } from '../../utils/semver.mts' 11 | 12 | const { NPM, PNPM } = constants 13 | 14 | export async function runFix({ 15 | autoMerge, 16 | cwd, 17 | limit, 18 | purls, 19 | rangeStyle, 20 | test, 21 | testScript, 22 | }: { 23 | autoMerge: boolean 24 | cwd: string 25 | limit: number 26 | purls: string[] 27 | rangeStyle: RangeStyle 28 | test: boolean 29 | testScript: string 30 | }): Promise> { 31 | const result = await detectAndValidatePackageEnvironment(cwd, { 32 | cmdName: CMD_NAME, 33 | logger, 34 | }) 35 | 36 | if (!result.ok) { 37 | return result 38 | } 39 | const pkgEnvDetails = result.data 40 | if (!pkgEnvDetails) { 41 | return { 42 | ok: false, 43 | message: 'No package found', 44 | cause: `No valid package environment was found in given cwd (${cwd})`, 45 | } 46 | } 47 | 48 | logger.info(`Fixing packages for ${pkgEnvDetails.agent}.\n`) 49 | 50 | const { agent } = pkgEnvDetails 51 | 52 | if (agent === NPM) { 53 | return await npmFix(pkgEnvDetails, { 54 | autoMerge, 55 | cwd, 56 | limit, 57 | purls, 58 | rangeStyle, 59 | test, 60 | testScript, 61 | }) 62 | } else if (agent === PNPM) { 63 | return await pnpmFix(pkgEnvDetails, { 64 | autoMerge, 65 | cwd, 66 | limit, 67 | purls, 68 | rangeStyle, 69 | test, 70 | testScript, 71 | }) 72 | } else { 73 | return { 74 | ok: false, 75 | message: 'Not supported', 76 | cause: `${agent} is not supported by this command at the moment.`, 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/commands/fix/shared.mts: -------------------------------------------------------------------------------- 1 | import type { GetAlertsMapFromPurlsOptions } from '../../utils/alerts-map.mts' 2 | import type { Remap } from '@socketsecurity/registry/lib/objects' 3 | 4 | export const CMD_NAME = 'socket fix' 5 | 6 | export function getAlertsMapOptions( 7 | options: GetAlertsMapFromPurlsOptions = {}, 8 | ) { 9 | return { 10 | __proto__: null, 11 | consolidate: true, 12 | nothrow: true, 13 | ...options, 14 | include: { 15 | __proto__: null, 16 | existing: true, 17 | unfixable: false, 18 | upgradable: false, 19 | ...options?.include, 20 | }, 21 | } as Remap< 22 | Omit & { 23 | include: Exclude 24 | } 25 | > 26 | } 27 | -------------------------------------------------------------------------------- /src/commands/info/fetch-package-info.mts: -------------------------------------------------------------------------------- 1 | import { getSeverityCount } from '../../utils/alert/severity.mts' 2 | import { 3 | handleApiCall, 4 | handleUnsuccessfulApiResponse, 5 | } from '../../utils/api.mts' 6 | import { getPublicToken, setupSdk } from '../../utils/sdk.mts' 7 | 8 | import type { PackageData } from './handle-package-info.mts' 9 | 10 | export async function fetchPackageInfo( 11 | pkgName: string, 12 | pkgVersion: string, 13 | includeAllIssues: boolean, 14 | ): Promise { 15 | const sockSdkResult = await setupSdk(getPublicToken()) 16 | if (!sockSdkResult.ok) { 17 | throw new Error('Was unable to setup sdk. Run `socket login` first.') 18 | } 19 | const sockSdk = sockSdkResult.data 20 | 21 | const result = await handleApiCall( 22 | sockSdk.getIssuesByNPMPackage(pkgName, pkgVersion), 23 | 'package issues', 24 | ) 25 | const scoreResult = await handleApiCall( 26 | sockSdk.getScoreByNPMPackage(pkgName, pkgVersion), 27 | 'package score', 28 | ) 29 | 30 | if (!result.ok) { 31 | handleUnsuccessfulApiResponse( 32 | 'getIssuesByNPMPackage', 33 | result.message, 34 | result.cause ?? '', 35 | (result.data as any)?.code ?? 0, 36 | ) 37 | } 38 | 39 | if (!scoreResult.ok) { 40 | handleUnsuccessfulApiResponse( 41 | 'getScoreByNPMPackage', 42 | scoreResult.message, 43 | scoreResult.cause ?? '', 44 | (scoreResult.data as any)?.code ?? 0, 45 | ) 46 | } 47 | 48 | const severityCount = getSeverityCount( 49 | result.data, 50 | includeAllIssues ? undefined : 'high', 51 | ) 52 | 53 | return { 54 | data: result.data, 55 | severityCount, 56 | score: scoreResult.data, 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/commands/info/handle-package-info.mts: -------------------------------------------------------------------------------- 1 | import { hasKeys } from '@socketsecurity/registry/lib/objects' 2 | 3 | import { fetchPackageInfo } from './fetch-package-info.mts' 4 | import { outputPackageInfo } from './output-package-info.mts' 5 | 6 | import type { OutputKind } from '../../types.mts' 7 | import type { SocketSdkAlert } from '../../utils/alert/severity.mts' 8 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 9 | 10 | export interface PackageData { 11 | data: SocketSdkReturnType<'getIssuesByNPMPackage'>['data'] 12 | severityCount: Record 13 | score: SocketSdkReturnType<'getScoreByNPMPackage'>['data'] 14 | } 15 | 16 | export async function handlePackageInfo({ 17 | commandName, 18 | includeAllIssues, 19 | outputKind, 20 | pkgName, 21 | pkgVersion, 22 | strict, 23 | }: { 24 | commandName: string 25 | includeAllIssues: boolean 26 | outputKind: OutputKind 27 | pkgName: string 28 | pkgVersion: string 29 | strict: boolean 30 | }) { 31 | const packageData = await fetchPackageInfo( 32 | pkgName, 33 | pkgVersion, 34 | includeAllIssues, 35 | ) 36 | 37 | if (packageData) { 38 | outputPackageInfo(packageData, { 39 | commandName, 40 | includeAllIssues, 41 | outputKind, 42 | pkgName, 43 | pkgVersion, 44 | }) 45 | 46 | if (strict && hasKeys(packageData.severityCount)) { 47 | // Let NodeJS exit gracefully but with exit(1) 48 | process.exitCode = 1 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/commands/install/cmd-install.mts: -------------------------------------------------------------------------------- 1 | import { cmdInstallCompletion } from './cmd-install-completion.mts' 2 | import { meowWithSubcommands } from '../../utils/meow-with-subcommands.mts' 3 | 4 | import type { CliSubcommand } from '../../utils/meow-with-subcommands.mts' 5 | 6 | const description = 'Setup the Socket CLI command in your environment' 7 | 8 | export const cmdInstall: CliSubcommand = { 9 | description, 10 | hidden: true, // beta; isTestingV1 11 | async run(argv, importMeta, { parentName }) { 12 | await meowWithSubcommands( 13 | { 14 | completion: cmdInstallCompletion, 15 | }, 16 | { 17 | argv, 18 | description, 19 | importMeta, 20 | name: `${parentName} install`, 21 | }, 22 | ) 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/install/handle-install-completion.mts: -------------------------------------------------------------------------------- 1 | import { outputInstallCompletion } from './output-install-completion.mts' 2 | import { setupTabCompletion } from './setup-tab-completion.mts' 3 | 4 | export async function handleInstallCompletion(targetName: string) { 5 | const result = await setupTabCompletion(targetName) 6 | await outputInstallCompletion(result) 7 | } 8 | -------------------------------------------------------------------------------- /src/commands/install/output-install-completion.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | 5 | import type { CResult } from '../../types.mts' 6 | 7 | export async function outputInstallCompletion( 8 | result: CResult<{ 9 | actions: string[] 10 | bashrcPath: string 11 | completionCommand: string 12 | bashrcUpdated: boolean 13 | foundBashrc: boolean 14 | sourcingCommand: string 15 | targetName: string 16 | targetPath: string 17 | }>, 18 | ) { 19 | if (!result.ok) { 20 | process.exitCode = result.code ?? 1 21 | 22 | logger.fail(failMsgWithBadge(result.message, result.cause)) 23 | return 24 | } 25 | 26 | logger.log('') 27 | logger.log( 28 | `Installation of tab completion for "${result.data.targetName}" finished!`, 29 | ) 30 | logger.log('') 31 | 32 | result.data.actions.forEach(action => { 33 | logger.log(` - ${action}`) 34 | }) 35 | logger.log('') 36 | logger.log('Socket tab completion works automatically in new terminals.') 37 | logger.log('') 38 | logger.log( 39 | 'Due to a bash limitation, tab completion cannot be enabled in the', 40 | ) 41 | logger.log('current shell (bash instance) through NodeJS. You must either:') 42 | logger.log('') 43 | logger.log('1. Reload your .bashrc script (best):') 44 | logger.log('') 45 | logger.log(` source ~/.bashrc`) 46 | logger.log('') 47 | logger.log('2. Run these commands to load the completion script:') 48 | logger.log('') 49 | logger.log(` source ${result.data.targetPath}`) 50 | logger.log(` ${result.data.completionCommand}`) 51 | logger.log('') 52 | logger.log('3. Or restart bash somehow (restart terminal or run `bash`)') 53 | logger.log('') 54 | } 55 | -------------------------------------------------------------------------------- /src/commands/json/cmd-json.mts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | 3 | import { handleCmdJson } from './handle-cmd-json.mts' 4 | import { commonFlags } from '../../flags.mts' 5 | import { meowOrExit } from '../../utils/meow-with-subcommands.mts' 6 | 7 | import type { CliCommandConfig } from '../../utils/meow-with-subcommands.mts' 8 | 9 | const config: CliCommandConfig = { 10 | commandName: 'json', 11 | description: 12 | 'Display the `socket.json` that would be applied for target folder', 13 | hidden: true, // This is a power tool. No need to clutter the toplevel. 14 | flags: { 15 | ...commonFlags, 16 | }, 17 | help: parentName => ` 18 | Usage 19 | $ ${parentName} [CWD=.] 20 | 21 | Display the \`socket.json\` file that would apply when running relevant commands 22 | in the target directory. 23 | `, 24 | } 25 | 26 | export const cmdJson = { 27 | description: config.description, 28 | hidden: config.hidden, 29 | run, 30 | } 31 | 32 | async function run( 33 | argv: string[] | readonly string[], 34 | importMeta: ImportMeta, 35 | { parentName }: { parentName: string }, 36 | ): Promise { 37 | const cli = meowOrExit({ 38 | argv, 39 | config, 40 | importMeta, 41 | parentName, 42 | }) 43 | 44 | let [cwd = '.'] = cli.input 45 | // Note: path.resolve vs .join: 46 | // If given path is absolute then cwd should not affect it. 47 | cwd = path.resolve(process.cwd(), cwd) 48 | 49 | await handleCmdJson(cwd) 50 | } 51 | -------------------------------------------------------------------------------- /src/commands/json/handle-cmd-json.mts: -------------------------------------------------------------------------------- 1 | import { outputCmdJson } from './output-cmd-json.mts' 2 | 3 | export async function handleCmdJson(cwd: string) { 4 | await outputCmdJson(cwd) 5 | } 6 | -------------------------------------------------------------------------------- /src/commands/json/output-cmd-json.mts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | 4 | import { logger } from '@socketsecurity/registry/lib/logger' 5 | 6 | import constants from '../../constants.mts' 7 | import { tildify } from '../../utils/tildify.mts' 8 | 9 | export async function outputCmdJson(cwd: string) { 10 | logger.info('Target cwd:', constants.ENV.VITEST ? '' : tildify(cwd)) 11 | 12 | const sjpath = path.join(cwd, 'socket.json') 13 | const tildeSjpath = constants.ENV.VITEST ? '' : tildify(sjpath) 14 | 15 | if (!fs.existsSync(sjpath)) { 16 | logger.fail(`Not found: ${tildeSjpath}`) 17 | process.exitCode = 1 18 | return 19 | } 20 | 21 | if (!fs.lstatSync(sjpath).isFile()) { 22 | logger.fail( 23 | `This is not a regular file (maybe a directory?): ${tildeSjpath}`, 24 | ) 25 | process.exitCode = 1 26 | return 27 | } 28 | 29 | const data = fs.readFileSync(sjpath, 'utf8') 30 | 31 | logger.success(`This is the contents of ${tildeSjpath}:`) 32 | logger.error('') 33 | logger.log(data) 34 | } 35 | -------------------------------------------------------------------------------- /src/commands/login/apply-login.mts: -------------------------------------------------------------------------------- 1 | import { updateConfigValue } from '../../utils/config.mts' 2 | 3 | export function applyLogin( 4 | apiToken: string, 5 | enforcedOrgs: string[], 6 | apiBaseUrl: string | undefined, 7 | apiProxy: string | undefined, 8 | ) { 9 | updateConfigValue('enforcedOrgs', enforcedOrgs) 10 | updateConfigValue('apiToken', apiToken) 11 | updateConfigValue('apiBaseUrl', apiBaseUrl) 12 | updateConfigValue('apiProxy', apiProxy) 13 | } 14 | -------------------------------------------------------------------------------- /src/commands/logout/apply-logout.mts: -------------------------------------------------------------------------------- 1 | import { updateConfigValue } from '../../utils/config.mts' 2 | 3 | export function applyLogout() { 4 | updateConfigValue('apiToken', null) 5 | updateConfigValue('apiBaseUrl', null) 6 | updateConfigValue('apiProxy', null) 7 | updateConfigValue('enforcedOrgs', null) 8 | } 9 | -------------------------------------------------------------------------------- /src/commands/logout/attempt-logout.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { applyLogout } from './apply-logout.mts' 4 | import { isReadOnlyConfig } from '../../utils/config.mts' 5 | 6 | export function attemptLogout() { 7 | try { 8 | applyLogout() 9 | logger.success('Successfully logged out') 10 | if (isReadOnlyConfig()) { 11 | logger.log('') 12 | logger.warn( 13 | 'Note: config is in read-only mode, at least one key was overridden through flag/env, so the logout was not persisted!', 14 | ) 15 | } 16 | } catch { 17 | logger.fail('Failed to complete logout steps') 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/logout/cmd-logout.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { attemptLogout } from './attempt-logout.mts' 4 | import constants from '../../constants.mts' 5 | import { commonFlags } from '../../flags.mts' 6 | import { meowOrExit } from '../../utils/meow-with-subcommands.mts' 7 | 8 | import type { CliCommandConfig } from '../../utils/meow-with-subcommands.mts' 9 | 10 | const { DRY_RUN_BAILING_NOW } = constants 11 | 12 | const config: CliCommandConfig = { 13 | commandName: 'logout', 14 | description: 'Socket API logout', 15 | hidden: false, 16 | flags: { 17 | ...commonFlags, 18 | }, 19 | help: (command, _config) => ` 20 | Usage 21 | $ ${command} 22 | 23 | Logs out of the Socket API and clears all Socket credentials from disk 24 | `, 25 | } 26 | 27 | export const cmdLogout = { 28 | description: config.description, 29 | hidden: config.hidden, 30 | run, 31 | } 32 | 33 | async function run( 34 | argv: string[] | readonly string[], 35 | importMeta: ImportMeta, 36 | { parentName }: { parentName: string }, 37 | ): Promise { 38 | const cli = meowOrExit({ 39 | argv, 40 | config, 41 | importMeta, 42 | parentName, 43 | }) 44 | 45 | if (cli.flags['dryRun']) { 46 | logger.log(DRY_RUN_BAILING_NOW) 47 | return 48 | } 49 | 50 | attemptLogout() 51 | } 52 | -------------------------------------------------------------------------------- /src/commands/manifest/README.md: -------------------------------------------------------------------------------- 1 | # Manifest 2 | 3 | (At the time of writing...) 4 | 5 | ## Dev 6 | 7 | Run it like these examples: 8 | 9 | ``` 10 | # Scala: 11 | npm run bs manifest scala -- --bin ~/apps/sbt/bin/sbt ~/socket/repos/scala/akka 12 | # Gradle/Kotlin 13 | npm run bs manifest yolo -- --cwd ~/socket/repos/kotlin/kotlinx.coroutines 14 | ``` 15 | 16 | And upload with this: 17 | 18 | ``` 19 | npm exec socket scan create -- --repo=depscantmp --branch=mastertmp --tmp --cwd ~/socket/repos/scala/akka socketdev . 20 | npm exec socket scan create -- --repo=depscantmp --branch=mastertmp --tmp --cwd ~/socket/repos/kotlin/kotlinx.coroutines . 21 | ``` 22 | 23 | (The `cwd` option for `create` is necessary because we can't go to the dir and run `npm exec`). 24 | 25 | ## Prod 26 | 27 | User flow look something like this: 28 | 29 | ``` 30 | socket manifest scala . 31 | socket manifest kotlin . 32 | socket manifest yolo 33 | 34 | socket scan create --repo=depscantmp --branch=mastertmp --tmp socketdev . 35 | ``` 36 | -------------------------------------------------------------------------------- /src/commands/manifest/handle-manifest-conda.mts: -------------------------------------------------------------------------------- 1 | import { convertCondaToRequirements } from './convert-conda-to-requirements.mts' 2 | import { outputRequirements } from './output-requirements.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleManifestConda({ 7 | cwd, 8 | filename, 9 | out, 10 | outputKind, 11 | verbose, 12 | }: { 13 | cwd: string 14 | filename: string 15 | out: string 16 | outputKind: OutputKind 17 | verbose: boolean 18 | }): Promise { 19 | const data = await convertCondaToRequirements(filename, cwd, verbose) 20 | 21 | await outputRequirements(data, outputKind, out) 22 | } 23 | -------------------------------------------------------------------------------- /src/commands/manifest/handle-manifest-setup.mts: -------------------------------------------------------------------------------- 1 | import { outputManifestSetup } from './output-manifest-setup.mts' 2 | import { setupManifestConfig } from './setup-manifest-config.mts' 3 | 4 | export async function handleManifestSetup( 5 | cwd: string, 6 | defaultOnReadError: boolean, 7 | ): Promise { 8 | const result = await setupManifestConfig(cwd, defaultOnReadError) 9 | 10 | await outputManifestSetup(result) 11 | } 12 | -------------------------------------------------------------------------------- /src/commands/manifest/output-manifest-setup.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | 5 | import type { CResult } from '../../types.mts' 6 | 7 | export async function outputManifestSetup(result: CResult) { 8 | if (!result.ok) { 9 | process.exitCode = result.code ?? 1 10 | } 11 | 12 | if (!result.ok) { 13 | logger.fail(failMsgWithBadge(result.message, result.cause)) 14 | return 15 | } 16 | 17 | logger.success('Setup complete') 18 | } 19 | -------------------------------------------------------------------------------- /src/commands/manifest/output-requirements.mts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | 3 | import { logger } from '@socketsecurity/registry/lib/logger' 4 | 5 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 6 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 7 | 8 | import type { CResult, OutputKind } from '../../types.mts' 9 | 10 | export async function outputRequirements( 11 | result: CResult<{ contents: string; pip: string }>, 12 | outputKind: OutputKind, 13 | out: string, 14 | ) { 15 | if (!result.ok) { 16 | process.exitCode = result.code ?? 1 17 | } 18 | 19 | if (!result.ok) { 20 | if (outputKind === 'json') { 21 | logger.log(serializeResultJson(result)) 22 | return 23 | } 24 | logger.fail(failMsgWithBadge(result.message, result.cause)) 25 | return 26 | } 27 | 28 | if (outputKind === 'json') { 29 | const json = serializeResultJson(result) 30 | 31 | if (out === '-') { 32 | logger.log(json) 33 | } else { 34 | fs.writeFileSync(out, json, 'utf8') 35 | } 36 | 37 | return 38 | } 39 | 40 | if (outputKind === 'markdown') { 41 | const arr = [] 42 | arr.push('# Converted Conda file') 43 | arr.push('') 44 | arr.push( 45 | 'This is the Conda `environment.yml` file converted to python `requirements.txt`:', 46 | ) 47 | arr.push('') 48 | arr.push('```file=requirements.txt') 49 | arr.push(result.data.pip) 50 | arr.push('```') 51 | arr.push('') 52 | const md = arr.join('\n') 53 | 54 | if (out === '-') { 55 | logger.log(md) 56 | } else { 57 | fs.writeFileSync(out, md, 'utf8') 58 | } 59 | return 60 | } 61 | 62 | if (out === '-') { 63 | logger.log(result.data.pip) 64 | logger.log('') 65 | } else { 66 | fs.writeFileSync(out, result.data.pip, 'utf8') 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/commands/npm/cmd-npm.mts: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'node:module' 2 | 3 | import { logger } from '@socketsecurity/registry/lib/logger' 4 | 5 | import constants from '../../constants.mts' 6 | import { commonFlags } from '../../flags.mts' 7 | import { meowOrExit } from '../../utils/meow-with-subcommands.mts' 8 | 9 | import type { CliCommandConfig } from '../../utils/meow-with-subcommands.mts' 10 | 11 | const require = createRequire(import.meta.url) 12 | 13 | const { DRY_RUN_BAILING_NOW } = constants 14 | 15 | const config: CliCommandConfig = { 16 | commandName: 'npm', 17 | description: `npm wrapper functionality`, 18 | hidden: false, 19 | flags: { 20 | ...commonFlags, 21 | }, 22 | help: (command, _config) => ` 23 | Usage 24 | $ ${command} 25 | `, 26 | } 27 | 28 | export const cmdNpm = { 29 | description: config.description, 30 | hidden: config.hidden, 31 | run, 32 | } 33 | 34 | async function run( 35 | argv: string[] | readonly string[], 36 | importMeta: ImportMeta, 37 | { parentName }: { parentName: string }, 38 | ): Promise { 39 | const cli = meowOrExit({ 40 | allowUnknownFlags: true, 41 | argv, 42 | config, 43 | importMeta, 44 | parentName, 45 | }) 46 | 47 | if (cli.flags['dryRun']) { 48 | logger.log(DRY_RUN_BAILING_NOW) 49 | return 50 | } 51 | 52 | // Lazily access constants.shadowNpmBinPath. 53 | const shadowBin = require(constants.shadowNpmBinPath) 54 | await shadowBin('npm', argv) 55 | } 56 | -------------------------------------------------------------------------------- /src/commands/npm/cmd-npm.test.mts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | 3 | import { describe, expect } from 'vitest' 4 | 5 | import constants from '../../../src/constants.mts' 6 | import { cmdit, invokeNpm } from '../../../test/utils.mts' 7 | 8 | describe('socket npm', async () => { 9 | // Lazily access constants.binCliPath. 10 | const { binCliPath } = constants 11 | 12 | cmdit( 13 | ['npm', '--help', '--config', '{}'], 14 | 'should support --help', 15 | async cmd => { 16 | const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) 17 | expect(stdout).toMatchInlineSnapshot( 18 | ` 19 | "npm wrapper functionality 20 | 21 | Usage 22 | $ socket npm" 23 | `, 24 | ) 25 | expect(`\n ${stderr}`).toMatchInlineSnapshot(` 26 | " 27 | _____ _ _ /--------------- 28 | | __|___ ___| |_ ___| |_ | Socket.dev CLI ver 29 | |__ | * | _| '_| -_| _| | Node: , API token set: 30 | |_____|___|___|_,_|___|_|.dev | Command: \`socket npm\`, cwd: " 31 | `) 32 | 33 | expect(code, 'explicit help should exit with code 0').toBe(0) 34 | expect(stderr, 'banner includes base command').toContain('`socket npm`') 35 | }, 36 | ) 37 | 38 | cmdit( 39 | ['npm', '--dry-run', '--config', '{"apiToken":"anything"}'], 40 | 'should require args with just dry-run', 41 | async cmd => { 42 | const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) 43 | expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`) 44 | expect(`\n ${stderr}`).toMatchInlineSnapshot(` 45 | " 46 | _____ _ _ /--------------- 47 | | __|___ ___| |_ ___| |_ | Socket.dev CLI ver 48 | |__ | * | _| '_| -_| _| | Node: , API token set: 49 | |_____|___|___|_,_|___|_|.dev | Command: \`socket npm\`, cwd: " 50 | `) 51 | 52 | expect(code, 'dry-run should exit with code 0 if input ok').toBe(0) 53 | }, 54 | ) 55 | }) 56 | -------------------------------------------------------------------------------- /src/commands/npx/cmd-npx.mts: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'node:module' 2 | 3 | import { logger } from '@socketsecurity/registry/lib/logger' 4 | 5 | import constants from '../../constants.mts' 6 | import { commonFlags } from '../../flags.mts' 7 | import { meowOrExit } from '../../utils/meow-with-subcommands.mts' 8 | 9 | import type { CliCommandConfig } from '../../utils/meow-with-subcommands.mts' 10 | 11 | const require = createRequire(import.meta.url) 12 | 13 | const { DRY_RUN_BAILING_NOW } = constants 14 | 15 | const config: CliCommandConfig = { 16 | commandName: 'npx', 17 | description: `npx wrapper functionality`, 18 | hidden: false, 19 | flags: { 20 | ...commonFlags, 21 | }, 22 | help: (command, _config) => ` 23 | Usage 24 | $ ${command} 25 | `, 26 | } 27 | 28 | export const cmdNpx = { 29 | description: config.description, 30 | hidden: config.hidden, 31 | run, 32 | } 33 | 34 | async function run( 35 | argv: string[] | readonly string[], 36 | importMeta: ImportMeta, 37 | { parentName }: { parentName: string }, 38 | ): Promise { 39 | const cli = meowOrExit({ 40 | allowUnknownFlags: true, 41 | argv, 42 | config, 43 | importMeta, 44 | parentName, 45 | }) 46 | 47 | if (cli.flags['dryRun']) { 48 | logger.log(DRY_RUN_BAILING_NOW) 49 | return 50 | } 51 | 52 | // Lazily access constants.shadowNpmBinPath. 53 | const shadowBin = require(constants.shadowNpmBinPath) 54 | await shadowBin('npx', argv) 55 | } 56 | -------------------------------------------------------------------------------- /src/commands/npx/cmd-npx.test.mts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | 3 | import { describe, expect } from 'vitest' 4 | 5 | import constants from '../../../src/constants.mts' 6 | import { cmdit, invokeNpm } from '../../../test/utils.mts' 7 | 8 | describe('socket npx', async () => { 9 | // Lazily access constants.binCliPath. 10 | const { binCliPath } = constants 11 | 12 | cmdit( 13 | ['npx', '--help', '--config', '{}'], 14 | 'should support --help', 15 | async cmd => { 16 | const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) 17 | expect(stdout).toMatchInlineSnapshot( 18 | ` 19 | "npx wrapper functionality 20 | 21 | Usage 22 | $ socket npx" 23 | `, 24 | ) 25 | expect(`\n ${stderr}`).toMatchInlineSnapshot(` 26 | " 27 | _____ _ _ /--------------- 28 | | __|___ ___| |_ ___| |_ | Socket.dev CLI ver 29 | |__ | * | _| '_| -_| _| | Node: , API token set: 30 | |_____|___|___|_,_|___|_|.dev | Command: \`socket npx\`, cwd: " 31 | `) 32 | 33 | expect(code, 'explicit help should exit with code 0').toBe(0) 34 | expect(stderr, 'banner includes base command').toContain('`socket npx`') 35 | }, 36 | ) 37 | 38 | cmdit( 39 | ['npx', '--dry-run', '--config', '{"apiToken":"anything"}'], 40 | 'should require args with just dry-run', 41 | async cmd => { 42 | const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) 43 | expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`) 44 | expect(`\n ${stderr}`).toMatchInlineSnapshot(` 45 | " 46 | _____ _ _ /--------------- 47 | | __|___ ___| |_ ___| |_ | Socket.dev CLI ver 48 | |__ | * | _| '_| -_| _| | Node: , API token set: 49 | |_____|___|___|_,_|___|_|.dev | Command: \`socket npx\`, cwd: " 50 | `) 51 | 52 | expect(code, 'dry-run should exit with code 0 if input ok').toBe(0) 53 | }, 54 | ) 55 | }) 56 | -------------------------------------------------------------------------------- /src/commands/oops/cmd-oops.test.mts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | 3 | import { describe, expect } from 'vitest' 4 | 5 | import constants from '../../../src/constants.mts' 6 | import { cmdit, invokeNpm } from '../../../test/utils.mts' 7 | 8 | describe('socket oops', async () => { 9 | // Lazily access constants.binCliPath. 10 | const { binCliPath } = constants 11 | 12 | cmdit( 13 | ['oops', '--help', '--config', '{}'], 14 | 'should support --help', 15 | async cmd => { 16 | const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) 17 | expect(stdout).toMatchInlineSnapshot( 18 | ` 19 | "Trigger an intentional error (for development) 20 | 21 | Usage 22 | $ socket oops oops 23 | 24 | Don't run me." 25 | `, 26 | ) 27 | expect(`\n ${stderr}`).toMatchInlineSnapshot(` 28 | " 29 | _____ _ _ /--------------- 30 | | __|___ ___| |_ ___| |_ | Socket.dev CLI ver 31 | |__ | * | _| '_| -_| _| | Node: , API token set: 32 | |_____|___|___|_,_|___|_|.dev | Command: \`socket oops\`, cwd: " 33 | `) 34 | 35 | expect(code, 'explicit help should exit with code 0').toBe(0) 36 | expect(stderr, 'banner includes base command').toContain('`socket oops`') 37 | }, 38 | ) 39 | 40 | cmdit( 41 | ['oops', '--dry-run', '--config', '{"apiToken":"anything"}'], 42 | 'should require args with just dry-run', 43 | async cmd => { 44 | const { code, stderr, stdout } = await invokeNpm(binCliPath, cmd) 45 | expect(stdout).toMatchInlineSnapshot(`"[DryRun]: Bailing now"`) 46 | expect(`\n ${stderr}`).toMatchInlineSnapshot(` 47 | " 48 | _____ _ _ /--------------- 49 | | __|___ ___| |_ ___| |_ | Socket.dev CLI ver 50 | |__ | * | _| '_| -_| _| | Node: , API token set: 51 | |_____|___|___|_,_|___|_|.dev | Command: \`socket oops\`, cwd: " 52 | `) 53 | 54 | expect(code, 'dry-run should exit with code 0 if input ok').toBe(0) 55 | }, 56 | ) 57 | }) 58 | -------------------------------------------------------------------------------- /src/commands/optimize/deps-includes-by-agent.mts: -------------------------------------------------------------------------------- 1 | import constants from '../../constants.mts' 2 | 3 | import type { Agent } from '../../utils/package-environment.mts' 4 | 5 | type AgentDepsIncludesFn = (stdout: string, name: string) => boolean 6 | 7 | const { BUN, NPM, PNPM, VLT, YARN_BERRY, YARN_CLASSIC } = constants 8 | 9 | function matchLsCmdViewHumanStdout(stdout: string, name: string) { 10 | return stdout.includes(` ${name}@`) 11 | } 12 | 13 | function matchQueryCmdStdout(stdout: string, name: string) { 14 | return stdout.includes(`"${name}"`) 15 | } 16 | 17 | export const depsIncludesByAgent = new Map([ 18 | [BUN, matchLsCmdViewHumanStdout], 19 | [NPM, matchQueryCmdStdout], 20 | [PNPM, matchQueryCmdStdout], 21 | [VLT, matchQueryCmdStdout], 22 | [YARN_BERRY, matchLsCmdViewHumanStdout], 23 | [YARN_CLASSIC, matchLsCmdViewHumanStdout], 24 | ]) 25 | -------------------------------------------------------------------------------- /src/commands/optimize/get-dependency-entries.mts: -------------------------------------------------------------------------------- 1 | import type { EnvDetails } from '../../utils/package-environment.mts' 2 | 3 | export function getDependencyEntries(pkgEnvDetails: EnvDetails) { 4 | const { 5 | dependencies, 6 | devDependencies, 7 | optionalDependencies, 8 | peerDependencies, 9 | } = pkgEnvDetails.editablePkgJson.content 10 | return [ 11 | [ 12 | 'dependencies', 13 | dependencies ? { __proto__: null, ...dependencies } : undefined, 14 | ], 15 | [ 16 | 'devDependencies', 17 | devDependencies ? { __proto__: null, ...devDependencies } : undefined, 18 | ], 19 | [ 20 | 'peerDependencies', 21 | peerDependencies ? { __proto__: null, ...peerDependencies } : undefined, 22 | ], 23 | [ 24 | 'optionalDependencies', 25 | optionalDependencies 26 | ? { __proto__: null, ...optionalDependencies } 27 | : undefined, 28 | ], 29 | ].filter(({ 1: o }) => o) as Array<[string, NonNullable]> 30 | } 31 | -------------------------------------------------------------------------------- /src/commands/optimize/handle-optimize.mts: -------------------------------------------------------------------------------- 1 | import { applyOptimization } from './apply-optimization.mts' 2 | import { outputOptimizeResult } from './output-optimize-result.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleOptimize({ 7 | cwd, 8 | outputKind, 9 | pin, 10 | prod, 11 | }: { 12 | cwd: string 13 | outputKind: OutputKind 14 | pin: boolean 15 | prod: boolean 16 | }) { 17 | const result = await applyOptimization(cwd, pin, prod) 18 | 19 | await outputOptimizeResult(result, outputKind) 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/optimize/output-optimize-result.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | import { pluralize } from '@socketsecurity/registry/lib/words' 3 | 4 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 5 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 6 | 7 | import type { CResult, OutputKind } from '../../types.mts' 8 | 9 | export async function outputOptimizeResult( 10 | result: CResult<{ 11 | addedCount: number 12 | updatedCount: number 13 | pkgJsonChanged: boolean 14 | updatedInWorkspaces: number 15 | addedInWorkspaces: number 16 | }>, 17 | outputKind: OutputKind, 18 | ) { 19 | if (!result.ok) { 20 | process.exitCode = result.code ?? 1 21 | } 22 | 23 | if (outputKind === 'json') { 24 | logger.log(serializeResultJson(result)) 25 | return 26 | } 27 | if (!result.ok) { 28 | logger.fail(failMsgWithBadge(result.message, result.cause)) 29 | return 30 | } 31 | 32 | const data = result.data 33 | 34 | if (data.updatedCount > 0) { 35 | logger?.log( 36 | `${createActionMessage('Updated', data.updatedCount, data.updatedInWorkspaces)}${data.addedCount ? '.' : '🚀'}`, 37 | ) 38 | } 39 | if (data.addedCount > 0) { 40 | logger?.log( 41 | `${createActionMessage('Added', data.addedCount, data.addedInWorkspaces)} 🚀`, 42 | ) 43 | } 44 | if (!data.pkgJsonChanged) { 45 | logger?.log('Scan complete. No Socket.dev optimized overrides applied.') 46 | } 47 | 48 | logger.log('') 49 | logger.success('Finished!') 50 | logger.log('') 51 | } 52 | 53 | function createActionMessage( 54 | verb: string, 55 | overrideCount: number, 56 | workspaceCount: number, 57 | ): string { 58 | return `${verb} ${overrideCount} Socket.dev optimized ${pluralize('override', overrideCount)}${workspaceCount ? ` in ${workspaceCount} ${pluralize('workspace', workspaceCount)}` : ''}` 59 | } 60 | -------------------------------------------------------------------------------- /src/commands/optimize/shared.mts: -------------------------------------------------------------------------------- 1 | export const CMD_NAME = 'socket optimize' 2 | -------------------------------------------------------------------------------- /src/commands/optimize/types.mts: -------------------------------------------------------------------------------- 1 | import type { StringKeyValueObject } from '../../types.mts' 2 | 3 | export type NpmOverrides = { [key: string]: string | StringKeyValueObject } 4 | 5 | export type PnpmOrYarnOverrides = { [key: string]: string } 6 | 7 | export type Overrides = NpmOverrides | PnpmOrYarnOverrides 8 | -------------------------------------------------------------------------------- /src/commands/optimize/update-lockfile.mts: -------------------------------------------------------------------------------- 1 | import { debugFn } from '@socketsecurity/registry/lib/debug' 2 | import { Spinner } from '@socketsecurity/registry/lib/spinner' 3 | 4 | import constants from '../../constants.mts' 5 | import { runAgentInstall } from '../../utils/agent.mts' 6 | import { cmdPrefixMessage } from '../../utils/cmd.mts' 7 | 8 | import type { CResult } from '../../types.mts' 9 | import type { EnvDetails } from '../../utils/package-environment.mts' 10 | import type { Logger } from '@socketsecurity/registry/lib/logger' 11 | 12 | const { NPM_BUGGY_OVERRIDES_PATCHED_VERSION } = constants 13 | 14 | export type UpdateLockfileOptions = { 15 | cmdName?: string | undefined 16 | logger?: Logger | undefined 17 | spinner?: Spinner | undefined 18 | } 19 | 20 | export async function updateLockfile( 21 | pkgEnvDetails: EnvDetails, 22 | options: UpdateLockfileOptions, 23 | ): Promise> { 24 | const { 25 | cmdName = '', 26 | logger, 27 | spinner, 28 | } = { 29 | __proto__: null, 30 | ...options, 31 | } as UpdateLockfileOptions 32 | const isSpinning = !!spinner?.['isSpinning'] 33 | if (!isSpinning) { 34 | spinner?.start() 35 | } 36 | spinner?.setText(`Updating ${pkgEnvDetails.lockName}...`) 37 | try { 38 | await runAgentInstall(pkgEnvDetails, { spinner }) 39 | if (pkgEnvDetails.features.npmBuggyOverrides) { 40 | spinner?.stop() 41 | logger?.log( 42 | `💡 Re-run ${cmdName ? `${cmdName} ` : ''}whenever ${pkgEnvDetails.lockName} changes.\n This can be skipped for ${pkgEnvDetails.agent} >=${NPM_BUGGY_OVERRIDES_PATCHED_VERSION}.`, 43 | ) 44 | } 45 | } catch (e) { 46 | spinner?.stop() 47 | 48 | debugFn('fail: update\n', e) 49 | 50 | return { 51 | ok: false, 52 | message: 'Update failed', 53 | cause: cmdPrefixMessage( 54 | cmdName, 55 | `${pkgEnvDetails.agent} install failed to update ${pkgEnvDetails.lockName}`, 56 | ), 57 | } 58 | } 59 | if (isSpinning) { 60 | spinner?.start() 61 | } else { 62 | spinner?.stop() 63 | } 64 | 65 | return { ok: true, data: undefined } 66 | } 67 | -------------------------------------------------------------------------------- /src/commands/organization/cmd-organization-policy.mts: -------------------------------------------------------------------------------- 1 | import { cmdOrganizationPolicyLicense } from './cmd-organization-policy-license.mts' 2 | import { cmdOrganizationPolicyPolicy } from './cmd-organization-policy-security.mts' 3 | import { meowWithSubcommands } from '../../utils/meow-with-subcommands.mts' 4 | 5 | import type { CliSubcommand } from '../../utils/meow-with-subcommands.mts' 6 | 7 | const description = 'Organization policy details' 8 | 9 | export const cmdOrganizationPolicy: CliSubcommand = { 10 | description, 11 | // Hidden because it was broken all this time (nobody could be using it) 12 | // and we're not sure if it's useful to anyone in its current state. 13 | // Until we do, we'll hide this to keep the help tidier. 14 | // And later, we may simply move this under `scan`, anyways. 15 | hidden: true, 16 | async run(argv, importMeta, { parentName }) { 17 | await meowWithSubcommands( 18 | { 19 | security: cmdOrganizationPolicyPolicy, 20 | license: cmdOrganizationPolicyLicense, 21 | }, 22 | { 23 | argv, 24 | description, 25 | defaultSub: 'list', // Backwards compat 26 | importMeta, 27 | name: parentName + ' policy', 28 | }, 29 | ) 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /src/commands/organization/cmd-organization.mts: -------------------------------------------------------------------------------- 1 | import { cmdOrganizationList } from './cmd-organization-list.mts' 2 | import { cmdOrganizationPolicyLicense } from './cmd-organization-policy-license.mts' 3 | import { cmdOrganizationPolicyPolicy } from './cmd-organization-policy-security.mts' 4 | import { cmdOrganizationPolicy } from './cmd-organization-policy.mts' 5 | import { cmdOrganizationQuota } from './cmd-organization-quota.mts' 6 | import { meowWithSubcommands } from '../../utils/meow-with-subcommands.mts' 7 | 8 | import type { CliSubcommand } from '../../utils/meow-with-subcommands.mts' 9 | 10 | const description = 'Account details' 11 | 12 | export const cmdOrganization: CliSubcommand = { 13 | description, 14 | hidden: false, 15 | async run(argv, importMeta, { parentName }) { 16 | await meowWithSubcommands( 17 | { 18 | list: cmdOrganizationList, 19 | quota: cmdOrganizationQuota, 20 | policy: cmdOrganizationPolicy, 21 | }, 22 | { 23 | aliases: { 24 | license: { 25 | description: cmdOrganizationPolicyLicense.description, 26 | hidden: true, 27 | argv: ['policy', 'license'], 28 | }, 29 | security: { 30 | description: cmdOrganizationPolicyPolicy.description, 31 | hidden: true, 32 | argv: ['policy', 'security'], 33 | }, 34 | }, 35 | argv, 36 | description, 37 | defaultSub: 'list', // Backwards compat 38 | importMeta, 39 | name: parentName + ' organization', 40 | }, 41 | ) 42 | }, 43 | } 44 | -------------------------------------------------------------------------------- /src/commands/organization/fetch-license-policy.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchLicensePolicy( 8 | orgSlug: string, 9 | ): Promise['data']>> { 10 | const sockSdkResult = await setupSdk() 11 | if (!sockSdkResult.ok) { 12 | return sockSdkResult 13 | } 14 | const sockSdk = sockSdkResult.data 15 | 16 | return await handleApiCall( 17 | sockSdk.getOrgLicensePolicy(orgSlug), 18 | 'organization license policy', 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/organization/fetch-organization-list.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchOrganization(): Promise< 8 | CResult['data']> 9 | > { 10 | const sockSdkResult = await setupSdk() 11 | if (!sockSdkResult.ok) { 12 | return sockSdkResult 13 | } 14 | const sockSdk = sockSdkResult.data 15 | 16 | return await handleApiCall(sockSdk.getOrganizations(), 'organization list') 17 | } 18 | -------------------------------------------------------------------------------- /src/commands/organization/fetch-quota.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchQuota(): Promise< 8 | CResult['data']> 9 | > { 10 | const sockSdkResult = await setupSdk() 11 | if (!sockSdkResult.ok) { 12 | return sockSdkResult 13 | } 14 | const sockSdk = sockSdkResult.data 15 | 16 | return await handleApiCall(sockSdk.getQuota(), 'token quota') 17 | } 18 | -------------------------------------------------------------------------------- /src/commands/organization/fetch-security-policy.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchSecurityPolicy( 8 | orgSlug: string, 9 | ): Promise['data']>> { 10 | const sockSdkResult = await setupSdk() 11 | if (!sockSdkResult.ok) { 12 | return sockSdkResult 13 | } 14 | const sockSdk = sockSdkResult.data 15 | 16 | return await handleApiCall( 17 | sockSdk.getOrgSecurityPolicy(orgSlug), 18 | 'organization security policy', 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/organization/handle-license-policy.mts: -------------------------------------------------------------------------------- 1 | import { fetchLicensePolicy } from './fetch-license-policy.mts' 2 | import { outputLicensePolicy } from './output-license-policy.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleLicensePolicy( 7 | orgSlug: string, 8 | outputKind: OutputKind, 9 | ): Promise { 10 | const data = await fetchLicensePolicy(orgSlug) 11 | 12 | await outputLicensePolicy(data, outputKind) 13 | } 14 | -------------------------------------------------------------------------------- /src/commands/organization/handle-organization-list.mts: -------------------------------------------------------------------------------- 1 | import { fetchOrganization } from './fetch-organization-list.mts' 2 | import { outputOrganizationList } from './output-organization-list.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleOrganizationList( 7 | outputKind: OutputKind = 'text', 8 | ): Promise { 9 | const data = await fetchOrganization() 10 | 11 | await outputOrganizationList(data, outputKind) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/organization/handle-quota.mts: -------------------------------------------------------------------------------- 1 | import { fetchQuota } from './fetch-quota.mts' 2 | import { outputQuota } from './output-quota.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleQuota( 7 | outputKind: OutputKind = 'text', 8 | ): Promise { 9 | const data = await fetchQuota() 10 | 11 | await outputQuota(data, outputKind) 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/organization/handle-security-policy.mts: -------------------------------------------------------------------------------- 1 | import { fetchSecurityPolicy } from './fetch-security-policy.mts' 2 | import { outputSecurityPolicy } from './output-security-policy.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleSecurityPolicy( 7 | orgSlug: string, 8 | outputKind: OutputKind, 9 | ): Promise { 10 | const data = await fetchSecurityPolicy(orgSlug) 11 | 12 | await outputSecurityPolicy(data, outputKind) 13 | } 14 | -------------------------------------------------------------------------------- /src/commands/organization/output-license-policy.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | import { mdTableOfPairs } from '../../utils/markdown.mts' 5 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 6 | 7 | import type { CResult, OutputKind } from '../../types.mts' 8 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 9 | 10 | export async function outputLicensePolicy( 11 | result: CResult['data']>, 12 | outputKind: OutputKind, 13 | ): Promise { 14 | if (!result.ok) { 15 | process.exitCode = result.code ?? 1 16 | } 17 | 18 | if (outputKind === 'json') { 19 | logger.log(serializeResultJson(result)) 20 | return 21 | } 22 | if (!result.ok) { 23 | logger.fail(failMsgWithBadge(result.message, result.cause)) 24 | return 25 | } 26 | 27 | logger.info('Use --json to get the full result') 28 | logger.log('# License policy') 29 | logger.log('') 30 | logger.log('This is the license policy for your organization:') 31 | logger.log('') 32 | const rules = result.data['license_policy']! 33 | const entries = rules ? Object.entries(rules) : [] 34 | const mapped: Array<[string, string]> = entries.map( 35 | ([key, value]) => 36 | [key, (value as any)?.['allowed'] ? ' yes' : ' no'] as const, 37 | ) 38 | mapped.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)) 39 | logger.log(mdTableOfPairs(mapped, ['License Name', 'Allowed'])) 40 | logger.log('') 41 | } 42 | -------------------------------------------------------------------------------- /src/commands/organization/output-quota.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 5 | 6 | import type { CResult, OutputKind } from '../../types.mts' 7 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 8 | 9 | export async function outputQuota( 10 | result: CResult['data']>, 11 | outputKind: OutputKind = 'text', 12 | ): Promise { 13 | if (!result.ok) { 14 | process.exitCode = result.code ?? 1 15 | } 16 | 17 | if (outputKind === 'json') { 18 | logger.log(serializeResultJson(result)) 19 | return 20 | } 21 | if (!result.ok) { 22 | logger.fail(failMsgWithBadge(result.message, result.cause)) 23 | return 24 | } 25 | 26 | if (outputKind === 'markdown') { 27 | logger.log('# Quota') 28 | logger.log('') 29 | logger.log(`Quota left on the current API token: ${result.data.quota}`) 30 | logger.log('') 31 | return 32 | } 33 | 34 | logger.log(`Quota left on the current API token: ${result.data.quota}`) 35 | logger.log('') 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/organization/output-security-policy.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | import { mdTableOfPairs } from '../../utils/markdown.mts' 5 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 6 | 7 | import type { CResult, OutputKind } from '../../types.mts' 8 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 9 | 10 | export async function outputSecurityPolicy( 11 | result: CResult['data']>, 12 | outputKind: OutputKind, 13 | ): Promise { 14 | if (!result.ok) { 15 | process.exitCode = result.code ?? 1 16 | } 17 | 18 | if (outputKind === 'json') { 19 | logger.log(serializeResultJson(result)) 20 | return 21 | } 22 | if (!result.ok) { 23 | logger.fail(failMsgWithBadge(result.message, result.cause)) 24 | return 25 | } 26 | 27 | logger.log('# Security policy') 28 | logger.log('') 29 | logger.log( 30 | `The default security policy setting is: "${result.data.securityPolicyDefault}"`, 31 | ) 32 | logger.log('') 33 | logger.log( 34 | 'These are the security policies per setting for your organization:', 35 | ) 36 | logger.log('') 37 | const rules = result.data.securityPolicyRules 38 | const entries: Array< 39 | [string, { action: 'defer' | 'error' | 'warn' | 'monitor' | 'ignore' }] 40 | > = rules ? Object.entries(rules) : [] 41 | const mapped: Array<[string, string]> = entries.map(([key, value]) => [ 42 | key, 43 | value.action, 44 | ]) 45 | mapped.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)) 46 | logger.log(mdTableOfPairs(mapped, ['name', 'action'])) 47 | logger.log('') 48 | } 49 | -------------------------------------------------------------------------------- /src/commands/package/cmd-package.mts: -------------------------------------------------------------------------------- 1 | import { cmdPackageScore } from './cmd-package-score.mts' 2 | import { cmdPackageShallow } from './cmd-package-shallow.mts' 3 | import { meowWithSubcommands } from '../../utils/meow-with-subcommands.mts' 4 | 5 | import type { CliSubcommand } from '../../utils/meow-with-subcommands.mts' 6 | 7 | const description = 'Commands relating to looking up published packages' 8 | 9 | export const cmdPackage: CliSubcommand = { 10 | description, 11 | hidden: false, 12 | async run(argv, importMeta, { parentName }) { 13 | await meowWithSubcommands( 14 | { 15 | score: cmdPackageScore, 16 | shallow: cmdPackageShallow, 17 | }, 18 | { 19 | aliases: { 20 | deep: { 21 | description, 22 | hidden: true, 23 | argv: ['score'], 24 | }, 25 | }, 26 | argv, 27 | description, 28 | importMeta, 29 | name: parentName + ' package', 30 | }, 31 | ) 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /src/commands/package/fetch-purl-deep-score.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { queryApiSafeJson } from '../../utils/api.mts' 4 | 5 | import type { CResult } from '../../types.mts' 6 | 7 | export interface PurlDataResponse { 8 | purl: string 9 | self: { 10 | purl: string 11 | score: { 12 | license: number 13 | maintenance: number 14 | overall: number 15 | quality: number 16 | supplyChain: number 17 | vulnerability: number 18 | } 19 | capabilities: string[] 20 | alerts: Array<{ 21 | name: string 22 | severity: string 23 | category: string 24 | example: string 25 | }> 26 | } 27 | transitively: { 28 | dependencyCount: number 29 | func: string 30 | score: { 31 | license: number 32 | maintenance: number 33 | overall: number 34 | quality: number 35 | supplyChain: number 36 | vulnerability: number 37 | } 38 | lowest: { 39 | license: string 40 | maintenance: string 41 | overall: string 42 | quality: string 43 | supplyChain: string 44 | vulnerability: string 45 | } 46 | capabilities: string[] 47 | alerts: Array<{ 48 | name: string 49 | severity: string 50 | category: string 51 | example: string 52 | }> 53 | } 54 | } 55 | 56 | export async function fetchPurlDeepScore( 57 | purl: string, 58 | ): Promise> { 59 | logger.info(`Requesting deep score data for this purl: ${purl}`) 60 | 61 | return await queryApiSafeJson( 62 | `purl/score/${encodeURIComponent(purl)}`, 63 | 'the deep package scores', 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /src/commands/package/fetch-purls-shallow-score.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { handleApiCall } from '../../utils/api.mts' 4 | import { setupSdk } from '../../utils/sdk.mts' 5 | 6 | import type { CResult } from '../../types.mts' 7 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 8 | 9 | export async function fetchPurlsShallowScore( 10 | purls: string[], 11 | ): Promise>> { 12 | logger.info( 13 | `Requesting shallow score data for ${purls.length} package urls (purl): ${purls.join(', ')}`, 14 | ) 15 | 16 | const sockSdkResult = await setupSdk() 17 | if (!sockSdkResult.ok) { 18 | return sockSdkResult 19 | } 20 | const sockSdk = sockSdkResult.data 21 | 22 | const result = await handleApiCall( 23 | sockSdk.batchPackageFetch( 24 | { 25 | alerts: 'true', 26 | }, 27 | { components: purls.map(purl => ({ purl })) }, 28 | ), 29 | 'looking up package', 30 | ) 31 | 32 | if (!result.ok) { 33 | return result 34 | } 35 | 36 | // TODO: seems like there's a bug in the typing since we absolutely have to return the .data here 37 | return { 38 | ok: true, 39 | data: result.data as SocketSdkReturnType<'batchPackageFetch'>, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/commands/package/handle-purl-deep-score.mts: -------------------------------------------------------------------------------- 1 | import { fetchPurlDeepScore } from './fetch-purl-deep-score.mts' 2 | import { outputPurlsDeepScore } from './output-purls-deep-score.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handlePurlDeepScore( 7 | purl: string, 8 | outputKind: OutputKind, 9 | ) { 10 | const result = await fetchPurlDeepScore(purl) 11 | 12 | await outputPurlsDeepScore(purl, result, outputKind) 13 | } 14 | -------------------------------------------------------------------------------- /src/commands/package/handle-purls-shallow-score.mts: -------------------------------------------------------------------------------- 1 | import { fetchPurlsShallowScore } from './fetch-purls-shallow-score.mts' 2 | import { outputPurlsShallowScore } from './output-purls-shallow-score.mts' 3 | 4 | import type { CResult, OutputKind } from '../../types.mts' 5 | import type { SocketArtifact } from '../../utils/alert/artifact.mts' 6 | 7 | export async function handlePurlsShallowScore({ 8 | outputKind, 9 | purls, 10 | }: { 11 | outputKind: OutputKind 12 | purls: string[] 13 | }) { 14 | const packageData = await fetchPurlsShallowScore(purls) 15 | 16 | outputPurlsShallowScore( 17 | purls, 18 | packageData as CResult, 19 | outputKind, 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/commands/package/parse-package-specifiers.mts: -------------------------------------------------------------------------------- 1 | // Either an ecosystem was given or all args must be (namespaced) purls 2 | // The `pkg:` part is optional here. We'll scan for `eco/name@version`. 3 | // Not hardcoding the namespace since we don't know what the server accepts. 4 | // The ecosystem is considered as the first package if it is not an a-z string. 5 | export function parsePackageSpecifiers( 6 | ecosystem: string, 7 | pkgs: string[], 8 | ): { purls: string[]; valid: boolean } { 9 | let valid = true 10 | const purls = [] 11 | if (!ecosystem) { 12 | valid = false 13 | } else if (/^[a-zA-Z]+$/.test(ecosystem)) { 14 | for (let i = 0; i < pkgs.length; ++i) { 15 | const pkg = pkgs[i] ?? '' 16 | if (!pkg) { 17 | valid = false 18 | break 19 | } else if (pkg.startsWith('pkg:')) { 20 | // keep 21 | purls.push(pkg) 22 | } else { 23 | purls.push('pkg:' + ecosystem + '/' + pkg) 24 | } 25 | } 26 | if (!purls.length) { 27 | valid = false 28 | } 29 | } else { 30 | // Assume ecosystem is a purl, too 31 | pkgs.unshift(ecosystem) 32 | 33 | for (let i = 0; i < pkgs.length; ++i) { 34 | const pkg = pkgs[i] ?? '' 35 | if (!/^(?:pkg:)?[a-zA-Z]+\/./.test(pkg)) { 36 | // At least one purl did not start with `pkg:eco/x` or `eco/x` 37 | valid = false 38 | break 39 | } else if (pkg.startsWith('pkg:')) { 40 | purls.push(pkg) 41 | } else { 42 | purls.push('pkg:' + pkg) 43 | } 44 | } 45 | 46 | if (!purls.length) { 47 | valid = false 48 | } 49 | } 50 | 51 | return { purls, valid } 52 | } 53 | -------------------------------------------------------------------------------- /src/commands/raw-npm/cmd-raw-npm.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { runRawNpm } from './run-raw-npm.mts' 4 | import constants from '../../constants.mts' 5 | import { meowOrExit } from '../../utils/meow-with-subcommands.mts' 6 | 7 | import type { CliCommandConfig } from '../../utils/meow-with-subcommands.mts' 8 | 9 | const { DRY_RUN_BAILING_NOW, NPM } = constants 10 | 11 | const config: CliCommandConfig = { 12 | commandName: 'raw-npm', 13 | description: `Temporarily disable the Socket ${NPM} wrapper`, 14 | hidden: false, 15 | flags: {}, 16 | help: command => ` 17 | Usage 18 | $ ${command} 19 | 20 | Examples 21 | $ ${command} install 22 | `, 23 | } 24 | 25 | export const cmdRawNpm = { 26 | description: config.description, 27 | hidden: config.hidden, 28 | run, 29 | } 30 | 31 | async function run( 32 | argv: readonly string[], 33 | importMeta: ImportMeta, 34 | { parentName }: { parentName: string }, 35 | ): Promise { 36 | const cli = meowOrExit({ 37 | allowUnknownFlags: true, 38 | argv, 39 | config, 40 | importMeta, 41 | parentName, 42 | }) 43 | 44 | if (cli.flags['dryRun']) { 45 | logger.log(DRY_RUN_BAILING_NOW) 46 | return 47 | } 48 | 49 | await runRawNpm(argv) 50 | } 51 | -------------------------------------------------------------------------------- /src/commands/raw-npm/run-raw-npm.mts: -------------------------------------------------------------------------------- 1 | import { spawn } from '@socketsecurity/registry/lib/spawn' 2 | 3 | import constants from '../../constants.mts' 4 | import { getNpmBinPath } from '../../utils/npm-paths.mts' 5 | 6 | export async function runRawNpm( 7 | argv: string[] | readonly string[], 8 | ): Promise { 9 | const spawnPromise = spawn(getNpmBinPath(), argv as string[], { 10 | // Lazily access constants.WIN32. 11 | shell: constants.WIN32, 12 | stdio: 'inherit', 13 | }) 14 | // See https://nodejs.org/api/child_process.html#event-exit. 15 | spawnPromise.process.on('exit', (code, signalName) => { 16 | if (signalName) { 17 | process.kill(process.pid, signalName) 18 | } else if (code !== null) { 19 | // eslint-disable-next-line n/no-process-exit 20 | process.exit(code) 21 | } 22 | }) 23 | await spawnPromise 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/raw-npx/cmd-raw-npx.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { runRawNpx } from './run-raw-npx.mts' 4 | import constants from '../../constants.mts' 5 | import { meowOrExit } from '../../utils/meow-with-subcommands.mts' 6 | 7 | import type { CliCommandConfig } from '../../utils/meow-with-subcommands.mts' 8 | 9 | const { DRY_RUN_BAILING_NOW, NPX } = constants 10 | 11 | const config: CliCommandConfig = { 12 | commandName: 'raw-npx', 13 | description: `Temporarily disable the Socket ${NPX} wrapper`, 14 | hidden: false, 15 | flags: {}, 16 | help: command => ` 17 | Usage 18 | $ ${command} 19 | 20 | Examples 21 | $ ${command} install 22 | `, 23 | } 24 | 25 | export const cmdRawNpx = { 26 | description: config.description, 27 | hidden: config.hidden, 28 | run, 29 | } 30 | 31 | async function run( 32 | argv: readonly string[], 33 | importMeta: ImportMeta, 34 | { parentName }: { parentName: string }, 35 | ): Promise { 36 | const cli = meowOrExit({ 37 | allowUnknownFlags: true, 38 | argv, 39 | config, 40 | importMeta, 41 | parentName, 42 | }) 43 | 44 | if (cli.flags['dryRun']) { 45 | logger.log(DRY_RUN_BAILING_NOW) 46 | return 47 | } 48 | 49 | await runRawNpx(argv) 50 | } 51 | -------------------------------------------------------------------------------- /src/commands/raw-npx/run-raw-npx.mts: -------------------------------------------------------------------------------- 1 | import { spawn } from '@socketsecurity/registry/lib/spawn' 2 | 3 | import constants from '../../constants.mts' 4 | import { getNpxBinPath } from '../../utils/npm-paths.mts' 5 | 6 | export async function runRawNpx( 7 | argv: string[] | readonly string[], 8 | ): Promise { 9 | const spawnPromise = spawn(getNpxBinPath(), argv as string[], { 10 | // Lazily access constants.WIN32. 11 | shell: constants.WIN32, 12 | stdio: 'inherit', 13 | }) 14 | // See https://nodejs.org/api/child_process.html#event-exit. 15 | spawnPromise.process.on('exit', (code, signalName) => { 16 | if (signalName) { 17 | process.kill(process.pid, signalName) 18 | } else if (code !== null) { 19 | // eslint-disable-next-line n/no-process-exit 20 | process.exit(code) 21 | } 22 | }) 23 | await spawnPromise 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/report/cmd-report-create.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { commonFlags, outputFlags } from '../../flags.mts' 4 | import { meowOrExit } from '../../utils/meow-with-subcommands.mts' 5 | 6 | import type { CliCommandConfig } from '../../utils/meow-with-subcommands.mts' 7 | 8 | const config: CliCommandConfig = { 9 | commandName: 'create', 10 | description: '[Deprecated] Create a project report', 11 | hidden: false, 12 | flags: { 13 | ...commonFlags, 14 | ...outputFlags, 15 | }, 16 | help: () => ` 17 | This command is deprecated in favor of \`socket scan view\`. 18 | It will be removed in the next major release of the CLI. 19 | `, 20 | } 21 | 22 | export const cmdReportCreate = { 23 | description: config.description, 24 | hidden: config.hidden, 25 | run, 26 | } 27 | 28 | async function run( 29 | argv: string[] | readonly string[], 30 | importMeta: ImportMeta, 31 | { parentName }: { parentName: string }, 32 | ): Promise { 33 | meowOrExit({ 34 | argv, 35 | config, 36 | importMeta, 37 | parentName, 38 | }) 39 | 40 | logger.fail( 41 | 'This command has been sunset. Instead, please look at `socket scan create` to create scans and `socket scan report` to view a report of your scans.', 42 | ) 43 | 44 | process.exitCode = 1 45 | } 46 | -------------------------------------------------------------------------------- /src/commands/report/cmd-report-view.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { commonFlags, outputFlags } from '../../flags.mts' 4 | import { meowOrExit } from '../../utils/meow-with-subcommands.mts' 5 | 6 | import type { CliCommandConfig } from '../../utils/meow-with-subcommands.mts' 7 | 8 | const config: CliCommandConfig = { 9 | commandName: 'view', 10 | description: '[Deprecated] View a project report', 11 | hidden: false, 12 | flags: { 13 | ...commonFlags, 14 | ...outputFlags, 15 | }, 16 | help: () => ` 17 | This command is deprecated in favor of \`socket scan view\`. 18 | It will be removed in the next major release of the CLI. 19 | `, 20 | } 21 | 22 | export const cmdReportView = { 23 | description: config.description, 24 | hidden: config.hidden, 25 | run, 26 | } 27 | 28 | async function run( 29 | argv: string[] | readonly string[], 30 | importMeta: ImportMeta, 31 | { parentName }: { parentName: string }, 32 | ): Promise { 33 | meowOrExit({ 34 | argv, 35 | config, 36 | importMeta, 37 | parentName, 38 | }) 39 | 40 | logger.fail( 41 | 'This command has been sunset. Instead, please look at `socket scan create` to create scans and `socket scan report` to view a report of your scans.', 42 | ) 43 | 44 | process.exitCode = 1 45 | } 46 | -------------------------------------------------------------------------------- /src/commands/report/cmd-report.mts: -------------------------------------------------------------------------------- 1 | import { cmdReportCreate } from './cmd-report-create.mts' 2 | import { cmdReportView } from './cmd-report-view.mts' 3 | import { meowWithSubcommands } from '../../utils/meow-with-subcommands.mts' 4 | 5 | import type { CliSubcommand } from '../../utils/meow-with-subcommands.mts' 6 | 7 | const description = '[Deprecated] Project report related commands' 8 | 9 | export const cmdReport: CliSubcommand = { 10 | description, 11 | hidden: true, // Deprecated in favor of `scan` 12 | async run(argv, importMeta, { parentName }) { 13 | await meowWithSubcommands( 14 | { 15 | create: cmdReportCreate, 16 | view: cmdReportView, 17 | }, 18 | { 19 | argv, 20 | description, 21 | importMeta, 22 | name: parentName + ' report', 23 | }, 24 | ) 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /src/commands/repos/cmd-repos.mts: -------------------------------------------------------------------------------- 1 | import { cmdReposCreate } from './cmd-repos-create.mts' 2 | import { cmdReposDel } from './cmd-repos-del.mts' 3 | import { cmdReposList } from './cmd-repos-list.mts' 4 | import { cmdReposUpdate } from './cmd-repos-update.mts' 5 | import { cmdReposView } from './cmd-repos-view.mts' 6 | import { meowWithSubcommands } from '../../utils/meow-with-subcommands.mts' 7 | 8 | import type { CliSubcommand } from '../../utils/meow-with-subcommands.mts' 9 | 10 | const description = 'Repositories related commands' 11 | 12 | export const cmdRepos: CliSubcommand = { 13 | description, 14 | async run(argv, importMeta, { parentName }) { 15 | await meowWithSubcommands( 16 | { 17 | create: cmdReposCreate, 18 | view: cmdReposView, 19 | list: cmdReposList, 20 | del: cmdReposDel, 21 | update: cmdReposUpdate, 22 | }, 23 | { 24 | argv, 25 | description, 26 | importMeta, 27 | name: `${parentName} repos`, 28 | }, 29 | ) 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /src/commands/repos/fetch-create-repo.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchCreateRepo({ 8 | default_branch, 9 | description, 10 | homepage, 11 | orgSlug, 12 | repoName, 13 | visibility, 14 | }: { 15 | orgSlug: string 16 | repoName: string 17 | description: string 18 | homepage: string 19 | default_branch: string 20 | visibility: string 21 | }): Promise['data']>> { 22 | const sockSdkResult = await setupSdk() 23 | if (!sockSdkResult.ok) { 24 | return sockSdkResult 25 | } 26 | const sockSdk = sockSdkResult.data 27 | 28 | return await handleApiCall( 29 | sockSdk.createOrgRepo(orgSlug, { 30 | name: repoName, 31 | description, 32 | homepage, 33 | default_branch, 34 | visibility, 35 | }), 36 | 'to create a repository', 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /src/commands/repos/fetch-delete-repo.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchDeleteRepo( 8 | orgSlug: string, 9 | repoName: string, 10 | ): Promise['data']>> { 11 | const sockSdkResult = await setupSdk() 12 | if (!sockSdkResult.ok) { 13 | return sockSdkResult 14 | } 15 | const sockSdk = sockSdkResult.data 16 | 17 | return await handleApiCall( 18 | sockSdk.deleteOrgRepo(orgSlug, repoName), 19 | 'to delete a repository', 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/commands/repos/fetch-list-all-repos.mts: -------------------------------------------------------------------------------- 1 | import { debugFn } from '@socketsecurity/registry/lib/debug' 2 | 3 | import { handleApiCall } from '../../utils/api.mts' 4 | import { setupSdk } from '../../utils/sdk.mts' 5 | 6 | import type { CResult } from '../../types.mts' 7 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 8 | 9 | export async function fetchListAllRepos({ 10 | direction, 11 | orgSlug, 12 | sort, 13 | }: { 14 | direction: string 15 | orgSlug: string 16 | sort: string 17 | }): Promise['data']>> { 18 | const sockSdkResult = await setupSdk() 19 | if (!sockSdkResult.ok) { 20 | return sockSdkResult 21 | } 22 | const sockSdk = sockSdkResult.data 23 | 24 | const rows: SocketSdkReturnType<'getOrgRepoList'>['data']['results'] = [] 25 | let protection = 0 26 | let nextPage = 0 27 | while (nextPage >= 0) { 28 | if (++protection > 100) { 29 | return { 30 | ok: false, 31 | message: 'Infinite loop detected', 32 | cause: `Either there are over 100 pages of results or the fetch has run into an infinite loop. Breaking it off now. nextPage=${nextPage}`, 33 | } 34 | } 35 | // eslint-disable-next-line no-await-in-loop 36 | const result = await handleApiCall( 37 | sockSdk.getOrgRepoList(orgSlug, { 38 | sort, 39 | direction, 40 | per_page: String(100), // max 41 | page: String(nextPage), 42 | }), 43 | 'list of repositories', 44 | ) 45 | if (!result.ok) { 46 | debugFn('fail: fetch repo\n', result) 47 | return result 48 | } 49 | 50 | result.data.results.forEach(row => rows.push(row)) 51 | nextPage = result.data.nextPage ?? -1 52 | } 53 | 54 | return { 55 | ok: true, 56 | data: { 57 | results: rows, 58 | nextPage: null, 59 | }, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/commands/repos/fetch-list-repos.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchListRepos({ 8 | direction, 9 | orgSlug, 10 | page, 11 | per_page, 12 | sort, 13 | }: { 14 | direction: string 15 | orgSlug: string 16 | page: number 17 | per_page: number 18 | sort: string 19 | }): Promise['data']>> { 20 | const sockSdkResult = await setupSdk() 21 | if (!sockSdkResult.ok) { 22 | return sockSdkResult 23 | } 24 | const sockSdk = sockSdkResult.data 25 | 26 | return await handleApiCall( 27 | sockSdk.getOrgRepoList(orgSlug, { 28 | sort, 29 | direction, 30 | per_page: String(per_page), 31 | page: String(page), 32 | }), 33 | 'list of repositories', 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/commands/repos/fetch-update-repo.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchUpdateRepo({ 8 | default_branch, 9 | description, 10 | homepage, 11 | orgSlug, 12 | repoName, 13 | visibility, 14 | }: { 15 | orgSlug: string 16 | repoName: string 17 | description: string 18 | homepage: string 19 | default_branch: string 20 | visibility: string 21 | }): Promise['data']>> { 22 | const sockSdkResult = await setupSdk() 23 | if (!sockSdkResult.ok) { 24 | return sockSdkResult 25 | } 26 | const sockSdk = sockSdkResult.data 27 | 28 | return await handleApiCall( 29 | sockSdk.updateOrgRepo(orgSlug, repoName, { 30 | orgSlug, 31 | name: repoName, 32 | description, 33 | homepage, 34 | default_branch, 35 | visibility, 36 | }), 37 | 'to update a repository', 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/commands/repos/fetch-view-repo.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchViewRepo( 8 | orgSlug: string, 9 | repoName: string, 10 | ): Promise['data']>> { 11 | const sockSdkResult = await setupSdk() 12 | if (!sockSdkResult.ok) { 13 | return sockSdkResult 14 | } 15 | const sockSdk = sockSdkResult.data 16 | 17 | return await handleApiCall( 18 | sockSdk.getOrgRepo(orgSlug, repoName), 19 | 'repository data', 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/commands/repos/handle-create-repo.mts: -------------------------------------------------------------------------------- 1 | import { fetchCreateRepo } from './fetch-create-repo.mts' 2 | import { outputCreateRepo } from './output-create-repo.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleCreateRepo( 7 | { 8 | default_branch, 9 | description, 10 | homepage, 11 | orgSlug, 12 | repoName, 13 | visibility, 14 | }: { 15 | orgSlug: string 16 | repoName: string 17 | description: string 18 | homepage: string 19 | default_branch: string 20 | visibility: string 21 | }, 22 | outputKind: OutputKind, 23 | ): Promise { 24 | const data = await fetchCreateRepo({ 25 | default_branch, 26 | description, 27 | homepage, 28 | orgSlug, 29 | repoName, 30 | visibility, 31 | }) 32 | outputCreateRepo(data, repoName, outputKind) 33 | } 34 | -------------------------------------------------------------------------------- /src/commands/repos/handle-delete-repo.mts: -------------------------------------------------------------------------------- 1 | import { fetchDeleteRepo } from './fetch-delete-repo.mts' 2 | import { outputDeleteRepo } from './output-delete-repo.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleDeleteRepo( 7 | orgSlug: string, 8 | repoName: string, 9 | outputKind: OutputKind, 10 | ) { 11 | const data = await fetchDeleteRepo(orgSlug, repoName) 12 | 13 | await outputDeleteRepo(data, repoName, outputKind) 14 | } 15 | -------------------------------------------------------------------------------- /src/commands/repos/handle-list-repos.mts: -------------------------------------------------------------------------------- 1 | import { fetchListAllRepos } from './fetch-list-all-repos.mts' 2 | import { fetchListRepos } from './fetch-list-repos.mts' 3 | import { outputListRepos } from './output-list-repos.mts' 4 | 5 | import type { OutputKind } from '../../types.mts' 6 | 7 | export async function handleListRepos({ 8 | all, 9 | direction, 10 | orgSlug, 11 | outputKind, 12 | page, 13 | per_page, 14 | sort, 15 | }: { 16 | all: boolean 17 | direction: 'asc' | 'desc' 18 | orgSlug: string 19 | outputKind: OutputKind 20 | page: number 21 | per_page: number 22 | sort: string 23 | }): Promise { 24 | if (all) { 25 | const data = await fetchListAllRepos({ direction, orgSlug, sort }) 26 | 27 | await outputListRepos(data, outputKind, 0, 0, sort, Infinity, direction) 28 | } else { 29 | const data = await fetchListRepos({ 30 | direction, 31 | orgSlug, 32 | page, 33 | per_page, 34 | sort, 35 | }) 36 | 37 | if (!data.ok) { 38 | await outputListRepos(data, outputKind, 0, 0, '', 0, direction) 39 | } else { 40 | // Note: nextPage defaults to 0, is null when there's no next page 41 | await outputListRepos( 42 | data, 43 | outputKind, 44 | page, 45 | data.data.nextPage, 46 | sort, 47 | per_page, 48 | direction, 49 | ) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/commands/repos/handle-update-repo.mts: -------------------------------------------------------------------------------- 1 | import { fetchUpdateRepo } from './fetch-update-repo.mts' 2 | import { outputUpdateRepo } from './output-update-repo.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleUpdateRepo( 7 | { 8 | default_branch, 9 | description, 10 | homepage, 11 | orgSlug, 12 | repoName, 13 | visibility, 14 | }: { 15 | orgSlug: string 16 | repoName: string 17 | description: string 18 | homepage: string 19 | default_branch: string 20 | visibility: string 21 | }, 22 | outputKind: OutputKind, 23 | ): Promise { 24 | const data = await fetchUpdateRepo({ 25 | default_branch, 26 | description, 27 | homepage, 28 | orgSlug, 29 | repoName, 30 | visibility, 31 | }) 32 | 33 | await outputUpdateRepo(data, repoName, outputKind) 34 | } 35 | -------------------------------------------------------------------------------- /src/commands/repos/handle-view-repo.mts: -------------------------------------------------------------------------------- 1 | import { fetchViewRepo } from './fetch-view-repo.mts' 2 | import { outputViewRepo } from './output-view-repo.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleViewRepo( 7 | orgSlug: string, 8 | repoName: string, 9 | outputKind: OutputKind, 10 | ): Promise { 11 | const data = await fetchViewRepo(orgSlug, repoName) 12 | 13 | await outputViewRepo(data, outputKind) 14 | } 15 | -------------------------------------------------------------------------------- /src/commands/repos/output-create-repo.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 5 | 6 | import type { CResult, OutputKind } from '../../types.mts' 7 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 8 | 9 | export function outputCreateRepo( 10 | result: CResult['data']>, 11 | requestedName: string, 12 | outputKind: OutputKind, 13 | ): void { 14 | if (!result.ok) { 15 | process.exitCode = result.code ?? 1 16 | } 17 | if (outputKind === 'json') { 18 | logger.log(serializeResultJson(result)) 19 | return 20 | } 21 | if (!result.ok) { 22 | logger.fail(failMsgWithBadge(result.message, result.cause)) 23 | return 24 | } 25 | const { slug } = result.data 26 | logger.success( 27 | `OK. Repository created successfully, slug: \`${slug}\`${slug !== requestedName ? ' (Warning: slug is not the same as name that was requested!)' : ''}`, 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /src/commands/repos/output-delete-repo.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 5 | 6 | import type { CResult, OutputKind } from '../../types.mts' 7 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 8 | 9 | export async function outputDeleteRepo( 10 | result: CResult['data']>, 11 | repoName: string, 12 | outputKind: OutputKind, 13 | ): Promise { 14 | if (!result.ok) { 15 | process.exitCode = result.code ?? 1 16 | } 17 | 18 | if (outputKind === 'json') { 19 | logger.log(serializeResultJson(result)) 20 | return 21 | } 22 | if (!result.ok) { 23 | logger.fail(failMsgWithBadge(result.message, result.cause)) 24 | return 25 | } 26 | 27 | logger.success(`OK. Repository \`${repoName}\` deleted successfully`) 28 | } 29 | -------------------------------------------------------------------------------- /src/commands/repos/output-update-repo.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 5 | 6 | import type { CResult, OutputKind } from '../../types.mts' 7 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 8 | 9 | export async function outputUpdateRepo( 10 | result: CResult['data']>, 11 | repoName: string, 12 | outputKind: OutputKind, 13 | ): Promise { 14 | if (!result.ok) { 15 | process.exitCode = result.code ?? 1 16 | } 17 | 18 | if (outputKind === 'json') { 19 | logger.log(serializeResultJson(result)) 20 | return 21 | } 22 | if (!result.ok) { 23 | logger.fail(failMsgWithBadge(result.message, result.cause)) 24 | return 25 | } 26 | 27 | logger.success(`Repository \`${repoName}\` updated successfully`) 28 | } 29 | -------------------------------------------------------------------------------- /src/commands/repos/output-view-repo.mts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import chalkTable from 'chalk-table' 3 | import colors from 'yoctocolors-cjs' 4 | 5 | import { logger } from '@socketsecurity/registry/lib/logger' 6 | 7 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 8 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 9 | 10 | import type { CResult, OutputKind } from '../../types.mts' 11 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 12 | 13 | export async function outputViewRepo( 14 | result: CResult['data']>, 15 | outputKind: OutputKind, 16 | ): Promise { 17 | if (!result.ok) { 18 | process.exitCode = result.code ?? 1 19 | } 20 | 21 | if (outputKind === 'json') { 22 | logger.log(serializeResultJson(result)) 23 | return 24 | } 25 | if (!result.ok) { 26 | logger.fail(failMsgWithBadge(result.message, result.cause)) 27 | return 28 | } 29 | 30 | const options = { 31 | columns: [ 32 | { field: 'id', name: colors.magenta('ID') }, 33 | { field: 'name', name: colors.magenta('Name') }, 34 | { field: 'visibility', name: colors.magenta('Visibility') }, 35 | { field: 'default_branch', name: colors.magenta('Default branch') }, 36 | { field: 'homepage', name: colors.magenta('Homepage') }, 37 | { field: 'archived', name: colors.magenta('Archived') }, 38 | { field: 'created_at', name: colors.magenta('Created at') }, 39 | ], 40 | } 41 | 42 | logger.log(chalkTable(options, [result.data])) 43 | } 44 | -------------------------------------------------------------------------------- /src/commands/scan/cmd-scan-reach.mts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | 3 | import { logger } from '@socketsecurity/registry/lib/logger' 4 | 5 | import { handleScanReach } from './handle-reach-scan.mts' 6 | import constants from '../../constants.mts' 7 | import { commonFlags, outputFlags } from '../../flags.mts' 8 | import { checkCommandInput } from '../../utils/check-input.mts' 9 | import { getOutputKind } from '../../utils/get-output-kind.mts' 10 | import { meowOrExit } from '../../utils/meow-with-subcommands.mts' 11 | import { getFlagListOutput } from '../../utils/output-formatting.mts' 12 | 13 | import type { CliCommandConfig } from '../../utils/meow-with-subcommands.mts' 14 | 15 | const { DRY_RUN_BAILING_NOW } = constants 16 | 17 | const config: CliCommandConfig = { 18 | commandName: 'reach', 19 | description: 'Compute tier 1 reachability', 20 | hidden: true, 21 | flags: { 22 | ...commonFlags, 23 | ...outputFlags, 24 | }, 25 | help: (command, config) => ` 26 | Usage 27 | $ ${command} [CWD=.] 28 | 29 | Options 30 | ${getFlagListOutput(config.flags, 6)} 31 | 32 | Examples 33 | $ ${command} 34 | $ ${command} ./proj 35 | `, 36 | } 37 | 38 | export const cmdScanReach = { 39 | description: config.description, 40 | hidden: config.hidden, 41 | run, 42 | } 43 | 44 | async function run( 45 | argv: string[] | readonly string[], 46 | importMeta: ImportMeta, 47 | { parentName }: { parentName: string }, 48 | ): Promise { 49 | const cli = meowOrExit({ 50 | argv, 51 | config, 52 | importMeta, 53 | parentName, 54 | }) 55 | 56 | const { dryRun, json, markdown } = cli.flags 57 | const outputKind = getOutputKind(json, markdown) 58 | let [cwd = '.'] = cli.input 59 | // Note: path.resolve vs .join: 60 | // If given path is absolute then cwd should not affect it. 61 | cwd = path.resolve(process.cwd(), cwd) 62 | 63 | const wasValidInput = checkCommandInput(outputKind) 64 | if (!wasValidInput) { 65 | return 66 | } 67 | 68 | if (dryRun) { 69 | logger.log(DRY_RUN_BAILING_NOW) 70 | return 71 | } 72 | 73 | await handleScanReach(argv, cwd, outputKind) 74 | } 75 | -------------------------------------------------------------------------------- /src/commands/scan/cmd-scan.mts: -------------------------------------------------------------------------------- 1 | import { cmdScanCreate } from './cmd-scan-create.mts' 2 | import { cmdScanDel } from './cmd-scan-del.mts' 3 | import { cmdScanDiff } from './cmd-scan-diff.mts' 4 | import { cmdScanGithub } from './cmd-scan-github.mts' 5 | import { cmdScanList } from './cmd-scan-list.mts' 6 | import { cmdScanMetadata } from './cmd-scan-metadata.mts' 7 | import { cmdScanReach } from './cmd-scan-reach.mts' 8 | import { cmdScanReport } from './cmd-scan-report.mts' 9 | import { cmdScanSetup } from './cmd-scan-setup.mts' 10 | import { cmdScanView } from './cmd-scan-view.mts' 11 | import { meowWithSubcommands } from '../../utils/meow-with-subcommands.mts' 12 | 13 | import type { CliSubcommand } from '../../utils/meow-with-subcommands.mts' 14 | 15 | const description = 'Scan related commands' 16 | 17 | export const cmdScan: CliSubcommand = { 18 | description, 19 | async run(argv, importMeta, { parentName }) { 20 | await meowWithSubcommands( 21 | { 22 | create: cmdScanCreate, 23 | del: cmdScanDel, 24 | diff: cmdScanDiff, 25 | github: cmdScanGithub, 26 | list: cmdScanList, 27 | metadata: cmdScanMetadata, 28 | reach: cmdScanReach, 29 | report: cmdScanReport, 30 | setup: cmdScanSetup, 31 | view: cmdScanView, 32 | }, 33 | { 34 | aliases: { 35 | meta: { 36 | description: cmdScanMetadata.description, 37 | hidden: true, 38 | argv: ['metadata'], 39 | }, 40 | reachability: { 41 | description: cmdScanReach.description, 42 | hidden: true, 43 | argv: ['reach'], 44 | }, 45 | // Backwards compat. TODO: Drop next major bump; isTestingV1 46 | stream: { 47 | description: cmdScanView.description, 48 | hidden: true, 49 | argv: ['view'], // Original args will be appended (!) 50 | }, 51 | }, 52 | argv, 53 | description, 54 | importMeta, 55 | name: parentName + ' scan', 56 | }, 57 | ) 58 | }, 59 | } 60 | -------------------------------------------------------------------------------- /src/commands/scan/fetch-create-org-full-scan.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchCreateOrgFullScan( 8 | packagePaths: string[], 9 | orgSlug: string, 10 | defaultBranch: boolean, 11 | pendingHead: boolean, 12 | tmp: boolean, 13 | cwd: string, 14 | { 15 | branchName, 16 | commitHash, 17 | commitMessage, 18 | committers, 19 | pullRequest, 20 | repoName, 21 | }: { 22 | branchName: string 23 | commitHash: string 24 | commitMessage: string 25 | committers: string 26 | pullRequest: number 27 | repoName: string 28 | }, 29 | ): Promise['data']>> { 30 | const sockSdkResult = await setupSdk() 31 | if (!sockSdkResult.ok) { 32 | return sockSdkResult 33 | } 34 | const sockSdk = sockSdkResult.data 35 | 36 | return await handleApiCall( 37 | sockSdk.createOrgFullScan( 38 | orgSlug, 39 | { 40 | ...(branchName ? { branch: branchName } : {}), 41 | ...(commitHash ? { commit_hash: commitHash } : {}), 42 | ...(commitMessage ? { commit_message: commitMessage } : {}), 43 | ...(committers ? { committers } : {}), 44 | make_default_branch: String(defaultBranch), 45 | ...(pullRequest ? { pull_request: String(pullRequest) } : {}), 46 | repo: repoName || 'socket-default-repository', // mandatory, this is server default for repo 47 | set_as_pending_head: String(pendingHead), 48 | tmp: String(tmp), 49 | }, 50 | packagePaths, 51 | cwd, 52 | ), 53 | 'to create a scan', 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /src/commands/scan/fetch-delete-org-full-scan.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchDeleteOrgFullScan( 8 | orgSlug: string, 9 | scanId: string, 10 | ): Promise['data']>> { 11 | const sockSdkResult = await setupSdk() 12 | if (!sockSdkResult.ok) { 13 | return sockSdkResult 14 | } 15 | const sockSdk = sockSdkResult.data 16 | 17 | return await handleApiCall( 18 | sockSdk.deleteOrgFullScan(orgSlug, scanId), 19 | 'to delete a scan', 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/commands/scan/fetch-diff-scan.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { queryApiSafeJson } from '../../utils/api.mts' 4 | 5 | import type { CResult } from '../../types.mts' 6 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 7 | 8 | export async function fetchDiffScan({ 9 | id1, 10 | id2, 11 | orgSlug, 12 | }: { 13 | id1: string 14 | id2: string 15 | orgSlug: string 16 | }): Promise['data']>> { 17 | logger.info('Scan ID 1:', id1) 18 | logger.info('Scan ID 2:', id2) 19 | logger.info('Note: this request may take some time if the scans are big') 20 | 21 | return await queryApiSafeJson['data']>( 22 | `orgs/${orgSlug}/full-scans/diff?before=${encodeURIComponent(id1)}&after=${encodeURIComponent(id2)}`, 23 | 'a scan diff', 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/scan/fetch-list-scans.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchListScans({ 8 | branch, 9 | direction, 10 | from_time, 11 | orgSlug, 12 | page, 13 | per_page, 14 | repo, 15 | sort, 16 | }: { 17 | branch: string 18 | direction: string 19 | from_time: string 20 | orgSlug: string 21 | page: number 22 | per_page: number 23 | repo: string 24 | sort: string 25 | }): Promise['data']>> { 26 | const sockSdkResult = await setupSdk() 27 | if (!sockSdkResult.ok) { 28 | return sockSdkResult 29 | } 30 | const sockSdk = sockSdkResult.data 31 | 32 | return await handleApiCall( 33 | sockSdk.getOrgFullScanList(orgSlug, { 34 | ...(branch ? { branch } : {}), 35 | ...(repo ? { repo } : {}), 36 | sort, 37 | direction, 38 | per_page: String(per_page), 39 | page: String(page), 40 | from: from_time, 41 | }), 42 | 'list of scans', 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/commands/scan/fetch-scan-metadata.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchScanMetadata( 8 | orgSlug: string, 9 | scanId: string, 10 | ): Promise['data']>> { 11 | const sockSdkResult = await setupSdk() 12 | if (!sockSdkResult.ok) { 13 | return sockSdkResult 14 | } 15 | const sockSdk = sockSdkResult.data 16 | 17 | return await handleApiCall( 18 | sockSdk.getOrgFullScanMetadata(orgSlug, scanId), 19 | 'meta data for a full scan', 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/commands/scan/fetch-scan.mts: -------------------------------------------------------------------------------- 1 | import { debugFn } from '@socketsecurity/registry/lib/debug' 2 | 3 | import { queryApiSafeText } from '../../utils/api.mts' 4 | 5 | import type { CResult } from '../../types.mts' 6 | import type { SocketArtifact } from '../../utils/alert/artifact.mts' 7 | 8 | export async function fetchScan( 9 | orgSlug: string, 10 | scanId: string, 11 | ): Promise> { 12 | const result = await queryApiSafeText( 13 | `orgs/${orgSlug}/full-scans/${encodeURIComponent(scanId)}`, 14 | 'a scan', 15 | ) 16 | 17 | if (!result.ok) { 18 | return result 19 | } 20 | 21 | const jsonsString = result.data 22 | 23 | // This is nd-json; each line is a json object 24 | const lines = jsonsString.split('\n').filter(Boolean) 25 | let ok = true 26 | const data = lines.map(line => { 27 | try { 28 | return JSON.parse(line) 29 | } catch { 30 | ok = false 31 | debugFn('fail: parse NDJSON\n', line) 32 | return null 33 | } 34 | }) as unknown as SocketArtifact[] 35 | 36 | if (ok) { 37 | return { ok: true, data } 38 | } 39 | 40 | return { 41 | ok: false, 42 | message: 'Invalid API response', 43 | cause: 44 | 'The API responded with at least one line that was not valid JSON. Please report if this persists.', 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/commands/scan/fetch-supported-scan-file-names.mts: -------------------------------------------------------------------------------- 1 | import { handleApiCall } from '../../utils/api.mts' 2 | import { setupSdk } from '../../utils/sdk.mts' 3 | 4 | import type { CResult } from '../../types.mts' 5 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 6 | 7 | export async function fetchSupportedScanFileNames(): Promise< 8 | CResult['data']> 9 | > { 10 | const sockSdkResult = await setupSdk() 11 | if (!sockSdkResult.ok) { 12 | return sockSdkResult 13 | } 14 | const sockSdk = sockSdkResult.data 15 | 16 | return await handleApiCall( 17 | sockSdk.getReportSupportedFiles(), 18 | 'supported scan file types', 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/scan/handle-create-github-scan.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { createScanFromGithub } from './create-scan-from-github.mts' 4 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 5 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 6 | 7 | import type { OutputKind } from '../../types.mts' 8 | 9 | export async function handleCreateGithubScan({ 10 | all, 11 | githubApiUrl, 12 | githubToken, 13 | interactive, 14 | orgGithub, 15 | orgSlug, 16 | outputKind, 17 | repos, 18 | }: { 19 | all: boolean 20 | githubApiUrl: string 21 | githubToken: string 22 | interactive: boolean 23 | orgSlug: string 24 | orgGithub: string 25 | outputKind: OutputKind 26 | repos: string 27 | }) { 28 | const result = await createScanFromGithub({ 29 | all: Boolean(all), 30 | githubApiUrl, 31 | githubToken, 32 | interactive: Boolean(interactive), 33 | orgSlug, 34 | orgGithub, 35 | outputKind, 36 | repos: String(repos || ''), 37 | }) 38 | 39 | if (outputKind === 'json') { 40 | logger.log(serializeResultJson(result)) 41 | return 42 | } 43 | 44 | if (!result.ok) { 45 | logger.fail(failMsgWithBadge(result.message, result.cause)) 46 | return 47 | } 48 | 49 | logger.log('') 50 | logger.success('Finished!') 51 | } 52 | -------------------------------------------------------------------------------- /src/commands/scan/handle-delete-scan.mts: -------------------------------------------------------------------------------- 1 | import { fetchDeleteOrgFullScan } from './fetch-delete-org-full-scan.mts' 2 | import { outputDeleteScan } from './output-delete-scan.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleDeleteScan( 7 | orgSlug: string, 8 | scanId: string, 9 | outputKind: OutputKind, 10 | ): Promise { 11 | const data = await fetchDeleteOrgFullScan(orgSlug, scanId) 12 | 13 | await outputDeleteScan(data, outputKind) 14 | } 15 | -------------------------------------------------------------------------------- /src/commands/scan/handle-diff-scan.mts: -------------------------------------------------------------------------------- 1 | import { fetchDiffScan } from './fetch-diff-scan.mts' 2 | import { outputDiffScan } from './output-diff-scan.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleDiffScan({ 7 | depth, 8 | file, 9 | id1, 10 | id2, 11 | orgSlug, 12 | outputKind, 13 | }: { 14 | depth: number 15 | file: string 16 | id1: string 17 | id2: string 18 | orgSlug: string 19 | outputKind: OutputKind 20 | }): Promise { 21 | const data = await fetchDiffScan({ 22 | id1, 23 | id2, 24 | orgSlug, 25 | }) 26 | 27 | await outputDiffScan(data, { 28 | depth, 29 | file, 30 | outputKind, 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/scan/handle-list-scans.mts: -------------------------------------------------------------------------------- 1 | import { fetchListScans } from './fetch-list-scans.mts' 2 | import { outputListScans } from './output-list-scans.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleListScans({ 7 | branch, 8 | direction, 9 | from_time, 10 | orgSlug, 11 | outputKind, 12 | page, 13 | per_page, 14 | repo, 15 | sort, 16 | }: { 17 | branch: string 18 | direction: string 19 | from_time: string 20 | orgSlug: string 21 | outputKind: OutputKind 22 | page: number 23 | per_page: number 24 | repo: string 25 | sort: string 26 | }): Promise { 27 | const data = await fetchListScans({ 28 | branch, 29 | direction, 30 | from_time, 31 | orgSlug, 32 | page, 33 | per_page, 34 | repo, 35 | sort, 36 | }) 37 | 38 | await outputListScans(data, outputKind) 39 | } 40 | -------------------------------------------------------------------------------- /src/commands/scan/handle-reach-scan.mts: -------------------------------------------------------------------------------- 1 | import { outputScanReach } from './output-scan-reach.mts' 2 | import { scanReachability } from './scan-reachability.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleScanReach( 7 | argv: string[] | readonly string[], 8 | cwd: string, 9 | outputKind: OutputKind, 10 | ) { 11 | const result = await scanReachability(argv, cwd) 12 | 13 | await outputScanReach(result, cwd, outputKind) 14 | } 15 | -------------------------------------------------------------------------------- /src/commands/scan/handle-scan-config.mts: -------------------------------------------------------------------------------- 1 | import { outputScanConfigResult } from './output-scan-config-result.mts' 2 | import { setupScanConfig } from './setup-scan-config.mts' 3 | 4 | export async function handleScanConfig( 5 | cwd: string, 6 | defaultOnReadError = false, 7 | ) { 8 | const result = await setupScanConfig(cwd, defaultOnReadError) 9 | 10 | await outputScanConfigResult(result) 11 | } 12 | -------------------------------------------------------------------------------- /src/commands/scan/handle-scan-metadata.mts: -------------------------------------------------------------------------------- 1 | import { fetchScanMetadata } from './fetch-scan-metadata.mts' 2 | import { outputScanMetadata } from './output-scan-metadata.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleOrgScanMetadata( 7 | orgSlug: string, 8 | scanId: string, 9 | outputKind: OutputKind, 10 | ): Promise { 11 | const data = await fetchScanMetadata(orgSlug, scanId) 12 | 13 | await outputScanMetadata(data, scanId, outputKind) 14 | } 15 | -------------------------------------------------------------------------------- /src/commands/scan/handle-scan-report.mts: -------------------------------------------------------------------------------- 1 | import { fetchReportData } from './fetch-report-data.mts' 2 | import { outputScanReport } from './output-scan-report.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleScanReport({ 7 | filePath, 8 | fold, 9 | includeLicensePolicy, 10 | orgSlug, 11 | outputKind, 12 | reportLevel, 13 | scanId, 14 | short, 15 | }: { 16 | orgSlug: string 17 | scanId: string 18 | includeLicensePolicy: boolean 19 | outputKind: OutputKind 20 | filePath: string 21 | fold: 'pkg' | 'version' | 'file' | 'none' 22 | reportLevel: 'defer' | 'ignore' | 'monitor' | 'warn' | 'error' 23 | short: boolean 24 | }): Promise { 25 | const result = await fetchReportData(orgSlug, scanId, includeLicensePolicy) 26 | 27 | await outputScanReport(result, { 28 | filePath, 29 | fold, 30 | scanId: scanId, 31 | includeLicensePolicy, 32 | orgSlug, 33 | outputKind, 34 | reportLevel, 35 | short, 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/scan/handle-scan-view.mts: -------------------------------------------------------------------------------- 1 | import { fetchScan } from './fetch-scan.mts' 2 | import { outputScanView } from './output-scan-view.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleScanView( 7 | orgSlug: string, 8 | scanId: string, 9 | filePath: string, 10 | outputKind: OutputKind, 11 | ): Promise { 12 | const data = await fetchScan(orgSlug, scanId) 13 | 14 | await outputScanView(data, orgSlug, scanId, filePath, outputKind) 15 | } 16 | -------------------------------------------------------------------------------- /src/commands/scan/output-create-new-scan.mts: -------------------------------------------------------------------------------- 1 | import open from 'open' 2 | import colors from 'yoctocolors-cjs' 3 | 4 | import { logger } from '@socketsecurity/registry/lib/logger' 5 | import { confirm } from '@socketsecurity/registry/lib/prompts' 6 | 7 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 8 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 9 | 10 | import type { CResult, OutputKind } from '../../types.mts' 11 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 12 | 13 | export async function outputCreateNewScan( 14 | result: CResult['data']>, 15 | outputKind: OutputKind, 16 | interactive: boolean, 17 | ) { 18 | if (!result.ok) { 19 | process.exitCode = result.code ?? 1 20 | } 21 | 22 | if (outputKind === 'json') { 23 | logger.log(serializeResultJson(result)) 24 | return 25 | } 26 | if (!result.ok) { 27 | logger.fail(failMsgWithBadge(result.message, result.cause)) 28 | return 29 | } 30 | 31 | if (!result.data.id) { 32 | logger.fail('Did not receive a scan ID from the API...') 33 | process.exitCode = 1 34 | } 35 | 36 | if (outputKind === 'markdown') { 37 | logger.log('# Create New Scan') 38 | logger.log('') 39 | if (result.data.id) { 40 | logger.log( 41 | `A [new Scan](${result.data.html_report_url}) was created with ID: ${result.data.id}`, 42 | ) 43 | logger.log('') 44 | } else { 45 | logger.log( 46 | `The server did not return a Scan ID while trying to create a new Scan. This could be an indication something went wrong.`, 47 | ) 48 | } 49 | logger.log('') 50 | return 51 | } 52 | 53 | const link = colors.underline(colors.cyan(`${result.data.html_report_url}`)) 54 | logger.log(`Available at: ${link}`) 55 | 56 | if ( 57 | interactive && 58 | (await confirm({ 59 | message: 'Would you like to open it in your browser?', 60 | default: false, 61 | })) 62 | ) { 63 | await open(`${result.data.html_report_url}`) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/commands/scan/output-delete-scan.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 5 | 6 | import type { CResult, OutputKind } from '../../types.mts' 7 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 8 | 9 | export async function outputDeleteScan( 10 | result: CResult['data']>, 11 | outputKind: OutputKind, 12 | ): Promise { 13 | if (!result.ok) { 14 | process.exitCode = result.code ?? 1 15 | } 16 | 17 | if (outputKind === 'json') { 18 | logger.log(serializeResultJson(result)) 19 | return 20 | } 21 | if (!result.ok) { 22 | logger.fail(failMsgWithBadge(result.message, result.cause)) 23 | return 24 | } 25 | 26 | logger.success('Scan deleted successfully') 27 | } 28 | -------------------------------------------------------------------------------- /src/commands/scan/output-list-scans.mts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import chalkTable from 'chalk-table' 3 | import colors from 'yoctocolors-cjs' 4 | 5 | import { logger } from '@socketsecurity/registry/lib/logger' 6 | 7 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 8 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 9 | 10 | import type { CResult, OutputKind } from '../../types.mts' 11 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 12 | 13 | export async function outputListScans( 14 | result: CResult['data']>, 15 | outputKind: OutputKind, 16 | ): Promise { 17 | if (!result.ok) { 18 | process.exitCode = result.code ?? 1 19 | } 20 | 21 | if (outputKind === 'json') { 22 | logger.log(serializeResultJson(result)) 23 | return 24 | } 25 | if (!result.ok) { 26 | logger.fail(failMsgWithBadge(result.message, result.cause)) 27 | return 28 | } 29 | 30 | const options = { 31 | columns: [ 32 | { field: 'id', name: colors.magenta('ID') }, 33 | { field: 'report_url', name: colors.magenta('Scan URL') }, 34 | { field: 'repo', name: colors.magenta('Repo') }, 35 | { field: 'branch', name: colors.magenta('Branch') }, 36 | { field: 'created_at', name: colors.magenta('Created at') }, 37 | ], 38 | } 39 | 40 | const formattedResults = result.data.results.map(d => { 41 | return { 42 | id: d.id, 43 | report_url: colors.underline(`${d.html_report_url}`), 44 | created_at: d.created_at 45 | ? new Date(d.created_at).toLocaleDateString('en-us', { 46 | year: 'numeric', 47 | month: 'numeric', 48 | day: 'numeric', 49 | }) 50 | : '', 51 | repo: d.repo, 52 | branch: d.branch, 53 | } 54 | }) 55 | 56 | logger.log(chalkTable(options, formattedResults)) 57 | } 58 | -------------------------------------------------------------------------------- /src/commands/scan/output-scan-config-result.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | 5 | import type { CResult } from '../../types.mts' 6 | 7 | export async function outputScanConfigResult(result: CResult) { 8 | if (!result.ok) { 9 | process.exitCode = result.code ?? 1 10 | } 11 | 12 | if (!result.ok) { 13 | logger.fail(failMsgWithBadge(result.message, result.cause)) 14 | return 15 | } 16 | 17 | logger.log('') 18 | logger.log('Finished') 19 | logger.log('') 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/scan/output-scan-metadata.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 5 | 6 | import type { CResult, OutputKind } from '../../types.mts' 7 | import type { SocketSdkReturnType } from '@socketsecurity/sdk' 8 | 9 | export async function outputScanMetadata( 10 | result: CResult['data']>, 11 | scanId: string, 12 | outputKind: OutputKind, 13 | ): Promise { 14 | if (!result.ok) { 15 | process.exitCode = result.code ?? 1 16 | } 17 | 18 | if (outputKind === 'json') { 19 | logger.log(serializeResultJson(result)) 20 | return 21 | } 22 | if (!result.ok) { 23 | logger.fail(failMsgWithBadge(result.message, result.cause)) 24 | return 25 | } 26 | 27 | if (outputKind === 'markdown') { 28 | logger.log('# Scan meta data\n') 29 | } 30 | logger.log(`Scan ID: ${scanId}\n`) 31 | for (const [key, value] of Object.entries(result.data)) { 32 | if ( 33 | [ 34 | 'id', 35 | 'updated_at', 36 | 'organization_id', 37 | 'repository_id', 38 | 'commit_hash', 39 | 'html_report_url', 40 | ].includes(key) 41 | ) { 42 | continue 43 | } 44 | logger.log(`- ${key}:`, value) 45 | } 46 | if (outputKind === 'markdown') { 47 | logger.log( 48 | `\nYou can view this report at: [${result.data.html_report_url}](${result.data.html_report_url})\n`, 49 | ) 50 | } else { 51 | logger.log( 52 | `\nYou can view this report at: ${result.data.html_report_url}]\n`, 53 | ) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/commands/scan/output-scan-reach.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | import { serializeResultJson } from '../../utils/serialize-result-json.mts' 5 | 6 | import type { CResult, OutputKind } from '../../types.mts' 7 | 8 | export async function outputScanReach( 9 | result: CResult, 10 | cwd: string, 11 | outputKind: OutputKind, 12 | ): Promise { 13 | if (!result.ok) { 14 | process.exitCode = result.code ?? 1 15 | } 16 | 17 | if (outputKind === 'json') { 18 | logger.log(serializeResultJson(result)) 19 | return 20 | } 21 | if (!result.ok) { 22 | logger.fail(failMsgWithBadge(result.message, result.cause)) 23 | return 24 | } 25 | 26 | logger.success('finished on', cwd) 27 | } 28 | -------------------------------------------------------------------------------- /src/commands/scan/scan-reachability.mts: -------------------------------------------------------------------------------- 1 | import { spawn } from '@socketsecurity/registry/lib/spawn' 2 | 3 | import constants from '../../constants.mts' 4 | 5 | import type { CResult } from '../../types.mts' 6 | 7 | const { DOT_SOCKET_DOT_FACTS_JSON } = constants 8 | 9 | export async function scanReachability( 10 | argv: string[] | readonly string[], 11 | cwd: string, 12 | ): Promise> { 13 | try { 14 | const result = await spawn( 15 | constants.execPath, 16 | [ 17 | // Lazily access constants.nodeNoWarningsFlags. 18 | ...constants.nodeNoWarningsFlags, 19 | // Lazily access constants.coanaBinPath. 20 | constants.coanaBinPath, 21 | 'run', 22 | cwd, 23 | '--output-dir', 24 | cwd, 25 | '--socket-mode', 26 | DOT_SOCKET_DOT_FACTS_JSON, 27 | '--disable-report-submission', 28 | ...argv, 29 | ], 30 | { 31 | cwd, 32 | env: { 33 | ...process.env, 34 | // Lazily access constants.ENV.SOCKET_CLI_API_TOKEN 35 | SOCKET_CLI_API_TOKEN: constants.ENV.SOCKET_CLI_API_TOKEN, 36 | }, 37 | }, 38 | ) 39 | return { ok: true, data: result.stdout.trim() } 40 | } catch (e) { 41 | const message = (e as any)?.stdout ?? (e as Error)?.message 42 | return { ok: false, data: e, message } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/commands/scan/stream-scan.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { handleApiCall } from '../../utils/api.mts' 4 | import { setupSdk } from '../../utils/sdk.mts' 5 | 6 | export async function streamScan( 7 | orgSlug: string, 8 | scanId: string, 9 | file: string | undefined, 10 | ) { 11 | const sockSdkResult = await setupSdk() 12 | if (!sockSdkResult.ok) { 13 | return sockSdkResult 14 | } 15 | const sockSdk = sockSdkResult.data 16 | 17 | logger.info('Requesting data from API...') 18 | 19 | // Note: this will write to stdout or target file. It's not a noop 20 | return await handleApiCall( 21 | sockSdk.getOrgFullScan(orgSlug, scanId, file === '-' ? undefined : file), 22 | 'a scan', 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/scan/suggest-org-slug.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | import { select } from '@socketsecurity/registry/lib/prompts' 3 | 4 | import { handleApiCall } from '../../utils/api.mts' 5 | import { setupSdk } from '../../utils/sdk.mts' 6 | 7 | export async function suggestOrgSlug(): Promise { 8 | const sockSdkResult = await setupSdk() 9 | if (!sockSdkResult.ok) { 10 | return 11 | } 12 | const sockSdk = sockSdkResult.data 13 | 14 | const result = await handleApiCall( 15 | sockSdk.getOrganizations(), 16 | 'list of organizations', 17 | ) 18 | 19 | // Ignore a failed request here. It was not the primary goal of 20 | // running this command and reporting it only leads to end-user confusion. 21 | if (result.ok) { 22 | const proceed = await select({ 23 | message: 24 | 'Missing org name; do you want to use any of these orgs for this scan?', 25 | choices: [ 26 | ...Object.values(result.data.organizations).map(org => { 27 | const name = org.name ?? org.slug 28 | return { 29 | name: `Yes [${name}]`, 30 | value: name, 31 | description: `Use "${name}" as the organization`, 32 | } 33 | }), 34 | { 35 | name: 'No', 36 | value: '', 37 | description: 38 | 'Do not use any of these organizations (will end in a no-op)', 39 | }, 40 | ], 41 | }) 42 | if (proceed) { 43 | return proceed 44 | } 45 | } else { 46 | logger.fail( 47 | 'Failed to lookup organization list from API, unable to suggest', 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/commands/scan/suggest_branch_slug.mts: -------------------------------------------------------------------------------- 1 | import { select } from '@socketsecurity/registry/lib/prompts' 2 | import { spawnSync } from '@socketsecurity/registry/lib/spawn' 3 | 4 | export async function suggestBranchSlug( 5 | repoDefaultBranch: string | undefined, 6 | ): Promise { 7 | const spawnResult = spawnSync('git', ['branch', '--show-current']) 8 | const currentBranch = spawnResult.stdout.toString('utf8').trim() 9 | if (currentBranch && spawnResult.status === 0) { 10 | const proceed = await select({ 11 | message: 'Use the current git branch as target branch name?', 12 | choices: [ 13 | { 14 | name: `Yes [${currentBranch}]`, 15 | value: currentBranch, 16 | description: 'Use the current git branch for branch name', 17 | }, 18 | ...(repoDefaultBranch && repoDefaultBranch !== currentBranch 19 | ? [ 20 | { 21 | name: `No, use the default branch [${repoDefaultBranch}]`, 22 | value: repoDefaultBranch, 23 | description: 24 | 'Use the default branch for target repo as the target branch name', 25 | }, 26 | ] 27 | : []), 28 | { 29 | name: 'No', 30 | value: '', 31 | description: 32 | 'Do not use the current git branch as name (will end in a no-op)', 33 | }, 34 | ].filter(Boolean), 35 | }) 36 | if (proceed) { 37 | return proceed 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/commands/scan/suggest_target.mts: -------------------------------------------------------------------------------- 1 | import { select } from '@socketsecurity/registry/lib/prompts' 2 | 3 | export async function suggestTarget(): Promise { 4 | // We could prefill this with sub-dirs of the current 5 | // dir ... but is that going to be useful? 6 | const proceed = await select({ 7 | message: 'No TARGET given. Do you want to use the current directory?', 8 | choices: [ 9 | { 10 | name: 'Yes', 11 | value: true, 12 | description: 'Target the current directory', 13 | }, 14 | { 15 | name: 'No', 16 | value: false, 17 | description: 18 | 'Do not use the current directory (this will end in a no-op)', 19 | }, 20 | ], 21 | }) 22 | if (proceed) { 23 | return ['.'] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/threat-feed/fetch-threat-feed.mts: -------------------------------------------------------------------------------- 1 | import { queryApiSafeJson } from '../../utils/api.mts' 2 | 3 | import type { ThreadFeedResponse } from './types.mts' 4 | import type { CResult } from '../../types.mts' 5 | 6 | export async function fetchThreatFeed({ 7 | direction, 8 | ecosystem, 9 | filter, 10 | orgSlug, 11 | page, 12 | perPage, 13 | pkg, 14 | version, 15 | }: { 16 | direction: string 17 | ecosystem: string 18 | filter: string 19 | orgSlug: string 20 | page: string 21 | perPage: number 22 | pkg: string 23 | version: string 24 | }): Promise> { 25 | const queryParams = new URLSearchParams([ 26 | ['direction', direction], 27 | ['ecosystem', ecosystem], 28 | filter ? ['filter', filter] : ['', ''], 29 | ['page_cursor', page], 30 | ['per_page', String(perPage)], 31 | pkg ? ['name', pkg] : ['', ''], 32 | version ? ['version', version] : ['', ''], 33 | ]) 34 | 35 | return await queryApiSafeJson( 36 | `orgs/${orgSlug}/threat-feed?${queryParams}`, 37 | 'the Threat Feed data', 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/commands/threat-feed/handle-threat-feed.mts: -------------------------------------------------------------------------------- 1 | import { fetchThreatFeed } from './fetch-threat-feed.mts' 2 | import { outputThreatFeed } from './output-threat-feed.mts' 3 | 4 | import type { OutputKind } from '../../types.mts' 5 | 6 | export async function handleThreatFeed({ 7 | direction, 8 | ecosystem, 9 | filter, 10 | orgSlug, 11 | outputKind, 12 | page, 13 | perPage, 14 | pkg, 15 | version, 16 | }: { 17 | direction: string 18 | ecosystem: string 19 | filter: string 20 | outputKind: OutputKind 21 | orgSlug: string 22 | page: string 23 | perPage: number 24 | pkg: string 25 | version: string 26 | }): Promise { 27 | const data = await fetchThreatFeed({ 28 | direction, 29 | ecosystem, 30 | filter, 31 | orgSlug, 32 | page, 33 | perPage, 34 | pkg, 35 | version, 36 | }) 37 | 38 | await outputThreatFeed(data, outputKind) 39 | } 40 | -------------------------------------------------------------------------------- /src/commands/threat-feed/types.mts: -------------------------------------------------------------------------------- 1 | export interface ThreadFeedResponse { 2 | results: ThreatResult[] 3 | nextPage: string 4 | } 5 | 6 | export type ThreatResult = { 7 | createdAt: string 8 | description: string 9 | id: number 10 | locationHtmlUrl: string 11 | packageHtmlUrl: string 12 | purl: string 13 | removedAt: string | null 14 | threatType: string 15 | } 16 | -------------------------------------------------------------------------------- /src/commands/uninstall/cmd-uninstall-completion.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { handleUninstallCompletion } from './handle-uninstall-completion.mts' 4 | import constants from '../../constants.mts' 5 | import { commonFlags } from '../../flags.mts' 6 | import { meowOrExit } from '../../utils/meow-with-subcommands.mts' 7 | import { getFlagListOutput } from '../../utils/output-formatting.mts' 8 | 9 | import type { CliCommandConfig } from '../../utils/meow-with-subcommands.mts' 10 | 11 | const { DRY_RUN_BAILING_NOW } = constants 12 | 13 | const config: CliCommandConfig = { 14 | commandName: 'completion', 15 | description: 'Uninstall bash completion for Socket CLI', 16 | hidden: true, // beta; isTestingV1 17 | flags: { 18 | ...commonFlags, 19 | }, 20 | help: (command, config) => ` 21 | Usage 22 | $ ${command} [name=socket] 23 | 24 | Uninstalls bash tab completion for the Socket CLI. This will: 25 | 1. Remove tab completion from your current shell for given command 26 | 2. Remove the setup for given command from your ~/.bashrc 27 | 28 | The optional name is required if you installed tab completion for an alias 29 | other than the default "socket". This will NOT remove the command, only the 30 | tab completion that is registered for it in bash. 31 | 32 | Options 33 | ${getFlagListOutput(config.flags, 6)} 34 | 35 | Examples 36 | 37 | $ ${command} 38 | $ ${command} sd 39 | `, 40 | } 41 | 42 | export const cmdUninstallCompletion = { 43 | description: config.description, 44 | hidden: config.hidden, 45 | run, 46 | } 47 | 48 | export async function run( 49 | argv: string[] | readonly string[], 50 | importMeta: ImportMeta, 51 | { parentName }: { parentName: string }, 52 | ): Promise { 53 | const cli = meowOrExit({ 54 | argv, 55 | config, 56 | importMeta, 57 | parentName, 58 | }) 59 | 60 | const targetName = cli.input[0] || 'socket' 61 | 62 | if (cli.flags['dryRun']) { 63 | logger.log(DRY_RUN_BAILING_NOW) 64 | return 65 | } 66 | 67 | await handleUninstallCompletion(String(targetName)) 68 | } 69 | -------------------------------------------------------------------------------- /src/commands/uninstall/cmd-uninstall.mts: -------------------------------------------------------------------------------- 1 | import { cmdUninstallCompletion } from './cmd-uninstall-completion.mts' 2 | import { meowWithSubcommands } from '../../utils/meow-with-subcommands.mts' 3 | 4 | import type { CliSubcommand } from '../../utils/meow-with-subcommands.mts' 5 | 6 | const description = 'Teardown the Socket command from your environment' 7 | 8 | export const cmdUninstall: CliSubcommand = { 9 | description, 10 | hidden: true, // beta; isTestingV1 11 | async run(argv, importMeta, { parentName }) { 12 | await meowWithSubcommands( 13 | { 14 | completion: cmdUninstallCompletion, 15 | }, 16 | { 17 | argv, 18 | description, 19 | importMeta, 20 | name: `${parentName} uninstall`, 21 | }, 22 | ) 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/uninstall/handle-uninstall-completion.mts: -------------------------------------------------------------------------------- 1 | import { outputUninstallCompletion } from './output-uninstall-completion.mts' 2 | import { teardownTabCompletion } from './teardown-tab-completion.mts' 3 | 4 | export async function handleUninstallCompletion(targetName: string) { 5 | const result = await teardownTabCompletion(targetName) 6 | await outputUninstallCompletion(result, targetName) 7 | } 8 | -------------------------------------------------------------------------------- /src/commands/uninstall/output-uninstall-completion.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { failMsgWithBadge } from '../../utils/fail-msg-with-badge.mts' 4 | 5 | import type { CResult } from '../../types.mts' 6 | 7 | export async function outputUninstallCompletion( 8 | result: CResult<{ action: string; left: string[] }>, 9 | targetName: string, 10 | ) { 11 | if (!result.ok) { 12 | process.exitCode = result.code ?? 1 13 | 14 | logger.fail(failMsgWithBadge(result.message, result.cause)) 15 | return 16 | } 17 | 18 | logger.log(result.message) 19 | logger.log('') 20 | logger.log( 21 | 'To remove the tab completion from the current shell (instance of bash) you', 22 | ) 23 | logger.log( 24 | 'can run this command (due to a bash limitation NodeJS cannot do this):', 25 | ) 26 | logger.log('') 27 | logger.log(` complete -r ${targetName}`) 28 | logger.log('') 29 | logger.log( 30 | 'Next time you open a terminal it should no longer be there, regardless.', 31 | ) 32 | logger.log('') 33 | if (result.data.left.length) { 34 | logger.log( 35 | 'Detected more Socket Alias completions left in bashrc. Run `socket uninstall ` to remove them too.', 36 | ) 37 | logger.log('') 38 | result.data.left.forEach(str => { 39 | logger.log(` - \`${str}\``) 40 | }) 41 | logger.log('') 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/commands/wrapper/add-socket-wrapper.mts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | 3 | import { logger } from '@socketsecurity/registry/lib/logger' 4 | 5 | export function addSocketWrapper(file: string): void { 6 | return fs.appendFile( 7 | file, 8 | 'alias npm="socket npm"\nalias npx="socket npx"\n', 9 | err => { 10 | if (err) { 11 | return new Error(`There was an error setting up the alias: ${err}`) 12 | } 13 | logger.success( 14 | `The alias was added to ${file}. Running 'npm install' will now be wrapped in Socket's "safe npm" 🎉`, 15 | ) 16 | logger.log( 17 | ` If you want to disable it at any time, run \`socket wrapper --disable\``, 18 | ) 19 | logger.log('') 20 | logger.info( 21 | `This will only be active in new terminal sessions going forward.`, 22 | ) 23 | logger.log( 24 | ` You will need to restart your terminal or run this command to activate the alias in the current session:`, 25 | ) 26 | logger.log('') 27 | logger.log(` source ${file}`) 28 | logger.log('') 29 | logger.log(`(You only need to do this once)`) 30 | }, 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/wrapper/check-socket-wrapper-setup.mts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | 3 | import { logger } from '@socketsecurity/registry/lib/logger' 4 | 5 | export function checkSocketWrapperSetup(file: string): boolean { 6 | const fileContent = fs.readFileSync(file, 'utf8') 7 | const linesWithSocketAlias = fileContent 8 | .split('\n') 9 | .filter( 10 | l => l === 'alias npm="socket npm"' || l === 'alias npx="socket npx"', 11 | ) 12 | 13 | if (linesWithSocketAlias.length) { 14 | logger.log( 15 | `The Socket npm/npx wrapper is set up in your bash profile (${file}).`, 16 | ) 17 | logger.log('') 18 | logger.log( 19 | `If you haven't already since enabling; Restart your terminal or run this command to activate it in the current session:`, 20 | ) 21 | logger.log('') 22 | logger.log(` source ${file}`) 23 | logger.log('') 24 | 25 | return true 26 | } 27 | return false 28 | } 29 | -------------------------------------------------------------------------------- /src/commands/wrapper/remove-socket-wrapper.mts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | 3 | import { logger } from '@socketsecurity/registry/lib/logger' 4 | 5 | export function removeSocketWrapper(file: string): void { 6 | return fs.readFile(file, 'utf8', function (err, data) { 7 | if (err) { 8 | logger.fail('There was an error removing the alias:') 9 | logger.error(err) 10 | return 11 | } 12 | const linesWithoutSocketAlias = data 13 | .split('\n') 14 | .filter( 15 | l => l !== 'alias npm="socket npm"' && l !== 'alias npx="socket npx"', 16 | ) 17 | 18 | const updatedFileContent = linesWithoutSocketAlias.join('\n') 19 | 20 | fs.writeFile(file, updatedFileContent, function (err) { 21 | if (err) { 22 | logger.error(err) 23 | return 24 | } 25 | logger.success( 26 | `The alias was removed from ${file}. Running 'npm install' will now run the standard npm command in new terminals going forward.`, 27 | ) 28 | logger.log('') 29 | logger.info( 30 | `Note: We cannot deactivate the alias from current terminal sessions. You have to restart existing terminal sessions to finalize this step.`, 31 | ) 32 | }) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /src/external/blessed-contrib/lib/layout/grid.mjs: -------------------------------------------------------------------------------- 1 | export { default } from 'blessed-contrib/lib/layout/grid' 2 | -------------------------------------------------------------------------------- /src/external/blessed-contrib/lib/widget/charts/bar.mjs: -------------------------------------------------------------------------------- 1 | export { default } from 'blessed-contrib/lib/widget/charts/bar' 2 | -------------------------------------------------------------------------------- /src/external/blessed-contrib/lib/widget/charts/line.mjs: -------------------------------------------------------------------------------- 1 | export { default } from 'blessed-contrib/lib/widget/charts/line' 2 | -------------------------------------------------------------------------------- /src/external/blessed-contrib/lib/widget/table.mjs: -------------------------------------------------------------------------------- 1 | export { default } from 'blessed-contrib/lib/widget/table' 2 | -------------------------------------------------------------------------------- /src/shadow/npm/arborist/index.mts: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'node:module' 2 | 3 | // @ts-ignore 4 | import UntypedEdge from '@npmcli/arborist/lib/edge.js' 5 | // @ts-ignore 6 | import UntypedNode from '@npmcli/arborist/lib/node.js' 7 | // @ts-ignore 8 | import UntypedOverrideSet from '@npmcli/arborist/lib/override-set.js' 9 | 10 | import { 11 | getArboristClassPath, 12 | getArboristEdgeClassPath, 13 | getArboristNodeClassPath, 14 | getArboristOverrideSetClassPath, 15 | } from '../paths.mts' 16 | import { Arborist, SafeArborist } from './lib/arborist/index.mts' 17 | 18 | import type { EdgeClass, NodeClass, OverrideSetClass } from './types.mts' 19 | 20 | const require = createRequire(import.meta.url) 21 | 22 | export const SAFE_ARBORIST_REIFY_OPTIONS_OVERRIDES = { 23 | __proto__: null, 24 | audit: false, 25 | dryRun: true, 26 | fund: false, 27 | ignoreScripts: true, 28 | progress: false, 29 | save: false, 30 | saveBundle: false, 31 | silent: true, 32 | } 33 | 34 | export { Arborist, SafeArborist } 35 | 36 | export const Edge: EdgeClass = UntypedEdge 37 | 38 | export const Node: NodeClass = UntypedNode 39 | 40 | export const OverrideSet: OverrideSetClass = UntypedOverrideSet 41 | 42 | export function installSafeArborist() { 43 | // Override '@npmcli/arborist' module exports with patched variants based on 44 | // https://github.com/npm/cli/pull/8089. 45 | const cache: { [key: string]: any } = require.cache 46 | cache[getArboristClassPath()] = { exports: SafeArborist } 47 | cache[getArboristEdgeClassPath()] = { exports: Edge } 48 | cache[getArboristNodeClassPath()] = { exports: Node } 49 | cache[getArboristOverrideSetClassPath()] = { exports: OverrideSet } 50 | } 51 | -------------------------------------------------------------------------------- /src/shadow/npm/inject.mts: -------------------------------------------------------------------------------- 1 | import { installSafeArborist } from './arborist/index.mts' 2 | 3 | installSafeArborist() 4 | -------------------------------------------------------------------------------- /src/shadow/npm/link.mts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | 3 | import cmdShim from 'cmd-shim' 4 | 5 | import constants from '../../constants.mts' 6 | import { 7 | getNpmBinPath, 8 | getNpxBinPath, 9 | isNpmBinPathShadowed, 10 | isNpxBinPathShadowed, 11 | } from '../../utils/npm-paths.mts' 12 | 13 | export async function installLinks( 14 | realBinPath: string, 15 | binName: 'npm' | 'npx', 16 | ): Promise { 17 | const isNpx = binName === 'npx' 18 | // Find package manager being shadowed by this process. 19 | const binPath = isNpx ? getNpxBinPath() : getNpmBinPath() 20 | // Lazily access constants.WIN32. 21 | const { WIN32 } = constants 22 | // TODO: Is this early exit needed? 23 | if (WIN32 && binPath) { 24 | return binPath 25 | } 26 | const shadowed = isNpx ? isNpxBinPathShadowed() : isNpmBinPathShadowed() 27 | // Move our bin directory to front of PATH so its found first. 28 | if (!shadowed) { 29 | if (WIN32) { 30 | await cmdShim( 31 | // Lazily access constants.distPath. 32 | path.join(constants.distPath, `${binName}-cli.js`), 33 | path.join(realBinPath, binName), 34 | ) 35 | } 36 | const { env } = process 37 | env['PATH'] = `${realBinPath}${path.delimiter}${env['PATH']}` 38 | } 39 | return binPath 40 | } 41 | -------------------------------------------------------------------------------- /src/types.mts: -------------------------------------------------------------------------------- 1 | export type StringKeyValueObject = { [key: string]: string } 2 | 3 | export type OutputKind = 'json' | 'markdown' | 'text' 4 | 5 | // CResult is akin to the "Result" or "Outcome" or "Either" pattern. 6 | // Main difference might be that it's less strict about the error side of 7 | // things, but still assumes a message is returned explaining the error. 8 | // "CResult" is easier to grep for than "result". Short for CliJsonResult. 9 | export type CResult = 10 | | { 11 | ok: true 12 | data: T 13 | // The message prop may contain warnings that we want to convey. 14 | message?: string 15 | } 16 | | { 17 | ok: false 18 | // This should be set to process.exitCode if this 19 | // payload is actually displayed to the user. 20 | // Defaults to 1 if not set. 21 | code?: number 22 | // Short message, for non-json this would show in 23 | // the red banner part of an error message. 24 | message: string 25 | // Full explanation. Shown after the red banner of 26 | // a non-json error message. Optional. 27 | cause?: string 28 | // If set, this may conform to the actual payload. 29 | data?: unknown 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/agent.mts: -------------------------------------------------------------------------------- 1 | import { spawn } from '@socketsecurity/registry/lib/spawn' 2 | import { Spinner } from '@socketsecurity/registry/lib/spinner' 3 | 4 | import constants from '../constants.mts' 5 | import { cmdFlagsToString } from './cmd.mts' 6 | import { safeNpmInstall } from '../shadow/npm/install.mts' 7 | 8 | import type { EnvDetails } from './package-environment.mts' 9 | 10 | const { NPM, PNPM } = constants 11 | 12 | type SpawnOption = Exclude[2], undefined> 13 | 14 | export type AgentInstallOptions = SpawnOption & { 15 | args?: string[] | readonly string[] | undefined 16 | spinner?: Spinner | undefined 17 | } 18 | 19 | export type AgentSpawnResult = ReturnType 20 | 21 | export function runAgentInstall( 22 | pkgEnvDetails: EnvDetails, 23 | options?: AgentInstallOptions | undefined, 24 | ): AgentSpawnResult { 25 | const { agent, agentExecPath } = pkgEnvDetails 26 | // All package managers support the "install" command. 27 | if (agent === NPM) { 28 | return safeNpmInstall({ 29 | agentExecPath, 30 | ...options, 31 | }) 32 | } 33 | const { 34 | args = [], 35 | spinner, 36 | ...spawnOptions 37 | } = { __proto__: null, ...options } as AgentInstallOptions 38 | const skipNodeHardenFlags = 39 | agent === PNPM && pkgEnvDetails.agentVersion.major < 11 40 | return spawn(agentExecPath, ['install', ...args], { 41 | // Lazily access constants.WIN32. 42 | shell: constants.WIN32, 43 | spinner, 44 | stdio: 'inherit', 45 | ...spawnOptions, 46 | env: { 47 | ...process.env, 48 | NODE_OPTIONS: cmdFlagsToString([ 49 | ...(skipNodeHardenFlags 50 | ? [] 51 | : // Lazily access constants.nodeHardenFlags. 52 | constants.nodeHardenFlags), 53 | // Lazily access constants.nodeNoWarningsFlags. 54 | ...constants.nodeNoWarningsFlags, 55 | ]), 56 | ...spawnOptions.env, 57 | }, 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /src/utils/alert/fix.mts: -------------------------------------------------------------------------------- 1 | import { createEnum } from '../objects.mts' 2 | 3 | export const ALERT_FIX_TYPE = createEnum({ 4 | cve: 'cve', 5 | remove: 'remove', 6 | upgrade: 'upgrade', 7 | }) 8 | -------------------------------------------------------------------------------- /src/utils/check-input.mts: -------------------------------------------------------------------------------- 1 | import colors from 'yoctocolors-cjs' 2 | 3 | import { logger } from '@socketsecurity/registry/lib/logger' 4 | 5 | import { failMsgWithBadge } from './fail-msg-with-badge.mts' 6 | import { serializeResultJson } from './serialize-result-json.mts' 7 | 8 | import type { OutputKind } from '../types.mts' 9 | 10 | export function checkCommandInput( 11 | outputKind: OutputKind, 12 | ...checks: Array<{ 13 | fail: string 14 | message: string 15 | pass: string 16 | test: boolean 17 | nook?: boolean | undefined 18 | }> 19 | ): boolean { 20 | if (checks.every(d => d.test)) { 21 | return true 22 | } 23 | 24 | const msg = ['Please review the input requirements and try again', ''] 25 | for (const d of checks) { 26 | // If nook, then ignore when test is ok 27 | if (d.nook && d.test) { 28 | continue 29 | } 30 | const lines = d.message.split('\n') 31 | const { length: lineCount } = lines 32 | if (!lineCount) { 33 | continue 34 | } 35 | // If the message has newlines then format the first line with the input 36 | // expectation and the rest indented below it. 37 | msg.push( 38 | ` - ${lines[0]} (${d.test ? colors.green(d.pass) : colors.red(d.fail)})`, 39 | ) 40 | if (lineCount > 1) { 41 | msg.push(...lines.slice(1).map(str => ` ${str}`)) 42 | } 43 | msg.push('') 44 | } 45 | 46 | // Use exit status of 2 to indicate incorrect usage, generally invalid 47 | // options or missing arguments. 48 | // https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html 49 | process.exitCode = 2 50 | 51 | if (outputKind === 'json') { 52 | logger.log( 53 | serializeResultJson({ 54 | ok: false, 55 | message: 'Input error', 56 | data: msg.join('\n'), 57 | }), 58 | ) 59 | } else { 60 | logger.fail(failMsgWithBadge('Input error', msg.join('\n'))) 61 | } 62 | 63 | return false 64 | } 65 | -------------------------------------------------------------------------------- /src/utils/cmd.mts: -------------------------------------------------------------------------------- 1 | const helpFlags = new Set(['--help', '-h']) 2 | 3 | export function cmdFlagsToString(args: string[]) { 4 | const result = [] 5 | for (let i = 0, { length } = args; i < length; i += 1) { 6 | if (args[i]!.startsWith('--')) { 7 | // Check if the next item exists and is NOT another flag. 8 | if (i + 1 < length && !args[i + 1]!.startsWith('--')) { 9 | result.push(`${args[i]}=${args[i + 1]}`) 10 | i += 1 11 | } else { 12 | result.push(args[i]) 13 | } 14 | } 15 | } 16 | return result.join(' ') 17 | } 18 | 19 | export function cmdPrefixMessage(cmdName: string, text: string): string { 20 | const cmdPrefix = cmdName ? `${cmdName}: ` : '' 21 | return `${cmdPrefix}${text}` 22 | } 23 | 24 | export function isHelpFlag(cmdArg: string) { 25 | return helpFlags.has(cmdArg) 26 | } 27 | -------------------------------------------------------------------------------- /src/utils/color-or-markdown.mts: -------------------------------------------------------------------------------- 1 | import terminalLink from 'terminal-link' 2 | import colors from 'yoctocolors-cjs' 3 | 4 | import indentString from '@socketregistry/indent-string/index.cjs' 5 | 6 | export class ColorOrMarkdown { 7 | public useMarkdown: boolean 8 | 9 | constructor(useMarkdown: boolean) { 10 | this.useMarkdown = !!useMarkdown 11 | } 12 | 13 | bold(text: string): string { 14 | return this.useMarkdown ? `**${text}**` : colors.bold(`${text}`) 15 | } 16 | 17 | header(text: string, level = 1): string { 18 | return this.useMarkdown 19 | ? `\n${''.padStart(level, '#')} ${text}\n` 20 | : colors.underline(`\n${level === 1 ? colors.bold(text) : text}\n`) 21 | } 22 | 23 | hyperlink( 24 | text: string, 25 | url: string | undefined, 26 | { 27 | fallback = true, 28 | fallbackToUrl, 29 | }: { 30 | fallback?: boolean | undefined 31 | fallbackToUrl?: boolean | undefined 32 | } = {}, 33 | ) { 34 | if (url) { 35 | return this.useMarkdown 36 | ? `[${text}](${url})` 37 | : terminalLink(text, url, { 38 | fallback: fallbackToUrl ? (_text, url) => url : fallback, 39 | }) 40 | } 41 | return text 42 | } 43 | 44 | indent( 45 | ...args: Parameters 46 | ): ReturnType { 47 | return indentString(...args) 48 | } 49 | 50 | italic(text: string): string { 51 | return this.useMarkdown ? `_${text}_` : colors.italic(`${text}`) 52 | } 53 | 54 | json(value: any): string { 55 | return this.useMarkdown 56 | ? '```json\n' + JSON.stringify(value) + '\n```' 57 | : JSON.stringify(value) 58 | } 59 | 60 | list(items: string[]): string { 61 | const indentedContent = items.map(item => this.indent(item).trimStart()) 62 | return this.useMarkdown 63 | ? `* ${indentedContent.join('\n* ')}\n` 64 | : `${indentedContent.join('\n')}\n` 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/utils/config.test.mts: -------------------------------------------------------------------------------- 1 | import { beforeEach, describe, expect, it } from 'vitest' 2 | 3 | import { overrideCachedConfig, updateConfigValue } from './config.mts' 4 | 5 | describe('utils/config', () => { 6 | describe('updateConfigValue', () => { 7 | beforeEach(() => { 8 | overrideCachedConfig({}) 9 | }) 10 | 11 | it('should return object for applying a change', () => { 12 | expect( 13 | updateConfigValue('defaultOrg', 'fake_test_org'), 14 | ).toMatchInlineSnapshot(` 15 | { 16 | "data": "Change applied but not persisted; current config is overridden through env var or flag", 17 | "message": "Config key 'defaultOrg' was updated", 18 | "ok": true, 19 | } 20 | `) 21 | }) 22 | 23 | it('should warn for invalid key', () => { 24 | expect( 25 | updateConfigValue( 26 | // @ts-ignore 27 | 'nawthiswontwork', 28 | 'fake_test_org', 29 | ), 30 | ).toMatchInlineSnapshot(` 31 | { 32 | "data": undefined, 33 | "message": "Invalid config key: nawthiswontwork", 34 | "ok": false, 35 | } 36 | `) 37 | }) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /src/utils/determine-org-slug.mts: -------------------------------------------------------------------------------- 1 | import { logger } from '@socketsecurity/registry/lib/logger' 2 | 3 | import { getConfigValueOrUndef, isTestingV1 } from './config.mts' 4 | import { suggestOrgSlug } from '../commands/scan/suggest-org-slug.mts' 5 | 6 | export async function determineOrgSlug( 7 | orgFlag: string, 8 | firstArg: string, 9 | interactive: boolean, 10 | dryRun: boolean, 11 | ): Promise<[string, string | undefined]> { 12 | const defaultOrgSlug = getConfigValueOrUndef('defaultOrg') 13 | let orgSlug = String(orgFlag || defaultOrgSlug || '') 14 | if (!orgSlug) { 15 | if (isTestingV1()) { 16 | // ask from server 17 | logger.warn( 18 | 'Missing the org slug and no --org flag set. Trying to auto-discover the org now...', 19 | ) 20 | logger.info( 21 | 'Note: you can set the default org slug to prevent this issue. You can also override all that with the --org flag.', 22 | ) 23 | if (dryRun) { 24 | logger.fail('Skipping auto-discovery of org in dry-run mode') 25 | } else if (!interactive) { 26 | logger.fail('Skipping auto-discovery of org when interactive = false') 27 | } else { 28 | orgSlug = (await suggestOrgSlug()) || '' 29 | } 30 | } else { 31 | orgSlug = firstArg || '' 32 | } 33 | } 34 | 35 | return [orgSlug, defaultOrgSlug] 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/errors.mts: -------------------------------------------------------------------------------- 1 | import { setTimeout as wait } from 'node:timers/promises' 2 | 3 | import { debugFn } from '@socketsecurity/registry/lib/debug' 4 | 5 | import constants from '../constants.mts' 6 | 7 | const { 8 | kInternalsSymbol, 9 | [kInternalsSymbol as unknown as 'Symbol(kInternalsSymbol)']: { getSentry }, 10 | } = constants 11 | 12 | type EventHintOrCaptureContext = { [key: string]: any } | Function 13 | 14 | export class AuthError extends Error {} 15 | 16 | export class InputError extends Error { 17 | public body: string | undefined 18 | 19 | constructor(message: string, body?: string) { 20 | super(message) 21 | this.body = body 22 | } 23 | } 24 | 25 | export async function captureException( 26 | exception: unknown, 27 | hint?: EventHintOrCaptureContext | undefined, 28 | ): Promise { 29 | const result = captureExceptionSync(exception, hint) 30 | // "Sleep" for a second, just in case, hopefully enough time to initiate fetch. 31 | await wait(1000) 32 | return result 33 | } 34 | 35 | export function captureExceptionSync( 36 | exception: unknown, 37 | hint?: EventHintOrCaptureContext | undefined, 38 | ): string { 39 | const Sentry = getSentry() 40 | if (!Sentry) { 41 | return '' 42 | } 43 | debugFn('send: exception to Sentry') 44 | return Sentry.captureException(exception, hint) as string 45 | } 46 | 47 | export function isErrnoException( 48 | value: unknown, 49 | ): value is NodeJS.ErrnoException { 50 | if (!(value instanceof Error)) { 51 | return false 52 | } 53 | return (value as NodeJS.ErrnoException).code !== undefined 54 | } 55 | -------------------------------------------------------------------------------- /src/utils/fail-msg-with-badge.mts: -------------------------------------------------------------------------------- 1 | import colors from 'yoctocolors-cjs' 2 | 3 | export function failMsgWithBadge( 4 | badge: string, 5 | msg: string | undefined, 6 | ): string { 7 | return `${colors.bgRed(colors.bold(colors.white(` ${badge}${msg ? ': ' : ''}`)))}${msg ? ' ' + colors.bold(msg) : ''}` 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/get-output-kind.mts: -------------------------------------------------------------------------------- 1 | import type { OutputKind } from '../types.mts' 2 | 3 | export function getOutputKind(json: unknown, markdown: unknown): OutputKind { 4 | if (json) { 5 | return 'json' 6 | } 7 | if (markdown) { 8 | return 'markdown' 9 | } 10 | return 'text' 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/map-to-object.mts: -------------------------------------------------------------------------------- 1 | interface NestedRecord { 2 | [key: string]: T | NestedRecord 3 | } 4 | 5 | /** 6 | * Convert a Map to a nested object of similar shape. 7 | * The goal is to serialize it with JSON.stringify, which Map can't do. 8 | */ 9 | export function mapToObject( 10 | map: Map>>, 11 | ): NestedRecord { 12 | return Object.fromEntries( 13 | Array.from(map.entries()).map(([k, v]) => [ 14 | k, 15 | v instanceof Map ? mapToObject(v) : v, 16 | ]), 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/markdown.test.mts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { mdTableOfPairs } from './markdown.mts' 4 | 5 | describe('markdown', () => { 6 | describe('mdTableOfPairs', () => { 7 | it('should convert an array of tuples to markdown', () => { 8 | expect( 9 | mdTableOfPairs( 10 | [ 11 | ['apple', 'green'], 12 | ['banana', 'yellow'], 13 | ['orange', 'orange'], 14 | ], 15 | ['name', 'color'], 16 | ), 17 | ).toMatchInlineSnapshot(` 18 | "| ------ | ------ | 19 | | name | color | 20 | | ------ | ------ | 21 | | apple | green | 22 | | banana | yellow | 23 | | orange | orange | 24 | | ------ | ------ |" 25 | `) 26 | }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /src/utils/npm-package-arg.mts: -------------------------------------------------------------------------------- 1 | import npmPackageArg from 'npm-package-arg' 2 | 3 | export type { 4 | AliasResult, 5 | FileResult, 6 | HostedGit, 7 | HostedGitResult, 8 | RegistryResult, 9 | Result, 10 | URLResult, 11 | } from 'npm-package-arg' 12 | 13 | export function npa( 14 | ...args: Parameters 15 | ): ReturnType | null { 16 | try { 17 | return Reflect.apply(npmPackageArg, undefined, args) 18 | } catch {} 19 | return null 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/objects.mts: -------------------------------------------------------------------------------- 1 | export function createEnum>( 2 | obj: T, 3 | ): Readonly { 4 | return Object.freeze({ __proto__: null, ...obj }) as any 5 | } 6 | 7 | export function pick, K extends keyof T>( 8 | input: T, 9 | keys: K[] | readonly K[], 10 | ): Pick { 11 | const result: Partial> = {} 12 | for (const key of keys) { 13 | result[key] = input[key] 14 | } 15 | return result as Pick 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/output-formatting.mts: -------------------------------------------------------------------------------- 1 | import type { MeowFlags } from '../flags.mts' 2 | 3 | type HelpListOptions = { 4 | keyPrefix?: string | undefined 5 | padName?: number | undefined 6 | } 7 | 8 | type ListDescription = 9 | | { description: string } 10 | | { description: string; hidden: boolean } 11 | 12 | export function getFlagListOutput( 13 | list: MeowFlags, 14 | indent: number, 15 | { keyPrefix = '--', padName } = {} as HelpListOptions, 16 | ): string { 17 | return getHelpListOutput( 18 | { 19 | ...list, 20 | }, 21 | indent, 22 | { keyPrefix, padName }, 23 | ) 24 | } 25 | 26 | export function getHelpListOutput( 27 | list: Record, 28 | indent: number, 29 | { keyPrefix = '', padName = 18 } = {} as HelpListOptions, 30 | ): string { 31 | let result = '' 32 | const names = Object.keys(list).sort() 33 | for (const name of names) { 34 | const entry = list[name] 35 | if (entry && 'hidden' in entry && entry?.hidden) { 36 | continue 37 | } 38 | const description = 39 | (typeof entry === 'object' ? entry.description : entry) || '' 40 | result += 41 | ''.padEnd(indent) + 42 | (keyPrefix + name).padEnd(padName) + 43 | description + 44 | '\n' 45 | } 46 | return result.trim() || '(none)' 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/purl.mts: -------------------------------------------------------------------------------- 1 | import { PackageURL } from '@socketregistry/packageurl-js' 2 | 3 | import type { SocketArtifact } from './alert/artifact.mts' 4 | 5 | export function getPurlObject(purl: string | PackageURL | SocketArtifact) { 6 | return typeof purl === 'string' ? PackageURL.fromString(purl) : purl 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/semver.mts: -------------------------------------------------------------------------------- 1 | import semver from 'semver' 2 | 3 | import type { SemVer } from 'semver' 4 | 5 | export const RangeStyles = ['caret', 'gt', 'lt', 'pin', 'preserve', 'tilde'] 6 | 7 | export type RangeStyle = 8 | | 'caret' 9 | | 'gt' 10 | | 'gte' 11 | | 'lt' 12 | | 'lte' 13 | | 'pin' 14 | | 'preserve' 15 | | 'tilde' 16 | 17 | export type { SemVer } 18 | 19 | export function applyRange( 20 | refRange: string, 21 | version: string, 22 | style: RangeStyle = 'preserve', 23 | ): string { 24 | switch (style) { 25 | case 'caret': 26 | return `^${version}` 27 | case 'gt': 28 | return `>${version}` 29 | case 'gte': 30 | return `>=${version}` 31 | case 'lt': 32 | return `<${version}` 33 | case 'lte': 34 | return `<=${version}` 35 | case 'preserve': { 36 | const range = new semver.Range(refRange) 37 | const { raw } = range 38 | const comparators = [...range.set].flat() 39 | const { length } = comparators 40 | if (length === 1) { 41 | const char = /^[<>]=?/.exec(raw)?.[0] 42 | if (char) { 43 | return `${char}${version}` 44 | } 45 | } else if (length === 2) { 46 | const char = /^[~^]/.exec(raw)?.[0] 47 | if (char) { 48 | return `${char}${version}` 49 | } 50 | } 51 | return version 52 | } 53 | case 'tilde': 54 | return `~${version}` 55 | case 'pin': 56 | default: 57 | return version 58 | } 59 | } 60 | 61 | export function getMajor(version: unknown): number | null { 62 | try { 63 | const coerced = semver.coerce(version as string) 64 | return coerced ? semver.major(coerced) : null 65 | } catch {} 66 | return null 67 | } 68 | 69 | export function getMinVersion(range: unknown): SemVer | null { 70 | try { 71 | return semver.minVersion(range as string) 72 | } catch {} 73 | return null 74 | } 75 | -------------------------------------------------------------------------------- /src/utils/serialize-result-json.mts: -------------------------------------------------------------------------------- 1 | import { debugFn } from '@socketsecurity/registry/lib/debug' 2 | import { logger } from '@socketsecurity/registry/lib/logger' 3 | 4 | import type { CResult } from '../types.mts' 5 | 6 | // Serialize the final result object before printing it 7 | // All commands that support the --json flag should call this before printing 8 | export function serializeResultJson(data: CResult): string { 9 | if (typeof data !== 'object' || !data) { 10 | process.exitCode = 1 11 | debugFn('typeof data=', typeof data) 12 | 13 | if (typeof data !== 'object' && data) { 14 | debugFn('data:\n', data) 15 | } 16 | 17 | // We should not allow the json value to be "null", or a boolean/number/string, 18 | // even if they are valid "json". 19 | const message = 20 | 'There was a problem converting the data set to JSON. The JSON was not an object. Please try again without --json' 21 | 22 | return ( 23 | JSON.stringify({ 24 | ok: false, 25 | message: 'Unable to serialize JSON', 26 | data: message, 27 | }).trim() + '\n' 28 | ) 29 | } 30 | 31 | try { 32 | return JSON.stringify(data, null, 2).trim() + '\n' 33 | } catch (e) { 34 | debugFn('catch: unexpected\n', e) 35 | process.exitCode = 1 36 | 37 | // This could be caused by circular references, which is an "us" problem 38 | const message = 39 | 'There was a problem converting the data set to JSON. Please try again without --json' 40 | logger.fail(message) 41 | return ( 42 | JSON.stringify({ 43 | ok: false, 44 | message: 'Unable to serialize JSON', 45 | data: message, 46 | }).trim() + '\n' 47 | ) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/utils/socket-url.mts: -------------------------------------------------------------------------------- 1 | import constants from '../constants.mts' 2 | import { getPurlObject } from './purl.mts' 3 | 4 | import type { PURL_Type, SocketArtifact } from './alert/artifact.mts' 5 | import type { PackageURL } from '@socketregistry/packageurl-js' 6 | 7 | const { SOCKET_WEBSITE_URL } = constants 8 | 9 | export function getPkgFullNameFromPurl( 10 | purl: string | PackageURL | SocketArtifact, 11 | ): string { 12 | const purlObj = getPurlObject(purl) 13 | const { name, namespace } = purlObj 14 | return namespace 15 | ? `${namespace}${purlObj.type === 'maven' ? ':' : '/'}${name}` 16 | : name 17 | } 18 | 19 | export function getSocketDevAlertUrl(alertType: string): string { 20 | return `${SOCKET_WEBSITE_URL}/alerts/${alertType}` 21 | } 22 | 23 | export function getSocketDevPackageOverviewUrlFromPurl( 24 | purl: string | PackageURL | SocketArtifact, 25 | ): string { 26 | const purlObj = getPurlObject(purl) 27 | const fullName = getPkgFullNameFromPurl(purlObj) 28 | return getSocketDevPackageOverviewUrl( 29 | purlObj.type as PURL_Type, 30 | fullName, 31 | purlObj.version, 32 | ) 33 | } 34 | 35 | export function getSocketDevPackageOverviewUrl( 36 | ecosystem: PURL_Type, 37 | fullName: string, 38 | version?: string | undefined, 39 | ): string { 40 | const url = `${SOCKET_WEBSITE_URL}/${ecosystem}/package/${fullName}` 41 | return ecosystem === 'golang' 42 | ? `${url}${version ? `?section=overview&version=${version}` : ''}` 43 | : `${url}${version ? `/overview/${version}` : ''}` 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/spec.mts: -------------------------------------------------------------------------------- 1 | import semver from 'semver' 2 | 3 | import { PackageURL } from '@socketregistry/packageurl-js' 4 | 5 | import { stripPnpmPeerSuffix } from './pnpm.mts' 6 | 7 | export function idToNpmPurl(id: string): string { 8 | return `pkg:npm/${id}` 9 | } 10 | 11 | export function idToPurl(id: string, type: string): string { 12 | return `pkg:${type}/${id}` 13 | } 14 | 15 | export function resolvePackageVersion(purlObj: PackageURL): string { 16 | const { version } = purlObj 17 | if (!version) { 18 | return '' 19 | } 20 | const { type } = purlObj 21 | return ( 22 | semver.coerce(type === 'npm' ? stripPnpmPeerSuffix(version) : version) 23 | ?.version ?? '' 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/strings.mts: -------------------------------------------------------------------------------- 1 | export function stringJoinWithSeparateFinalSeparator( 2 | list: string[], 3 | separator: string = ' and ', 4 | ): string { 5 | const values = list.filter(Boolean) 6 | const { length } = values 7 | if (!length) { 8 | return '' 9 | } 10 | if (length === 1) { 11 | return values[0]! 12 | } 13 | const finalValue = values.pop() 14 | return `${values.join(', ')}${separator}${finalValue}` 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/tildify.mts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | 3 | import { escapeRegExp } from '@socketsecurity/registry/lib/regexps' 4 | 5 | import constants from '../constants.mts' 6 | 7 | // Replace the start of a path with ~/ when it starts with your home dir. 8 | // A common way to abbreviate the user home dir (though not strictly posix). 9 | export function tildify(cwd: string) { 10 | return cwd.replace( 11 | new RegExp(`^${escapeRegExp(constants.homePath)}(?:${path.sep}|$)`, 'i'), 12 | '~/', 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/translations.mts: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'node:module' 2 | import path from 'node:path' 3 | 4 | import constants from '../constants.mts' 5 | 6 | const require = createRequire(import.meta.url) 7 | 8 | let _translations: typeof import('../../translations.json') | undefined 9 | 10 | export function getTranslations() { 11 | if (_translations === undefined) { 12 | _translations = require( 13 | // Lazily access constants.rootPath. 14 | path.join(constants.rootPath, 'translations.json'), 15 | ) 16 | } 17 | return _translations! 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/walk-nested-map.mts: -------------------------------------------------------------------------------- 1 | type NestedMap = Map> 2 | 3 | export function* walkNestedMap( 4 | map: NestedMap, 5 | keys: string[] = [], 6 | ): Generator<{ keys: string[]; value: T }> { 7 | for (const [key, value] of map.entries()) { 8 | if (value instanceof Map) { 9 | yield* walkNestedMap(value as NestedMap, keys.concat(key)) 10 | } else { 11 | yield { keys: keys.concat(key), value: value } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/errors.test.mts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'node:fs' 2 | import path from 'node:path' 3 | import { fileURLToPath } from 'node:url' 4 | 5 | import { describe, expect, it } from 'vitest' 6 | 7 | import { isErrnoException } from '../src/utils/errors.mts' 8 | 9 | const __filename = fileURLToPath(import.meta.url) 10 | const __dirname = path.dirname(__filename) 11 | 12 | const testPath = __dirname 13 | 14 | describe('Error Narrowing', () => { 15 | it('should properly detect node errors', () => { 16 | try { 17 | readFileSync(path.join(testPath, 'enoent')) 18 | } catch (e) { 19 | expect(isErrnoException(e)).toBe(true) 20 | } 21 | }) 22 | it('should properly only detect node errors', () => { 23 | expect(isErrnoException(new Error())).toBe(false) 24 | expect(isErrnoException({ ...new Error() })).toBe(false) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /test/socket-npm-fixtures/lacking-typosquat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": {} 3 | } 4 | -------------------------------------------------------------------------------- /test/socket-npm-fixtures/manifest-conda/environment.yml: -------------------------------------------------------------------------------- 1 | name: my_stuff 2 | 3 | channels: 4 | - conda-thing 5 | - defaults 6 | dependencies: 7 | - python=3.8 8 | - pandas=1.3.4 9 | - numpy=1.19.0 10 | - scipy 11 | - mkl-service 12 | - libpython 13 | - m2w64-toolchain 14 | - pytest 15 | - requests 16 | - pip 17 | - pip: 18 | - qgrid==1.3.0 19 | - mplstereonet 20 | - pyqt5 21 | - gempy==2.1.0 22 | -------------------------------------------------------------------------------- /test/socket-npm-fixtures/npm10/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm10", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "dependencies": { 9 | "npm": "^10.9.2" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/socket-npm-fixtures/npm11/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm11", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "dependencies": { 9 | "npm": "^11.2.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/socket-npm-fixtures/npm9/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm9", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "dependencies": { 9 | "npm": "^9.9.4" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/socket-npm-fixtures/sjtest/socket.json: -------------------------------------------------------------------------------- 1 | { 2 | " _____ _ _ ": "Local config file for Socket CLI tool ( https://npmjs.org/socket ), to work with https://socket.dev", 3 | "| __|___ ___| |_ ___| |_ ": " The config in this file is used to set as defaults for flags or cmmand args when using the CLI", 4 | "|__ | . | _| '_| -_| _| ": " in this dir, often a repo root. You can choose commit or .ignore this file, both works.", 5 | "|_____|___|___|_,_|___|_|.dev": "Warning: This file may be overwritten without warning by `socket manifest setup` or other commands", 6 | "version": 1, 7 | "defaults": { 8 | "manifest": { 9 | "sbt": { 10 | "bin": "/bin/sbt", 11 | "outfile": "sbt.pom.xml", 12 | "stdout": false, 13 | "verbose": true 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.dts.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.config/tsconfig.base.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "declarationDir": "dist/types", 6 | "declarationMap": true, 7 | "emitDeclarationOnly": true, 8 | "module": "preserve", 9 | "moduleResolution": "bundler", 10 | "noEmit": false, 11 | "outDir": "dist/types", 12 | "rootDir": "src" 13 | }, 14 | // @typescript/native-preview currently cannot resolve paths for "include" if 15 | // the config is not in the root of the repository. This is why tsconfig.dts.json 16 | // is in the repository root with ./tsconfig.json instead of the ./config folder. 17 | "include": ["src/**/*.mts"], 18 | "exclude": ["test", "**/*.test.mts"] 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.config/tsconfig.base.json", 3 | "include": ["src/**/*.mts"], 4 | "exclude": ["src/**/*.test.mts"] 5 | } 6 | -------------------------------------------------------------------------------- /vitest.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | coverage: { 6 | exclude: [ 7 | '**/{eslint,vitest}.config.*', 8 | '**/node_modules/**', 9 | '**/[.]**', 10 | '**/*.d.mts', 11 | '**/virtual:*', 12 | 'coverage/**', 13 | 'dist/**', 14 | 'scripts/**', 15 | 'src/**/types.mts', 16 | 'test/**', 17 | ], 18 | }, 19 | }, 20 | }) 21 | --------------------------------------------------------------------------------