git-userhub 3.0.7 → 3.0.8

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.
Files changed (2) hide show
  1. package/bin/git-user.js +65 -48
  2. package/package.json +3 -2
package/bin/git-user.js CHANGED
@@ -12,6 +12,31 @@ const PKG_JSON = require('../package.json');
12
12
  const REPO = 'divyo-argha/git-user';
13
13
  const VERSION = `v${PKG_JSON.version}`;
14
14
 
15
+ // --- START PINNED HASHES ---
16
+ const PINNED_HASHES = {
17
+ "git-user_darwin_arm64.tar.gz": {
18
+ "archive": "759e6a4137bc9caed58b393f751fa4c670a3dea98ef072a6551f38d41a9fc9ce",
19
+ "binary": "9b39841ca909f8dfcffff9a856771a65a8615ffb5e872e4c1108f0fa2e0d1752"
20
+ },
21
+ "git-user_darwin_x86_64.tar.gz": {
22
+ "archive": "eda5a46ea9f440174408d33659de38b7623da28b94ee447a3dc7fa509ffced83",
23
+ "binary": "efd1e62a40659f5e9132448ae5d1c5a5f23069a5277db73641de726cd28c55f1"
24
+ },
25
+ "git-user_linux_arm64.tar.gz": {
26
+ "archive": "3e311264bedf0672eee3625396d2e42d1c72d2a263a56fe2a7de9e5c54a2e2d3",
27
+ "binary": "f177a44c4c7d0a79840a404b669ce924c0117d58db6d407880007684efea64a2"
28
+ },
29
+ "git-user_linux_x86_64.tar.gz": {
30
+ "archive": "aab7e800a2b49a265409e0fbc3c64d4282b334bca222ea975532bd92dc7869dc",
31
+ "binary": "31172516986f3020bda2d5186ef7034919c09690399f849dcc980b542ed060f8"
32
+ },
33
+ "git-user_windows_x86_64.tar.gz": {
34
+ "archive": "eee27a75cfa131053b0901d8432216881aae25fda4586258b9b357ffa3558455",
35
+ "binary": "36e2ebc6272fe828b9b6a49d2623a049d001e826cca2c6ad428f178e9ca09403"
36
+ }
37
+ };
38
+ // --- END PINNED HASHES ---
39
+
15
40
  const platform = os.platform();
16
41
  const arch = os.arch();
17
42
 
@@ -30,17 +55,41 @@ if (!osName || !archName) {
30
55
  const finalBinaryName = `git-user-${platform}-${arch}${ext}`;
31
56
  const finalBinaryPath = path.join(__dirname, finalBinaryName);
32
57
  const assetName = `git-user_${osName}_${archName}.tar.gz`;
58
+ const pinnedData = PINNED_HASHES[assetName];
59
+
60
+ function computeHashSync(filePath) {
61
+ const hash = crypto.createHash('sha256');
62
+ const buffer = fs.readFileSync(filePath);
63
+ hash.update(buffer);
64
+ return hash.digest('hex');
65
+ }
66
+
67
+ function computeHash(file) {
68
+ return new Promise((resolve, reject) => {
69
+ const hash = crypto.createHash('sha256');
70
+ const stream = fs.createReadStream(file);
71
+ stream.on('data', d => hash.update(d));
72
+ stream.on('end', () => resolve(hash.digest('hex')));
73
+ stream.on('error', reject);
74
+ });
75
+ }
33
76
 
34
77
  function runBinary() {
78
+ if (pinnedData) {
79
+ const currentHash = computeHashSync(finalBinaryPath);
80
+ if (currentHash !== pinnedData.binary) {
81
+ console.error(`❌ Security Error: Cached binary hash mismatch! Deleting compromised binary.`);
82
+ fs.unlinkSync(finalBinaryPath);
83
+ process.exit(1);
84
+ }
85
+ }
86
+
35
87
  const child = spawn(finalBinaryPath, process.argv.slice(2), {
36
88
  stdio: 'inherit',
37
89
  shell: false
38
90
  });
39
91
 
40
- child.on('exit', (code) => {
41
- process.exit(code || 0);
42
- });
43
-
92
+ child.on('exit', (code) => process.exit(code || 0));
44
93
  child.on('error', (err) => {
45
94
  console.error('❌ Failed to start git-user:', err.message);
46
95
  process.exit(1);
@@ -53,18 +102,7 @@ if (fs.existsSync(finalBinaryPath)) {
53
102
  }
54
103
 
55
104
  // FIRST RUN: Download binary
56
- console.log(`[git-user] First run detected. Downloading native binary for ${platform}-${arch} (~8MB)...`);
57
-
58
- function fetchJson(url) {
59
- return new Promise((resolve, reject) => {
60
- https.get(url, { headers: { 'User-Agent': 'node' } }, (res) => {
61
- if (res.statusCode !== 200) return reject(new Error(`API Error ${res.statusCode}`));
62
- let data = '';
63
- res.on('data', c => data += c);
64
- res.on('end', () => resolve(JSON.parse(data)));
65
- }).on('error', reject);
66
- });
67
- }
105
+ console.log(`[git-user] Downloading cryptographically signed binary for ${platform}-${arch}...`);
68
106
 
69
107
  function fetchFile(url, dest) {
70
108
  return new Promise((resolve, reject) => {
@@ -80,39 +118,19 @@ function fetchFile(url, dest) {
80
118
  });
81
119
  }
82
120
 
83
- function verifyHash(file, expected) {
84
- return new Promise((resolve, reject) => {
85
- const hash = crypto.createHash('sha256');
86
- const stream = fs.createReadStream(file);
87
- stream.on('data', d => hash.update(d));
88
- stream.on('end', () => {
89
- if (hash.digest('hex') !== expected) reject(new Error("Checksum mismatch!"));
90
- else resolve();
91
- });
92
- });
93
- }
94
-
95
121
  async function install() {
96
122
  try {
97
- const release = await fetchJson(`https://api.github.com/repos/${REPO}/releases/tags/${VERSION}`);
98
- const checksumAsset = release.assets.find(a => a.name.endsWith('checksums.txt'));
99
- const binAsset = release.assets.find(a => a.name === assetName);
100
-
101
- if (!checksumAsset || !binAsset) {
102
- throw new Error("Could not find required assets on GitHub Release");
103
- }
104
-
105
- const checksumPath = path.join(__dirname, 'checksums.txt');
106
123
  const archivePath = path.join(__dirname, assetName);
107
-
108
- await fetchFile(checksumAsset.browser_download_url, checksumPath);
109
- const checksums = fs.readFileSync(checksumPath, 'utf8');
110
- const hashLine = checksums.split('\n').find(l => l.endsWith(assetName));
111
- if (!hashLine) throw new Error("Checksum missing in txt file");
112
- const expectedHash = hashLine.split(/\s+/)[0];
113
-
114
- await fetchFile(binAsset.browser_download_url, archivePath);
115
- await verifyHash(archivePath, expectedHash);
124
+ const downloadUrl = `https://github.com/${REPO}/releases/download/${VERSION}/${assetName}`;
125
+
126
+ await fetchFile(downloadUrl, archivePath);
127
+
128
+ if (pinnedData) {
129
+ const archiveHash = await computeHash(archivePath);
130
+ if (archiveHash !== pinnedData.archive) {
131
+ throw new Error("Archive checksum mismatch! Connection may be compromised.");
132
+ }
133
+ }
116
134
 
117
135
  const binaryNameInArchive = platform === 'win32' ? 'git-user.exe' : 'git-user';
118
136
  await tar.extract({
@@ -127,9 +145,8 @@ async function install() {
127
145
  if (platform !== 'win32') fs.chmodSync(finalBinaryPath, 0o755);
128
146
 
129
147
  fs.unlinkSync(archivePath);
130
- fs.unlinkSync(checksumPath);
131
148
 
132
- console.log(`[git-user] Installation complete.\n`);
149
+ console.log(`[git-user] Installation and verification complete.\n`);
133
150
  runBinary();
134
151
  } catch (err) {
135
152
  console.error(`\n❌ git-user installation failed: ${err.message}`);
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "git-userhub",
3
- "version": "3.0.7",
3
+ "version": "3.0.8",
4
4
  "description": "Switch Git accounts in one command. No config editing. No SSH key chaos.",
5
5
  "bin": {
6
6
  "git-user": "bin/git-user.js"
7
7
  },
8
8
  "scripts": {
9
- "test": "echo \"No tests yet\" && exit 0"
9
+ "test": "echo \"No tests yet\" && exit 0",
10
+ "prepublishOnly": "node scripts/inject-hashes.js"
10
11
  },
11
12
  "keywords": [
12
13
  "git",