@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, rmSync } from "node:fs";
22import { join } from "node:path" ;
33import { BailianError } from "../errors/base.ts" ;
44import { ExitCode } from "../errors/codes.ts" ;
5- import { atomicSwap , extractTarBr } from "./extract.ts" ;
5+ import { atomicSwap , computeDirContentHash , extractTarBr } from "./extract.ts" ;
66import { getSkillsDir } from "./lock.ts" ;
77import { downloadSkillAsset } from "./registry.ts" ;
88import { isSafeSkillName } from "./sanitize.ts" ;
@@ -34,6 +34,7 @@ function assertSafeName(name: string): void {
3434export async function installSkillFromBuffer (
3535 name : string ,
3636 tarBrBuffer : Buffer ,
37+ expectedContentHash ?: string ,
3738) : Promise < InstalledSkill > {
3839 assertSafeName ( name ) ;
3940 const skillsDir = getSkillsDir ( ) ;
@@ -43,6 +44,18 @@ export async function installSkillFromBuffer(
4344 try {
4445 mkdirSync ( tmpDir , { recursive : true } ) ;
4546 await extractTarBr ( tarBrBuffer , tmpDir ) ;
47+ // Integrity check before touching canonical: recompute the publisher fingerprint over
48+ // the extracted files; on mismatch the current installation is left untouched
49+ if ( expectedContentHash ?. startsWith ( "sha256:" ) ) {
50+ const actualContentHash = computeDirContentHash ( tmpDir ) ;
51+ if ( actualContentHash !== expectedContentHash ) {
52+ throw new BailianError (
53+ `Skill ${ name } failed integrity check: index says ${ expectedContentHash } , archive is ${ actualContentHash } ` ,
54+ ExitCode . GENERAL ,
55+ "Downloaded archive does not match the index fingerprint (registry may be mid-publish); retry later" ,
56+ ) ;
57+ }
58+ }
4659 const meta = validateSkillDir ( tmpDir , name ) ;
4760 atomicSwap ( tmpDir , dest ) ;
4861 return { name, path : dest , meta } ;
@@ -60,8 +73,8 @@ export async function installSkill(name: string, entry: SkillIndexEntry): Promis
6073 "Upgrade bailian-cli to the latest version and retry" ,
6174 ) ;
6275 }
63- const buffer = await downloadSkillAsset ( name ) ;
64- return installSkillFromBuffer ( name , buffer ) ;
76+ const buffer = await downloadSkillAsset ( name , entry ) ;
77+ return installSkillFromBuffer ( name , buffer , entry . contentHash ) ;
6578}
6679
6780/** Remove the skill directory under canonical; returns whether it was actually deleted (dir absent → false) */
0 commit comments