mirror of
https://github.com/prompt-security/clawsec.git
synced 2026-06-24 02:41:20 +03:00
chore(release): bump all public skills (#283)
* chore(skill): bump clawhub checker release * chore(release): bump all public skills * fix(release): require skillspector PR comments * fix(release): align skill verification versions * fix(release): checksum standalone release assets * fix(release): narrow skillspector comment permissions
This commit is contained in:
@@ -484,6 +484,13 @@ async function main() {
|
||||
await cp(path.join(tempSkillDir, "README.md"), path.join(releaseAssetsDir, "README.md"));
|
||||
}
|
||||
|
||||
for (const artifact of ["skill.json", "SKILL.md", "README.md"]) {
|
||||
if (existsSync(path.join(releaseAssetsDir, artifact))) {
|
||||
await addReleaseAssetChecksum({ releaseAssetsDir, manifest, asset: artifact });
|
||||
}
|
||||
}
|
||||
await writeJson(path.join(releaseAssetsDir, "checksums.json"), manifest);
|
||||
|
||||
const { privateKeyPath, publicKeyPath } = await createSigningKeyPair(tempRoot);
|
||||
await signFileBase64({
|
||||
keyPath: privateKeyPath,
|
||||
|
||||
@@ -141,6 +141,23 @@ if [ -f "$SKILL_PATH/SKILL.md" ]; then
|
||||
|
||||
echo " ✓ Version updated to $VERSION"
|
||||
|
||||
echo "Updating release verification VERSION assignments in SKILL.md..."
|
||||
VERSION_ASSIGNMENT_PATTERN='^VERSION="[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?"$'
|
||||
if grep -qE "$VERSION_ASSIGNMENT_PATTERN" "$TEMP_DIR/SKILL.md"; then
|
||||
sed -E "s|$VERSION_ASSIGNMENT_PATTERN|VERSION=\"$VERSION\"|g" "$TEMP_DIR/SKILL.md" > "$TEMP_DIR/SKILL.md.tmp"
|
||||
|
||||
if ! grep -qF "VERSION=\"$VERSION\"" "$TEMP_DIR/SKILL.md.tmp"; then
|
||||
echo "Warning: VERSION assignment found but substitution may have failed" >&2
|
||||
else
|
||||
VERSION_ASSIGNMENT_COUNT=$(grep -cF "VERSION=\"$VERSION\"" "$TEMP_DIR/SKILL.md.tmp")
|
||||
echo " ✓ Updated $VERSION_ASSIGNMENT_COUNT VERSION assignment(s)"
|
||||
fi
|
||||
|
||||
mv "$TEMP_DIR/SKILL.md.tmp" "$TEMP_DIR/SKILL.md"
|
||||
else
|
||||
echo " ℹ No hardcoded release verification VERSION assignments found"
|
||||
fi
|
||||
|
||||
echo "Updating hardcoded version URLs in SKILL.md to use tag $TAG..."
|
||||
# Replace all hardcoded version URLs: download/SKILLNAME-vX.Y.Z(-prerelease)?/ -> download/TAG/
|
||||
# This handles patterns like: download/clawsec-feed-v1.0.0/ or download/prompt-agent-v1.0.0-beta1/
|
||||
|
||||
@@ -8,6 +8,7 @@ const validateSkillInstallDocsPath = new URL('./ci/validate_skill_install_docs.m
|
||||
const installClawhubCliPath = new URL('./ci/install_clawhub_cli.sh', import.meta.url);
|
||||
const patchClawhubPayloadPath = new URL('./ci/patch_clawhub_publish_payload.mjs', import.meta.url);
|
||||
const guardClawhubSlugOwnerPath = new URL('./ci/guard_clawhub_slug_owner.sh', import.meta.url);
|
||||
const releaseSkillScriptPath = new URL('./release-skill.sh', import.meta.url);
|
||||
const workflow = await readFile(workflowPath, 'utf8');
|
||||
const ciWorkflow = await readFile(ciWorkflowPath, 'utf8');
|
||||
const clawhubLock = JSON.parse(await readFile(clawhubLockPath, 'utf8'));
|
||||
@@ -15,6 +16,7 @@ const validateSkillInstallDocs = await readFile(validateSkillInstallDocsPath, 'u
|
||||
const installClawhubCli = await readFile(installClawhubCliPath, 'utf8');
|
||||
const patchClawhubPayload = await readFile(patchClawhubPayloadPath, 'utf8');
|
||||
const guardClawhubSlugOwner = await readFile(guardClawhubSlugOwnerPath, 'utf8');
|
||||
const releaseSkillScript = await readFile(releaseSkillScriptPath, 'utf8');
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
@@ -220,6 +222,29 @@ for (const artifact of ['skill-card.md', 'permissions.json', 'install.md', 'skil
|
||||
);
|
||||
}
|
||||
|
||||
for (const artifact of ['skill.json', 'SKILL.md']) {
|
||||
assert.match(
|
||||
workflow,
|
||||
new RegExp(
|
||||
String.raw`cp [\s\S]*? "\$\{out_assets\}/${escapeRegExp(artifact)}"[\s\S]*?` +
|
||||
String.raw`if ! add_release_asset_checksum "\$\{out_assets\}" "${escapeRegExp(artifact)}"; then`,
|
||||
),
|
||||
`PR dry-run validation must checksum standalone downloadable ${artifact} after copying it to release assets`,
|
||||
);
|
||||
}
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/if \[ -f "\$\{out_assets\}\/README\.md" \] && ! add_release_asset_checksum "\$\{out_assets\}" "README\.md"; then/,
|
||||
'PR dry-run validation must checksum standalone downloadable README.md when it is shipped',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/cp "\$SKILL_PATH\/skill\.json" release-assets\/skill\.json[\s\S]*add_release_asset_checksum "skill\.json"[\s\S]*add_release_asset_checksum "SKILL\.md"[\s\S]*add_release_asset_checksum "README\.md"/,
|
||||
'Tag release validation must checksum standalone downloadable skill files before signing checksums.json',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/add_release_asset_checksum "skill-card\.md"/,
|
||||
@@ -253,26 +278,38 @@ assert.match(
|
||||
assert.match(
|
||||
workflow,
|
||||
/comment-skillspector-report:[\s\S]*needs: release[\s\S]*issues: write[\s\S]*actions\/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8\.0\.1/,
|
||||
'Skill release workflow must download generated SkillSpector reports in a separate PR comment job with comment permissions',
|
||||
'Skill release workflow must download generated SkillSpector reports in a separate PR comment job with issue-comment permissions',
|
||||
);
|
||||
|
||||
const commentJob = workflow.match(/[ ]{2}comment-skillspector-report:[\s\S]*?\n[ ]{2}[a-z][^:\n]*:/)?.[0] || "";
|
||||
assert.match(
|
||||
commentJob,
|
||||
/issues: write/,
|
||||
'SkillSpector PR comment publishing must request issues write permissions so report comments can be created',
|
||||
);
|
||||
|
||||
assert.doesNotMatch(
|
||||
commentJob,
|
||||
/pull-requests: write/,
|
||||
'SkillSpector PR comment publishing should not request redundant pull-requests write permissions',
|
||||
'SkillSpector PR comment publishing must not broaden the token with pull-requests write permissions',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/comment-skillspector-report:[\s\S]*if: always\(\) && github\.event_name == 'pull_request' && needs\.release\.result != 'cancelled'[\s\S]*Download SkillSpector reports[\s\S]*continue-on-error: true/,
|
||||
/comment-skillspector-report:[\s\S]*if: always\(\) && github\.event_name == 'pull_request' && needs\.release\.result != 'cancelled'[\s\S]*Download SkillSpector reports/,
|
||||
'SkillSpector PR comments must still run when the release dry-run produced reports but the release job failed later',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/Comment SkillSpector reports[\s\S]*continue-on-error: true[\s\S]*actions\/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9\.0\.0/,
|
||||
'SkillSpector PR comment publishing must not fail the release dry-run check',
|
||||
/Comment SkillSpector reports[\s\S]*actions\/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9\.0\.0/,
|
||||
'SkillSpector PR comment publishing must use the pinned GitHub script action',
|
||||
);
|
||||
|
||||
assert.doesNotMatch(
|
||||
commentJob,
|
||||
/continue-on-error: true/,
|
||||
'SkillSpector PR comment publishing must fail visibly when report artifacts or PR comments cannot be created',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
@@ -316,6 +353,16 @@ assert.ok(
|
||||
'Skill release workflow must accept every prerelease version format that release-skill.sh accepts',
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
releaseSkillScript.includes(`VERSION_ASSIGNMENT_PATTERN='^VERSION="[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9.]+)?"$'`),
|
||||
'release-skill.sh must detect hardcoded release verification VERSION assignments in SKILL.md',
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
releaseSkillScript.includes('sed -E "s|$VERSION_ASSIGNMENT_PATTERN|VERSION=\\"$VERSION\\"|g"'),
|
||||
'release-skill.sh must update hardcoded release verification VERSION assignments when bumping a skill',
|
||||
);
|
||||
|
||||
assert.match(
|
||||
workflow,
|
||||
/clawhub_slug: \$\{\{ steps\.publishable\.outputs\.clawhub_slug \}\}/,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { chmod, cp, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
@@ -7,6 +9,10 @@ import { spawnSync } from "node:child_process";
|
||||
const tempRoot = await mkdtemp(path.join(tmpdir(), "clawsec-tag-release-sim-"));
|
||||
const fakeSkillspector = path.join(tempRoot, "skillspector");
|
||||
|
||||
function sha256(buffer) {
|
||||
return createHash("sha256").update(buffer).digest("hex");
|
||||
}
|
||||
|
||||
async function prereleaseFixture(sourceSkillDir, version, fixtureGroup) {
|
||||
const fixtureDir = path.join(tempRoot, fixtureGroup, path.basename(sourceSkillDir));
|
||||
await cp(sourceSkillDir, fixtureDir, { recursive: true });
|
||||
@@ -77,6 +83,24 @@ async function runSimulation({ skillDir, outputDir, expectedOriginal, expectedSi
|
||||
assert.ok(file.length > 0, `${artifact} should not be empty`);
|
||||
}
|
||||
|
||||
for (const artifact of ["skill.json", "SKILL.md", "skillspector-report.md"]) {
|
||||
const file = await readFile(path.join(releaseAssetsDir, artifact));
|
||||
assert.equal(
|
||||
checksums.files[artifact]?.sha256,
|
||||
sha256(file),
|
||||
`${artifact} must be downloadable and covered by checksums.json`,
|
||||
);
|
||||
}
|
||||
|
||||
if (existsSync(path.join(releaseAssetsDir, "README.md"))) {
|
||||
const file = await readFile(path.join(releaseAssetsDir, "README.md"));
|
||||
assert.equal(
|
||||
checksums.files["README.md"]?.sha256,
|
||||
sha256(file),
|
||||
"README.md must be downloadable and covered by checksums.json when shipped",
|
||||
);
|
||||
}
|
||||
|
||||
const archive = await readFile(path.join(releaseAssetsDir, `${expectedTag}.zip`));
|
||||
assert.ok(archive.length > 0, "release archive should not be empty");
|
||||
|
||||
@@ -140,16 +164,16 @@ writeFileSync(process.argv[outputIndex + 1], "# Fake SkillSpector Report\\n\\nNo
|
||||
await runSimulation({
|
||||
skillDir: "skills/clawsec-suite",
|
||||
outputDir: path.join(tempRoot, "stable"),
|
||||
expectedOriginal: "0.1.11",
|
||||
expectedSimulated: "0.1.12",
|
||||
expectedOriginal: "0.1.12",
|
||||
expectedSimulated: "0.1.13",
|
||||
expectedAgent: "openclaw",
|
||||
});
|
||||
|
||||
await runSimulation({
|
||||
skillDir: "skills/hermes-traffic-guardian",
|
||||
outputDir: path.join(tempRoot, "beta"),
|
||||
expectedOriginal: "0.0.1-beta4",
|
||||
expectedSimulated: "0.0.1-beta5",
|
||||
expectedOriginal: "0.0.1-beta5",
|
||||
expectedSimulated: "0.0.1-beta6",
|
||||
expectedAgent: "hermes-agent",
|
||||
});
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ function runTrustPacket(skillDir, targetDir, tag) {
|
||||
}
|
||||
|
||||
try {
|
||||
const result = runTrustPacket("skills/clawsec-suite", outputDir, "clawsec-suite-v0.1.11");
|
||||
const result = runTrustPacket("skills/clawsec-suite", outputDir, "clawsec-suite-v0.1.12");
|
||||
|
||||
assert.equal(
|
||||
result.status,
|
||||
@@ -41,10 +41,10 @@ try {
|
||||
assert.match(skillCard, /## License\/Terms of Use/);
|
||||
assert.match(skillCard, /AGPL-3\.0-or-later/);
|
||||
assert.match(skillCard, /skillspector-report\.md/);
|
||||
assert.match(skillCard, /clawsec-suite-v0\.1\.11/);
|
||||
assert.match(skillCard, /clawsec-suite-v0\.1\.12/);
|
||||
|
||||
assert.equal(permissions.skill, "clawsec-suite");
|
||||
assert.equal(permissions.version, "0.1.11");
|
||||
assert.equal(permissions.version, "0.1.12");
|
||||
assert.equal(permissions.platform, "openclaw");
|
||||
assert.deepEqual(
|
||||
permissions.required_binaries,
|
||||
@@ -62,7 +62,7 @@ try {
|
||||
const hermesResult = runTrustPacket(
|
||||
"skills/hermes-attestation-guardian",
|
||||
hermesOutputDir,
|
||||
"hermes-attestation-guardian-v0.1.5",
|
||||
"hermes-attestation-guardian-v0.1.6",
|
||||
);
|
||||
assert.equal(
|
||||
hermesResult.status,
|
||||
|
||||
Reference in New Issue
Block a user