socket 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -25,6 +25,7 @@ var registry = require('../external/@socketsecurity/registry');
25
25
  var packages = require('../external/@socketsecurity/registry/lib/packages');
26
26
  var require$$12 = require('../external/@socketsecurity/registry/lib/promises');
27
27
  var regexps = require('../external/@socketsecurity/registry/lib/regexps');
28
+ var require$$0$1 = require('node:crypto');
28
29
  var require$$1 = require('node:util');
29
30
  var os = require('node:os');
30
31
  var promises = require('node:stream/promises');
@@ -3716,68 +3717,27 @@ const cmdFix = {
3716
3717
  hidden: hidden$q,
3717
3718
  run: run$I
3718
3719
  };
3719
- async function run$I(argv, importMeta, {
3720
- parentName
3721
- }) {
3722
- const config = {
3723
- commandName: CMD_NAME$r,
3724
- description: description$x,
3725
- hidden: hidden$q,
3726
- flags: {
3727
- ...flags.commonFlags,
3728
- ...flags.outputFlags,
3729
- autoMerge: {
3730
- type: 'boolean',
3731
- default: false,
3732
- description: `Enable auto-merge for pull requests that Socket opens.\nSee ${vendor.terminalLinkExports('GitHub documentation', 'https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-auto-merge-for-pull-requests-in-your-repository')} for managing auto-merge for pull requests in your repository.`
3733
- },
3734
- autopilot: {
3735
- type: 'boolean',
3736
- default: false,
3737
- description: `Shorthand for --auto-merge --test`,
3738
- hidden: true
3739
- },
3740
- id: {
3741
- type: 'string',
3742
- default: [],
3743
- description: `Provide a list of ${vendor.terminalLinkExports('GHSA IDs', 'https://docs.github.com/en/code-security/security-advisories/working-with-global-security-advisories-from-the-github-advisory-database/about-the-github-advisory-database#about-ghsa-ids')} to compute fixes for, as either a comma separated value or as multiple flags`,
3744
- isMultiple: true
3745
- },
3746
- limit: {
3747
- type: 'number',
3748
- default: DEFAULT_LIMIT,
3749
- description: `The number of fixes to attempt at a time (default ${DEFAULT_LIMIT})`
3750
- },
3751
- maxSatisfying: {
3752
- type: 'boolean',
3753
- default: true,
3754
- description: 'Use the maximum satisfying version for dependency updates',
3755
- hidden: true
3756
- },
3757
- minSatisfying: {
3758
- type: 'boolean',
3759
- default: false,
3760
- description: 'Constrain dependency updates to the minimum satisfying version',
3761
- hidden: true
3762
- },
3763
- prCheck: {
3764
- type: 'boolean',
3765
- default: true,
3766
- description: 'Check for an existing PR before attempting a fix',
3767
- hidden: true
3768
- },
3769
- purl: {
3770
- type: 'string',
3771
- default: [],
3772
- description: `Provide a list of ${vendor.terminalLinkExports('PURLs', 'https://github.com/package-url/purl-spec?tab=readme-ov-file#purl')} to compute fixes for, as either a comma separated value or as\nmultiple flags, instead of querying the Socket API`,
3773
- isMultiple: true,
3774
- shortFlag: 'p',
3775
- hidden: true
3776
- },
3777
- rangeStyle: {
3778
- type: 'string',
3779
- default: 'preserve',
3780
- description: `
3720
+ const generalFlags$2 = {
3721
+ autoMerge: {
3722
+ type: 'boolean',
3723
+ default: false,
3724
+ description: `Enable auto-merge for pull requests that Socket opens.\nSee ${vendor.terminalLinkExports('GitHub documentation', 'https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-auto-merge-for-pull-requests-in-your-repository')} for managing auto-merge for pull requests in your repository.`
3725
+ },
3726
+ id: {
3727
+ type: 'string',
3728
+ default: [],
3729
+ description: `Provide a list of ${vendor.terminalLinkExports('GHSA IDs', 'https://docs.github.com/en/code-security/security-advisories/working-with-global-security-advisories-from-the-github-advisory-database/about-the-github-advisory-database#about-ghsa-ids')} to compute fixes for, as either a comma separated value or as multiple flags`,
3730
+ isMultiple: true
3731
+ },
3732
+ limit: {
3733
+ type: 'number',
3734
+ default: DEFAULT_LIMIT,
3735
+ description: `The number of fixes to attempt at a time (default ${DEFAULT_LIMIT})`
3736
+ },
3737
+ rangeStyle: {
3738
+ type: 'string',
3739
+ default: 'preserve',
3740
+ description: `
3781
3741
  Define how dependency version ranges are updated in package.json (default 'preserve').
3782
3742
  Available styles:
3783
3743
  * caret - Use ^ range for compatible updates (e.g. ^1.2.3)
@@ -3789,19 +3749,70 @@ Available styles:
3789
3749
  * preserve - Retain the existing version range style as-is
3790
3750
  * tilde - Use ~ range for patch/minor updates (e.g. ~1.2.3)
3791
3751
  `.trim()
3792
- },
3793
- test: {
3794
- type: 'boolean',
3795
- default: false,
3796
- description: 'Verify the fix by running unit tests',
3797
- hidden: true
3798
- },
3799
- testScript: {
3800
- type: 'string',
3801
- default: 'test',
3802
- description: "The test script to run for fix attempts (default 'test')",
3803
- hidden: true
3804
- }
3752
+ }
3753
+ };
3754
+ const hiddenFlags = {
3755
+ autopilot: {
3756
+ type: 'boolean',
3757
+ default: false,
3758
+ description: `Shorthand for --auto-merge --test`,
3759
+ hidden: true
3760
+ },
3761
+ ghsa: {
3762
+ ...generalFlags$2['id'],
3763
+ hidden: true
3764
+ },
3765
+ maxSatisfying: {
3766
+ type: 'boolean',
3767
+ default: true,
3768
+ description: 'Use the maximum satisfying version for dependency updates',
3769
+ hidden: true
3770
+ },
3771
+ minSatisfying: {
3772
+ type: 'boolean',
3773
+ default: false,
3774
+ description: 'Constrain dependency updates to the minimum satisfying version',
3775
+ hidden: true
3776
+ },
3777
+ prCheck: {
3778
+ type: 'boolean',
3779
+ default: true,
3780
+ description: 'Check for an existing PR before attempting a fix',
3781
+ hidden: true
3782
+ },
3783
+ purl: {
3784
+ type: 'string',
3785
+ default: [],
3786
+ description: `Provide a list of ${vendor.terminalLinkExports('PURLs', 'https://github.com/package-url/purl-spec?tab=readme-ov-file#purl')} to compute fixes for, as either a comma separated value or as\nmultiple flags`,
3787
+ isMultiple: true,
3788
+ shortFlag: 'p',
3789
+ hidden: true
3790
+ },
3791
+ test: {
3792
+ type: 'boolean',
3793
+ default: false,
3794
+ description: 'Verify the fix by running unit tests',
3795
+ hidden: true
3796
+ },
3797
+ testScript: {
3798
+ type: 'string',
3799
+ default: 'test',
3800
+ description: "The test script to run for fix attempts (default 'test')",
3801
+ hidden: true
3802
+ }
3803
+ };
3804
+ async function run$I(argv, importMeta, {
3805
+ parentName
3806
+ }) {
3807
+ const config = {
3808
+ commandName: CMD_NAME$r,
3809
+ description: description$x,
3810
+ hidden: hidden$q,
3811
+ flags: {
3812
+ ...flags.commonFlags,
3813
+ ...flags.outputFlags,
3814
+ ...generalFlags$2,
3815
+ ...hiddenFlags
3805
3816
  },
3806
3817
  help: (command, config) => `
3807
3818
  Usage
@@ -3889,7 +3900,7 @@ Available styles:
3889
3900
  // We patched in this feature with `npx custompatch meow` at
3890
3901
  // socket-cli/patches/meow#13.2.0.patch.
3891
3902
  const unknownFlags = cli.unknownFlags ?? [];
3892
- const ghsas = utils.cmdFlagValueToArray(cli.flags['ghsa']);
3903
+ const ghsas = arrays.arrayUnique([...utils.cmdFlagValueToArray(cli.flags['id']), ...utils.cmdFlagValueToArray(cli.flags['ghsa'])]);
3893
3904
  const limit = Number(cli.flags['limit']) || DEFAULT_LIMIT;
3894
3905
  const maxSatisfying = Boolean(cli.flags['maxSatisfying']);
3895
3906
  const minSatisfying = Boolean(cli.flags['minSatisfying']) || !maxSatisfying;
@@ -8662,6 +8673,30 @@ const cmdPackage = {
8662
8673
  }
8663
8674
  };
8664
8675
 
8676
+ const PatchRecordSchema = vendor.object({
8677
+ exportedAt: vendor.string(),
8678
+ files: vendor.record(vendor.string(),
8679
+ // File path
8680
+ vendor.object({
8681
+ beforeHash: vendor.string(),
8682
+ afterHash: vendor.string()
8683
+ })),
8684
+ vulnerabilities: vendor.record(vendor.string(),
8685
+ // Vulnerability ID like "GHSA-jrhj-2j3q-xf3v"
8686
+ vendor.object({
8687
+ cves: vendor.array(vendor.string()),
8688
+ summary: vendor.string(),
8689
+ severity: vendor.string(),
8690
+ description: vendor.string(),
8691
+ patchExplanation: vendor.string()
8692
+ }))
8693
+ });
8694
+ const PatchManifestSchema = vendor.object({
8695
+ patches: vendor.record(
8696
+ // Package identifier like "npm:simplehttpserver@0.0.6".
8697
+ vendor.string(), PatchRecordSchema)
8698
+ });
8699
+
8665
8700
  async function outputPatchResult(result, outputKind) {
8666
8701
  if (!result.ok) {
8667
8702
  process.exitCode = result.code ?? 1;
@@ -8689,21 +8724,221 @@ async function outputPatchResult(result, outputKind) {
8689
8724
  logger.logger.success('Patch command completed!');
8690
8725
  }
8691
8726
 
8727
+ async function applyNPMPatches(patches, dryRun, socketDir, packages) {
8728
+ const patchLookup = new Map();
8729
+ for (const patchInfo of patches) {
8730
+ const {
8731
+ purl
8732
+ } = patchInfo;
8733
+ const fullName = purl.namespace ? `@${purl.namespace}/${purl.name}` : purl.name;
8734
+ const lookupKey = `${fullName}@${purl.version}`;
8735
+ patchLookup.set(lookupKey, patchInfo);
8736
+ }
8737
+ const nodeModulesFolders = await findNodeModulesFolders(process.cwd());
8738
+ logger.logger.log(`Found ${nodeModulesFolders.length} node_modules folders`);
8739
+ for (const nodeModulesPath of nodeModulesFolders) {
8740
+ try {
8741
+ // eslint-disable-next-line no-await-in-loop
8742
+ const entries = await fs$1.promises.readdir(nodeModulesPath);
8743
+ for (const entry of entries) {
8744
+ const entryPath = path.join(nodeModulesPath, entry);
8745
+ if (entry.startsWith('@')) {
8746
+ try {
8747
+ // eslint-disable-next-line no-await-in-loop
8748
+ const scopedEntries = await fs$1.promises.readdir(entryPath);
8749
+ for (const scopedEntry of scopedEntries) {
8750
+ const packagePath = path.join(entryPath, scopedEntry);
8751
+ // eslint-disable-next-line no-await-in-loop
8752
+ const pkg = await readPackageJson(packagePath);
8753
+ if (pkg) {
8754
+ // Skip if specific packages requested and this isn't one of them
8755
+ if (packages.length > 0 && !packages.includes(pkg.name)) {
8756
+ continue;
8757
+ }
8758
+ const lookupKey = `${pkg.name}@${pkg.version}`;
8759
+ const patchInfo = patchLookup.get(lookupKey);
8760
+ if (patchInfo) {
8761
+ logger.logger.log(`Found match: ${pkg.name}@${pkg.version} at ${packagePath}`);
8762
+ logger.logger.log(` Patch key: ${patchInfo.key}`);
8763
+ logger.logger.log(` Processing files:`);
8764
+ for (const [fileName, fileInfo] of Object.entries(patchInfo.patch.files)) {
8765
+ // eslint-disable-next-line no-await-in-loop
8766
+ await processFilePatch(packagePath, fileName, fileInfo, dryRun, socketDir);
8767
+ }
8768
+ }
8769
+ }
8770
+ }
8771
+ } catch {
8772
+ // Ignore errors reading scoped packages
8773
+ }
8774
+ } else {
8775
+ // eslint-disable-next-line no-await-in-loop
8776
+ const pkg = await readPackageJson(entryPath);
8777
+ if (pkg) {
8778
+ // Skip if specific packages requested and this isn't one of them
8779
+ if (packages.length > 0 && !packages.includes(pkg.name)) {
8780
+ continue;
8781
+ }
8782
+ const lookupKey = `${pkg.name}@${pkg.version}`;
8783
+ const patchInfo = patchLookup.get(lookupKey);
8784
+ if (patchInfo) {
8785
+ logger.logger.log(`Found match: ${pkg.name}@${pkg.version} at ${entryPath}`);
8786
+ logger.logger.log(` Patch key: ${patchInfo.key}`);
8787
+ logger.logger.log(` Processing files:`);
8788
+ for (const [fileName, fileInfo] of Object.entries(patchInfo.patch.files)) {
8789
+ // eslint-disable-next-line no-await-in-loop
8790
+ await processFilePatch(entryPath, fileName, fileInfo, dryRun, socketDir);
8791
+ }
8792
+ }
8793
+ }
8794
+ }
8795
+ }
8796
+ } catch (error) {
8797
+ logger.logger.error(`Error processing ${nodeModulesPath}:`, error);
8798
+ }
8799
+ }
8800
+ }
8801
+ async function computeSHA256(filePath) {
8802
+ try {
8803
+ const content = await fs$1.promises.readFile(filePath);
8804
+ const hash = require$$0$1.createHash('sha256');
8805
+ hash.update(content);
8806
+ return hash.digest('hex');
8807
+ } catch {
8808
+ return null;
8809
+ }
8810
+ }
8811
+ async function findNodeModulesFolders(rootDir) {
8812
+ const nodeModulesPaths = [];
8813
+ async function searchDir(dir) {
8814
+ try {
8815
+ const entries = await fs$1.promises.readdir(dir);
8816
+ for (const entry of entries) {
8817
+ if (entry.startsWith('.') || entry === 'dist' || entry === 'build') {
8818
+ continue;
8819
+ }
8820
+ const fullPath = path.join(dir, entry);
8821
+ // eslint-disable-next-line no-await-in-loop
8822
+ const stats = await fs$1.promises.stat(fullPath);
8823
+ if (stats.isDirectory()) {
8824
+ if (entry === 'node_modules') {
8825
+ nodeModulesPaths.push(fullPath);
8826
+ } else {
8827
+ // eslint-disable-next-line no-await-in-loop
8828
+ await searchDir(fullPath);
8829
+ }
8830
+ }
8831
+ }
8832
+ } catch (error) {
8833
+ // Ignore permission errors or missing directories
8834
+ }
8835
+ }
8836
+ await searchDir(rootDir);
8837
+ return nodeModulesPaths;
8838
+ }
8839
+ function parsePURL(purlString) {
8840
+ const [ecosystem, rest] = purlString.split(':', 2);
8841
+ const [nameAndNamespace, version] = (rest ?? '').split('@', 2);
8842
+ let namespace;
8843
+ let name;
8844
+ if (ecosystem === 'npm' && nameAndNamespace?.startsWith('@')) {
8845
+ const parts = nameAndNamespace.split('/');
8846
+ namespace = parts[0]?.substring(1);
8847
+ name = parts.slice(1).join('/');
8848
+ } else {
8849
+ name = nameAndNamespace ?? '';
8850
+ }
8851
+ return {
8852
+ type: ecosystem ?? 'unknown',
8853
+ namespace: namespace ?? '',
8854
+ name: name ?? '',
8855
+ version: version ?? '0.0.0'
8856
+ };
8857
+ }
8858
+ async function processFilePatch(packagePath, fileName, fileInfo, dryRun, socketDir) {
8859
+ const filePath = path.join(packagePath, fileName);
8860
+ if (!fs$1.existsSync(filePath)) {
8861
+ logger.logger.log(`File not found: ${fileName}`);
8862
+ return;
8863
+ }
8864
+ const currentHash = await computeSHA256(filePath);
8865
+ if (!currentHash) {
8866
+ logger.logger.log(`Failed to compute hash for: ${fileName}`);
8867
+ return;
8868
+ }
8869
+ if (currentHash === fileInfo.beforeHash) {
8870
+ logger.logger.success(`File matches expected hash: ${fileName}`);
8871
+ logger.logger.log(`Current hash: ${currentHash}`);
8872
+ logger.logger.log(`Ready to patch to: ${fileInfo.afterHash}`);
8873
+ if (!dryRun) {
8874
+ const blobPath = path.join(socketDir, 'blobs', fileInfo.afterHash);
8875
+ if (!fs$1.existsSync(blobPath)) {
8876
+ logger.logger.fail(`Error: Patch file not found at ${blobPath}`);
8877
+ return;
8878
+ }
8879
+ try {
8880
+ await fs$1.promises.copyFile(blobPath, filePath);
8881
+ logger.logger.success(`Patch applied successfully`);
8882
+ } catch (error) {
8883
+ logger.logger.log(`Error applying patch: ${error}`);
8884
+ }
8885
+ } else {
8886
+ logger.logger.log(`(dry run - no changes made)`);
8887
+ }
8888
+ } else if (currentHash === fileInfo.afterHash) {
8889
+ logger.logger.success(`File already patched: ${fileName}`);
8890
+ logger.logger.log(`Current hash: ${currentHash}`);
8891
+ } else {
8892
+ logger.logger.fail(`File hash mismatch: ${fileName}`);
8893
+ logger.logger.log(`Expected: ${fileInfo.beforeHash}`);
8894
+ logger.logger.log(`Current: ${currentHash}`);
8895
+ logger.logger.log(`Target: ${fileInfo.afterHash}`);
8896
+ }
8897
+ }
8898
+ async function readPackageJson(packagePath) {
8899
+ const pkgJsonPath = path.join(packagePath, 'package.json');
8900
+ const pkg = await fs$2.readJson(pkgJsonPath, {
8901
+ throws: false
8902
+ });
8903
+ if (pkg) {
8904
+ return {
8905
+ name: pkg.name || '',
8906
+ version: pkg.version || ''
8907
+ };
8908
+ }
8909
+ return null;
8910
+ }
8692
8911
  async function handlePatch({
8912
+ cwd,
8913
+ dryRun,
8693
8914
  outputKind,
8694
8915
  packages,
8695
8916
  spinner
8696
8917
  }) {
8697
- spinner.start('Analyzing dependencies for security patches...');
8698
8918
  try {
8699
- // TODO: Implement actual patch logic
8700
- // This is a stub implementation
8701
- const result = {
8702
- ok: true,
8703
- data: {
8704
- patchedPackages: packages.length > 0 ? packages : ['example-package']
8919
+ const dotSocketDirPath = path.join(cwd, '.socket');
8920
+ const manifestPath = path.join(dotSocketDirPath, 'manifest.json');
8921
+
8922
+ // Read the manifest file.
8923
+ const manifestContent = await fs$1.promises.readFile(manifestPath, 'utf-8');
8924
+ const manifestData = JSON.parse(manifestContent);
8925
+
8926
+ // Validate the schema.
8927
+ const validated = PatchManifestSchema.parse(manifestData);
8928
+
8929
+ // Parse PURLs and group by ecosystem.
8930
+ const patchesByEcosystem = {};
8931
+ for (const [key, patch] of Object.entries(validated.patches)) {
8932
+ const purl = parsePURL(key);
8933
+ if (!patchesByEcosystem[purl.type]) {
8934
+ patchesByEcosystem[purl.type] = [];
8705
8935
  }
8706
- };
8936
+ patchesByEcosystem[purl.type]?.push({
8937
+ key,
8938
+ purl,
8939
+ patch
8940
+ });
8941
+ }
8707
8942
  spinner.stop();
8708
8943
  logger.logger.log('');
8709
8944
  if (packages.length > 0) {
@@ -8712,14 +8947,32 @@ async function handlePatch({
8712
8947
  logger.logger.info('Scanning all dependencies for available patches');
8713
8948
  }
8714
8949
  logger.logger.log('');
8950
+ if (patchesByEcosystem['npm']) {
8951
+ await applyNPMPatches(patchesByEcosystem['npm'], dryRun, dotSocketDirPath, packages);
8952
+ }
8953
+ const result = {
8954
+ ok: true,
8955
+ data: {
8956
+ patchedPackages: packages.length > 0 ? packages : ['patched successfully']
8957
+ }
8958
+ };
8715
8959
  await outputPatchResult(result, outputKind);
8716
8960
  } catch (e) {
8717
8961
  spinner.stop();
8962
+ let message = 'Failed to apply patches';
8963
+ let cause = e?.message || 'Unknown error';
8964
+ if (e instanceof SyntaxError) {
8965
+ message = 'Invalid JSON in manifest.json';
8966
+ cause = e.message;
8967
+ } else if (e instanceof Error && 'issues' in e) {
8968
+ message = 'Schema validation failed';
8969
+ cause = String(e);
8970
+ }
8718
8971
  const result = {
8719
8972
  ok: false,
8720
8973
  code: 1,
8721
- message: 'Failed to apply patches',
8722
- cause: e?.message || 'Unknown error'
8974
+ message,
8975
+ cause
8723
8976
  };
8724
8977
  await outputPatchResult(result, outputKind);
8725
8978
  }
@@ -8785,19 +9038,26 @@ async function run$k(argv, importMeta, {
8785
9038
  if (!wasValidInput) {
8786
9039
  return;
8787
9040
  }
8788
- if (dryRun) {
8789
- logger.logger.log(constants.DRY_RUN_NOT_SAVING);
8790
- return;
8791
- }
8792
9041
  let [cwd = '.'] = cli.input;
8793
9042
  // Note: path.resolve vs .join:
8794
9043
  // If given path is absolute then cwd should not affect it.
8795
9044
  cwd = path.resolve(process.cwd(), cwd);
9045
+ const dotSocketDirPath = path.join(cwd, '.socket');
9046
+ if (!fs$1.existsSync(dotSocketDirPath)) {
9047
+ logger.logger.error('Error: No .socket directory found in current directory');
9048
+ return;
9049
+ }
9050
+ const manifestPath = path.join(dotSocketDirPath, 'manifest.json');
9051
+ if (!fs$1.existsSync(manifestPath)) {
9052
+ logger.logger.error('Error: No manifest.json found in .socket directory');
9053
+ }
8796
9054
  const {
8797
9055
  spinner
8798
9056
  } = constants;
8799
- const packages = Array.isArray(cli.flags['package']) ? cli.flags['package'].flatMap(p => String(p).split(',')) : String(cli.flags['package'] || '').split(',').filter(Boolean);
9057
+ const packages = utils.cmdFlagValueToArray(cli.flags['package']);
8800
9058
  await handlePatch({
9059
+ cwd,
9060
+ dryRun,
8801
9061
  outputKind,
8802
9062
  packages,
8803
9063
  spinner
@@ -14002,5 +14262,5 @@ void (async () => {
14002
14262
  await utils.captureException(e);
14003
14263
  }
14004
14264
  })();
14005
- //# debugId=11a3cbfe-6b5a-4bf7-afd9-6885b9deef59
14265
+ //# debugId=8481439d-81fb-4c40-8fb9-cbf6be031d3
14006
14266
  //# sourceMappingURL=cli.js.map