@kinlab/kin 0.6.5 → 0.6.6

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/lib/provision.mjs +45 -6
  2. package/package.json +1 -1
package/lib/provision.mjs CHANGED
@@ -386,9 +386,40 @@ function windowsSystemTarPath(env) {
386
386
  return path.win32.join(systemRoot, 'System32', 'tar.exe');
387
387
  }
388
388
 
389
- function archiveExtraction(platform, env, file) {
389
+ // Where a Unix system tool is allowed to come from.
390
+ //
391
+ // `spawnSync('tar', ...)` resolves through PATH, and PATH belongs to whoever
392
+ // started this process. A `tar` planted earlier in it unpacks the archive
393
+ // whose SHA-256 was just verified, so the integrity check ends up protecting
394
+ // bytes that somebody else's program reads. These directories are root-owned
395
+ // on macOS and Linux and SIP-protected on macOS. The Windows arm has named its
396
+ // extractor absolutely since it was written; this is the same rule on Unix.
397
+ const UNIX_TOOL_DIRECTORIES = ['/usr/bin', '/bin'];
398
+
399
+ // The absolute path of a Unix system tool, refused when it is in none of the
400
+ // trusted directories.
401
+ //
402
+ // Refusing rather than falling back to PATH: a fallback makes the guard
403
+ // advisory, and provision already reports an extractor that will not run.
404
+ function unixSystemToolPath(name) {
405
+ for (const directory of UNIX_TOOL_DIRECTORIES) {
406
+ const candidate = path.posix.join(directory, name);
407
+ if (fs.existsSync(candidate)) return candidate;
408
+ }
409
+ throw new Error(
410
+ `${name} was not found in ${UNIX_TOOL_DIRECTORIES.join(' or ')}; refusing to resolve it ` +
411
+ 'through PATH, where a planted binary would run in its place',
412
+ );
413
+ }
414
+
415
+ // The archive layout comes from the TARGET, the extractor from the HOST, and
416
+ // conflating the two is what broke the native Windows leg once already: a
417
+ // Windows host has no /usr/bin, and the cross-target tests unpack a darwin
418
+ // archive on it. `host` is a parameter rather than a read of `process.platform`
419
+ // so every combination is testable from one machine.
420
+ export function archiveExtraction(platform, env, file, host = process.platform) {
390
421
  if (platform === 'win32') {
391
- if (process.platform === 'win32') {
422
+ if (host === 'win32') {
392
423
  return {
393
424
  executable: windowsSystemTarPath(env),
394
425
  args: ['-xf', file, '-C', '.'],
@@ -402,7 +433,14 @@ function archiveExtraction(platform, env, file) {
402
433
  args: ['-q', file, '-d', '.'],
403
434
  };
404
435
  }
405
- return { executable: 'tar', args: ['-xf', file, '-C', '.'] };
436
+ // A Unix archive. On a Unix host that is the absolute system tar; on a
437
+ // Windows host, which only ever happens under a cross-target test, it is the
438
+ // same System32 bsdtar the Windows arm above uses, and bsdtar reads .tar.gz.
439
+ // Either way the extractor is named absolutely rather than found on PATH.
440
+ return {
441
+ executable: host === 'win32' ? windowsSystemTarPath(env) : unixSystemToolPath('tar'),
442
+ args: ['-xf', file, '-C', '.'],
443
+ };
406
444
  }
407
445
 
408
446
  /**
@@ -493,9 +531,10 @@ export async function provision(version, opts = {}) {
493
531
 
494
532
  const toolEnv = mergeEnvironment(process.env, env);
495
533
  const extraction = archiveExtraction(platform, toolEnv, file);
496
- // Windows authority is the absolute System32 bsdtar, never a Git/MSYS or
497
- // user-provided `tar` found through PATH. Relative operands also avoid the
498
- // GNU remote-host interpretation of `C:\\...` paths.
534
+ // The extractor is named absolutely on both platforms: System32 bsdtar on
535
+ // Windows, never a Git/MSYS `tar` earlier in PATH, and /usr/bin/tar or
536
+ // /bin/tar on Unix. Relative operands also avoid the GNU remote-host
537
+ // interpretation of `C:\\...` paths.
499
538
  const extracted = spawnSync(extraction.executable, extraction.args, {
500
539
  cwd: tmp,
501
540
  encoding: 'utf8',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kinlab/kin",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "description": "Canonical installer and launcher for Kin, the system of record for AI-written software. Provisions and runs the managed kin + kin-daemon release. MCP is one included mode (kin mcp start).",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",