@@ -15,6 +15,7 @@ const tempRoot = await mkdtemp(path.join(os.tmpdir(), "clawhub-trust-extensions-
1515try {
1616 const schemaDir = path . join ( tempRoot , "node_modules" , "clawhub" , "dist" , "schema" ) ;
1717 const schemaPath = path . join ( schemaDir , "textFiles.js" ) ;
18+ const skillsPath = path . join ( tempRoot , "node_modules" , "clawhub" , "dist" , "skills.js" ) ;
1819 await mkdir ( schemaDir , { recursive : true } ) ;
1920 await writeFile ( path . join ( tempRoot , "node_modules" , "clawhub" , "package.json" ) , '{"type":"module"}\n' ) ;
2021 await writeFile (
@@ -29,16 +30,31 @@ try {
2930 "" ,
3031 ] . join ( "\n" ) ,
3132 ) ;
33+ await writeFile (
34+ skillsPath ,
35+ [
36+ "const ext = relPath.split('.').at(-1)?.toLowerCase() ?? '';" ,
37+ "const contentType = mime.getType(relPath) ?? 'text/plain';" ,
38+ "" ,
39+ ] . join ( "\n" ) ,
40+ ) ;
3241
3342 const first = await patchClawhubTrustExtensions ( tempRoot ) ;
3443 assert . equal ( first . patched , true ) ;
3544 const patchedSource = await readFile ( schemaPath , "utf8" ) ;
45+ const patchedSkillsSource = await readFile ( skillsPath , "utf8" ) ;
3646 assert . match ( patchedSource , / ' p e m ' , / ) ;
3747 assert . match ( patchedSource , / ' s i g ' , / ) ;
48+ assert . match (
49+ patchedSkillsSource ,
50+ / \[ " p e m " , " s i g " \] \. i n c l u d e s \( e x t \) \? " t e x t \/ p l a i n " / ,
51+ "The patched client must upload trust extensions as text/plain" ,
52+ ) ;
3853
3954 const second = await patchClawhubTrustExtensions ( tempRoot ) ;
4055 assert . equal ( second . patched , false , "ClawHub extension patch must be idempotent" ) ;
4156 assert . equal ( await readFile ( schemaPath , "utf8" ) , patchedSource ) ;
57+ assert . equal ( await readFile ( skillsPath , "utf8" ) , patchedSkillsSource ) ;
4258
4359 const schema = await import ( `${ pathToFileURL ( schemaPath ) . href } ?test=${ Date . now ( ) } ` ) ;
4460 assert . equal ( schema . TEXT_FILE_EXTENSION_SET . has ( "pem" ) , true ) ;
7389 ) ;
7490 await rm ( path . join ( packageDir , "runtime.bin" ) ) ;
7591
92+ await writeFile ( path . join ( packageDir , "test_scanner_fixture.py" ) , "raise AssertionError\n" ) ;
93+ await assert . rejects (
94+ collectClawhubPackageFiles ( packageDir ) ,
95+ / m u s t n o t c o n t a i n t e s t - o n l y f i l e : t e s t _ s c a n n e r _ f i x t u r e \. p y / ,
96+ "ClawHub package preparation must reject test-named files before upload" ,
97+ ) ;
98+ await rm ( path . join ( packageDir , "test_scanner_fixture.py" ) ) ;
99+
100+ await mkdir ( path . join ( packageDir , "tests" ) ) ;
101+ await writeFile ( path . join ( packageDir , "tests" , "scanner-fixture.md" ) , "# Test fixture\n" ) ;
102+ await assert . rejects (
103+ collectClawhubPackageFiles ( packageDir ) ,
104+ / m u s t n o t c o n t a i n t e s t - o n l y f i l e : t e s t s \/ s c a n n e r - f i x t u r e \. m d / ,
105+ "ClawHub package preparation must reject files in test directories before upload" ,
106+ ) ;
107+ await rm ( path . join ( packageDir , "tests" ) , { recursive : true } ) ;
108+
76109 const installedClientSource = path . resolve ( ".github" , "clawhub-cli" ) ;
77110 const installedSkillsModule = path . join (
78111 installedClientSource ,
@@ -82,9 +115,40 @@ try {
82115 "skills.js" ,
83116 ) ;
84117 if ( existsSync ( installedSkillsModule ) ) {
118+ const mimeFailureCopy = path . join ( tempRoot , "mime-failure-clawhub-client" ) ;
119+ await cp ( installedClientSource , mimeFailureCopy , { recursive : true } ) ;
120+ await patchClawhubTrustExtensions ( mimeFailureCopy ) ;
121+ await cp (
122+ installedSkillsModule ,
123+ path . join ( mimeFailureCopy , "node_modules" , "clawhub" , "dist" , "skills.js" ) ,
124+ ) ;
125+ await assert . rejects (
126+ verifyClawhubClientSelection ( {
127+ packageDir,
128+ cliPrefix : mimeFailureCopy ,
129+ } ) ,
130+ / n o n - t e x t M I M E t y p e : a p p l i c a t i o n \/ / ,
131+ "The release simulation must reproduce ClawHub's rejection of non-text trust MIME types" ,
132+ ) ;
133+
85134 const installedClientCopy = path . join ( tempRoot , "installed-clawhub-client" ) ;
86135 await cp ( installedClientSource , installedClientCopy , { recursive : true } ) ;
87136 await patchClawhubTrustExtensions ( installedClientCopy ) ;
137+ const installedSkills = await import (
138+ `${ pathToFileURL ( path . join ( installedClientCopy , "node_modules" , "clawhub" , "dist" , "skills.js" ) ) . href } ?mime=${ Date . now ( ) } `
139+ ) ;
140+ const trustFiles = ( await installedSkills . listTextFiles ( packageDir ) )
141+ . filter ( ( entry ) => / \. (?: p e m | s i g ) $ / . test ( entry . relPath ) ) ;
142+ assert . deepEqual (
143+ trustFiles
144+ . map ( ( entry ) => ( { path : entry . relPath , contentType : entry . contentType } ) )
145+ . sort ( ( left , right ) => left . path . localeCompare ( right . path ) ) ,
146+ [
147+ { path : "key.pem" , contentType : "text/plain" } ,
148+ { path : "signature.sig" , contentType : "text/plain" } ,
149+ ] ,
150+ "The patched pinned ClawHub client must send trust files as text/plain" ,
151+ ) ;
88152 assert . deepEqual (
89153 await verifyClawhubClientSelection ( {
90154 packageDir,
0 commit comments