feat: ship skill and MCP workflows with signed updater for v0.0.7

This commit is contained in:
xiaohan.yuan committed 2026-10-21 00:04:17 +08:00
1 parent 1b52df0603
commit a01085c656
136 files changed
+18646 -217

No files matched your search

@@ -0,0 +1,223 @@
// Real Archify package audit for the recommended-skill installer.
// Downloads the pinned archive, verifies its digest, audits the ZIP structure and
// metadata, then imports it through the production skills module against an
// isolated Electron userData directory. No package script is ever executed.
import assert from 'node:assert/strict'
import fs from 'node:fs/promises'
import fsSync from 'node:fs'
import path from 'node:path'
import crypto from 'node:crypto'
import { pathToFileURL } from 'node:url'
import { build } from 'esbuild'
import { unzipSync } from 'fflate'
const COMMIT = '25cf37080fc8b1dbe758a4efc3935b55f3327083'
const REPOSITORY = 'tt-a1i/archify'
const PACKAGE_PATH = 'archify.zip'
const EXPECTED_SHA256 = '70e210265c59f24dcbdf2a502ff671104b5454e7ee7d3554aecd86bad32384f5'
const EXPECTED_BYTES = 5271959
const DOWNLOAD_URL = `https://api.github.com/repos/${REPOSITORY}/contents/${PACKAGE_PATH}?ref=${COMMIT}`
const EXPECTED_SKILL_NAME = 'archify'
const EXPECTED_SKILL_VERSION = '3.2'
const EXPECTED_PACKAGE_VERSION = '3.2.0'
const EXPECTED_LICENSE = 'MIT'
const EXPECTED_FILE_COUNT = 200
const EXPECTED_EXPANDED_BYTES = 17812933
const repository = path.resolve('.')
const testRoot = await fs.mkdtemp(path.resolve('.tmp/archify-package-'))
const relative = path.relative(path.resolve('.tmp'), testRoot)
assert.ok(relative.startsWith('archify-package-') && !relative.includes('..') && !path.isAbsolute(relative))
const userData = path.join(testRoot, 'data')
const workspace = path.join(testRoot, 'workspace')
await Promise.all([fs.mkdir(userData, { recursive: true }), fs.mkdir(workspace, { recursive: true })])
const checks = []
const check = (name, actual, expected) => {
assert.deepEqual(actual, expected, name)
checks.push(name)
}
const finding = (name, detail) => checks.push(`${name} :: ${detail}`)
const note = (name, detail) => checks.push(`${name} :: ${detail}`)
async function download() {
const local = process.env.YCODE_ARCHIFY_ZIP
if (local) {
const bytes = await fs.readFile(path.resolve(local))
note('archive source', `local file ${path.resolve(local)}`)
return bytes
}
const response = await fetch(DOWNLOAD_URL, { redirect: 'error', signal: AbortSignal.timeout(120000),
headers: { Accept: 'application/vnd.github.raw+json', 'X-GitHub-Api-Version': '2022-11-28' } })
assert.equal(response.status, 200, 'pinned archive download must succeed')
const bytes = Buffer.from(await response.arrayBuffer())
note('archive source', DOWNLOAD_URL)
return bytes
}
const bytes = await download()
const archivePath = path.join(testRoot, PACKAGE_PATH)
await fs.writeFile(archivePath, bytes)
check('pinned archive byte length matches the audited package', bytes.length, EXPECTED_BYTES)
const actualSha256 = crypto.createHash('sha256').update(bytes).digest('hex')
check('pinned archive digest matches the audited package', actualSha256, EXPECTED_SHA256)
check('archive is served from the pinned commit URL', DOWNLOAD_URL.includes(COMMIT), true)
const entries = unzipSync(bytes)
const names = Object.keys(entries)
const expandedBytes = names.reduce((sum, name) => sum + entries[name].length, 0)
check('archive contains the audited file count', names.length, EXPECTED_FILE_COUNT)
check('archive expands to the audited size', expandedBytes, EXPECTED_EXPANDED_BYTES)
check('every archive entry lives under a single skill directory',
names.every(name => name.startsWith('archify/')), true)
check('archive has exactly one SKILL.md at the package root',
names.filter(name => name === 'archify/SKILL.md').length, 1)
check('archive contains no nested second skill',
names.filter(name => name.split('/').filter(part => part === 'SKILL.md').length > 1).length, 0)
const text = (name) => Buffer.from(entries[name]).toString('utf8')
const skillMarkdown = text('archify/SKILL.md')
const frontMatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(skillMarkdown)
assert.ok(frontMatter, 'SKILL.md must carry YAML front matter')
const skillMeta = frontMatter[1]
check('SKILL.md declares the audited skill name', /(^|\n)name:\s*archify\s*(\n|$)/.test(skillMeta), true)
check('SKILL.md declares the audited license', /(^|\n)license:\s*MIT\s*(\n|$)/.test(skillMeta), true)
check('SKILL.md declares the audited metadata version', /version:\s*"?3\.2"?/.test(skillMeta), true)
check('SKILL.md declares the audited author', /author:\s*tt-a1i/.test(skillMeta), true)
const packageJson = JSON.parse(text('archify/package.json'))
check('package.json declares the audited package name', packageJson.name, EXPECTED_SKILL_NAME)
check('package.json declares the audited package version', packageJson.version, EXPECTED_PACKAGE_VERSION)
check('package.json declares the audited license', packageJson.license, EXPECTED_LICENSE)
check('package.json keeps the package private', packageJson.private, true)
const release = JSON.parse(text('archify/skill-release.json'))
check('release manifest declares the audited version', release.version, EXPECTED_PACKAGE_VERSION)
check('release manifest points at the audited repository', release.source.repository, `https://github.com/${REPOSITORY}`)
check('release manifest exposes an update URL that the host must not auto-follow',
typeof release.updateManifestUrl === 'string' && release.updateManifestUrl.length > 0, true)
check('archive ships the MIT license text', text('archify/LICENSE').includes('MIT License'), true)
check('archive ships the third-party notices', names.includes('archify/THIRD_PARTY_NOTICES.md'), true)
const scriptEntries = names.filter(name => /\.(mjs|cjs|js|ts|sh|ps1|bat|cmd)$/i.test(name))
check('archive ships executable helper scripts as inert files', scriptEntries.length > 0, true)
note('script entries (not executed)', `${scriptEntries.length} files, e.g. ${scriptEntries.slice(0, 3).join(', ')}`)
// Import through the production skills module. Only Electron's userData path is
// redirected; every other code path is the shipped implementation.
const bundled = path.join(testRoot, 'skills-archify.mjs')
await build({
entryPoints: [path.resolve('src/main/capabilities/skills.ts')],
bundle: true,
platform: 'node',
format: 'esm',
outfile: bundled,
logLevel: 'warning',
banner: { js: "import { createRequire as createArchifyTestRequire } from 'node:module';const require=createArchifyTestRequire(import.meta.url);" },
plugins: [{
name: 'isolated-electron-path',
setup(context) {
context.onResolve({ filter: /^electron$/ }, () => ({ path: 'electron', namespace: 'archify-test' }))
context.onLoad({ filter: /.*/, namespace: 'archify-test' }, () => ({
contents: `export const app={getPath:(name)=>{if(name!=='userData')throw Error('Unexpected app path');return ${JSON.stringify(userData)}}};export const safeStorage={isEncryptionAvailable:()=>false,encryptString:(v)=>Buffer.from(String(v),'utf8'),decryptString:(v)=>Buffer.from(v).toString('utf8')}`
}))
}
}, {
name: 'record-child-process',
setup(context) {
context.onResolve({ filter: /^(node:)?child_process$/ }, () => ({ path: 'child-process', namespace: 'archify-test' }))
context.onLoad({ filter: /.*/, namespace: 'archify-test' }, () => ({
contents: `const record=(name)=>globalThis.__archifyChildProcesses.push(name);export const spawn=(...a)=>{record('spawn');throw Error('child process blocked in package audit')};export const spawnSync=(...a)=>{record('spawnSync');throw Error('child process blocked in package audit')};export const exec=(...a)=>{record('exec');throw Error('child process blocked in package audit')};export const execSync=(...a)=>{record('execSync');throw Error('child process blocked in package audit')};export const fork=(...a)=>{record('fork');throw Error('child process blocked in package audit')};export default{spawn,spawnSync,exec,execSync,fork};`
}))
}
}]
})
globalThis.__archifyChildProcesses = []
const bundleSource = await fs.readFile(bundled, 'utf8')
check('the production skills module does not spawn a child process during import',
globalThis.__archifyChildProcesses.length, 0)
const api = await import(pathToFileURL(bundled).href)
check('the isolated skill library starts empty', await api.listSkills(), [])
const imported = await api.importSkill(archivePath)
check('the pinned archive imports as the audited skill name', imported.name, EXPECTED_SKILL_NAME)
check('the imported skill is a user-scoped import', imported.scope, 'user')
check('the imported skill is enabled by default', imported.enabled, true)
check('the imported skill is not flagged as locally modified', imported.userModified, false)
// Archify nests its version under `metadata:` in the YAML front matter. The host
// parser only reads a top-level `version`, so record what the host can see and
// keep the package manifest as the authoritative version for validation.
finding('host-visible skill version', `imported.version=${String(imported.version)} (front matter nests version under metadata:; expected ${EXPECTED_SKILL_VERSION})`)
check('the package manifest carries the authoritative version', packageJson.version, EXPECTED_PACKAGE_VERSION)
check('the imported skill keeps the audited license text in its body',
(await api.readSkill(imported.id)).content.includes('license: MIT'), true)
const installedPath = path.resolve(imported.path)
check('the installed package stays inside the isolated userData directory',
installedPath.startsWith(path.resolve(userData) + path.sep), true)
check('the installed package never lands in the real user skill library',
installedPath.includes(path.join('capabilities', 'skills', 'packages')), true)
const document = await api.readSkill(imported.id)
check('the imported skill exposes every audited file', document.files.length, EXPECTED_FILE_COUNT)
// The host strips the single enclosing directory, so installed paths are
// relative to the skill root rather than to the archive root.
const installedNames = names.map(name => name.replace(/^archify\//, ''))
check('the imported file list is relative to the skill root',
document.files.every(name => !name.startsWith('archify/')), true)
check('the imported file list is sorted and complete',
document.files.join('|') === installedNames.slice().sort().join('|'), true)
check('the imported skill body is the audited SKILL.md',
document.content === skillMarkdown, true)
const listed = (await api.listSkills(workspace)).find(item => item.id === imported.id)
check('the installed skill is listed for the workspace', listed?.name, EXPECTED_SKILL_NAME)
check('the installed skill stays unmodified after import', listed?.userModified, false)
const reference = await api.readSkillResource(imported.id, 'references/repository-authoring.md', workspace)
check('a shipped reference resource is readable through the host', reference.length > 0, true)
await assert.rejects(api.readSkillResource(imported.id, '../escape.md', workspace), /越界|非法|不安全/)
checks.push('resource reads reject traversal outside the package')
const context = await api.buildSkillContext([imported.id], workspace)
check('the explicit skill context carries the real instructions', context.includes('<skill id='), true)
check('the explicit skill context lists the package resources', context.includes('bin/archify.mjs'), true)
// Nothing in the package may run during installation.
const workspaceEntries = await fs.readdir(workspace)
check('importing the package creates no workspace output', workspaceEntries, [])
check('importing the package creates no generated artifacts directory',
fsSync.existsSync(path.join(workspace, '.archify')), false)
const exampleFiles = []
const collectExamples = async (folder, prefix) => {
for (const item of await fs.readdir(folder, { withFileTypes: true })) {
if (item.isDirectory()) await collectExamples(path.join(folder, item.name), `${prefix}${item.name}/`)
else exampleFiles.push(`${prefix}${item.name}`)
}
}
await collectExamples(path.join(installedPath, 'examples'), '')
check('importing the package leaves the shipped examples untouched',
exampleFiles.length, names.filter(name => name.startsWith('archify/examples/')).length)
await fs.writeFile(path.join(testRoot, 'report.json'), JSON.stringify({
result: 'PASS',
scope: 'Real pinned GitHub archive; digest and ZIP structure audited; import through the production skills module against an isolated Electron userData directory. No package script is executed.',
source: { repository: REPOSITORY, commit: COMMIT, packagePath: PACKAGE_PATH, downloadUrl: DOWNLOAD_URL },
archive: { bytes: bytes.length, sha256: actualSha256, fileCount: names.length, expandedBytes },
metadata: {
skillName: EXPECTED_SKILL_NAME,
skillVersion: imported.version,
packageVersion: packageJson.version,
license: packageJson.license,
author: 'tt-a1i',
releaseVersion: release.version
},
checks,
limitations: [
'The digest and metadata confirm the bytes match the audited package; they are not a security audit of the skill code.',
'Package scripts are shipped as inert files and were never executed, so runtime dependencies and rendering behavior remain unverified.',
'The update manifest URL inside the package is reported only; the host must not auto-follow it.'
]
}, null, 2))
console.log(JSON.stringify({ result: 'PASS', checks: checks.length, directory: testRoot, sha256: actualSha256 }))