mirror of
https://github.com/prompt-security/clawsec.git
synced 2026-06-15 14:31:21 +03:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 543b256901 |
@@ -1,56 +0,0 @@
|
|||||||
{
|
|
||||||
"plugins": [
|
|
||||||
{
|
|
||||||
"name": "global-skills",
|
|
||||||
"source": "./",
|
|
||||||
"skills": [
|
|
||||||
"./skills/clawtributor"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "hermes-skills",
|
|
||||||
"source": "./",
|
|
||||||
"skills": [
|
|
||||||
"./skills/hermes-attestation-guardian",
|
|
||||||
"./skills/hermes-traffic-guardian"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "nano-claw-skills",
|
|
||||||
"source": "./",
|
|
||||||
"skills": [
|
|
||||||
"./skills/clawsec-nanoclaw",
|
|
||||||
"./skills/nanoclaw-traffic-guardian"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "open-claw-skills",
|
|
||||||
"source": "./",
|
|
||||||
"skills": [
|
|
||||||
"./skills/clawsec-clawhub-checker",
|
|
||||||
"./skills/clawsec-feed",
|
|
||||||
"./skills/clawsec-scanner",
|
|
||||||
"./skills/clawsec-suite",
|
|
||||||
"./skills/openclaw-audit-watchdog",
|
|
||||||
"./skills/openclaw-traffic-guardian",
|
|
||||||
"./skills/soul-guardian"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pico-claw-skills",
|
|
||||||
"source": "./",
|
|
||||||
"skills": [
|
|
||||||
"./skills/picoclaw-security-guardian",
|
|
||||||
"./skills/picoclaw-self-pen-testing",
|
|
||||||
"./skills/picoclaw-traffic-guardian"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "repo-internal-skills",
|
|
||||||
"source": "./",
|
|
||||||
"skills": [
|
|
||||||
"./skills/claw-release"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Generated
-406
@@ -1,406 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "clawhub-cli-pin",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "clawhub-cli-pin",
|
|
||||||
"dependencies": {
|
|
||||||
"clawhub": "0.7.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ark/schema": {
|
|
||||||
"version": "0.56.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/@ark/schema/-/schema-0.56.0.tgz",
|
|
||||||
"integrity": "sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA==",
|
|
||||||
"dependencies": {
|
|
||||||
"@ark/util": "0.56.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ark/util": {
|
|
||||||
"version": "0.56.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/@ark/util/-/util-0.56.0.tgz",
|
|
||||||
"integrity": "sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA=="
|
|
||||||
},
|
|
||||||
"node_modules/@clack/core": {
|
|
||||||
"version": "0.5.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/@clack/core/-/core-0.5.0.tgz",
|
|
||||||
"integrity": "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==",
|
|
||||||
"dependencies": {
|
|
||||||
"picocolors": "^1.0.0",
|
|
||||||
"sisteransi": "^1.0.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@clack/prompts": {
|
|
||||||
"version": "0.11.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/@clack/prompts/-/prompts-0.11.0.tgz",
|
|
||||||
"integrity": "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@clack/core": "0.5.0",
|
|
||||||
"picocolors": "^1.0.0",
|
|
||||||
"sisteransi": "^1.0.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ansi-regex": {
|
|
||||||
"version": "6.2.2",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
|
||||||
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/arkregex": {
|
|
||||||
"version": "0.0.5",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/arkregex/-/arkregex-0.0.5.tgz",
|
|
||||||
"integrity": "sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw==",
|
|
||||||
"dependencies": {
|
|
||||||
"@ark/util": "0.56.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/arktype": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/arktype/-/arktype-2.2.0.tgz",
|
|
||||||
"integrity": "sha512-t54MZ7ti5BhOEvzEkgKnWvqj+UbDfWig+DHr5I34xatymPusKLS0lQpNJd8M6DzmIto2QGszHfNKoFIT8tMCZQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@ark/schema": "0.56.0",
|
|
||||||
"@ark/util": "0.56.0",
|
|
||||||
"arkregex": "0.0.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/chalk": {
|
|
||||||
"version": "5.6.2",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/chalk/-/chalk-5.6.2.tgz",
|
|
||||||
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
|
||||||
"engines": {
|
|
||||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/clawhub": {
|
|
||||||
"version": "0.7.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/clawhub/-/clawhub-0.7.0.tgz",
|
|
||||||
"integrity": "sha512-volW6SbX8PawlnRxxCoUTKv5Pi+N3MrBi3hlO5/m9bVaO43UFciEeYti9+01c2U5n/SKhUkw7ASvnleyNmcoSA==",
|
|
||||||
"dependencies": {
|
|
||||||
"@clack/prompts": "^0.11.0",
|
|
||||||
"arktype": "^2.1.29",
|
|
||||||
"commander": "^14.0.2",
|
|
||||||
"fflate": "^0.8.2",
|
|
||||||
"ignore": "^7.0.5",
|
|
||||||
"json5": "^2.2.3",
|
|
||||||
"mime": "^4.1.0",
|
|
||||||
"ora": "^9.0.0",
|
|
||||||
"p-retry": "^7.1.1",
|
|
||||||
"semver": "^7.7.3",
|
|
||||||
"undici": "^7.16.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"clawdhub": "bin/clawdhub.js",
|
|
||||||
"clawhub": "bin/clawdhub.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cli-cursor": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/cli-cursor/-/cli-cursor-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
|
|
||||||
"dependencies": {
|
|
||||||
"restore-cursor": "^5.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cli-spinners": {
|
|
||||||
"version": "3.4.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/cli-spinners/-/cli-spinners-3.4.0.tgz",
|
|
||||||
"integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.20"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/commander": {
|
|
||||||
"version": "14.0.3",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/commander/-/commander-14.0.3.tgz",
|
|
||||||
"integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fflate": {
|
|
||||||
"version": "0.8.3",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/fflate/-/fflate-0.8.3.tgz",
|
|
||||||
"integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="
|
|
||||||
},
|
|
||||||
"node_modules/get-east-asian-width": {
|
|
||||||
"version": "1.6.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
|
|
||||||
"integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ignore": {
|
|
||||||
"version": "7.0.5",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/ignore/-/ignore-7.0.5.tgz",
|
|
||||||
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-interactive": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/is-interactive/-/is-interactive-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-network-error": {
|
|
||||||
"version": "1.3.2",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/is-network-error/-/is-network-error-1.3.2.tgz",
|
|
||||||
"integrity": "sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-unicode-supported": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/json5": {
|
|
||||||
"version": "2.2.3",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/json5/-/json5-2.2.3.tgz",
|
|
||||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
|
||||||
"bin": {
|
|
||||||
"json5": "lib/cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/log-symbols": {
|
|
||||||
"version": "7.0.1",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/log-symbols/-/log-symbols-7.0.1.tgz",
|
|
||||||
"integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==",
|
|
||||||
"dependencies": {
|
|
||||||
"is-unicode-supported": "^2.0.0",
|
|
||||||
"yoctocolors": "^2.1.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mime": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/mime/-/mime-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==",
|
|
||||||
"funding": [
|
|
||||||
"https://github.com/sponsors/broofa"
|
|
||||||
],
|
|
||||||
"bin": {
|
|
||||||
"mime": "bin/cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mimic-function": {
|
|
||||||
"version": "5.0.1",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/mimic-function/-/mimic-function-5.0.1.tgz",
|
|
||||||
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/onetime": {
|
|
||||||
"version": "7.0.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/onetime/-/onetime-7.0.0.tgz",
|
|
||||||
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"mimic-function": "^5.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ora": {
|
|
||||||
"version": "9.4.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/ora/-/ora-9.4.0.tgz",
|
|
||||||
"integrity": "sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"chalk": "^5.6.2",
|
|
||||||
"cli-cursor": "^5.0.0",
|
|
||||||
"cli-spinners": "^3.2.0",
|
|
||||||
"is-interactive": "^2.0.0",
|
|
||||||
"is-unicode-supported": "^2.1.0",
|
|
||||||
"log-symbols": "^7.0.1",
|
|
||||||
"stdin-discarder": "^0.3.2",
|
|
||||||
"string-width": "^8.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/p-retry": {
|
|
||||||
"version": "7.1.1",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/p-retry/-/p-retry-7.1.1.tgz",
|
|
||||||
"integrity": "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==",
|
|
||||||
"dependencies": {
|
|
||||||
"is-network-error": "^1.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/picocolors": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/picocolors/-/picocolors-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
|
||||||
},
|
|
||||||
"node_modules/restore-cursor": {
|
|
||||||
"version": "5.1.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/restore-cursor/-/restore-cursor-5.1.0.tgz",
|
|
||||||
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
|
|
||||||
"dependencies": {
|
|
||||||
"onetime": "^7.0.0",
|
|
||||||
"signal-exit": "^4.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/semver": {
|
|
||||||
"version": "7.8.4",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/semver/-/semver-7.8.4.tgz",
|
|
||||||
"integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==",
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/signal-exit": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/signal-exit/-/signal-exit-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/sisteransi": {
|
|
||||||
"version": "1.0.5",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/sisteransi/-/sisteransi-1.0.5.tgz",
|
|
||||||
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
|
|
||||||
},
|
|
||||||
"node_modules/stdin-discarder": {
|
|
||||||
"version": "0.3.2",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/stdin-discarder/-/stdin-discarder-0.3.2.tgz",
|
|
||||||
"integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/string-width": {
|
|
||||||
"version": "8.2.1",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/string-width/-/string-width-8.2.1.tgz",
|
|
||||||
"integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==",
|
|
||||||
"dependencies": {
|
|
||||||
"get-east-asian-width": "^1.5.0",
|
|
||||||
"strip-ansi": "^7.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/strip-ansi": {
|
|
||||||
"version": "7.2.0",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
|
||||||
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
|
|
||||||
"dependencies": {
|
|
||||||
"ansi-regex": "^6.2.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/undici": {
|
|
||||||
"version": "7.27.2",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/undici/-/undici-7.27.2.tgz",
|
|
||||||
"integrity": "sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.18.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/yoctocolors": {
|
|
||||||
"version": "2.1.2",
|
|
||||||
"resolved": "https://prompt-security-443370709039.d.codeartifact.eu-north-1.amazonaws.com/npm/npm-proxy/yoctocolors/-/yoctocolors-2.1.2.tgz",
|
|
||||||
"integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "clawhub-cli-pin",
|
|
||||||
"private": true,
|
|
||||||
"description": "Pins the clawhub CLI used by skill-release.yml; package-lock.json provides the integrity hashes. Bump the version here and regenerate the lockfile with: npm install --package-lock-only",
|
|
||||||
"dependencies": {
|
|
||||||
"clawhub": "0.7.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -53,21 +53,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Collect traffic
|
- name: Collect traffic
|
||||||
env:
|
env:
|
||||||
# Traffic endpoints reject the Actions GITHUB_TOKEN ("Resource not
|
GH_TRAFFIC_TOKEN: ${{ secrets.TRAFFIC_ARCHIVE_TOKEN || github.token }}
|
||||||
# accessible by integration") — a PAT from a user with push access
|
|
||||||
# is required: classic with repo scope, or fine-grained with read
|
|
||||||
# access to Administration on this repository.
|
|
||||||
GH_TRAFFIC_TOKEN: ${{ secrets.TRAFFIC_ARCHIVE_TOKEN }}
|
|
||||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||||
run: |
|
run: node scripts/archive-github-traffic.mjs --archive-dir "${TRAFFIC_ARCHIVE_DIR}"
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [ -z "${GH_TRAFFIC_TOKEN}" ]; then
|
|
||||||
echo "::error::No traffic-capable token configured. Set the TRAFFIC_ARCHIVE_TOKEN secret to a PAT with push access (classic: repo scope; fine-grained: Administration read)."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
node scripts/archive-github-traffic.mjs --archive-dir "${TRAFFIC_ARCHIVE_DIR}"
|
|
||||||
|
|
||||||
- name: Commit archive
|
- name: Commit archive
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -111,12 +111,8 @@ jobs:
|
|||||||
run: node scripts/test-nvd-ghsa-consolidation-workflow.mjs
|
run: node scripts/test-nvd-ghsa-consolidation-workflow.mjs
|
||||||
- name: NVD + GHSA Pipeline Dry Run
|
- name: NVD + GHSA Pipeline Dry Run
|
||||||
run: node scripts/test-nvd-ghsa-pipeline-dry-run.mjs
|
run: node scripts/test-nvd-ghsa-pipeline-dry-run.mjs
|
||||||
- name: Skill Release Tooling Tests
|
- name: Skill Release Workflow Tests
|
||||||
run: |
|
run: node scripts/test-skill-release-workflow.mjs
|
||||||
set -euo pipefail
|
|
||||||
for test_file in scripts/test-skill-*.mjs; do
|
|
||||||
node "$test_file"
|
|
||||||
done
|
|
||||||
- name: Deploy Pages Advisory Checksums Tests
|
- name: Deploy Pages Advisory Checksums Tests
|
||||||
run: node scripts/test-deploy-pages-checksums.mjs
|
run: node scripts/test-deploy-pages-checksums.mjs
|
||||||
- name: GitHub Traffic Archive Tests
|
- name: GitHub Traffic Archive Tests
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ jobs:
|
|||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4
|
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
config-file: ./.github/codeql/codeql-config.yml
|
config-file: ./.github/codeql/codeql-config.yml
|
||||||
@@ -38,4 +38,4 @@ jobs:
|
|||||||
- name: Build project
|
- name: Build project
|
||||||
run: npm run build
|
run: npm run build
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4
|
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
|
||||||
|
|||||||
@@ -1069,7 +1069,7 @@ jobs:
|
|||||||
--event workflow_dispatch \
|
--event workflow_dispatch \
|
||||||
--limit 50 \
|
--limit 50 \
|
||||||
--json databaseId,createdAt,headSha \
|
--json databaseId,createdAt,headSha \
|
||||||
| jq -r --arg since "$DISPATCHED_AT" --arg sha "$EXPECTED_HEAD_SHA" '
|
--jq --arg since "$DISPATCHED_AT" --arg sha "$EXPECTED_HEAD_SHA" '
|
||||||
map(select(.createdAt >= $since and .headSha == $sha))
|
map(select(.createdAt >= $since and .headSha == $sha))
|
||||||
| sort_by(.createdAt)
|
| sort_by(.createdAt)
|
||||||
| last
|
| last
|
||||||
|
|||||||
@@ -84,6 +84,6 @@ jobs:
|
|||||||
# Upload the results to GitHub's code scanning dashboard (optional).
|
# Upload the results to GitHub's code scanning dashboard (optional).
|
||||||
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
|
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
+143
-571
@@ -7,9 +7,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'skills/**'
|
- 'skills/**'
|
||||||
- '.github/workflows/skill-release.yml'
|
|
||||||
- 'scripts/ci/**'
|
|
||||||
- 'scripts/test-skill-*.mjs'
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
@@ -19,8 +16,8 @@ on:
|
|||||||
|
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
|
|
||||||
# The clawhub CLI version is pinned (with integrity hashes) in
|
env:
|
||||||
# .github/clawhub-cli/package-lock.json — bump it there.
|
CLAWHUB_CLI_VERSION: 0.7.0
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: skill-release-${{ github.ref }}
|
group: skill-release-${{ github.ref }}
|
||||||
@@ -38,11 +35,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
|
|
||||||
- name: Verify signing key consistency (repo + docs)
|
- name: Verify signing key consistency (repo + docs)
|
||||||
run: ./scripts/ci/verify_signing_key_consistency.sh
|
run: ./scripts/ci/verify_signing_key_consistency.sh
|
||||||
|
|
||||||
@@ -90,15 +82,7 @@ jobs:
|
|||||||
'skills/*/**' \
|
'skills/*/**' \
|
||||||
':(exclude)skills/*/test/**' \
|
':(exclude)skills/*/test/**' \
|
||||||
':(exclude)skills/*/tests/**' \
|
':(exclude)skills/*/tests/**' \
|
||||||
| awk -F/ '
|
| awk -F/ 'NF >= 3 {print $1 "/" $2}' \
|
||||||
NF >= 3 {
|
|
||||||
path = tolower($0)
|
|
||||||
name = tolower($NF)
|
|
||||||
if (path ~ /(^|\/)(__tests__|test|tests)\//) next
|
|
||||||
if (name ~ /^(test|spec)[_-]/ || name ~ /\.(test|spec)\./) next
|
|
||||||
print $1 "/" $2
|
|
||||||
}
|
|
||||||
' \
|
|
||||||
| sort -u > "${touched_skills_file}"
|
| sort -u > "${touched_skills_file}"
|
||||||
|
|
||||||
if [ ! -s "${touched_skills_file}" ]; then
|
if [ ! -s "${touched_skills_file}" ]; then
|
||||||
@@ -160,6 +144,14 @@ jobs:
|
|||||||
md_version_changed=true
|
md_version_changed=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "${json_version_changed}" != "true" ] && [ "${md_version_changed}" != "true" ]; then
|
||||||
|
echo "::error file=${skill_dir}::Changed skill package has no version bump. Update skill.json and SKILL.md versions and add CHANGELOG.md release notes."
|
||||||
|
failures=$((failures + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Version bump detected for ${skill_dir} (skill.json changed: ${json_version_changed}, SKILL.md changed: ${md_version_changed})"
|
||||||
|
|
||||||
if [ ! -f "${json_path}" ]; then
|
if [ ! -f "${json_path}" ]; then
|
||||||
echo "::error file=${json_path}::Missing skill.json after version bump."
|
echo "::error file=${json_path}::Missing skill.json after version bump."
|
||||||
failures=$((failures + 1))
|
failures=$((failures + 1))
|
||||||
@@ -190,20 +182,6 @@ jobs:
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
skill_release_name="$(basename "${skill_dir}")"
|
|
||||||
release_tag="${skill_release_name}-v${head_json_version}"
|
|
||||||
if [ "${json_version_changed}" != "true" ] && [ "${md_version_changed}" != "true" ]; then
|
|
||||||
if git show-ref --verify --quiet "refs/tags/${release_tag}"; then
|
|
||||||
echo "::error file=${skill_dir}::Changed skill package has no version bump and release tag ${release_tag} already exists. Update skill.json and SKILL.md versions and add CHANGELOG.md release notes."
|
|
||||||
failures=$((failures + 1))
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "No version bump detected for ${skill_dir}, but release tag ${release_tag} does not exist; treating ${head_json_version} as unreleased."
|
|
||||||
else
|
|
||||||
echo "Version bump detected for ${skill_dir} (skill.json changed: ${json_version_changed}, SKILL.md changed: ${md_version_changed})"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Version parity OK for ${skill_dir}: ${head_json_version}"
|
echo "Version parity OK for ${skill_dir}: ${head_json_version}"
|
||||||
|
|
||||||
changelog_path="${skill_dir}/CHANGELOG.md"
|
changelog_path="${skill_dir}/CHANGELOG.md"
|
||||||
@@ -245,17 +223,11 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${failures}" -gt 0 ]; then
|
if [ "${failures}" -gt 0 ]; then
|
||||||
echo "::error::Found ${failures} skill metadata/release-notes issue(s) across ${checked_skills} changed skill(s)."
|
echo "::error::Found ${failures} skill metadata/release-notes issue(s) across ${checked_skills} bumped skill(s)."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Validated ${checked_skills} changed skill(s): version parity and changelog release notes are present."
|
echo "Validated ${checked_skills} bumped skill(s): version parity and changelog release notes are present."
|
||||||
|
|
||||||
- name: Validate npx skills install docs
|
|
||||||
env:
|
|
||||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
||||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
||||||
run: node scripts/ci/validate_skill_install_docs.mjs --base "$BASE_SHA" --head "$HEAD_SHA"
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
@@ -269,21 +241,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
|
|
||||||
- name: Install SkillSpector
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
python3 -m venv /tmp/skillspector-venv
|
|
||||||
. /tmp/skillspector-venv/bin/activate
|
|
||||||
git clone --depth 1 https://github.com/NVIDIA/SkillSpector.git /tmp/skillspector
|
|
||||||
make -C /tmp/skillspector install
|
|
||||||
echo "/tmp/skillspector-venv/bin" >> "$GITHUB_PATH"
|
|
||||||
skillspector --help >/dev/null
|
|
||||||
|
|
||||||
- name: Generate test signing key for dry-run
|
- name: Generate test signing key for dry-run
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -408,15 +365,12 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
touched_skills_file="$(mktemp)"
|
touched_skills_file="$(mktemp)"
|
||||||
git diff --name-only "${BASE_SHA}...${HEAD_SHA}" -- \
|
git diff --name-only "${BASE_SHA}...${HEAD_SHA}" -- 'skills/*/skill.json' 'skills/*/SKILL.md' \
|
||||||
'skills/*/**' \
|
|
||||||
':(exclude)skills/*/test/**' \
|
|
||||||
':(exclude)skills/*/tests/**' \
|
|
||||||
| awk -F/ 'NF >= 3 {print $1 "/" $2}' \
|
| awk -F/ 'NF >= 3 {print $1 "/" $2}' \
|
||||||
| sort -u > "${touched_skills_file}"
|
| sort -u > "${touched_skills_file}"
|
||||||
|
|
||||||
if [ ! -s "${touched_skills_file}" ]; then
|
if [ ! -s "${touched_skills_file}" ]; then
|
||||||
echo "No release-relevant skill package files changed in this PR."
|
echo "No skill metadata files changed in this PR."
|
||||||
rm -f "${touched_skills_file}"
|
rm -f "${touched_skills_file}"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@@ -442,52 +396,7 @@ jobs:
|
|||||||
|
|
||||||
is_test_release_path() {
|
is_test_release_path() {
|
||||||
local lower="${1,,}"
|
local lower="${1,,}"
|
||||||
local name="${lower##*/}"
|
[[ "$lower" == test/* || "$lower" == tests/* || "$lower" == */test/* || "$lower" == */tests/* ]]
|
||||||
[[ "$lower" == test/* || "$lower" == tests/* || "$lower" == __tests__/* || "$lower" == */test/* || "$lower" == */tests/* || "$lower" == */__tests__/* || "$name" == test_* || "$name" == test-* || "$name" == spec_* || "$name" == spec-* || "$name" == *.test.* || "$name" == *.spec.* ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
generate_skillspector_report() {
|
|
||||||
local skill_dir="$1"
|
|
||||||
local report_path="$2"
|
|
||||||
|
|
||||||
set +e
|
|
||||||
skillspector scan "${skill_dir}" --no-llm --format markdown --output "${report_path}"
|
|
||||||
local status=$?
|
|
||||||
set -e
|
|
||||||
|
|
||||||
if [ ! -s "${report_path}" ]; then
|
|
||||||
echo "::error file=${skill_dir}::SkillSpector did not produce a report."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${status}" -ne 0 ]; then
|
|
||||||
echo "::warning file=${report_path}::SkillSpector returned exit code ${status}; report is included for review."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
add_release_asset_checksum() {
|
|
||||||
local out_assets="$1"
|
|
||||||
local asset="$2"
|
|
||||||
local file_path="${out_assets}/${asset}"
|
|
||||||
local sha256
|
|
||||||
local size
|
|
||||||
local tmp_json
|
|
||||||
|
|
||||||
if [ ! -s "${file_path}" ]; then
|
|
||||||
echo "::error file=${file_path}::Required release trust artifact is missing or empty."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
sha256="$(sha256sum "${file_path}" | awk '{print $1}')"
|
|
||||||
size="$(stat -c%s "${file_path}" 2>/dev/null || stat -f%z "${file_path}")"
|
|
||||||
tmp_json="$(mktemp)"
|
|
||||||
jq \
|
|
||||||
--arg key "${asset}" \
|
|
||||||
--arg sha "${sha256}" \
|
|
||||||
--argjson sz "${size}" \
|
|
||||||
'.files += {($key): {sha256: $sha, size: $sz, path: $key}}' \
|
|
||||||
"${out_assets}/checksums.json" > "${tmp_json}"
|
|
||||||
mv "${tmp_json}" "${out_assets}/checksums.json"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while IFS= read -r skill_dir; do
|
while IFS= read -r skill_dir; do
|
||||||
@@ -538,6 +447,11 @@ jobs:
|
|||||||
md_version_changed=true
|
md_version_changed=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "${json_version_changed}" != "true" ] && [ "${md_version_changed}" != "true" ]; then
|
||||||
|
echo "No version bump detected for ${skill_dir}; skipping dry-run."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "${head_json_version}" ] || [ -z "${head_md_version}" ] || [ "${head_json_version}" != "${head_md_version}" ]; then
|
if [ -z "${head_json_version}" ] || [ -z "${head_md_version}" ] || [ "${head_json_version}" != "${head_md_version}" ]; then
|
||||||
echo "::error file=${skill_dir}::Version metadata is invalid for dry-run. Ensure validate-pr-version-sync passes."
|
echo "::error file=${skill_dir}::Version metadata is invalid for dry-run. Ensure validate-pr-version-sync passes."
|
||||||
failures=$((failures + 1))
|
failures=$((failures + 1))
|
||||||
@@ -626,9 +540,9 @@ jobs:
|
|||||||
# --- Create zip preserving directory structure ---
|
# --- Create zip preserving directory structure ---
|
||||||
zip_name="${skill_name}-v${version}.zip"
|
zip_name="${skill_name}-v${version}.zip"
|
||||||
(cd "${staging_dir}" && zip -qr "${OLDPWD}/${out_assets}/${zip_name}" .)
|
(cd "${staging_dir}" && zip -qr "${OLDPWD}/${out_assets}/${zip_name}" .)
|
||||||
if unzip -Z1 "${out_assets}/${zip_name}" | grep -Eiq '(^|/)(__tests__|test|tests)/|(^|/)(test|spec)[_-]|(^|/).*\.(test|spec)\.'; then
|
if unzip -Z1 "${out_assets}/${zip_name}" | grep -Eiq '(^|/)(test|tests)/'; then
|
||||||
echo "::error::Dry-run release archive contains test-only files: ${zip_name}"
|
echo "::error::Dry-run release archive contains test-only files: ${zip_name}"
|
||||||
unzip -Z1 "${out_assets}/${zip_name}" | grep -Ei '(^|/)(__tests__|test|tests)/|(^|/)(test|spec)[_-]|(^|/).*\.(test|spec)\.' || true
|
unzip -Z1 "${out_assets}/${zip_name}" | grep -Ei '(^|/)(test|tests)/' || true
|
||||||
failures=$((failures + 1))
|
failures=$((failures + 1))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -708,58 +622,6 @@ jobs:
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Generate release trust packet and include it in signed checksums ---
|
|
||||||
node scripts/ci/generate_skill_release_trust_packet.mjs \
|
|
||||||
"${skill_dir}" \
|
|
||||||
"${out_assets}" \
|
|
||||||
--repository "${{ github.repository }}" \
|
|
||||||
--tag "${tag}" \
|
|
||||||
--source-ref "${HEAD_SHA}"
|
|
||||||
|
|
||||||
# --- Generate SkillSpector report ---
|
|
||||||
if ! generate_skillspector_report "${inner_dir}" "${out_assets}/skillspector-report.md"; then
|
|
||||||
failures=$((failures + 1))
|
|
||||||
rm -rf "${staging_dir}"
|
|
||||||
echo "::endgroup::"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! add_release_asset_checksum "${out_assets}" "skill-card.md"; then
|
|
||||||
failures=$((failures + 1))
|
|
||||||
rm -rf "${staging_dir}"
|
|
||||||
echo "::endgroup::"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! add_release_asset_checksum "${out_assets}" "permissions.json"; then
|
|
||||||
failures=$((failures + 1))
|
|
||||||
rm -rf "${staging_dir}"
|
|
||||||
echo "::endgroup::"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! add_release_asset_checksum "${out_assets}" "install.md"; then
|
|
||||||
failures=$((failures + 1))
|
|
||||||
rm -rf "${staging_dir}"
|
|
||||||
echo "::endgroup::"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! add_release_asset_checksum "${out_assets}" "skillspector-report.md"; then
|
|
||||||
failures=$((failures + 1))
|
|
||||||
rm -rf "${staging_dir}"
|
|
||||||
echo "::endgroup::"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! jq -e . "${out_assets}/checksums.json" >/dev/null 2>&1; then
|
|
||||||
echo "::error::Generated checksums.json is invalid JSON after adding release trust artifacts."
|
|
||||||
failures=$((failures + 1))
|
|
||||||
rm -rf "${staging_dir}"
|
|
||||||
echo "::endgroup::"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- Copy skill.json and root-level docs alongside the zip ---
|
# --- Copy skill.json and root-level docs alongside the zip ---
|
||||||
cp "${json_path}" "${out_assets}/skill.json"
|
cp "${json_path}" "${out_assets}/skill.json"
|
||||||
if [ -f "${skill_dir}/SKILL.md" ]; then
|
if [ -f "${skill_dir}/SKILL.md" ]; then
|
||||||
@@ -784,227 +646,12 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${dry_run_count}" -eq 0 ]; then
|
if [ "${dry_run_count}" -eq 0 ]; then
|
||||||
echo "No changed skill directories required dry-run assets."
|
echo "No version bumps detected in changed skill metadata files."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Release dry-run completed successfully for ${dry_run_count} changed skill(s)."
|
echo "Release dry-run completed successfully for ${dry_run_count} changed skill(s)."
|
||||||
|
|
||||||
- name: Prepare SkillSpector PR report artifact
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
rm -rf dist/skillspector-pr-reports
|
|
||||||
mkdir -p dist/dry-run dist/skillspector-pr-reports
|
|
||||||
|
|
||||||
found_reports=false
|
|
||||||
while IFS= read -r report_path; do
|
|
||||||
tag="${report_path#dist/dry-run/}"
|
|
||||||
tag="${tag%%/*}"
|
|
||||||
mkdir -p "dist/skillspector-pr-reports/${tag}"
|
|
||||||
cp "${report_path}" "dist/skillspector-pr-reports/${tag}/skillspector-report.md"
|
|
||||||
found_reports=true
|
|
||||||
done < <(find dist/dry-run -path '*/release-assets/skillspector-report.md' -type f | sort)
|
|
||||||
|
|
||||||
if [ "${found_reports}" != "true" ]; then
|
|
||||||
printf 'No SkillSpector reports were generated for this pull request.\n' > dist/skillspector-pr-reports/NO_SKILLSPECTOR_REPORTS.txt
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Upload SkillSpector PR reports
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
||||||
with:
|
|
||||||
name: skillspector-pr-reports
|
|
||||||
path: dist/skillspector-pr-reports
|
|
||||||
retention-days: 14
|
|
||||||
|
|
||||||
comment-skillspector-report:
|
|
||||||
if: always() && github.event_name == 'pull_request' && needs.release.result != 'cancelled'
|
|
||||||
needs: release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
continue-on-error: true
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
issues: write
|
|
||||||
pull-requests: read
|
|
||||||
steps:
|
|
||||||
- name: Download SkillSpector reports
|
|
||||||
continue-on-error: true
|
|
||||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
||||||
with:
|
|
||||||
name: skillspector-pr-reports
|
|
||||||
path: skillspector-pr-reports
|
|
||||||
|
|
||||||
- name: Comment SkillSpector reports
|
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const fs = require("node:fs/promises");
|
|
||||||
const path = require("node:path");
|
|
||||||
|
|
||||||
const root = "skillspector-pr-reports";
|
|
||||||
const maxCommentLength = 65000;
|
|
||||||
|
|
||||||
async function findReports(dir) {
|
|
||||||
let entries;
|
|
||||||
try {
|
|
||||||
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code === "ENOENT") {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reports = [];
|
|
||||||
for (const entry of entries) {
|
|
||||||
const fullPath = path.join(dir, entry.name);
|
|
||||||
if (entry.isDirectory()) {
|
|
||||||
reports.push(...await findReports(fullPath));
|
|
||||||
} else if (entry.isFile() && entry.name === "skillspector-report.md") {
|
|
||||||
reports.push(fullPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return reports;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tagFromReportPath(reportPath) {
|
|
||||||
const parts = reportPath.split(path.sep);
|
|
||||||
const releaseAssetsIndex = parts.lastIndexOf("release-assets");
|
|
||||||
if (releaseAssetsIndex > 0) {
|
|
||||||
return parts[releaseAssetsIndex - 1];
|
|
||||||
}
|
|
||||||
return path.basename(path.dirname(reportPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
function sanitizeReportForComment(report) {
|
|
||||||
const omittedBlock = "_[code block omitted from PR comment; download the workflow artifact for raw details]_";
|
|
||||||
return report
|
|
||||||
.replace(/```[\s\S]*?```/g, omittedBlock)
|
|
||||||
.split(/\r?\n/)
|
|
||||||
.filter((line) => !/^\s{4,}\S/.test(line))
|
|
||||||
.join("\n")
|
|
||||||
.replace(/`[^`\n]*`/g, "`[inline snippet omitted]`")
|
|
||||||
.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, "[redacted-email]")
|
|
||||||
.replace(/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g, "[redacted-aws-key]")
|
|
||||||
.replace(/\b(?:ghp|gho|ghu|ghs|ghr|github_pat|glpat|xox[baprs]?|sk|pk)_[A-Za-z0-9_=-]{12,}\b/gi, "[redacted-token]")
|
|
||||||
.replace(/\b[A-Za-z0-9+/]{40,}={0,2}\b/g, "[redacted-secret-like-value]")
|
|
||||||
.trimEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildComment({ tag, report }) {
|
|
||||||
const marker = `<!-- clawsec-skillspector-report:${tag} -->`;
|
|
||||||
const sanitizedReport = sanitizeReportForComment(report);
|
|
||||||
const footer = [
|
|
||||||
"_Generated by the Skill Release dry-run for `" + tag + "`._",
|
|
||||||
"_Raw snippets, code blocks, inline code, emails, and token-like values are omitted from this PR comment._",
|
|
||||||
"_Download the `skillspector-pr-reports` workflow artifact for the full report._",
|
|
||||||
].join("\n");
|
|
||||||
let body = `${marker}\n${sanitizedReport}\n\n${footer}`;
|
|
||||||
|
|
||||||
if (body.length <= maxCommentLength) {
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
const truncatedFooter = [
|
|
||||||
"_Report truncated because it exceeds GitHub's comment size limit._",
|
|
||||||
"_Download the `skillspector-pr-reports` workflow artifact for the full report._",
|
|
||||||
footer,
|
|
||||||
].join("\n");
|
|
||||||
const budget = maxCommentLength - marker.length - truncatedFooter.length - 8;
|
|
||||||
return `${marker}\n${sanitizedReport.slice(0, Math.max(0, budget)).trimEnd()}\n\n${truncatedFooter}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reports = await findReports(root);
|
|
||||||
if (reports.length === 0) {
|
|
||||||
core.info("No SkillSpector reports found; nothing to comment.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: context.issue.number,
|
|
||||||
per_page: 100,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const reportPath of reports.sort()) {
|
|
||||||
const tag = tagFromReportPath(reportPath);
|
|
||||||
const report = await fs.readFile(reportPath, "utf8");
|
|
||||||
const marker = `<!-- clawsec-skillspector-report:${tag} -->`;
|
|
||||||
const body = buildComment({ tag, report });
|
|
||||||
const existing = comments.find((comment) => comment.body?.includes(marker));
|
|
||||||
|
|
||||||
if (existing) {
|
|
||||||
await github.rest.issues.updateComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
comment_id: existing.id,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
core.info(`Updated SkillSpector PR comment for ${tag}.`);
|
|
||||||
} else {
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: context.issue.number,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
core.info(`Created SkillSpector PR comment for ${tag}.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
simulate-tag-release-build:
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
needs: validate-pr-version-sync
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
|
|
||||||
- name: Install SkillSpector
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
python3 -m venv /tmp/skillspector-venv
|
|
||||||
. /tmp/skillspector-venv/bin/activate
|
|
||||||
git clone --depth 1 https://github.com/NVIDIA/SkillSpector.git /tmp/skillspector
|
|
||||||
make -C /tmp/skillspector install
|
|
||||||
echo "/tmp/skillspector-venv/bin" >> "$GITHUB_PATH"
|
|
||||||
skillspector --help >/dev/null
|
|
||||||
|
|
||||||
- name: Simulate tag release build
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
mkdir -p dist/tag-release-simulation
|
|
||||||
|
|
||||||
for skill_json in skills/*/skill.json; do
|
|
||||||
skill_dir="${skill_json%/skill.json}"
|
|
||||||
skill_name="$(basename "${skill_dir}")"
|
|
||||||
echo "::group::Simulate tag release build for ${skill_name}"
|
|
||||||
node scripts/ci/simulate_skill_tag_release.mjs \
|
|
||||||
"${skill_dir}" \
|
|
||||||
"dist/tag-release-simulation/${skill_name}" \
|
|
||||||
--repository "${{ github.repository }}" \
|
|
||||||
--source-ref "${{ github.event.pull_request.head.sha }}"
|
|
||||||
jq -e '.simulated_version | test("^[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9]+)?$")' \
|
|
||||||
"dist/tag-release-simulation/${skill_name}/simulation-summary.json" >/dev/null
|
|
||||||
test -s "dist/tag-release-simulation/${skill_name}/release-assets/checksums.json"
|
|
||||||
test -s "dist/tag-release-simulation/${skill_name}/release-assets/checksums.sig"
|
|
||||||
test -s "dist/tag-release-simulation/${skill_name}/release-assets/signing-public.pem"
|
|
||||||
test -s "dist/tag-release-simulation/${skill_name}/release-assets/skillspector-report.md"
|
|
||||||
echo "::endgroup::"
|
|
||||||
done
|
|
||||||
|
|
||||||
release-tag:
|
release-tag:
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -1017,7 +664,6 @@ jobs:
|
|||||||
publishable: ${{ steps.publishable.outputs.publishable }}
|
publishable: ${{ steps.publishable.outputs.publishable }}
|
||||||
openclaw_skill: ${{ steps.publishable.outputs.openclaw_skill }}
|
openclaw_skill: ${{ steps.publishable.outputs.openclaw_skill }}
|
||||||
publish_clawhub: ${{ steps.publishable.outputs.publish_clawhub }}
|
publish_clawhub: ${{ steps.publishable.outputs.publish_clawhub }}
|
||||||
clawhub_slug: ${{ steps.publishable.outputs.clawhub_slug }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Parse tag
|
- name: Parse tag
|
||||||
id: parse
|
id: parse
|
||||||
@@ -1115,32 +761,16 @@ jobs:
|
|||||||
PUBLISH_CLAWHUB=true
|
PUBLISH_CLAWHUB=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CLAWHUB_SLUG=$(node scripts/ci/resolve_clawhub_slug.mjs "$SKILL_PATH")
|
|
||||||
|
|
||||||
echo "internal=${INTERNAL}" >> $GITHUB_OUTPUT
|
echo "internal=${INTERNAL}" >> $GITHUB_OUTPUT
|
||||||
echo "openclaw_skill=${OPENCLAW_SKILL}" >> $GITHUB_OUTPUT
|
echo "openclaw_skill=${OPENCLAW_SKILL}" >> $GITHUB_OUTPUT
|
||||||
echo "publish_clawhub=${PUBLISH_CLAWHUB}" >> $GITHUB_OUTPUT
|
echo "publish_clawhub=${PUBLISH_CLAWHUB}" >> $GITHUB_OUTPUT
|
||||||
echo "publishable=${PUBLISHABLE}" >> $GITHUB_OUTPUT
|
echo "publishable=${PUBLISHABLE}" >> $GITHUB_OUTPUT
|
||||||
echo "clawhub_slug=${CLAWHUB_SLUG}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
- name: Validate npx skills install docs
|
|
||||||
run: node scripts/ci/validate_skill_install_docs.mjs --skills "${{ steps.parse.outputs.skill_path }}"
|
|
||||||
|
|
||||||
- name: Install SkillSpector
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
python3 -m venv /tmp/skillspector-venv
|
|
||||||
. /tmp/skillspector-venv/bin/activate
|
|
||||||
git clone --depth 1 https://github.com/NVIDIA/SkillSpector.git /tmp/skillspector
|
|
||||||
make -C /tmp/skillspector install
|
|
||||||
echo "/tmp/skillspector-venv/bin" >> "$GITHUB_PATH"
|
|
||||||
skillspector --help >/dev/null
|
|
||||||
|
|
||||||
- name: Sign embedded advisory feed and verify
|
- name: Sign embedded advisory feed and verify
|
||||||
if: hashFiles(format('skills/{0}/advisories/feed.json', steps.parse.outputs.skill_name)) != ''
|
if: hashFiles(format('skills/{0}/advisories/feed.json', steps.parse.outputs.skill_name)) != ''
|
||||||
uses: ./.github/actions/sign-and-verify
|
uses: ./.github/actions/sign-and-verify
|
||||||
@@ -1237,51 +867,7 @@ jobs:
|
|||||||
|
|
||||||
is_test_release_path() {
|
is_test_release_path() {
|
||||||
local lower="${1,,}"
|
local lower="${1,,}"
|
||||||
local name="${lower##*/}"
|
[[ "$lower" == test/* || "$lower" == tests/* || "$lower" == */test/* || "$lower" == */tests/* ]]
|
||||||
[[ "$lower" == test/* || "$lower" == tests/* || "$lower" == __tests__/* || "$lower" == */test/* || "$lower" == */tests/* || "$lower" == */__tests__/* || "$name" == test_* || "$name" == test-* || "$name" == spec_* || "$name" == spec-* || "$name" == *.test.* || "$name" == *.spec.* ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
generate_skillspector_report() {
|
|
||||||
local skill_dir="$1"
|
|
||||||
local report_path="$2"
|
|
||||||
|
|
||||||
set +e
|
|
||||||
skillspector scan "${skill_dir}" --no-llm --format markdown --output "${report_path}"
|
|
||||||
local status=$?
|
|
||||||
set -e
|
|
||||||
|
|
||||||
if [ ! -s "${report_path}" ]; then
|
|
||||||
echo "::error file=${skill_dir}::SkillSpector did not produce a report."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${status}" -ne 0 ]; then
|
|
||||||
echo "::warning file=${report_path}::SkillSpector returned exit code ${status}; report is included for review."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
add_release_asset_checksum() {
|
|
||||||
local asset="$1"
|
|
||||||
local file_path="release-assets/${asset}"
|
|
||||||
local sha256
|
|
||||||
local size
|
|
||||||
local tmp_json
|
|
||||||
|
|
||||||
if [ ! -s "${file_path}" ]; then
|
|
||||||
echo "::error file=${file_path}::Required release trust artifact is missing or empty."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
sha256="$(sha256sum "${file_path}" | awk '{print $1}')"
|
|
||||||
size="$(stat -c%s "${file_path}" 2>/dev/null || stat -f%z "${file_path}")"
|
|
||||||
tmp_json="$(mktemp)"
|
|
||||||
jq \
|
|
||||||
--arg key "${asset}" \
|
|
||||||
--arg sha "${sha256}" \
|
|
||||||
--argjson sz "${size}" \
|
|
||||||
'.files += {($key): {sha256: $sha, size: $sz, path: $key}}' \
|
|
||||||
release-assets/checksums.json > "${tmp_json}"
|
|
||||||
mv "${tmp_json}" release-assets/checksums.json
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Stage SBOM files preserving directory structure ---
|
# --- Stage SBOM files preserving directory structure ---
|
||||||
@@ -1319,9 +905,9 @@ jobs:
|
|||||||
# --- Create zip preserving directory structure ---
|
# --- Create zip preserving directory structure ---
|
||||||
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
|
||||||
(cd "$STAGING_DIR" && zip -qr "$OLDPWD/release-assets/$ZIP_NAME" .)
|
(cd "$STAGING_DIR" && zip -qr "$OLDPWD/release-assets/$ZIP_NAME" .)
|
||||||
if unzip -Z1 "release-assets/$ZIP_NAME" | grep -Eiq '(^|/)(__tests__|test|tests)/|(^|/)(test|spec)[_-]|(^|/).*\.(test|spec)\.'; then
|
if unzip -Z1 "release-assets/$ZIP_NAME" | grep -Eiq '(^|/)(test|tests)/'; then
|
||||||
echo "::error::Release archive contains test-only files: $ZIP_NAME"
|
echo "::error::Release archive contains test-only files: $ZIP_NAME"
|
||||||
unzip -Z1 "release-assets/$ZIP_NAME" | grep -Ei '(^|/)(__tests__|test|tests)/|(^|/)(test|spec)[_-]|(^|/).*\.(test|spec)\.' || true
|
unzip -Z1 "release-assets/$ZIP_NAME" | grep -Ei '(^|/)(test|tests)/' || true
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1385,32 +971,6 @@ jobs:
|
|||||||
files: $files
|
files: $files
|
||||||
}' > "release-assets/checksums.json"
|
}' > "release-assets/checksums.json"
|
||||||
|
|
||||||
# --- Generate release trust packet and include it in signed checksums ---
|
|
||||||
node scripts/ci/generate_skill_release_trust_packet.mjs \
|
|
||||||
"$SKILL_PATH" \
|
|
||||||
release-assets \
|
|
||||||
--repository "${{ github.repository }}" \
|
|
||||||
--tag "$TAG" \
|
|
||||||
--source-ref "$TAG"
|
|
||||||
|
|
||||||
# --- Generate SkillSpector report ---
|
|
||||||
generate_skillspector_report "$INNER_DIR" "release-assets/skillspector-report.md"
|
|
||||||
|
|
||||||
test -s release-assets/skill-card.md
|
|
||||||
test -s release-assets/permissions.json
|
|
||||||
test -s release-assets/install.md
|
|
||||||
test -s release-assets/skillspector-report.md
|
|
||||||
|
|
||||||
add_release_asset_checksum "skill-card.md"
|
|
||||||
add_release_asset_checksum "permissions.json"
|
|
||||||
add_release_asset_checksum "install.md"
|
|
||||||
add_release_asset_checksum "skillspector-report.md"
|
|
||||||
|
|
||||||
if ! jq -e . "release-assets/checksums.json" >/dev/null 2>&1; then
|
|
||||||
echo "::error::Generated checksums.json is invalid JSON after adding release trust artifacts."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- Copy skill.json and root-level docs alongside the zip ---
|
# --- Copy skill.json and root-level docs alongside the zip ---
|
||||||
cp "$SKILL_PATH/skill.json" release-assets/skill.json
|
cp "$SKILL_PATH/skill.json" release-assets/skill.json
|
||||||
if [ -f "$SKILL_PATH/SKILL.md" ]; then
|
if [ -f "$SKILL_PATH/SKILL.md" ]; then
|
||||||
@@ -1495,7 +1055,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
SKILL_NAME="${{ steps.parse.outputs.skill_name }}"
|
SKILL_NAME="${{ steps.parse.outputs.skill_name }}"
|
||||||
CLAWHUB_SLUG="${{ steps.publishable.outputs.clawhub_slug }}"
|
|
||||||
VERSION="${{ steps.parse.outputs.version }}"
|
VERSION="${{ steps.parse.outputs.version }}"
|
||||||
REPO="${{ github.repository }}"
|
REPO="${{ github.repository }}"
|
||||||
TAG="${{ github.ref_name }}"
|
TAG="${{ github.ref_name }}"
|
||||||
@@ -1503,33 +1062,13 @@ jobs:
|
|||||||
{
|
{
|
||||||
echo "quick_install<<INSTALL_EOF"
|
echo "quick_install<<INSTALL_EOF"
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
### Agent Skills CLI
|
|
||||||
|
|
||||||
**Codex global install:**
|
|
||||||
\`\`\`bash
|
|
||||||
npx skills add ${REPO} --skill ${SKILL_NAME} --agent codex --global --yes
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
**OpenClaw global install:**
|
|
||||||
\`\`\`bash
|
|
||||||
npx skills add ${REPO} --skill ${SKILL_NAME} --agent openclaw --global --yes
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
**Update an installed skill:**
|
|
||||||
\`\`\`bash
|
|
||||||
npx skills update ${SKILL_NAME}
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
EOF
|
|
||||||
|
|
||||||
if [ "${{ steps.publishable.outputs.publish_clawhub }}" = "true" ] && [ "${{ steps.publishable.outputs.openclaw_skill }}" = "true" ]; then
|
if [ "${{ steps.publishable.outputs.publish_clawhub }}" = "true" ] && [ "${{ steps.publishable.outputs.openclaw_skill }}" = "true" ]; then
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
### Quick Install
|
### Quick Install
|
||||||
|
|
||||||
**Via ClawHub (recommended):**
|
**Via ClawHub (recommended):**
|
||||||
\`\`\`bash
|
\`\`\`bash
|
||||||
npx clawhub@latest install ${CLAWHUB_SLUG}
|
npx clawhub@latest install ${SKILL_NAME}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
**If you already have \`clawsec-suite\` installed:**
|
**If you already have \`clawsec-suite\` installed:**
|
||||||
@@ -1576,61 +1115,38 @@ jobs:
|
|||||||
echo "INSTALL_EOF"
|
echo "INSTALL_EOF"
|
||||||
} >> "$GITHUB_OUTPUT"
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Prepare GitHub release body
|
|
||||||
env:
|
|
||||||
SKILL_NAME: ${{ steps.parse.outputs.skill_name }}
|
|
||||||
VERSION: ${{ steps.parse.outputs.version }}
|
|
||||||
CHANGELOG: ${{ steps.changelog.outputs.changelog }}
|
|
||||||
QUICK_INSTALL: ${{ steps.install.outputs.quick_install }}
|
|
||||||
REPO: ${{ github.repository }}
|
|
||||||
TAG: ${{ github.ref_name }}
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
node -e '
|
|
||||||
const { readFileSync, writeFileSync } = require("node:fs");
|
|
||||||
const bodyPath = `${process.env.RUNNER_TEMP}/skill-release-body.md`;
|
|
||||||
const report = readFileSync("release-assets/skillspector-report.md", "utf8").trimEnd();
|
|
||||||
const body = [
|
|
||||||
`## ${process.env.SKILL_NAME} ${process.env.VERSION}`,
|
|
||||||
"",
|
|
||||||
process.env.CHANGELOG || "",
|
|
||||||
"",
|
|
||||||
process.env.QUICK_INSTALL || "",
|
|
||||||
"",
|
|
||||||
report,
|
|
||||||
"",
|
|
||||||
`Download the generated release-payload scan: [skillspector-report.md](https://github.com/${process.env.REPO}/releases/download/${process.env.TAG}/skillspector-report.md)`,
|
|
||||||
"",
|
|
||||||
"### Verification",
|
|
||||||
"",
|
|
||||||
"`checksums.json` is cryptographically signed (`checksums.sig`) using the ClawSec CI signing key.",
|
|
||||||
"Verify the signature first, then trust hashes from `checksums.json`:",
|
|
||||||
"```bash",
|
|
||||||
`curl -sLO https://github.com/${process.env.REPO}/releases/download/${process.env.TAG}/checksums.json`,
|
|
||||||
`curl -sLO https://github.com/${process.env.REPO}/releases/download/${process.env.TAG}/checksums.sig`,
|
|
||||||
`curl -sLO https://github.com/${process.env.REPO}/releases/download/${process.env.TAG}/signing-public.pem`,
|
|
||||||
"openssl base64 -d -A -in checksums.sig -out checksums.sig.bin",
|
|
||||||
"openssl pkeyutl -verify -rawin -pubin -inkey signing-public.pem -sigfile checksums.sig.bin -in checksums.json",
|
|
||||||
"```",
|
|
||||||
"",
|
|
||||||
"### Files",
|
|
||||||
"",
|
|
||||||
"See `checksums.json` for the complete file manifest with SHA256 hashes.",
|
|
||||||
"The zip archive preserves the full directory structure of the skill.",
|
|
||||||
"",
|
|
||||||
"---",
|
|
||||||
"*Released by ClawSec skill distribution pipeline*",
|
|
||||||
].join("\n");
|
|
||||||
writeFileSync(bodyPath, `${body}\n`);
|
|
||||||
'
|
|
||||||
|
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||||
with:
|
with:
|
||||||
name: "${{ steps.parse.outputs.skill_name }} ${{ steps.parse.outputs.version }}"
|
name: "${{ steps.parse.outputs.skill_name }} ${{ steps.parse.outputs.version }}"
|
||||||
tag_name: ${{ github.ref_name }}
|
tag_name: ${{ github.ref_name }}
|
||||||
files: release-assets/*
|
files: release-assets/*
|
||||||
body_path: ${{ runner.temp }}/skill-release-body.md
|
body: |
|
||||||
|
## ${{ steps.parse.outputs.skill_name }} ${{ steps.parse.outputs.version }}
|
||||||
|
|
||||||
|
${{ steps.changelog.outputs.changelog }}
|
||||||
|
|
||||||
|
${{ steps.install.outputs.quick_install }}
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
|
||||||
|
`checksums.json` is cryptographically signed (`checksums.sig`) using the ClawSec CI signing key.
|
||||||
|
Verify the signature first, then trust hashes from `checksums.json`:
|
||||||
|
```bash
|
||||||
|
curl -sLO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/checksums.json
|
||||||
|
curl -sLO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/checksums.sig
|
||||||
|
curl -sLO https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/signing-public.pem
|
||||||
|
openssl base64 -d -A -in checksums.sig -out checksums.sig.bin
|
||||||
|
openssl pkeyutl -verify -rawin -pubin -inkey signing-public.pem -sigfile checksums.sig.bin -in checksums.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Files
|
||||||
|
|
||||||
|
See `checksums.json` for the complete file manifest with SHA256 hashes.
|
||||||
|
The zip archive preserves the full directory structure of the skill.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Released by ClawSec skill distribution pipeline*
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }}
|
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }}
|
||||||
env:
|
env:
|
||||||
@@ -1685,10 +1201,6 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
env:
|
env:
|
||||||
CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }}
|
CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }}
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }}
|
|
||||||
AWS_REGION: eu-north-1
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check if publishable
|
- name: Check if publishable
|
||||||
if: needs.release-tag.outputs.publish_clawhub != 'true'
|
if: needs.release-tag.outputs.publish_clawhub != 'true'
|
||||||
@@ -1708,12 +1220,51 @@ jobs:
|
|||||||
|
|
||||||
- name: Install clawhub CLI
|
- name: Install clawhub CLI
|
||||||
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
|
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
|
||||||
run: bash scripts/ci/install_clawhub_cli.sh
|
run: npm install -g clawhub@${CLAWHUB_CLI_VERSION}
|
||||||
|
|
||||||
- name: Patch clawhub publish payload workaround
|
- name: Patch clawhub publish payload workaround
|
||||||
# Temporary: clawhub@0.7.0 publish payload is missing acceptLicenseTerms.
|
# Temporary: clawhub@0.7.0 publish payload is missing acceptLicenseTerms.
|
||||||
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
|
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
|
||||||
run: node scripts/ci/patch_clawhub_publish_payload.mjs
|
run: |
|
||||||
|
node <<'NODE'
|
||||||
|
const { execSync } = require("node:child_process");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
|
||||||
|
const npmRoot = execSync("npm root -g", { encoding: "utf8" }).trim();
|
||||||
|
const publishScriptPath = path.join(
|
||||||
|
npmRoot,
|
||||||
|
"clawhub",
|
||||||
|
"dist",
|
||||||
|
"cli",
|
||||||
|
"commands",
|
||||||
|
"publish.js"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fs.existsSync(publishScriptPath)) {
|
||||||
|
throw new Error(`clawhub publish script not found: ${publishScriptPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const original = fs.readFileSync(publishScriptPath, "utf8");
|
||||||
|
if (original.includes("acceptLicenseTerms: true")) {
|
||||||
|
console.log(`[patch-clawhub] Already patched: ${publishScriptPath}`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payloadPattern = /changelog,\r?\n(\s*)tags,/;
|
||||||
|
if (!payloadPattern.test(original)) {
|
||||||
|
throw new Error(
|
||||||
|
`[patch-clawhub] Could not find expected publish payload pattern in ${publishScriptPath}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const patched = original.replace(
|
||||||
|
payloadPattern,
|
||||||
|
(_, indent) => `changelog,\n${indent}acceptLicenseTerms: true,\n${indent}tags,`
|
||||||
|
);
|
||||||
|
fs.writeFileSync(publishScriptPath, patched, "utf8");
|
||||||
|
console.log(`[patch-clawhub] Patched: ${publishScriptPath}`);
|
||||||
|
NODE
|
||||||
|
|
||||||
- name: Login to ClawHub
|
- name: Login to ClawHub
|
||||||
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
|
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
|
||||||
@@ -1733,24 +1284,23 @@ jobs:
|
|||||||
SITE=${CLAWHUB_SITE:-https://clawhub.ai}
|
SITE=${CLAWHUB_SITE:-https://clawhub.ai}
|
||||||
REGISTRY=${CLAWHUB_REGISTRY:-$SITE}
|
REGISTRY=${CLAWHUB_REGISTRY:-$SITE}
|
||||||
SKILL_NAME="${{ needs.release-tag.outputs.skill_name }}"
|
SKILL_NAME="${{ needs.release-tag.outputs.skill_name }}"
|
||||||
CLAWHUB_SLUG="${{ needs.release-tag.outputs.clawhub_slug }}"
|
|
||||||
VERSION="${{ needs.release-tag.outputs.version }}"
|
VERSION="${{ needs.release-tag.outputs.version }}"
|
||||||
export CLAWHUB_CONFIG_PATH="$HOME/.clawhub-ci/config.json"
|
export CLAWHUB_CONFIG_PATH="$HOME/.clawhub-ci/config.json"
|
||||||
|
|
||||||
set +e
|
set +e
|
||||||
CLAWHUB_DISABLE_TELEMETRY=1 CLAWHUB_SITE="$SITE" CLAWHUB_REGISTRY="$REGISTRY" \
|
CLAWHUB_DISABLE_TELEMETRY=1 CLAWHUB_SITE="$SITE" CLAWHUB_REGISTRY="$REGISTRY" \
|
||||||
clawhub inspect "$CLAWHUB_SLUG" --version "$VERSION" --json \
|
clawhub inspect "$SKILL_NAME" --version "$VERSION" --json \
|
||||||
> /tmp/clawhub-existing-version.json 2> /tmp/clawhub-existing-version.err
|
> /tmp/clawhub-existing-version.json 2> /tmp/clawhub-existing-version.err
|
||||||
STATUS=$?
|
STATUS=$?
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ "$STATUS" -eq 0 ]; then
|
if [ "$STATUS" -eq 0 ]; then
|
||||||
echo "::error::ClawHub already contains ${CLAWHUB_SLUG}@${VERSION}. Bump the version before tagging."
|
echo "::error::ClawHub already contains ${SKILL_NAME}@${VERSION}. Bump the version before tagging."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if grep -Eqi "Version not found|Skill not found" /tmp/clawhub-existing-version.err; then
|
if grep -Eqi "Version not found|Skill not found" /tmp/clawhub-existing-version.err; then
|
||||||
echo "No existing ${CLAWHUB_SLUG}@${VERSION} detected in ClawHub. Proceeding."
|
echo "No existing ${SKILL_NAME}@${VERSION} detected in ClawHub. Proceeding."
|
||||||
else
|
else
|
||||||
echo "::error::Failed to verify ClawHub version precondition."
|
echo "::error::Failed to verify ClawHub version precondition."
|
||||||
cat /tmp/clawhub-existing-version.err
|
cat /tmp/clawhub-existing-version.err
|
||||||
@@ -1765,7 +1315,6 @@ jobs:
|
|||||||
REGISTRY=${CLAWHUB_REGISTRY:-$SITE}
|
REGISTRY=${CLAWHUB_REGISTRY:-$SITE}
|
||||||
SKILL_PATH="${{ needs.release-tag.outputs.skill_path }}"
|
SKILL_PATH="${{ needs.release-tag.outputs.skill_path }}"
|
||||||
SKILL_NAME="${{ needs.release-tag.outputs.skill_name }}"
|
SKILL_NAME="${{ needs.release-tag.outputs.skill_name }}"
|
||||||
CLAWHUB_SLUG="${{ needs.release-tag.outputs.clawhub_slug }}"
|
|
||||||
VERSION="${{ needs.release-tag.outputs.version }}"
|
VERSION="${{ needs.release-tag.outputs.version }}"
|
||||||
NAME=$(jq -r '.name' "$SKILL_PATH/skill.json")
|
NAME=$(jq -r '.name' "$SKILL_PATH/skill.json")
|
||||||
CHANGELOG="Release ${VERSION} via CI"
|
CHANGELOG="Release ${VERSION} via CI"
|
||||||
@@ -1774,7 +1323,7 @@ jobs:
|
|||||||
|
|
||||||
if ! CLAWHUB_DISABLE_TELEMETRY=1 CLAWHUB_SITE="$SITE" CLAWHUB_REGISTRY="$REGISTRY" \
|
if ! CLAWHUB_DISABLE_TELEMETRY=1 CLAWHUB_SITE="$SITE" CLAWHUB_REGISTRY="$REGISTRY" \
|
||||||
clawhub publish "$SKILL_PATH" \
|
clawhub publish "$SKILL_PATH" \
|
||||||
--slug "$CLAWHUB_SLUG" \
|
--slug "$SKILL_NAME" \
|
||||||
--name "$NAME" \
|
--name "$NAME" \
|
||||||
--version "$VERSION" \
|
--version "$VERSION" \
|
||||||
--changelog "$CHANGELOG" \
|
--changelog "$CHANGELOG" \
|
||||||
@@ -1784,7 +1333,7 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✓ Successfully published $SKILL_NAME@$VERSION to ClawHub as $CLAWHUB_SLUG"
|
echo "✓ Successfully published $SKILL_NAME@$VERSION to ClawHub"
|
||||||
|
|
||||||
republish-clawhub:
|
republish-clawhub:
|
||||||
# Manual workflow to republish a specific tag to ClawHub
|
# Manual workflow to republish a specific tag to ClawHub
|
||||||
@@ -1795,10 +1344,6 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
env:
|
env:
|
||||||
CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }}
|
CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }}
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }}
|
|
||||||
AWS_REGION: eu-north-1
|
|
||||||
steps:
|
steps:
|
||||||
- name: Parse tag
|
- name: Parse tag
|
||||||
id: parse
|
id: parse
|
||||||
@@ -1815,12 +1360,6 @@ jobs:
|
|||||||
|
|
||||||
echo "Parsed tag: skill=${SKILL_NAME}, version=${VERSION}"
|
echo "Parsed tag: skill=${SKILL_NAME}, version=${VERSION}"
|
||||||
|
|
||||||
- name: Checkout workflow helpers
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
|
|
||||||
- name: Prepare ClawHub slug helper
|
|
||||||
run: cp scripts/ci/resolve_clawhub_slug.mjs "$RUNNER_TEMP/resolve_clawhub_slug.mjs"
|
|
||||||
|
|
||||||
- name: Checkout tag
|
- name: Checkout tag
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
@@ -1850,8 +1389,6 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CLAWHUB_SLUG=$(node "$RUNNER_TEMP/resolve_clawhub_slug.mjs" "$SKILL_PATH")
|
|
||||||
echo "clawhub_slug=${CLAWHUB_SLUG}" >> $GITHUB_OUTPUT
|
|
||||||
echo "Skill is publishable to ClawHub"
|
echo "Skill is publishable to ClawHub"
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
@@ -1859,15 +1396,51 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
- name: Validate npx skills install docs
|
|
||||||
run: node scripts/ci/validate_skill_install_docs.mjs --skills "${{ steps.parse.outputs.skill_path }}"
|
|
||||||
|
|
||||||
- name: Install clawhub CLI
|
- name: Install clawhub CLI
|
||||||
run: bash scripts/ci/install_clawhub_cli.sh
|
run: npm install -g clawhub@${CLAWHUB_CLI_VERSION}
|
||||||
|
|
||||||
- name: Patch clawhub publish payload workaround
|
- name: Patch clawhub publish payload workaround
|
||||||
# Temporary: clawhub@0.7.0 publish payload is missing acceptLicenseTerms.
|
# Temporary: clawhub@0.7.0 publish payload is missing acceptLicenseTerms.
|
||||||
run: node scripts/ci/patch_clawhub_publish_payload.mjs
|
run: |
|
||||||
|
node <<'NODE'
|
||||||
|
const { execSync } = require("node:child_process");
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
|
||||||
|
const npmRoot = execSync("npm root -g", { encoding: "utf8" }).trim();
|
||||||
|
const publishScriptPath = path.join(
|
||||||
|
npmRoot,
|
||||||
|
"clawhub",
|
||||||
|
"dist",
|
||||||
|
"cli",
|
||||||
|
"commands",
|
||||||
|
"publish.js"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fs.existsSync(publishScriptPath)) {
|
||||||
|
throw new Error(`clawhub publish script not found: ${publishScriptPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const original = fs.readFileSync(publishScriptPath, "utf8");
|
||||||
|
if (original.includes("acceptLicenseTerms: true")) {
|
||||||
|
console.log(`[patch-clawhub] Already patched: ${publishScriptPath}`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payloadPattern = /changelog,\r?\n(\s*)tags,/;
|
||||||
|
if (!payloadPattern.test(original)) {
|
||||||
|
throw new Error(
|
||||||
|
`[patch-clawhub] Could not find expected publish payload pattern in ${publishScriptPath}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const patched = original.replace(
|
||||||
|
payloadPattern,
|
||||||
|
(_, indent) => `changelog,\n${indent}acceptLicenseTerms: true,\n${indent}tags,`
|
||||||
|
);
|
||||||
|
fs.writeFileSync(publishScriptPath, patched, "utf8");
|
||||||
|
console.log(`[patch-clawhub] Patched: ${publishScriptPath}`);
|
||||||
|
NODE
|
||||||
|
|
||||||
- name: Login to ClawHub
|
- name: Login to ClawHub
|
||||||
run: |
|
run: |
|
||||||
@@ -1891,19 +1464,18 @@ jobs:
|
|||||||
REGISTRY=${CLAWHUB_REGISTRY:-$SITE}
|
REGISTRY=${CLAWHUB_REGISTRY:-$SITE}
|
||||||
SKILL_PATH="${{ steps.parse.outputs.skill_path }}"
|
SKILL_PATH="${{ steps.parse.outputs.skill_path }}"
|
||||||
SKILL_NAME="${{ steps.parse.outputs.skill_name }}"
|
SKILL_NAME="${{ steps.parse.outputs.skill_name }}"
|
||||||
CLAWHUB_SLUG="${{ steps.publishable.outputs.clawhub_slug }}"
|
|
||||||
VERSION="${{ steps.parse.outputs.version }}"
|
VERSION="${{ steps.parse.outputs.version }}"
|
||||||
NAME=$(jq -r '.name' "$SKILL_PATH/skill.json")
|
NAME=$(jq -r '.name' "$SKILL_PATH/skill.json")
|
||||||
CHANGELOG="Manual republish of ${VERSION} via workflow_dispatch"
|
CHANGELOG="Manual republish of ${VERSION} via workflow_dispatch"
|
||||||
|
|
||||||
export CLAWHUB_CONFIG_PATH="$HOME/.clawhub-ci/config.json"
|
export CLAWHUB_CONFIG_PATH="$HOME/.clawhub-ci/config.json"
|
||||||
|
|
||||||
echo "Publishing $SKILL_NAME@$VERSION to ClawHub as $CLAWHUB_SLUG..."
|
echo "Publishing $SKILL_NAME@$VERSION to ClawHub..."
|
||||||
|
|
||||||
# Publish with idempotent retry handling
|
# Publish with idempotent retry handling
|
||||||
if ! CLAWHUB_DISABLE_TELEMETRY=1 CLAWHUB_SITE="$SITE" CLAWHUB_REGISTRY="$REGISTRY" \
|
if ! CLAWHUB_DISABLE_TELEMETRY=1 CLAWHUB_SITE="$SITE" CLAWHUB_REGISTRY="$REGISTRY" \
|
||||||
clawhub publish "$SKILL_PATH" \
|
clawhub publish "$SKILL_PATH" \
|
||||||
--slug "$CLAWHUB_SLUG" \
|
--slug "$SKILL_NAME" \
|
||||||
--name "$NAME" \
|
--name "$NAME" \
|
||||||
--version "$VERSION" \
|
--version "$VERSION" \
|
||||||
--changelog "$CHANGELOG" \
|
--changelog "$CHANGELOG" \
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ ClawSec is a **complete security skill suite for AI agent platforms**. It provid
|
|||||||
| clawsec-nanoclaw | NanoClaw | Yes | Yes | Yes | Yes | No |
|
| clawsec-nanoclaw | NanoClaw | Yes | Yes | Yes | Yes | No |
|
||||||
| clawsec-scanner | OpenClaw | Yes | No | Yes | Yes | No |
|
| clawsec-scanner | OpenClaw | Yes | No | Yes | Yes | No |
|
||||||
| clawsec-suite | OpenClaw | Yes | Yes | No | Yes | No |
|
| clawsec-suite | OpenClaw | Yes | Yes | No | Yes | No |
|
||||||
| clawtributor | All core platforms | No | No | No | No | No |
|
| clawtributor | OpenClaw | Yes | No | No | No | No |
|
||||||
| hermes-attestation-guardian | Hermes | Yes (signed advisory feed verification) | Yes | No | Limited (advisory preflight gating only; no artifact signature/provenance install verification) | No |
|
| hermes-attestation-guardian | Hermes | Yes (signed advisory feed verification) | Yes | No | Limited (advisory preflight gating only; no artifact signature/provenance install verification) | No |
|
||||||
| hermes-traffic-guardian | Hermes | No | Planned posture export only | No | No | Spec baseline |
|
| hermes-traffic-guardian | Hermes | No | Planned posture export only | No | No | Spec baseline |
|
||||||
| nanoclaw-traffic-guardian | NanoClaw | No | No | No | No | Spec baseline |
|
| nanoclaw-traffic-guardian | NanoClaw | No | No | No | No | Spec baseline |
|
||||||
|
|||||||
+1519
-1211
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
|||||||
jPrlTYwicRwoQgTs5Rk3Y3g6Lz78jNRs9ZNf0R09M4jkJokZENxfvhvHphI9MH4u+7wv0sFZ+yZbQtJ42y+hCQ==
|
v+PiWmjIkY6zdIyI9xJX0l0aTy0Azp1+LoZR6qaiDZJnXFuSBX4Sw/x5tMdTb0xSbqdDTJOZwwWI8coPVepzBw==
|
||||||
+210
-278
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
|||||||
M1Jm4YHXsm0msygmd+XCJBRWMrXIjQfv1Y5v7XS8RCachLQwEzUJ1nhhic6CXxItNLmvgmDjVCMPVdHpnOMqDA==
|
SCkRaPMF6IYDwZuR7/JJXxpB7A7ebuMvLqK827uWX0yfEJr7l2gyLpxvHsEpWJDzE4gchxd5yqJx5qF/yqNwAg==
|
||||||
+2
-2
@@ -7,7 +7,7 @@
|
|||||||
ClawSec
|
ClawSec
|
||||||
Security skill suite for AI agents (integrity checks, drift detection, advisory feed).
|
Security skill suite for AI agents (integrity checks, drift detection, advisory feed).
|
||||||
Agent install:
|
Agent install:
|
||||||
Available via npx skills: npx skills add prompt-security/clawsec --skill clawsec-suite -a openclaw -y
|
Available via clawhub: npx clawhub@latest install clawsec-suite
|
||||||
OR
|
OR
|
||||||
curl -sL https://clawsec.prompt.security/releases/latest/download/SKILL.md
|
curl -sL https://clawsec.prompt.security/releases/latest/download/SKILL.md
|
||||||
-->
|
-->
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
ClawSec
|
ClawSec
|
||||||
Security skill suite for AI agents (integrity checks, drift detection, advisory feed).
|
Security skill suite for AI agents (integrity checks, drift detection, advisory feed).
|
||||||
Agent install:
|
Agent install:
|
||||||
Available via npx skills: npx skills add prompt-security/clawsec --skill clawsec-suite -a openclaw -y
|
Available via clawhub: npx clawhub@latest install clawsec-suite
|
||||||
OR
|
OR
|
||||||
curl -sL https://clawsec.prompt.security/releases/latest/download/SKILL.md
|
curl -sL https://clawsec.prompt.security/releases/latest/download/SKILL.md
|
||||||
</noscript>
|
</noscript>
|
||||||
|
|||||||
Generated
+948
-478
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -31,13 +31,13 @@
|
|||||||
"@types/node": "^25.8.0",
|
"@types/node": "^25.8.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.55.0",
|
"@typescript-eslint/eslint-plugin": "^8.55.0",
|
||||||
"@typescript-eslint/parser": "^8.58.1",
|
"@typescript-eslint/parser": "^8.58.1",
|
||||||
"@vitejs/plugin-react": "^6.0.2",
|
"@vitejs/plugin-react": "^5.1.4",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "^9.39.4",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"fast-check": "^4.7.0",
|
"fast-check": "^4.7.0",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^8.0.16"
|
"vite": "^7.3.2"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"ajv": "6.14.0",
|
"ajv": "6.14.0",
|
||||||
|
|||||||
+2
-14
@@ -13,7 +13,7 @@ export const Home: React.FC = () => {
|
|||||||
const [currentFileIndex, setCurrentFileIndex] = useState(0);
|
const [currentFileIndex, setCurrentFileIndex] = useState(0);
|
||||||
const [currentPlatformIndex, setCurrentPlatformIndex] = useState(0);
|
const [currentPlatformIndex, setCurrentPlatformIndex] = useState(0);
|
||||||
|
|
||||||
const curlCommand = `npx skills add prompt-security/clawsec --skill clawsec-suite -a openclaw -y`;
|
const curlCommand = `npx clawhub@latest install clawsec-suite`;
|
||||||
|
|
||||||
// Rotate file names every 2-3 seconds
|
// Rotate file names every 2-3 seconds
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -44,7 +44,7 @@ export const Home: React.FC = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const humanInstruction = `Please install clawsec-suite with npx skills add prompt-security/clawsec --skill clawsec-suite -a openclaw -y`;
|
const humanInstruction = `Please install clawsec-suite from clawhubnpx clawhub@latest install clawsec-suite`;
|
||||||
|
|
||||||
const handleCopyCurl = () => {
|
const handleCopyCurl = () => {
|
||||||
navigator.clipboard.writeText(curlCommand);
|
navigator.clipboard.writeText(curlCommand);
|
||||||
@@ -285,18 +285,6 @@ export const Home: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<p className="mt-4 text-center text-xs leading-relaxed text-gray-500">
|
|
||||||
* For harnesses other than OpenClaw, consult the{' '}
|
|
||||||
<a
|
|
||||||
href="https://github.com/prompt-security/clawsec#skill-feature-matrix"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="text-clawd-accent hover:text-clawd-accent/80 underline underline-offset-2"
|
|
||||||
>
|
|
||||||
README Skill Feature Matrix
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -321,14 +321,7 @@ const fetchJson = async ({ repo, token, pathname, fetchImpl }) => {
|
|||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const body = await response.text().catch(() => '');
|
const body = await response.text().catch(() => '');
|
||||||
const suffix = body ? ` ${body.slice(0, 500)}` : '';
|
const suffix = body ? ` ${body.slice(0, 500)}` : '';
|
||||||
const lacksPushAccess = response.status === 403
|
throw new Error(`GitHub traffic API request failed for ${repo}: ${url.pathname}${url.search} returned ${response.status}.${suffix}`);
|
||||||
&& /resource not accessible|must have push access/i.test(body);
|
|
||||||
const hint = lacksPushAccess
|
|
||||||
? ' Traffic endpoints require a token with push access to the repository; the Actions GITHUB_TOKEN is always rejected. Use a classic PAT with the repo scope or a fine-grained PAT with read access to Administration.'
|
|
||||||
: response.status === 401
|
|
||||||
? ' The token was rejected as invalid — it may be expired or revoked. Rotate the TRAFFIC_ARCHIVE_TOKEN secret.'
|
|
||||||
: '';
|
|
||||||
throw new Error(`GitHub traffic API request failed for ${repo}: ${url.pathname}${url.search} returned ${response.status}.${suffix}${hint}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
|
|||||||
@@ -1,359 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
||||||
import path from "node:path";
|
|
||||||
import { installAgentForSkill, PLATFORM_KEYS } from "./skill_platforms.mjs";
|
|
||||||
|
|
||||||
const KNOWN_AGENT_TYPES = new Set(["codex", "hermes-agent", "openclaw", "universal"]);
|
|
||||||
|
|
||||||
function usage() {
|
|
||||||
return [
|
|
||||||
"Usage: node scripts/ci/generate_skill_release_trust_packet.mjs <skill-dir> <output-dir> [options]",
|
|
||||||
"",
|
|
||||||
"Options:",
|
|
||||||
" --repository <owner/repo> Source repository used in install instructions",
|
|
||||||
" --tag <tag> Release tag for this skill",
|
|
||||||
" --source-ref <ref> Source ref for npx skills examples",
|
|
||||||
].join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseArgs(argv) {
|
|
||||||
const positional = [];
|
|
||||||
const options = {
|
|
||||||
repository: "prompt-security/clawsec",
|
|
||||||
tag: "",
|
|
||||||
sourceRef: "main",
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < argv.length; i += 1) {
|
|
||||||
const token = argv[i];
|
|
||||||
if (token === "--repository") {
|
|
||||||
options.repository = argv[++i];
|
|
||||||
} else if (token === "--tag") {
|
|
||||||
options.tag = argv[++i];
|
|
||||||
} else if (token === "--source-ref") {
|
|
||||||
options.sourceRef = argv[++i];
|
|
||||||
} else if (token === "--help" || token === "-h") {
|
|
||||||
console.log(usage());
|
|
||||||
process.exit(0);
|
|
||||||
} else if (token.startsWith("--")) {
|
|
||||||
throw new Error(`Unknown option: ${token}`);
|
|
||||||
} else {
|
|
||||||
positional.push(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (positional.length !== 2) {
|
|
||||||
throw new Error(usage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
skillDir: positional[0],
|
|
||||||
outputDir: positional[1],
|
|
||||||
...options,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseFrontmatter(markdown) {
|
|
||||||
if (!markdown.startsWith("---\n")) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const end = markdown.indexOf("\n---", 4);
|
|
||||||
if (end === -1) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = {};
|
|
||||||
const frontmatter = markdown.slice(4, end).split("\n");
|
|
||||||
for (const line of frontmatter) {
|
|
||||||
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
||||||
if (match) {
|
|
||||||
result[match[1]] = match[2].replace(/^["']|["']$/g, "").trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function asArray(value) {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value.filter((item) => item !== null && item !== undefined).map(String);
|
|
||||||
}
|
|
||||||
if (typeof value === "string" && value.trim()) {
|
|
||||||
return [value.trim()];
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function unique(values) {
|
|
||||||
return [...new Set(values.filter(Boolean))];
|
|
||||||
}
|
|
||||||
|
|
||||||
function detectPlatform(skill) {
|
|
||||||
for (const key of PLATFORM_KEYS) {
|
|
||||||
if (skill[key] && typeof skill[key] === "object") {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return skill.platform || "agent-skills";
|
|
||||||
}
|
|
||||||
|
|
||||||
function platformMetadata(skill, platform) {
|
|
||||||
const direct = skill[platform];
|
|
||||||
return direct && typeof direct === "object" ? direct : {};
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectRequiredBinaries(metadata) {
|
|
||||||
const requires = metadata.requires && typeof metadata.requires === "object" ? metadata.requires : {};
|
|
||||||
const bins = asArray(requires.bins);
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(requires)) {
|
|
||||||
if (key !== "bins" && typeof value === "string") {
|
|
||||||
bins.push(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return unique(bins);
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectOptionalBinaries(metadata) {
|
|
||||||
return unique([
|
|
||||||
...asArray(metadata.runtime?.optional_bins),
|
|
||||||
...asArray(metadata.runtime?.optionalBins),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectRequiredEnv(metadata) {
|
|
||||||
const requires = metadata.requires && typeof metadata.requires === "object" ? metadata.requires : {};
|
|
||||||
return unique([
|
|
||||||
...asArray(requires.env),
|
|
||||||
...asArray(metadata.runtime?.required_env),
|
|
||||||
...asArray(metadata.runtime?.requiredEnv),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectOptionalEnv(metadata) {
|
|
||||||
return unique([
|
|
||||||
...asArray(metadata.runtime?.optional_env),
|
|
||||||
...asArray(metadata.runtime?.optionalEnv),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringifyCapabilities(skill, metadata) {
|
|
||||||
const capabilities = metadata.capabilities ?? skill.capabilities ?? {};
|
|
||||||
if (Array.isArray(capabilities)) {
|
|
||||||
return capabilities;
|
|
||||||
}
|
|
||||||
if (capabilities && typeof capabilities === "object") {
|
|
||||||
return Object.entries(capabilities).map(([key, value]) => `${key}: ${String(value)}`);
|
|
||||||
}
|
|
||||||
if (typeof capabilities === "string") {
|
|
||||||
return [capabilities];
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function requireField(skill, fieldName) {
|
|
||||||
if (!skill[fieldName] || typeof skill[fieldName] !== "string" || !skill[fieldName].trim()) {
|
|
||||||
throw new Error(`skill.json missing required trust-packet field: ${fieldName}`);
|
|
||||||
}
|
|
||||||
return skill[fieldName].trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function codeBlock(command) {
|
|
||||||
return ["```bash", command, "```"].join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildPermissions({ skill, metadata, platform, generatedAt }) {
|
|
||||||
const execution = metadata.execution && typeof metadata.execution === "object" ? metadata.execution : {};
|
|
||||||
const permissions = {
|
|
||||||
schema_version: "1",
|
|
||||||
generated_at: generatedAt,
|
|
||||||
skill: skill.name,
|
|
||||||
version: skill.version,
|
|
||||||
platform,
|
|
||||||
required_binaries: collectRequiredBinaries(metadata),
|
|
||||||
optional_binaries: collectOptionalBinaries(metadata),
|
|
||||||
required_env: collectRequiredEnv(metadata),
|
|
||||||
optional_env: collectOptionalEnv(metadata),
|
|
||||||
network_egress: execution.network_egress || "Not declared in skill metadata.",
|
|
||||||
persistence: execution.persistence || "Not declared in skill metadata.",
|
|
||||||
automatic_execution: typeof execution.always === "boolean" ? execution.always : "Not declared in skill metadata.",
|
|
||||||
capabilities: stringifyCapabilities(skill, metadata),
|
|
||||||
operator_review: asArray(metadata.operator_review),
|
|
||||||
};
|
|
||||||
|
|
||||||
return permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildSkillCard({ skill, frontmatter, permissions, repository, tag, sourceRef }) {
|
|
||||||
const homepage = skill.homepage || frontmatter.homepage || `https://github.com/${repository}`;
|
|
||||||
const supportRef = `${repository}@${tag || sourceRef}`;
|
|
||||||
const licenseRef = `https://github.com/${repository}/blob/${tag || sourceRef}/LICENSE`;
|
|
||||||
const outputTypes = ["Markdown instructions", "release artifact files"];
|
|
||||||
if (permissions.capabilities.length > 0) {
|
|
||||||
outputTypes.push("local security findings or status reports");
|
|
||||||
}
|
|
||||||
|
|
||||||
return `# Skill Card
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
The \`${skill.name}\` skill provides this capability: ${skill.description}
|
|
||||||
|
|
||||||
This skill is intended for operator-reviewed security workflows, not unattended production mutation without the review steps declared in the skill instructions.
|
|
||||||
|
|
||||||
## Owner
|
|
||||||
|
|
||||||
prompt-security
|
|
||||||
|
|
||||||
## License/Terms of Use
|
|
||||||
|
|
||||||
${skill.license}
|
|
||||||
|
|
||||||
License reference: ${licenseRef}
|
|
||||||
|
|
||||||
Project homepage: ${homepage}
|
|
||||||
|
|
||||||
## Use Case
|
|
||||||
|
|
||||||
Use this skill for ${permissions.platform} workflows where an agent or operator needs the capability described in \`${skill.name}\`.
|
|
||||||
|
|
||||||
## Deployment Geography for Use
|
|
||||||
|
|
||||||
Global, subject to the operator's local compliance, network, and data-handling requirements.
|
|
||||||
|
|
||||||
## Known Risks and Mitigations
|
|
||||||
|
|
||||||
Risk: The skill may run commands, inspect local files, install hooks, or fetch remote security metadata depending on the workflow.
|
|
||||||
|
|
||||||
Mitigation: Review \`permissions.json\`, \`SKILL.md\`, and the signed \`checksums.json\` before enabling the skill. Keep high-impact actions approval-gated.
|
|
||||||
|
|
||||||
Risk: Security findings and remediation guidance can be incomplete or wrong.
|
|
||||||
|
|
||||||
Mitigation: Treat output as operator guidance. Review proposed removals, installs, configuration changes, and reports before acting.
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- Source release: ${supportRef}
|
|
||||||
- Skill instructions: SKILL.md
|
|
||||||
- Permission summary: permissions.json
|
|
||||||
- SkillSpector scan: skillspector-report.md
|
|
||||||
- Signed release manifest: checksums.json and checksums.sig
|
|
||||||
|
|
||||||
## Skill Output
|
|
||||||
|
|
||||||
Output type(s): ${outputTypes.join(", ")}
|
|
||||||
|
|
||||||
Output format: Markdown, JSON, shell commands, or local files as documented by the skill.
|
|
||||||
|
|
||||||
Output parameters: See \`SKILL.md\`, \`permissions.json\`, and release checksums for exact files and side effects.
|
|
||||||
|
|
||||||
Other properties: Release assets are covered by signed SHA-256 checksums.
|
|
||||||
|
|
||||||
## Skill Version
|
|
||||||
|
|
||||||
${skill.version}${tag ? ` (${tag})` : ""}
|
|
||||||
|
|
||||||
## Ethical Considerations
|
|
||||||
|
|
||||||
Use this skill only on systems, agents, repositories, and workspaces where you have authorization. Review generated security reports before sharing them because they may contain operational details.
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildInstallDoc({ skill, repository, tag, sourceRef }) {
|
|
||||||
const refSuffix = sourceRef && sourceRef !== "main" ? `#${sourceRef}` : "";
|
|
||||||
const source = `${repository}${refSuffix}`;
|
|
||||||
const releaseUrl = tag ? `https://github.com/${repository}/releases/tag/${tag}` : `https://github.com/${repository}`;
|
|
||||||
const agent = installAgentForSkill(skill, KNOWN_AGENT_TYPES);
|
|
||||||
|
|
||||||
return `# Install and Update ${skill.name}
|
|
||||||
|
|
||||||
## Install With Agent Skills CLI
|
|
||||||
|
|
||||||
Harness-aware global install:
|
|
||||||
|
|
||||||
${codeBlock(`npx skills add ${source} --skill ${skill.name} --agent ${agent} --global --yes`)}
|
|
||||||
|
|
||||||
Project-local install for compatible agents:
|
|
||||||
|
|
||||||
${codeBlock(`npx skills add ${source} --skill ${skill.name} --yes`)}
|
|
||||||
|
|
||||||
## Update
|
|
||||||
|
|
||||||
Update this skill when installed through the Skills CLI:
|
|
||||||
|
|
||||||
${codeBlock(`npx skills update ${skill.name}`)}
|
|
||||||
|
|
||||||
List installed skills:
|
|
||||||
|
|
||||||
${codeBlock("npx skills list")}
|
|
||||||
|
|
||||||
## Verify Release Artifact
|
|
||||||
|
|
||||||
When installing from a GitHub release instead of the Skills CLI, download the archive, \`checksums.json\`, \`checksums.sig\`, and \`signing-public.pem\` from:
|
|
||||||
|
|
||||||
${releaseUrl}
|
|
||||||
|
|
||||||
Verify \`checksums.json\` before trusting the archive or standalone files.
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const args = parseArgs(process.argv.slice(2));
|
|
||||||
const skillDir = path.resolve(args.skillDir);
|
|
||||||
const outputDir = path.resolve(args.outputDir);
|
|
||||||
|
|
||||||
const skillJsonPath = path.join(skillDir, "skill.json");
|
|
||||||
const skillMdPath = path.join(skillDir, "SKILL.md");
|
|
||||||
const [skillJsonRaw, skillMdRaw] = await Promise.all([
|
|
||||||
readFile(skillJsonPath, "utf8"),
|
|
||||||
readFile(skillMdPath, "utf8"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const skill = JSON.parse(skillJsonRaw);
|
|
||||||
const frontmatter = parseFrontmatter(skillMdRaw);
|
|
||||||
skill.name = requireField(skill, "name");
|
|
||||||
skill.version = requireField(skill, "version");
|
|
||||||
skill.description = requireField(skill, "description");
|
|
||||||
skill.license = requireField(skill, "license");
|
|
||||||
|
|
||||||
const platform = detectPlatform(skill);
|
|
||||||
const metadata = platformMetadata(skill, platform);
|
|
||||||
const generatedAt = new Date().toISOString();
|
|
||||||
const permissions = buildPermissions({ skill, metadata, platform, generatedAt });
|
|
||||||
|
|
||||||
await mkdir(outputDir, { recursive: true });
|
|
||||||
await Promise.all([
|
|
||||||
writeFile(
|
|
||||||
path.join(outputDir, "permissions.json"),
|
|
||||||
`${JSON.stringify(permissions, null, 2)}\n`,
|
|
||||||
),
|
|
||||||
writeFile(
|
|
||||||
path.join(outputDir, "skill-card.md"),
|
|
||||||
buildSkillCard({
|
|
||||||
skill,
|
|
||||||
frontmatter,
|
|
||||||
permissions,
|
|
||||||
repository: args.repository,
|
|
||||||
tag: args.tag,
|
|
||||||
sourceRef: args.sourceRef,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
writeFile(
|
|
||||||
path.join(outputDir, "install.md"),
|
|
||||||
buildInstallDoc({
|
|
||||||
skill,
|
|
||||||
repository: args.repository,
|
|
||||||
tag: args.tag,
|
|
||||||
sourceRef: args.sourceRef,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
console.log(`Generated release trust packet for ${skill.name} in ${outputDir}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((error) => {
|
|
||||||
console.error(error.message);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
CLI_PREFIX="${CLAWHUB_CLI_PREFIX:-.github/clawhub-cli}"
|
|
||||||
CODEARTIFACT_DOMAIN="${CODEARTIFACT_DOMAIN:-prompt-security}"
|
|
||||||
CODEARTIFACT_DOMAIN_OWNER="${CODEARTIFACT_DOMAIN_OWNER:-443370709039}"
|
|
||||||
CODEARTIFACT_REPOSITORY="${CODEARTIFACT_REPOSITORY:-npm-proxy}"
|
|
||||||
AWS_REGION="${AWS_REGION:-${AWS_DEFAULT_REGION:-eu-north-1}}"
|
|
||||||
|
|
||||||
if ! command -v aws >/dev/null 2>&1; then
|
|
||||||
echo "::error::aws CLI is required to authenticate npm against CodeArtifact"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! aws sts get-caller-identity >/dev/null 2>&1; then
|
|
||||||
echo "::error::AWS credentials are required before installing the CodeArtifact-pinned clawhub CLI"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
aws codeartifact login \
|
|
||||||
--tool npm \
|
|
||||||
--domain "$CODEARTIFACT_DOMAIN" \
|
|
||||||
--domain-owner "$CODEARTIFACT_DOMAIN_OWNER" \
|
|
||||||
--repository "$CODEARTIFACT_REPOSITORY" \
|
|
||||||
--region "$AWS_REGION"
|
|
||||||
|
|
||||||
npm ci --prefix "$CLI_PREFIX"
|
|
||||||
|
|
||||||
if [ -n "${GITHUB_PATH:-}" ]; then
|
|
||||||
workspace="${GITHUB_WORKSPACE:-$(pwd)}"
|
|
||||||
echo "${workspace}/${CLI_PREFIX}/node_modules/.bin" >> "$GITHUB_PATH"
|
|
||||||
fi
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import fs from "node:fs";
|
|
||||||
import path from "node:path";
|
|
||||||
|
|
||||||
const workspace = process.env.GITHUB_WORKSPACE || process.cwd();
|
|
||||||
const npmRoot = path.join(workspace, ".github", "clawhub-cli", "node_modules");
|
|
||||||
const publishScriptPath = path.join(
|
|
||||||
npmRoot,
|
|
||||||
"clawhub",
|
|
||||||
"dist",
|
|
||||||
"cli",
|
|
||||||
"commands",
|
|
||||||
"publish.js",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!fs.existsSync(publishScriptPath)) {
|
|
||||||
throw new Error(`clawhub publish script not found: ${publishScriptPath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const original = fs.readFileSync(publishScriptPath, "utf8");
|
|
||||||
if (original.includes("acceptLicenseTerms: true")) {
|
|
||||||
console.log(`[patch-clawhub] Already patched: ${publishScriptPath}`);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payloadPattern = /changelog,\r?\n(\s*)tags,/;
|
|
||||||
if (!payloadPattern.test(original)) {
|
|
||||||
throw new Error(`[patch-clawhub] Could not find expected publish payload pattern in ${publishScriptPath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const patched = original.replace(
|
|
||||||
payloadPattern,
|
|
||||||
(_, indent) => `changelog,\n${indent}acceptLicenseTerms: true,\n${indent}tags,`,
|
|
||||||
);
|
|
||||||
fs.writeFileSync(publishScriptPath, patched, "utf8");
|
|
||||||
console.log(`[patch-clawhub] Patched: ${publishScriptPath}`);
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
import { existsSync, readFileSync } from "node:fs";
|
|
||||||
import path from "node:path";
|
|
||||||
import { collectDeclaredPlatforms, PLATFORM_KEYS } from "./skill_platforms.mjs";
|
|
||||||
|
|
||||||
const EXPLICIT_SLUGS = new Map([
|
|
||||||
["openclaw-traffic-guardian", "clawsec-openclaw-traffic-guardian"],
|
|
||||||
["openclaw-audit-watchdog", "clawsec-openclaw-audit-watchdog"],
|
|
||||||
["soul-guardian", "clawsec-openclaw-soul-guardian"],
|
|
||||||
["hermes-attestation-guardian", "clawsec-hermes-attestation-guardian"],
|
|
||||||
["hermes-traffic-guardian", "clawsec-hermes-traffic-guardian"],
|
|
||||||
["nanoclaw-traffic-guardian", "clawsec-nanoclaw-traffic-guardian"],
|
|
||||||
["picoclaw-security-guardian", "clawsec-picoclaw-security-guardian"],
|
|
||||||
["picoclaw-self-pen-testing", "clawsec-picoclaw-self-pen-testing"],
|
|
||||||
["picoclaw-traffic-guardian", "clawsec-picoclaw-traffic-guardian"],
|
|
||||||
["clawtributor", "clawsec-clawtributor"],
|
|
||||||
]);
|
|
||||||
|
|
||||||
function usage() {
|
|
||||||
return [
|
|
||||||
"Usage: node scripts/ci/resolve_clawhub_slug.mjs <skill-dir-or-name>",
|
|
||||||
"",
|
|
||||||
"Prints the ClawHub slug for a skill without changing the GitHub release tag or skill package name.",
|
|
||||||
].join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadSkill(input) {
|
|
||||||
const skillJsonPath = existsSync(path.join(input, "skill.json")) ? path.join(input, "skill.json") : null;
|
|
||||||
if (!skillJsonPath) {
|
|
||||||
return { name: input, platforms: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
const skill = JSON.parse(readFileSync(skillJsonPath, "utf8"));
|
|
||||||
if (!skill.name || typeof skill.name !== "string") {
|
|
||||||
throw new Error(`${skillJsonPath} missing string field: name`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { name: skill.name, platforms: collectDeclaredPlatforms(skill) };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveClawHubSlug({ name, platforms = [] }) {
|
|
||||||
if (!/^[a-z0-9-]+$/.test(name)) {
|
|
||||||
throw new Error(`Invalid skill name for ClawHub slug mapping: ${name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name.startsWith("clawsec-")) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EXPLICIT_SLUGS.has(name)) {
|
|
||||||
return EXPLICIT_SLUGS.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PLATFORM_KEYS.some((platform) => name.startsWith(`${platform}-`))) {
|
|
||||||
return `clawsec-${name}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const declaredPlatforms = collectDeclaredPlatforms({ platforms });
|
|
||||||
if (declaredPlatforms.length === 1 && PLATFORM_KEYS.includes(declaredPlatforms[0])) {
|
|
||||||
return `clawsec-${declaredPlatforms[0]}-${name}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `clawsec-${name}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
||||||
const input = process.argv[2];
|
|
||||||
if (!input || input === "--help" || input === "-h") {
|
|
||||||
console.log(usage());
|
|
||||||
process.exit(input ? 0 : 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log(resolveClawHubSlug(loadSkill(input)));
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error instanceof Error ? error.message : String(error));
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,520 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
import { createHash } from "node:crypto";
|
|
||||||
import { spawnSync } from "node:child_process";
|
|
||||||
import {
|
|
||||||
cp,
|
|
||||||
mkdir,
|
|
||||||
mkdtemp,
|
|
||||||
readFile,
|
|
||||||
rm,
|
|
||||||
stat,
|
|
||||||
writeFile,
|
|
||||||
} from "node:fs/promises";
|
|
||||||
import { existsSync } from "node:fs";
|
|
||||||
import { tmpdir } from "node:os";
|
|
||||||
import path from "node:path";
|
|
||||||
|
|
||||||
const TRUST_ARTIFACTS = [
|
|
||||||
"skill-card.md",
|
|
||||||
"permissions.json",
|
|
||||||
"install.md",
|
|
||||||
"skillspector-report.md",
|
|
||||||
];
|
|
||||||
|
|
||||||
function usage() {
|
|
||||||
return [
|
|
||||||
"Usage: node scripts/ci/simulate_skill_tag_release.mjs <skill-dir> <output-dir> [options]",
|
|
||||||
"",
|
|
||||||
"Options:",
|
|
||||||
" --repository <owner/repo> Source repository used in release metadata",
|
|
||||||
" --source-ref <ref> Source ref used in npx skills examples",
|
|
||||||
" --skillspector-bin <path> SkillSpector executable to run",
|
|
||||||
].join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseArgs(argv) {
|
|
||||||
const positional = [];
|
|
||||||
const options = {
|
|
||||||
repository: "prompt-security/clawsec",
|
|
||||||
sourceRef: "main",
|
|
||||||
skillspectorBin: "skillspector",
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < argv.length; i += 1) {
|
|
||||||
const token = argv[i];
|
|
||||||
if (token === "--repository") {
|
|
||||||
options.repository = argv[++i];
|
|
||||||
} else if (token === "--source-ref") {
|
|
||||||
options.sourceRef = argv[++i];
|
|
||||||
} else if (token === "--skillspector-bin") {
|
|
||||||
options.skillspectorBin = argv[++i];
|
|
||||||
} else if (token === "--help" || token === "-h") {
|
|
||||||
console.log(usage());
|
|
||||||
process.exit(0);
|
|
||||||
} else if (token.startsWith("--")) {
|
|
||||||
throw new Error(`Unknown option: ${token}`);
|
|
||||||
} else {
|
|
||||||
positional.push(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (positional.length !== 2) {
|
|
||||||
throw new Error(usage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
skillDir: positional[0],
|
|
||||||
outputDir: positional[1],
|
|
||||||
...options,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function run(command, args, options = {}) {
|
|
||||||
const result = spawnSync(command, args, {
|
|
||||||
encoding: "utf8",
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.status !== 0) {
|
|
||||||
throw new Error(
|
|
||||||
[
|
|
||||||
`Command failed: ${command} ${args.join(" ")}`,
|
|
||||||
result.stdout ? `stdout:\n${result.stdout}` : "",
|
|
||||||
result.stderr ? `stderr:\n${result.stderr}` : "",
|
|
||||||
].filter(Boolean).join("\n"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.stdout;
|
|
||||||
}
|
|
||||||
|
|
||||||
function runAllowFailure(command, args, options = {}) {
|
|
||||||
return spawnSync(command, args, {
|
|
||||||
encoding: "utf8",
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function nextSimulatedReleaseVersion(version) {
|
|
||||||
const versionMatch = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9]+))?$/);
|
|
||||||
if (!versionMatch) {
|
|
||||||
throw new Error(`Cannot derive simulated release version from unsupported version: ${version}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [, major, minor, patch, prerelease] = versionMatch;
|
|
||||||
if (!prerelease) {
|
|
||||||
return `${major}.${minor}.${Number(patch) + 1}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const prereleaseMatch = prerelease.match(/^(.*?)(\d+)$/);
|
|
||||||
if (prereleaseMatch) {
|
|
||||||
const [, label, number] = prereleaseMatch;
|
|
||||||
return `${major}.${minor}.${patch}-${label}${Number(number) + 1}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${major}.${minor}.${patch}-${prerelease}1`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeReleasePath(rawPath) {
|
|
||||||
let releasePath = rawPath.replaceAll("\\", "/");
|
|
||||||
while (releasePath.startsWith("./")) {
|
|
||||||
releasePath = releasePath.slice(2);
|
|
||||||
}
|
|
||||||
while (releasePath.includes("//")) {
|
|
||||||
releasePath = releasePath.replaceAll("//", "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
releasePath === "" ||
|
|
||||||
releasePath.startsWith("/") ||
|
|
||||||
/^[A-Za-z]:/.test(releasePath) ||
|
|
||||||
releasePath === ".." ||
|
|
||||||
releasePath.startsWith("../") ||
|
|
||||||
releasePath.endsWith("/..") ||
|
|
||||||
releasePath.includes("/../")
|
|
||||||
) {
|
|
||||||
throw new Error(`Unsafe release path: ${rawPath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return releasePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isTestReleasePath(releasePath) {
|
|
||||||
const lower = releasePath.toLowerCase();
|
|
||||||
return lower === "test" ||
|
|
||||||
lower === "tests" ||
|
|
||||||
lower.startsWith("test/") ||
|
|
||||||
lower.startsWith("tests/") ||
|
|
||||||
lower.includes("/test/") ||
|
|
||||||
lower.includes("/tests/");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sha256File(filePath) {
|
|
||||||
const buffer = await readFile(filePath);
|
|
||||||
return createHash("sha256").update(buffer).digest("hex");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fileSize(filePath) {
|
|
||||||
return (await stat(filePath)).size;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checksumEntry(filePath, releasePath) {
|
|
||||||
return {
|
|
||||||
sha256: await sha256File(filePath),
|
|
||||||
size: await fileSize(filePath),
|
|
||||||
path: releasePath,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceSkillMarkdownVersion(markdown, version) {
|
|
||||||
if (!markdown.startsWith("---\n")) {
|
|
||||||
throw new Error("SKILL.md is missing YAML frontmatter");
|
|
||||||
}
|
|
||||||
|
|
||||||
const end = markdown.indexOf("\n---", 4);
|
|
||||||
if (end === -1) {
|
|
||||||
throw new Error("SKILL.md frontmatter is not closed");
|
|
||||||
}
|
|
||||||
|
|
||||||
const frontmatter = markdown.slice(0, end);
|
|
||||||
if (!/^version:\s*.+$/m.test(frontmatter)) {
|
|
||||||
throw new Error("SKILL.md frontmatter is missing a version field");
|
|
||||||
}
|
|
||||||
|
|
||||||
return markdown.replace(/^version:\s*.+$/m, `version: ${version}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addSimulatedChangelogEntry(skillDir, version) {
|
|
||||||
const changelogPath = path.join(skillDir, "CHANGELOG.md");
|
|
||||||
if (!existsSync(changelogPath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const today = new Date().toISOString().slice(0, 10);
|
|
||||||
const original = await readFile(changelogPath, "utf8");
|
|
||||||
if (original.includes(`## [${version}] -`)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const entry = [
|
|
||||||
`## [${version}] - ${today}`,
|
|
||||||
"",
|
|
||||||
"- Simulated prerelease build for release-pipeline validation.",
|
|
||||||
"",
|
|
||||||
"---",
|
|
||||||
"",
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
await writeFile(changelogPath, `${entry}${original}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function writeJson(filePath, value) {
|
|
||||||
await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function signFileBase64({ keyPath, inputPath, outputPath, tempRoot }) {
|
|
||||||
const sigBin = path.join(tempRoot, `${path.basename(outputPath)}.bin`);
|
|
||||||
run("openssl", ["pkeyutl", "-sign", "-rawin", "-inkey", keyPath, "-in", inputPath, "-out", sigBin]);
|
|
||||||
run("openssl", ["base64", "-A", "-in", sigBin, "-out", outputPath]);
|
|
||||||
await rm(sigBin, { force: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function verifyFileBase64Signature({ publicKeyPath, inputPath, signaturePath, tempRoot }) {
|
|
||||||
const sigBin = path.join(tempRoot, `${path.basename(signaturePath)}.verify.bin`);
|
|
||||||
run("openssl", ["base64", "-d", "-A", "-in", signaturePath, "-out", sigBin]);
|
|
||||||
run("openssl", [
|
|
||||||
"pkeyutl",
|
|
||||||
"-verify",
|
|
||||||
"-rawin",
|
|
||||||
"-pubin",
|
|
||||||
"-inkey",
|
|
||||||
publicKeyPath,
|
|
||||||
"-sigfile",
|
|
||||||
sigBin,
|
|
||||||
"-in",
|
|
||||||
inputPath,
|
|
||||||
]);
|
|
||||||
await rm(sigBin, { force: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createSigningKeyPair(tempRoot) {
|
|
||||||
const keyDir = await mkdtemp(path.join(tempRoot, "signing-"));
|
|
||||||
const privateKeyPath = path.join(keyDir, "private.pem");
|
|
||||||
const publicKeyPath = path.join(keyDir, "public.pem");
|
|
||||||
|
|
||||||
run("openssl", ["genpkey", "-algorithm", "ED25519", "-out", privateKeyPath]);
|
|
||||||
run("openssl", ["pkey", "-in", privateKeyPath, "-pubout", "-out", publicKeyPath]);
|
|
||||||
|
|
||||||
return { privateKeyPath, publicKeyPath };
|
|
||||||
}
|
|
||||||
|
|
||||||
async function signAdvisoryArtifacts(skillDir, tempRoot) {
|
|
||||||
const advisoryDir = path.join(skillDir, "advisories");
|
|
||||||
const feedPath = path.join(advisoryDir, "feed.json");
|
|
||||||
if (!existsSync(feedPath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { privateKeyPath, publicKeyPath } = await createSigningKeyPair(tempRoot);
|
|
||||||
const feedSignaturePath = path.join(advisoryDir, "feed.json.sig");
|
|
||||||
const checksumsPath = path.join(advisoryDir, "checksums.json");
|
|
||||||
const checksumsSignaturePath = path.join(advisoryDir, "checksums.json.sig");
|
|
||||||
const publicKeyOutputPath = path.join(advisoryDir, "feed-signing-public.pem");
|
|
||||||
|
|
||||||
await signFileBase64({
|
|
||||||
keyPath: privateKeyPath,
|
|
||||||
inputPath: feedPath,
|
|
||||||
outputPath: feedSignaturePath,
|
|
||||||
tempRoot,
|
|
||||||
});
|
|
||||||
await verifyFileBase64Signature({
|
|
||||||
publicKeyPath,
|
|
||||||
inputPath: feedPath,
|
|
||||||
signaturePath: feedSignaturePath,
|
|
||||||
tempRoot,
|
|
||||||
});
|
|
||||||
|
|
||||||
await writeJson(checksumsPath, {
|
|
||||||
schema_version: "1",
|
|
||||||
algorithm: "sha256",
|
|
||||||
version: "simulation",
|
|
||||||
generated_at: new Date().toISOString(),
|
|
||||||
files: {
|
|
||||||
"advisories/feed.json": await checksumEntry(feedPath, "advisories/feed.json"),
|
|
||||||
"advisories/feed.json.sig": await checksumEntry(feedSignaturePath, "advisories/feed.json.sig"),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await signFileBase64({
|
|
||||||
keyPath: privateKeyPath,
|
|
||||||
inputPath: checksumsPath,
|
|
||||||
outputPath: checksumsSignaturePath,
|
|
||||||
tempRoot,
|
|
||||||
});
|
|
||||||
await verifyFileBase64Signature({
|
|
||||||
publicKeyPath,
|
|
||||||
inputPath: checksumsPath,
|
|
||||||
signaturePath: checksumsSignaturePath,
|
|
||||||
tempRoot,
|
|
||||||
});
|
|
||||||
|
|
||||||
await cp(publicKeyPath, publicKeyOutputPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addReleaseAssetChecksum({ releaseAssetsDir, manifest, asset }) {
|
|
||||||
const filePath = path.join(releaseAssetsDir, asset);
|
|
||||||
if (!existsSync(filePath) || (await fileSize(filePath)) === 0) {
|
|
||||||
throw new Error(`Required release trust artifact is missing or empty: ${filePath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest.files[asset] = await checksumEntry(filePath, asset);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function stageSbomFiles({ skillDir, innerDir, sbomFiles }) {
|
|
||||||
for (const entry of sbomFiles) {
|
|
||||||
const releasePath = normalizeReleasePath(entry.path);
|
|
||||||
if (isTestReleasePath(releasePath)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fullPath = path.join(skillDir, releasePath);
|
|
||||||
if (!existsSync(fullPath)) {
|
|
||||||
throw new Error(`SBOM references missing file: ${releasePath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const destination = path.join(innerDir, releasePath);
|
|
||||||
await mkdir(path.dirname(destination), { recursive: true });
|
|
||||||
await cp(fullPath, destination);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildFilesManifest({ skillDir, skillJsonPath, sbomFiles }) {
|
|
||||||
const files = {};
|
|
||||||
for (const entry of sbomFiles) {
|
|
||||||
const releasePath = normalizeReleasePath(entry.path);
|
|
||||||
if (isTestReleasePath(releasePath)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fullPath = path.join(skillDir, releasePath);
|
|
||||||
if (existsSync(fullPath)) {
|
|
||||||
files[releasePath] = await checksumEntry(fullPath, releasePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
files["skill.json"] = {
|
|
||||||
sha256: await sha256File(skillJsonPath),
|
|
||||||
size: await fileSize(skillJsonPath),
|
|
||||||
};
|
|
||||||
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runSkillSpector({ skillspectorBin, skillDir, reportPath }) {
|
|
||||||
const result = runAllowFailure(skillspectorBin, [
|
|
||||||
"scan",
|
|
||||||
skillDir,
|
|
||||||
"--no-llm",
|
|
||||||
"--format",
|
|
||||||
"markdown",
|
|
||||||
"--output",
|
|
||||||
reportPath,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!existsSync(reportPath) || (await fileSize(reportPath)) === 0) {
|
|
||||||
throw new Error(
|
|
||||||
[
|
|
||||||
"SkillSpector did not produce a report.",
|
|
||||||
result.stdout ? `stdout:\n${result.stdout}` : "",
|
|
||||||
result.stderr ? `stderr:\n${result.stderr}` : "",
|
|
||||||
].filter(Boolean).join("\n"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.status !== 0) {
|
|
||||||
console.warn(`SkillSpector returned exit code ${result.status}; report is included for review.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const args = parseArgs(process.argv.slice(2));
|
|
||||||
const sourceSkillDir = path.resolve(args.skillDir);
|
|
||||||
const outputDir = path.resolve(args.outputDir);
|
|
||||||
const releaseAssetsDir = path.join(outputDir, "release-assets");
|
|
||||||
const tempRoot = await mkdtemp(path.join(tmpdir(), "clawsec-release-sim-"));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const skillName = path.basename(sourceSkillDir);
|
|
||||||
const tempSkillDir = path.join(tempRoot, skillName);
|
|
||||||
await cp(sourceSkillDir, tempSkillDir, { recursive: true });
|
|
||||||
|
|
||||||
const skillJsonPath = path.join(tempSkillDir, "skill.json");
|
|
||||||
const skillMdPath = path.join(tempSkillDir, "SKILL.md");
|
|
||||||
const skill = JSON.parse(await readFile(skillJsonPath, "utf8"));
|
|
||||||
const originalVersion = skill.version;
|
|
||||||
const simulatedVersion = nextSimulatedReleaseVersion(originalVersion);
|
|
||||||
const tag = `${skillName}-v${simulatedVersion}`;
|
|
||||||
const zipName = `${tag}.zip`;
|
|
||||||
|
|
||||||
skill.version = simulatedVersion;
|
|
||||||
await writeJson(skillJsonPath, skill);
|
|
||||||
await writeFile(
|
|
||||||
skillMdPath,
|
|
||||||
replaceSkillMarkdownVersion(await readFile(skillMdPath, "utf8"), simulatedVersion),
|
|
||||||
);
|
|
||||||
await addSimulatedChangelogEntry(tempSkillDir, simulatedVersion);
|
|
||||||
await signAdvisoryArtifacts(tempSkillDir, tempRoot);
|
|
||||||
|
|
||||||
if (!skill.sbom || !Array.isArray(skill.sbom.files)) {
|
|
||||||
throw new Error(`skill.json missing required release field: sbom.files`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await mkdir(releaseAssetsDir, { recursive: true });
|
|
||||||
|
|
||||||
const stagingDir = await mkdtemp(path.join(tempRoot, "staging-"));
|
|
||||||
const innerDir = path.join(stagingDir, skillName);
|
|
||||||
await mkdir(innerDir, { recursive: true });
|
|
||||||
await stageSbomFiles({
|
|
||||||
skillDir: tempSkillDir,
|
|
||||||
innerDir,
|
|
||||||
sbomFiles: skill.sbom.files,
|
|
||||||
});
|
|
||||||
await cp(skillJsonPath, path.join(innerDir, "skill.json"));
|
|
||||||
|
|
||||||
run("python3", ["scripts/ci/verify_skill_release_import_closure.py", innerDir], {
|
|
||||||
cwd: process.cwd(),
|
|
||||||
});
|
|
||||||
|
|
||||||
run("zip", ["-qr", path.join(releaseAssetsDir, zipName), "."], {
|
|
||||||
cwd: stagingDir,
|
|
||||||
});
|
|
||||||
|
|
||||||
const zipContents = run("unzip", ["-Z1", path.join(releaseAssetsDir, zipName)]);
|
|
||||||
if (zipContents.split("\n").some((entry) => /(^|\/)(test|tests)\//i.test(entry))) {
|
|
||||||
throw new Error(`Simulated release archive contains test-only files: ${zipName}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const manifest = {
|
|
||||||
skill: skillName,
|
|
||||||
version: simulatedVersion,
|
|
||||||
generated_at: new Date().toISOString(),
|
|
||||||
repository: args.repository,
|
|
||||||
tag,
|
|
||||||
archive: {
|
|
||||||
filename: zipName,
|
|
||||||
sha256: await sha256File(path.join(releaseAssetsDir, zipName)),
|
|
||||||
size: await fileSize(path.join(releaseAssetsDir, zipName)),
|
|
||||||
url: `https://github.com/${args.repository}/releases/download/${tag}/${zipName}`,
|
|
||||||
},
|
|
||||||
files: await buildFilesManifest({
|
|
||||||
skillDir: tempSkillDir,
|
|
||||||
skillJsonPath,
|
|
||||||
sbomFiles: skill.sbom.files,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
await writeJson(path.join(releaseAssetsDir, "checksums.json"), manifest);
|
|
||||||
|
|
||||||
run(process.execPath, [
|
|
||||||
"scripts/ci/generate_skill_release_trust_packet.mjs",
|
|
||||||
tempSkillDir,
|
|
||||||
releaseAssetsDir,
|
|
||||||
"--repository",
|
|
||||||
args.repository,
|
|
||||||
"--tag",
|
|
||||||
tag,
|
|
||||||
"--source-ref",
|
|
||||||
args.sourceRef,
|
|
||||||
]);
|
|
||||||
|
|
||||||
await runSkillSpector({
|
|
||||||
skillspectorBin: args.skillspectorBin,
|
|
||||||
skillDir: innerDir,
|
|
||||||
reportPath: path.join(releaseAssetsDir, "skillspector-report.md"),
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const artifact of TRUST_ARTIFACTS) {
|
|
||||||
await addReleaseAssetChecksum({ releaseAssetsDir, manifest, asset: artifact });
|
|
||||||
}
|
|
||||||
await writeJson(path.join(releaseAssetsDir, "checksums.json"), manifest);
|
|
||||||
|
|
||||||
await cp(skillJsonPath, path.join(releaseAssetsDir, "skill.json"));
|
|
||||||
await cp(skillMdPath, path.join(releaseAssetsDir, "SKILL.md"));
|
|
||||||
if (existsSync(path.join(tempSkillDir, "README.md"))) {
|
|
||||||
await cp(path.join(tempSkillDir, "README.md"), path.join(releaseAssetsDir, "README.md"));
|
|
||||||
}
|
|
||||||
|
|
||||||
const { privateKeyPath, publicKeyPath } = await createSigningKeyPair(tempRoot);
|
|
||||||
await signFileBase64({
|
|
||||||
keyPath: privateKeyPath,
|
|
||||||
inputPath: path.join(releaseAssetsDir, "checksums.json"),
|
|
||||||
outputPath: path.join(releaseAssetsDir, "checksums.sig"),
|
|
||||||
tempRoot,
|
|
||||||
});
|
|
||||||
await verifyFileBase64Signature({
|
|
||||||
publicKeyPath,
|
|
||||||
inputPath: path.join(releaseAssetsDir, "checksums.json"),
|
|
||||||
signaturePath: path.join(releaseAssetsDir, "checksums.sig"),
|
|
||||||
tempRoot,
|
|
||||||
});
|
|
||||||
await cp(publicKeyPath, path.join(releaseAssetsDir, "signing-public.pem"));
|
|
||||||
|
|
||||||
await writeJson(path.join(outputDir, "simulation-summary.json"), {
|
|
||||||
skill: skillName,
|
|
||||||
original_version: originalVersion,
|
|
||||||
simulated_version: simulatedVersion,
|
|
||||||
tag,
|
|
||||||
release_assets: path.relative(outputDir, releaseAssetsDir),
|
|
||||||
archive: `release-assets/${zipName}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Simulated tag release build for ${skillName}: ${tag}`);
|
|
||||||
} finally {
|
|
||||||
await rm(tempRoot, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((error) => {
|
|
||||||
console.error(error.message);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
export const PLATFORM_KEYS = Object.freeze(["openclaw", "nanoclaw", "hermes", "picoclaw"]);
|
|
||||||
|
|
||||||
const PLATFORM_AGENT_ALIASES = new Map([["hermes", "hermes-agent"]]);
|
|
||||||
|
|
||||||
function asStringArray(value) {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value.filter((item) => typeof item === "string" && item.trim()).map((item) => item.trim());
|
|
||||||
}
|
|
||||||
if (typeof value === "string" && value.trim()) {
|
|
||||||
return [value.trim()];
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function collectDeclaredPlatforms(skill) {
|
|
||||||
const platforms = new Set([
|
|
||||||
...asStringArray(skill.platform),
|
|
||||||
...asStringArray(skill.platforms),
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (const key of PLATFORM_KEYS) {
|
|
||||||
if (skill[key] && typeof skill[key] === "object") {
|
|
||||||
platforms.add(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [...platforms];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function installAgentForSkill(skill, agentTypes, fallback = "openclaw") {
|
|
||||||
const platforms = collectDeclaredPlatforms(skill);
|
|
||||||
if (platforms.length === 0) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchedAgents = new Set();
|
|
||||||
let allPlatformsMatched = true;
|
|
||||||
for (const platform of platforms) {
|
|
||||||
const candidate = PLATFORM_AGENT_ALIASES.get(platform) || platform;
|
|
||||||
if (agentTypes.has(candidate)) {
|
|
||||||
matchedAgents.add(candidate);
|
|
||||||
} else {
|
|
||||||
allPlatformsMatched = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allPlatformsMatched && matchedAgents.size === 1) {
|
|
||||||
return [...matchedAgents][0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
@@ -1,266 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
import { readFile, readdir } from "node:fs/promises";
|
|
||||||
import { existsSync } from "node:fs";
|
|
||||||
import { spawnSync } from "node:child_process";
|
|
||||||
import https from "node:https";
|
|
||||||
import path from "node:path";
|
|
||||||
import { installAgentForSkill } from "./skill_platforms.mjs";
|
|
||||||
|
|
||||||
const DEFAULT_REPOSITORY = "prompt-security/clawsec";
|
|
||||||
const DEFAULT_AGENT_TYPES_URL = "https://raw.githubusercontent.com/vercel-labs/skills/main/src/types.ts";
|
|
||||||
const DOC_FILENAMES = ["README.md", "SKILL.md"];
|
|
||||||
|
|
||||||
function usage() {
|
|
||||||
return [
|
|
||||||
"Usage: node scripts/ci/validate_skill_install_docs.mjs [options]",
|
|
||||||
"",
|
|
||||||
"Options:",
|
|
||||||
" --root <dir> Repository root. Defaults to current working directory.",
|
|
||||||
" --repository <owner/repo> Expected npx skills source. Defaults to prompt-security/clawsec.",
|
|
||||||
" --base <sha> Base ref for changed-skill detection.",
|
|
||||||
" --head <sha> Head ref for changed-skill detection.",
|
|
||||||
" --skills <dir[,dir...]> Skill directories to validate.",
|
|
||||||
" --all Validate every skill directory with skill.json.",
|
|
||||||
" --agent-types-file <path> Read Vercel AgentType source from a local file.",
|
|
||||||
" --agent-types-url <url> Read Vercel AgentType source from a URL.",
|
|
||||||
].join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseArgs(argv) {
|
|
||||||
const options = {
|
|
||||||
root: process.cwd(),
|
|
||||||
repository: DEFAULT_REPOSITORY,
|
|
||||||
base: process.env.BASE_SHA || "",
|
|
||||||
head: process.env.HEAD_SHA || "",
|
|
||||||
skillDirs: [],
|
|
||||||
all: false,
|
|
||||||
agentTypesFile: "",
|
|
||||||
agentTypesUrl: DEFAULT_AGENT_TYPES_URL,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < argv.length; i += 1) {
|
|
||||||
const token = argv[i];
|
|
||||||
if (token === "--root") {
|
|
||||||
options.root = argv[++i];
|
|
||||||
} else if (token === "--repository") {
|
|
||||||
options.repository = argv[++i];
|
|
||||||
} else if (token === "--base") {
|
|
||||||
options.base = argv[++i];
|
|
||||||
} else if (token === "--head") {
|
|
||||||
options.head = argv[++i];
|
|
||||||
} else if (token === "--skills") {
|
|
||||||
options.skillDirs.push(...argv[++i].split(",").map((item) => item.trim()).filter(Boolean));
|
|
||||||
} else if (token === "--all") {
|
|
||||||
options.all = true;
|
|
||||||
} else if (token === "--agent-types-file") {
|
|
||||||
options.agentTypesFile = argv[++i];
|
|
||||||
} else if (token === "--agent-types-url") {
|
|
||||||
options.agentTypesUrl = argv[++i];
|
|
||||||
} else if (token === "--help" || token === "-h") {
|
|
||||||
console.log(usage());
|
|
||||||
process.exit(0);
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unknown option: ${token}\n${usage()}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...options,
|
|
||||||
root: path.resolve(options.root),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchText(url) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
https
|
|
||||||
.get(url, (response) => {
|
|
||||||
if (response.statusCode !== 200) {
|
|
||||||
reject(new Error(`Failed to fetch ${url}: HTTP ${response.statusCode}`));
|
|
||||||
response.resume();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
response.setEncoding("utf8");
|
|
||||||
let body = "";
|
|
||||||
response.on("data", (chunk) => {
|
|
||||||
body += chunk;
|
|
||||||
});
|
|
||||||
response.on("end", () => resolve(body));
|
|
||||||
})
|
|
||||||
.on("error", reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readAgentTypeSource(options) {
|
|
||||||
if (options.agentTypesFile) {
|
|
||||||
return readFile(path.resolve(options.agentTypesFile), "utf8");
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetchText(options.agentTypesUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseAgentTypes(source) {
|
|
||||||
const match = source.match(/export\s+type\s+AgentType\s*=\s*([\s\S]*?);/);
|
|
||||||
if (!match) {
|
|
||||||
throw new Error("Could not find export type AgentType in Vercel skills type source.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const agents = new Set();
|
|
||||||
const agentTypeBody = match[1];
|
|
||||||
for (const agentMatch of agentTypeBody.matchAll(/['"]([^'"]+)['"]/g)) {
|
|
||||||
agents.add(agentMatch[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (agents.size === 0) {
|
|
||||||
throw new Error("Vercel AgentType list was empty.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return agents;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function listAllSkillDirs(root) {
|
|
||||||
const skillsRoot = path.join(root, "skills");
|
|
||||||
const entries = await readdir(skillsRoot, { withFileTypes: true });
|
|
||||||
return entries
|
|
||||||
.filter((entry) => entry.isDirectory())
|
|
||||||
.map((entry) => `skills/${entry.name}`)
|
|
||||||
.filter((skillDir) => existsSync(path.join(root, skillDir, "skill.json")))
|
|
||||||
.sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
function changedSkillDirs({ root, base, head }) {
|
|
||||||
if (!base || !head) {
|
|
||||||
throw new Error("Provide --skills, --all, or both --base and --head for changed-skill detection.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = spawnSync(
|
|
||||||
"git",
|
|
||||||
[
|
|
||||||
"-C",
|
|
||||||
root,
|
|
||||||
"diff",
|
|
||||||
"--name-only",
|
|
||||||
`${base}...${head}`,
|
|
||||||
"--",
|
|
||||||
"skills/*/**",
|
|
||||||
":(exclude)skills/*/test/**",
|
|
||||||
":(exclude)skills/*/tests/**",
|
|
||||||
],
|
|
||||||
{ encoding: "utf8" },
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.status !== 0) {
|
|
||||||
throw new Error(`git diff failed\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
...new Set(
|
|
||||||
result.stdout
|
|
||||||
.split("\n")
|
|
||||||
.map((line) => line.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((filePath) => filePath.split("/").slice(0, 2).join("/"))
|
|
||||||
.filter((skillDir) => /^skills\/[^/]+$/.test(skillDir)),
|
|
||||||
),
|
|
||||||
].sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readJson(filePath) {
|
|
||||||
return JSON.parse(await readFile(filePath, "utf8"));
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasRequiredCommand(markdown, { repository, skillName, agent }) {
|
|
||||||
return markdown
|
|
||||||
.split("\n")
|
|
||||||
.map((line) => line.replace(/\s+/g, " ").trim())
|
|
||||||
.filter((line) => line.includes("npx skills add"))
|
|
||||||
.some((line) => {
|
|
||||||
return (
|
|
||||||
line.includes(`npx skills add ${repository}`) &&
|
|
||||||
line.includes(`--skill ${skillName}`) &&
|
|
||||||
(line.includes(`-a ${agent}`) || line.includes(`--agent ${agent}`)) &&
|
|
||||||
(line.includes(" -y") || line.includes(" --yes"))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validateSkill({ root, skillDir, repository, agentTypes }) {
|
|
||||||
const skillJsonPath = path.join(root, skillDir, "skill.json");
|
|
||||||
const skill = await readJson(skillJsonPath);
|
|
||||||
const skillName = skill.name || path.basename(skillDir);
|
|
||||||
const agent = installAgentForSkill(skill, agentTypes);
|
|
||||||
const command = `npx skills add ${repository} --skill ${skillName} -a ${agent} -y`;
|
|
||||||
const failures = [];
|
|
||||||
|
|
||||||
for (const filename of DOC_FILENAMES) {
|
|
||||||
const docPath = path.join(root, skillDir, filename);
|
|
||||||
if (!existsSync(docPath)) {
|
|
||||||
failures.push(`Missing required install documentation file: ${path.join(skillDir, filename)}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const markdown = await readFile(docPath, "utf8");
|
|
||||||
if (!hasRequiredCommand(markdown, { repository, skillName, agent })) {
|
|
||||||
failures.push(`Missing required npx skills install command in ${path.join(skillDir, filename)}: ${command}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
skillDir,
|
|
||||||
skillName,
|
|
||||||
agent,
|
|
||||||
failures,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const options = parseArgs(process.argv.slice(2));
|
|
||||||
const agentTypes = parseAgentTypes(await readAgentTypeSource(options));
|
|
||||||
let skillDirs = options.skillDirs;
|
|
||||||
|
|
||||||
if (options.all) {
|
|
||||||
skillDirs = await listAllSkillDirs(options.root);
|
|
||||||
} else if (skillDirs.length === 0) {
|
|
||||||
skillDirs = changedSkillDirs(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skillDirs.length === 0) {
|
|
||||||
console.log("No skill install docs to validate.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = [];
|
|
||||||
for (const skillDir of skillDirs) {
|
|
||||||
const skillJsonPath = path.join(options.root, skillDir, "skill.json");
|
|
||||||
if (!existsSync(skillJsonPath)) {
|
|
||||||
console.log(`Skipping removed skill directory: ${skillDir}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
results.push(
|
|
||||||
await validateSkill({
|
|
||||||
root: options.root,
|
|
||||||
skillDir,
|
|
||||||
repository: options.repository,
|
|
||||||
agentTypes,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const failures = results.flatMap((result) => result.failures);
|
|
||||||
if (failures.length > 0) {
|
|
||||||
for (const failure of failures) {
|
|
||||||
console.error(`::error::${failure}`);
|
|
||||||
}
|
|
||||||
throw new Error(`Found ${failures.length} npx skills install documentation issue(s).`);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const result of results) {
|
|
||||||
console.log(`npx skills install docs OK for ${result.skillName}: -a ${result.agent}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((error) => {
|
|
||||||
console.error(error.message);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -76,40 +76,6 @@ test('fetchGitHubTraffic requests the daily GitHub traffic endpoints with auth',
|
|||||||
assert.deepEqual(snapshot.clones.clones, responses[`/repos/${TEST_REPOSITORY}/traffic/clones?per=day`].clones);
|
assert.deepEqual(snapshot.clones.clones, responses[`/repos/${TEST_REPOSITORY}/traffic/clones?per=day`].clones);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fetchGitHubTraffic explains traffic token requirements on 403', async () => {
|
|
||||||
const fetchImpl = async () => new globalThis.Response(
|
|
||||||
JSON.stringify({ message: 'Resource not accessible by integration' }),
|
|
||||||
{ status: 403 },
|
|
||||||
);
|
|
||||||
|
|
||||||
await assert.rejects(
|
|
||||||
fetchGitHubTraffic({
|
|
||||||
repo: TEST_REPOSITORY,
|
|
||||||
token: 'installation-token',
|
|
||||||
capturedAt,
|
|
||||||
fetchImpl,
|
|
||||||
}),
|
|
||||||
/returned 403\..*push access/,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('fetchGitHubTraffic flags invalid tokens on 401', async () => {
|
|
||||||
const fetchImpl = async () => new globalThis.Response(
|
|
||||||
JSON.stringify({ message: 'Bad credentials' }),
|
|
||||||
{ status: 401 },
|
|
||||||
);
|
|
||||||
|
|
||||||
await assert.rejects(
|
|
||||||
fetchGitHubTraffic({
|
|
||||||
repo: TEST_REPOSITORY,
|
|
||||||
token: 'expired-token',
|
|
||||||
capturedAt,
|
|
||||||
fetchImpl,
|
|
||||||
}),
|
|
||||||
/returned 401\..*expired or revoked/,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('mergeTrafficArchive upserts daily views and clones without double-counting overlapping windows', () => {
|
test('mergeTrafficArchive upserts daily views and clones without double-counting overlapping windows', () => {
|
||||||
const archive = mergeTrafficArchive(
|
const archive = mergeTrafficArchive(
|
||||||
{
|
{
|
||||||
@@ -266,8 +232,7 @@ test('traffic archive workflow uses a daily schedule and a dedicated archive bra
|
|||||||
|
|
||||||
assert.match(workflow, /cron:\s+'17 3 \* \* \*'/);
|
assert.match(workflow, /cron:\s+'17 3 \* \* \*'/);
|
||||||
assert.match(workflow, /TRAFFIC_ARCHIVE_BRANCH:\s+traffic-archive/);
|
assert.match(workflow, /TRAFFIC_ARCHIVE_BRANCH:\s+traffic-archive/);
|
||||||
assert.match(workflow, /GH_TRAFFIC_TOKEN:\s*\$\{\{\s*secrets\.TRAFFIC_ARCHIVE_TOKEN\b/);
|
assert.match(workflow, /TRAFFIC_ARCHIVE_TOKEN/);
|
||||||
assert.doesNotMatch(workflow, /GH_TRAFFIC_TOKEN:[^\n]*github\.token/);
|
|
||||||
assert.match(workflow, /node scripts\/archive-github-traffic\.mjs/);
|
assert.match(workflow, /node scripts\/archive-github-traffic\.mjs/);
|
||||||
assert.match(workflow, /git add traffic\/archive\.json traffic\/summary\.json/);
|
assert.match(workflow, /git add traffic\/archive\.json traffic\/summary\.json/);
|
||||||
assert.match(workflow, /git rm --ignore-unmatch traffic\/README\.md/);
|
assert.match(workflow, /git rm --ignore-unmatch traffic\/README\.md/);
|
||||||
|
|||||||
@@ -47,16 +47,6 @@ assert.match(
|
|||||||
/git add "\$FEED_PATH" "\$FEED_SIG_PATH" "\$GHSA_FEED_PATH" "\$GHSA_FEED_SIG_PATH" "\$SKILL_FEED_PATH" "\$SKILL_FEED_SIG_PATH"/,
|
/git add "\$FEED_PATH" "\$FEED_SIG_PATH" "\$GHSA_FEED_PATH" "\$GHSA_FEED_SIG_PATH" "\$SKILL_FEED_PATH" "\$SKILL_FEED_SIG_PATH"/,
|
||||||
'NVD workflow PR must include both NVD and GHSA feed artifacts',
|
'NVD workflow PR must include both NVD and GHSA feed artifacts',
|
||||||
);
|
);
|
||||||
assert.doesNotMatch(
|
|
||||||
workflow,
|
|
||||||
/gh run list[\s\S]*--jq --arg/,
|
|
||||||
'CodeQL run lookup must not pass jq CLI flags through gh --jq',
|
|
||||||
);
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/gh run list[\s\S]*--json databaseId,createdAt,headSha \\\s*\n\s+\| jq -r --arg since "\$DISPATCHED_AT" --arg sha "\$EXPECTED_HEAD_SHA"/,
|
|
||||||
'CodeQL run lookup must filter the gh JSON output with jq variables',
|
|
||||||
);
|
|
||||||
assert.match(
|
assert.match(
|
||||||
ciWorkflow,
|
ciWorkflow,
|
||||||
/name: NVD \+ GHSA Pipeline Dry Run[\s\S]*node scripts\/test-nvd-ghsa-pipeline-dry-run\.mjs/,
|
/name: NVD \+ GHSA Pipeline Dry Run[\s\S]*node scripts\/test-nvd-ghsa-pipeline-dry-run\.mjs/,
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
import assert from "node:assert/strict";
|
|
||||||
import { resolveClawHubSlug } from "./ci/resolve_clawhub_slug.mjs";
|
|
||||||
import { collectDeclaredPlatforms, installAgentForSkill } from "./ci/skill_platforms.mjs";
|
|
||||||
|
|
||||||
const cases = [
|
|
||||||
["openclaw-traffic-guardian", ["openclaw"], "clawsec-openclaw-traffic-guardian"],
|
|
||||||
["openclaw-audit-watchdog", ["openclaw"], "clawsec-openclaw-audit-watchdog"],
|
|
||||||
["soul-guardian", ["openclaw"], "clawsec-openclaw-soul-guardian"],
|
|
||||||
["hermes-attestation-guardian", ["hermes"], "clawsec-hermes-attestation-guardian"],
|
|
||||||
["hermes-traffic-guardian", ["hermes"], "clawsec-hermes-traffic-guardian"],
|
|
||||||
["nanoclaw-traffic-guardian", ["nanoclaw"], "clawsec-nanoclaw-traffic-guardian"],
|
|
||||||
["picoclaw-security-guardian", ["picoclaw"], "clawsec-picoclaw-security-guardian"],
|
|
||||||
["picoclaw-self-pen-testing", ["picoclaw"], "clawsec-picoclaw-self-pen-testing"],
|
|
||||||
["picoclaw-traffic-guardian", ["picoclaw"], "clawsec-picoclaw-traffic-guardian"],
|
|
||||||
["clawtributor", ["openclaw", "nanoclaw", "hermes", "picoclaw"], "clawsec-clawtributor"],
|
|
||||||
["clawsec-feed", ["openclaw"], "clawsec-feed"],
|
|
||||||
["clawsec-suite", ["openclaw"], "clawsec-suite"],
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const [name, platforms, expected] of cases) {
|
|
||||||
assert.equal(resolveClawHubSlug({ name, platforms }), expected, `${name} should map to ${expected}`);
|
|
||||||
assert.equal(resolveClawHubSlug({ name }), expected, `${name} should map to ${expected} without metadata`);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.throws(
|
|
||||||
() => resolveClawHubSlug({ name: "../openclaw-traffic-guardian", platforms: ["openclaw"] }),
|
|
||||||
/Invalid skill name/,
|
|
||||||
"unsafe skill names must be rejected",
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.deepEqual(
|
|
||||||
collectDeclaredPlatforms({
|
|
||||||
platform: "openclaw",
|
|
||||||
platforms: ["hermes", "openclaw", ""],
|
|
||||||
picoclaw: { requires: {} },
|
|
||||||
}),
|
|
||||||
["openclaw", "hermes", "picoclaw"],
|
|
||||||
"declared platform parsing should combine legacy fields, arrays, and platform metadata keys",
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
installAgentForSkill({ platform: "hermes" }, new Set(["codex", "hermes-agent", "openclaw"])),
|
|
||||||
"hermes-agent",
|
|
||||||
"install agent selection should reuse platform aliases",
|
|
||||||
);
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
import assert from "node:assert/strict";
|
|
||||||
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
||||||
import { tmpdir } from "node:os";
|
|
||||||
import path from "node:path";
|
|
||||||
import { spawnSync } from "node:child_process";
|
|
||||||
|
|
||||||
const validator = "scripts/ci/validate_skill_install_docs.mjs";
|
|
||||||
const workflow = await readFile(".github/workflows/skill-release.yml", "utf8");
|
|
||||||
const tempRoot = await mkdtemp(path.join(tmpdir(), "clawsec-install-docs-"));
|
|
||||||
const agentTypesPath = path.join(tempRoot, "vercel-types.ts");
|
|
||||||
|
|
||||||
function runValidator(args) {
|
|
||||||
return spawnSync(
|
|
||||||
process.execPath,
|
|
||||||
[validator, "--root", tempRoot, "--agent-types-file", agentTypesPath, ...args],
|
|
||||||
{
|
|
||||||
encoding: "utf8",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function writeSkill({ name, metadata, readme, skillMd }) {
|
|
||||||
const skillDir = path.join(tempRoot, "skills", name);
|
|
||||||
await mkdir(skillDir, { recursive: true });
|
|
||||||
await writeFile(
|
|
||||||
path.join(skillDir, "skill.json"),
|
|
||||||
JSON.stringify(
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
version: "1.0.0",
|
|
||||||
description: `${name} test skill`,
|
|
||||||
license: "AGPL-3.0-or-later",
|
|
||||||
...metadata,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await writeFile(path.join(skillDir, "README.md"), readme);
|
|
||||||
await writeFile(path.join(skillDir, "SKILL.md"), skillMd);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await writeFile(
|
|
||||||
agentTypesPath,
|
|
||||||
"export type AgentType = | 'codex' | 'hermes-agent' | 'openclaw' | 'universal';\n",
|
|
||||||
);
|
|
||||||
|
|
||||||
await writeSkill({
|
|
||||||
name: "hermes-example",
|
|
||||||
metadata: { hermes: { category: "security" } },
|
|
||||||
readme: "# Hermes Example\n\n## Installation\n\nMissing the Skills CLI command.\n",
|
|
||||||
skillMd: "---\nname: hermes-example\nversion: 1.0.0\n---\n\n## Installation\n\nMissing the Skills CLI command.\n",
|
|
||||||
});
|
|
||||||
|
|
||||||
const missingHermes = runValidator(["--skills", "skills/hermes-example"]);
|
|
||||||
assert.equal(missingHermes.status, 1, "missing Hermes install docs must fail validation");
|
|
||||||
assert.match(
|
|
||||||
missingHermes.stderr,
|
|
||||||
/npx skills add prompt-security\/clawsec --skill hermes-example -a hermes-agent -y/,
|
|
||||||
"Hermes skills must require the hermes-agent installer target",
|
|
||||||
);
|
|
||||||
|
|
||||||
await writeSkill({
|
|
||||||
name: "hermes-example",
|
|
||||||
metadata: { hermes: { category: "security" } },
|
|
||||||
readme:
|
|
||||||
"# Hermes Example\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill hermes-example -a hermes-agent -y\n```\n",
|
|
||||||
skillMd:
|
|
||||||
"---\nname: hermes-example\nversion: 1.0.0\n---\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill hermes-example -a hermes-agent -y\n```\n",
|
|
||||||
});
|
|
||||||
|
|
||||||
const validHermes = runValidator(["--skills", "skills/hermes-example"]);
|
|
||||||
assert.equal(
|
|
||||||
validHermes.status,
|
|
||||||
0,
|
|
||||||
`valid Hermes install docs should pass\nstdout:\n${validHermes.stdout}\nstderr:\n${validHermes.stderr}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
await writeSkill({
|
|
||||||
name: "codex-example",
|
|
||||||
metadata: { platform: "codex" },
|
|
||||||
readme:
|
|
||||||
"# Codex Example\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill codex-example -a openclaw -y\n```\n",
|
|
||||||
skillMd:
|
|
||||||
"---\nname: codex-example\nversion: 1.0.0\n---\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill codex-example -a openclaw -y\n```\n",
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrongExactTarget = runValidator(["--skills", "skills/codex-example"]);
|
|
||||||
assert.equal(wrongExactTarget.status, 1, "exact AgentType matches must use their matched target");
|
|
||||||
assert.match(
|
|
||||||
wrongExactTarget.stderr,
|
|
||||||
/npx skills add prompt-security\/clawsec --skill codex-example -a codex -y/,
|
|
||||||
"Exact AgentType matches must not fall back to openclaw",
|
|
||||||
);
|
|
||||||
|
|
||||||
await writeSkill({
|
|
||||||
name: "nanoclaw-example",
|
|
||||||
metadata: { platform: "nanoclaw", nanoclaw: { category: "security" } },
|
|
||||||
readme:
|
|
||||||
"# NanoClaw Example\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill nanoclaw-example -a hermes-agent -y\n```\n",
|
|
||||||
skillMd:
|
|
||||||
"---\nname: nanoclaw-example\nversion: 1.0.0\n---\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill nanoclaw-example -a hermes-agent -y\n```\n",
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrongNanoTarget = runValidator(["--skills", "skills/nanoclaw-example"]);
|
|
||||||
assert.equal(wrongNanoTarget.status, 1, "NanoClaw docs must fail when they use the Hermes target");
|
|
||||||
assert.match(
|
|
||||||
wrongNanoTarget.stderr,
|
|
||||||
/npx skills add prompt-security\/clawsec --skill nanoclaw-example -a openclaw -y/,
|
|
||||||
"NanoClaw skills must install through the openclaw target",
|
|
||||||
);
|
|
||||||
|
|
||||||
await writeSkill({
|
|
||||||
name: "nanoclaw-example",
|
|
||||||
metadata: { platform: "nanoclaw", nanoclaw: { category: "security" } },
|
|
||||||
readme:
|
|
||||||
"# NanoClaw Example\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill nanoclaw-example -a openclaw -y\n```\n",
|
|
||||||
skillMd:
|
|
||||||
"---\nname: nanoclaw-example\nversion: 1.0.0\n---\n\n## Vercel Skills Installation\n\n```bash\nnpx skills add prompt-security/clawsec --skill nanoclaw-example -a openclaw -y\n```\n",
|
|
||||||
});
|
|
||||||
|
|
||||||
const validNano = runValidator(["--skills", "skills/nanoclaw-example"]);
|
|
||||||
assert.equal(
|
|
||||||
validNano.status,
|
|
||||||
0,
|
|
||||||
`valid NanoClaw install docs should pass\nstdout:\n${validNano.stdout}\nstderr:\n${validNano.stderr}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/Validate npx skills install docs/,
|
|
||||||
"Skill release workflow must run the install-doc validator",
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
await rm(tempRoot, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
@@ -2,13 +2,7 @@ import assert from 'node:assert/strict';
|
|||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
|
|
||||||
const workflowPath = new URL('../.github/workflows/skill-release.yml', import.meta.url);
|
const workflowPath = new URL('../.github/workflows/skill-release.yml', import.meta.url);
|
||||||
const ciWorkflowPath = new URL('../.github/workflows/ci.yml', import.meta.url);
|
|
||||||
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 workflow = await readFile(workflowPath, 'utf8');
|
const workflow = await readFile(workflowPath, 'utf8');
|
||||||
const ciWorkflow = await readFile(ciWorkflowPath, 'utf8');
|
|
||||||
const installClawhubCli = await readFile(installClawhubCliPath, 'utf8');
|
|
||||||
const patchClawhubPayload = await readFile(patchClawhubPayloadPath, 'utf8');
|
|
||||||
|
|
||||||
assert.match(
|
assert.match(
|
||||||
workflow,
|
workflow,
|
||||||
@@ -16,379 +10,20 @@ assert.match(
|
|||||||
'Skill release workflow must run when any skill package file changes',
|
'Skill release workflow must run when any skill package file changes',
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/pull_request:[\s\S]*paths:[\s\S]*- '\.github\/workflows\/skill-release\.yml'[\s\S]*- 'scripts\/ci\/\*\*'/,
|
|
||||||
'Skill release workflow must also run when the release pipeline itself changes',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.ok(
|
|
||||||
ciWorkflow.includes(` - name: Skill Release Tooling Tests
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
for test_file in scripts/test-skill-*.mjs; do
|
|
||||||
node "$test_file"
|
|
||||||
done`),
|
|
||||||
'CI must run every scripts/test-skill-*.mjs file so new skill release tests are not orphaned',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
assert.match(
|
||||||
workflow,
|
workflow,
|
||||||
/git diff --name-only "\$\{BASE_SHA\}\.\.\.\$\{HEAD_SHA\}" --[\s\S]*'skills\/\*\/\*\*'[\s\S]*':\(exclude\)skills\/\*\/test\/\*\*'[\s\S]*':\(exclude\)skills\/\*\/tests\/\*\*'/,
|
/git diff --name-only "\$\{BASE_SHA\}\.\.\.\$\{HEAD_SHA\}" --[\s\S]*'skills\/\*\/\*\*'[\s\S]*':\(exclude\)skills\/\*\/test\/\*\*'[\s\S]*':\(exclude\)skills\/\*\/tests\/\*\*'/,
|
||||||
'Skill release validation must ignore test-only skill changes while inspecting release-relevant skill files',
|
'Skill release validation must ignore test-only skill changes while inspecting release-relevant skill files',
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
|
||||||
workflow.includes('name = tolower($NF)')
|
|
||||||
&& workflow.includes('name ~ /^(test|spec)[_-]/')
|
|
||||||
&& workflow.includes('name ~ /\\.(test|spec)\\./'),
|
|
||||||
'Skill release validation must filter test-named skill files such as scripts/test_*.py before selecting dry-run skill directories',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.doesNotMatch(
|
assert.doesNotMatch(
|
||||||
workflow,
|
workflow,
|
||||||
/No version bump detected for \$\{skill_dir\}; skipping\./,
|
/No version bump detected for \$\{skill_dir\}; skipping\./,
|
||||||
'Changed skill directories without a version bump must not be skipped without release-tag validation',
|
'Changed skill directories without a version bump must fail validation instead of being skipped',
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.match(
|
assert.match(
|
||||||
workflow,
|
workflow,
|
||||||
/skill_release_name="\$\(basename "\$\{skill_dir\}"\)"/,
|
/::error file=\$\{skill_dir\}::Changed skill package has no version bump\./,
|
||||||
'Skill release validation must derive the release tag prefix from the skill package directory',
|
'Skill release validation must emit an explicit missing-version-bump error',
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/release_tag="\$\{skill_release_name\}-v\$\{head_json_version\}"/,
|
|
||||||
'Skill release validation must use the skill package directory name for release tag checks',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.doesNotMatch(
|
|
||||||
workflow,
|
|
||||||
/release_tag="\$\{head_skill_name\}-v\$\{head_json_version\}"/,
|
|
||||||
'Skill release validation must not use skill.json name for release tag checks because release tags resolve to skill directories',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/git show-ref --verify --quiet "refs\/tags\/\$\{release_tag\}"/,
|
|
||||||
'Skill release validation must check whether the current skill version has already been tagged',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/No version bump detected for \$\{skill_dir\}, but release tag \$\{release_tag\} does not exist; treating \$\{head_json_version\} as unreleased\./,
|
|
||||||
'Skill release validation must allow edits to an unchanged version when that release tag does not exist yet',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/::error file=\$\{skill_dir\}::Changed skill package has no version bump and release tag \$\{release_tag\} already exists\./,
|
|
||||||
'Skill release validation must still fail unchanged versions after their release tag exists',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/Install SkillSpector/,
|
|
||||||
'Skill release workflow must install SkillSpector before publishing release evidence',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/Generate SkillSpector report/,
|
|
||||||
'Skill release workflow must generate a SkillSpector report for each released skill',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.doesNotMatch(
|
|
||||||
workflow,
|
|
||||||
/"### SkillSpector Security Report"/,
|
|
||||||
'GitHub release notes must not add a duplicate SkillSpector heading before the generated report',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/readFileSync\("release-assets\/skillspector-report\.md", "utf8"\)[\s\S]*report,[\s\S]*\[skillspector-report\.md\]\(https:\/\/github\.com\/\$\{process\.env\.REPO\}\/releases\/download\/\$\{process\.env\.TAG\}\/skillspector-report\.md\)/,
|
|
||||||
'GitHub release notes must embed the generated SkillSpector report and include a direct report link',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/readFileSync\("release-assets\/skillspector-report\.md", "utf8"\)/,
|
|
||||||
'GitHub release notes must load the generated SkillSpector report content into the release body file',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/body_path: \$\{\{ runner\.temp \}\}\/skill-release-body\.md/,
|
|
||||||
'GitHub release creation must use body_path for the generated release body file',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.doesNotMatch(
|
|
||||||
workflow,
|
|
||||||
/SKILLSPECTOR_REPORT_EOF|\$\{\{ steps\.skillspector_report\.outputs\.body \}\}|cat release-assets\/skillspector-report\.md[\s\S]*>> "\$GITHUB_OUTPUT"/,
|
|
||||||
'SkillSpector report content must not be sent through GitHub Actions step outputs',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/generate_skillspector_report "\$\{inner_dir\}" "\$\{out_assets\}\/skillspector-report\.md"/,
|
|
||||||
'PR dry-run SkillSpector scan must target the staged release payload, not the source skill directory',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/Run release dry-run for changed skills[\s\S]*git diff --name-only "\$\{BASE_SHA\}\.\.\.\$\{HEAD_SHA\}" --[\s\S]*'skills\/\*\/\*\*'[\s\S]*':\(exclude\)skills\/\*\/test\/\*\*'[\s\S]*':\(exclude\)skills\/\*\/tests\/\*\*'/,
|
|
||||||
'PR dry-run SkillSpector scan must run when any release-relevant skill package file changes',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.ok(
|
|
||||||
workflow.includes('local name="${lower##*/}"')
|
|
||||||
&& workflow.includes('"$name" == test_*')
|
|
||||||
&& workflow.includes('"$name" == *.test.*')
|
|
||||||
&& workflow.includes('(__tests__|test|tests)/|(^|/)(test|spec)[_-]|(^|/).*\\.(test|spec)\\.'),
|
|
||||||
'Skill release archives must exclude test directories and test-named files from staged release payloads',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.doesNotMatch(
|
|
||||||
workflow,
|
|
||||||
/generate_skillspector_report "\$\{skill_dir\}" "\$\{out_assets\}\/skillspector-report\.md"/,
|
|
||||||
'PR dry-run SkillSpector scan must not include source-only test directories',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/generate_skillspector_report "\$INNER_DIR" "release-assets\/skillspector-report\.md"/,
|
|
||||||
'Tag release SkillSpector scan must target the staged release payload, not the source skill directory',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.doesNotMatch(
|
|
||||||
workflow,
|
|
||||||
/generate_skillspector_report "\$SKILL_PATH" "release-assets\/skillspector-report\.md"/,
|
|
||||||
'Tag release SkillSpector scan must not include source-only test directories',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/Generate release trust packet/,
|
|
||||||
'Skill release workflow must generate skill cards, permission summaries, and npx install instructions',
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const artifact of ['skill-card.md', 'permissions.json', 'install.md', 'skillspector-report.md']) {
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
new RegExp(`release-assets/${artifact.replace('.', '\\.')}`),
|
|
||||||
`Skill release workflow must publish ${artifact} in release assets`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const escapeRegExp = (literal) => literal.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
||||||
|
|
||||||
for (const artifact of ['skill-card.md', 'permissions.json', 'install.md', 'skillspector-report.md']) {
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
new RegExp(
|
|
||||||
String.raw`if ! add_release_asset_checksum "\$\{out_assets\}" "${escapeRegExp(artifact)}"; then` +
|
|
||||||
String.raw`[\s\S]*?failures=\$\(\(failures \+ 1\)\)[\s\S]*?continue[\s\S]*?fi`,
|
|
||||||
),
|
|
||||||
`PR dry-run validation must aggregate and continue when ${artifact} cannot be checksummed`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/add_release_asset_checksum "skill-card\.md"/,
|
|
||||||
'Skill card must be included in the signed checksums manifest',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/add_release_asset_checksum "permissions\.json"/,
|
|
||||||
'Permissions summary must be included in the signed checksums manifest',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/add_release_asset_checksum "install\.md"/,
|
|
||||||
'npx install/update instructions must be included in the signed checksums manifest',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/add_release_asset_checksum "skillspector-report\.md"/,
|
|
||||||
'SkillSpector report must be included in the signed checksums manifest',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/Upload SkillSpector PR reports[\s\S]*actions\/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7\.0\.1[\s\S]*name: skillspector-pr-reports/,
|
|
||||||
'PR dry-run must upload generated SkillSpector reports as workflow artifacts',
|
|
||||||
);
|
|
||||||
|
|
||||||
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',
|
|
||||||
);
|
|
||||||
|
|
||||||
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/,
|
|
||||||
'SkillSpector PR comments must still run when the release dry-run produced reports but the release job failed later',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/function sanitizeReportForComment\(report\)[\s\S]*code block omitted from PR comment[\s\S]*inline snippet omitted[\s\S]*redacted-email[\s\S]*redacted-token/,
|
|
||||||
'SkillSpector PR comments must sanitize raw report content before posting to the PR',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/const sanitizedReport = sanitizeReportForComment\(report\);[\s\S]*`\$\{marker\}\\n\$\{sanitizedReport\}/,
|
|
||||||
'SkillSpector PR comments must use the sanitized report body, not the raw artifact text',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.doesNotMatch(
|
|
||||||
workflow,
|
|
||||||
/`\$\{marker\}\\n\$\{report\.trimEnd\(\)\}/,
|
|
||||||
'SkillSpector PR comments must not post report.trimEnd() verbatim',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/clawsec-skillspector-report:\$\{tag\}[\s\S]*github\.rest\.issues\.updateComment[\s\S]*github\.rest\.issues\.createComment/,
|
|
||||||
'SkillSpector PR comments must use stable per-skill markers and update existing comments before creating new ones',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/Simulate tag release build/,
|
|
||||||
'Skill release workflow must simulate a tag release build during PR validation',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/simulate_skill_tag_release\.mjs/,
|
|
||||||
'Skill release workflow must call the tag release simulation script',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.ok(
|
|
||||||
workflow.includes('simulated_version | test("^[0-9]+\\\\.[0-9]+\\\\.[0-9]+(-[a-zA-Z0-9]+)?$")'),
|
|
||||||
'Skill release workflow must accept every prerelease version format that release-skill.sh accepts',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/clawhub_slug: \$\{\{ steps\.publishable\.outputs\.clawhub_slug \}\}/,
|
|
||||||
'Skill release workflow must expose the resolved ClawHub slug from release-tag outputs',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/CLAWHUB_SLUG=\$\(node scripts\/ci\/resolve_clawhub_slug\.mjs "\$SKILL_PATH"\)/,
|
|
||||||
'Skill release workflow must resolve the ClawHub slug from the skill package path',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/cp scripts\/ci\/resolve_clawhub_slug\.mjs "\$RUNNER_TEMP\/resolve_clawhub_slug\.mjs"/,
|
|
||||||
'Manual ClawHub republish must preserve the current slug helper before checking out an older release tag',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/CLAWHUB_SLUG=\$\(node "\$RUNNER_TEMP\/resolve_clawhub_slug\.mjs" "\$SKILL_PATH"\)/,
|
|
||||||
'Manual ClawHub republish must resolve slugs with the preserved helper against the checked-out tag metadata',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/npx clawhub@latest install \$\{CLAWHUB_SLUG\}/,
|
|
||||||
'GitHub release quick install instructions must use the resolved ClawHub slug',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/clawhub inspect "\$CLAWHUB_SLUG" --version "\$VERSION" --json/,
|
|
||||||
'Duplicate ClawHub version guard must inspect the resolved ClawHub slug',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
/--slug "\$CLAWHUB_SLUG"/,
|
|
||||||
'ClawHub publish must use the resolved ClawHub slug',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
workflow.match(/bash scripts\/ci\/install_clawhub_cli\.sh/g)?.length,
|
|
||||||
2,
|
|
||||||
'ClawHub publish and republish jobs must share the same pinned CLI installer',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
workflow.match(/node scripts\/ci\/patch_clawhub_publish_payload\.mjs/g)?.length,
|
|
||||||
2,
|
|
||||||
'ClawHub publish and republish jobs must share the same payload patch helper',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.doesNotMatch(
|
|
||||||
workflow,
|
|
||||||
/npm ci --prefix \.github\/clawhub-cli/,
|
|
||||||
'ClawHub CLI installation must not be duplicated inline in the workflow',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.doesNotMatch(
|
|
||||||
workflow,
|
|
||||||
/node <<'NODE'[\s\S]*acceptLicenseTerms: true/,
|
|
||||||
'ClawHub payload patching must not be duplicated inline in the workflow',
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const secret of ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN']) {
|
|
||||||
assert.match(
|
|
||||||
workflow,
|
|
||||||
new RegExp(`${secret}: \\$\\{\\{ secrets\\.${secret} \\}\\}`),
|
|
||||||
`ClawHub jobs must expose ${secret} for CodeArtifact npm authentication`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
installClawhubCli,
|
|
||||||
/aws codeartifact login[\s\S]*--domain "\$CODEARTIFACT_DOMAIN"[\s\S]*--domain-owner "\$CODEARTIFACT_DOMAIN_OWNER"[\s\S]*--repository "\$CODEARTIFACT_REPOSITORY"[\s\S]*--region "\$AWS_REGION"/,
|
|
||||||
'ClawHub CLI installer must authenticate npm against CodeArtifact before npm ci',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
installClawhubCli,
|
|
||||||
/npm ci --prefix "\$CLI_PREFIX"/,
|
|
||||||
'ClawHub CLI installer must install from the committed lockfile prefix',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
installClawhubCli,
|
|
||||||
/"\$\{workspace\}\/\$\{CLI_PREFIX\}\/node_modules\/\.bin" >> "\$GITHUB_PATH"/,
|
|
||||||
'ClawHub CLI installer must expose the pinned clawhub binary on GITHUB_PATH',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
patchClawhubPayload,
|
|
||||||
/const payloadPattern = \/changelog,\\r\?\\n\(\\s\*\)tags,\/;/,
|
|
||||||
'ClawHub payload patch helper must target the expected publish payload shape',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.match(
|
|
||||||
patchClawhubPayload,
|
|
||||||
/acceptLicenseTerms: true/,
|
|
||||||
'ClawHub payload patch helper must preserve the acceptLicenseTerms workaround',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.doesNotMatch(
|
|
||||||
workflow,
|
|
||||||
/clawhub inspect "\$SKILL_NAME" --version "\$VERSION" --json/,
|
|
||||||
'Duplicate ClawHub version guard must not inspect the raw skill package name',
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.doesNotMatch(
|
|
||||||
workflow,
|
|
||||||
/--slug "\$SKILL_NAME"/,
|
|
||||||
'ClawHub publish must not use the raw skill package name as the ClawHub slug',
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,184 +0,0 @@
|
|||||||
import assert from "node:assert/strict";
|
|
||||||
import { chmod, cp, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
||||||
import { tmpdir } from "node:os";
|
|
||||||
import path from "node:path";
|
|
||||||
import { spawnSync } from "node:child_process";
|
|
||||||
|
|
||||||
const tempRoot = await mkdtemp(path.join(tmpdir(), "clawsec-tag-release-sim-"));
|
|
||||||
const fakeSkillspector = path.join(tempRoot, "skillspector");
|
|
||||||
|
|
||||||
async function prereleaseFixture(sourceSkillDir, version, fixtureGroup) {
|
|
||||||
const fixtureDir = path.join(tempRoot, fixtureGroup, path.basename(sourceSkillDir));
|
|
||||||
await cp(sourceSkillDir, fixtureDir, { recursive: true });
|
|
||||||
|
|
||||||
const skillJsonPath = path.join(fixtureDir, "skill.json");
|
|
||||||
const skill = JSON.parse(await readFile(skillJsonPath, "utf8"));
|
|
||||||
skill.version = version;
|
|
||||||
await writeFile(skillJsonPath, `${JSON.stringify(skill, null, 2)}\n`);
|
|
||||||
|
|
||||||
const skillMdPath = path.join(fixtureDir, "SKILL.md");
|
|
||||||
const skillMd = await readFile(skillMdPath, "utf8");
|
|
||||||
await writeFile(skillMdPath, skillMd.replace(/^version:\s*.+$/m, `version: ${version}`));
|
|
||||||
|
|
||||||
return fixtureDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function runSimulation({ skillDir, outputDir, expectedOriginal, expectedSimulated, expectedAgent }) {
|
|
||||||
const result = spawnSync(
|
|
||||||
process.execPath,
|
|
||||||
[
|
|
||||||
"scripts/ci/simulate_skill_tag_release.mjs",
|
|
||||||
skillDir,
|
|
||||||
outputDir,
|
|
||||||
"--repository",
|
|
||||||
"prompt-security/clawsec",
|
|
||||||
"--source-ref",
|
|
||||||
"pull-request-head",
|
|
||||||
"--skillspector-bin",
|
|
||||||
fakeSkillspector,
|
|
||||||
],
|
|
||||||
{ encoding: "utf8" },
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
result.status,
|
|
||||||
0,
|
|
||||||
`tag release simulation failed\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const skillName = path.basename(skillDir);
|
|
||||||
const expectedTag = `${skillName}-v${expectedSimulated}`;
|
|
||||||
const summary = JSON.parse(await readFile(path.join(outputDir, "simulation-summary.json"), "utf8"));
|
|
||||||
assert.equal(summary.skill, skillName);
|
|
||||||
assert.equal(summary.original_version, expectedOriginal);
|
|
||||||
assert.equal(summary.simulated_version, expectedSimulated);
|
|
||||||
assert.equal(summary.tag, expectedTag);
|
|
||||||
|
|
||||||
const releaseAssetsDir = path.join(outputDir, "release-assets");
|
|
||||||
const checksums = JSON.parse(await readFile(path.join(releaseAssetsDir, "checksums.json"), "utf8"));
|
|
||||||
assert.equal(checksums.skill, skillName);
|
|
||||||
assert.equal(checksums.version, expectedSimulated);
|
|
||||||
assert.equal(checksums.tag, expectedTag);
|
|
||||||
assert.equal(checksums.archive.filename, `${expectedTag}.zip`);
|
|
||||||
|
|
||||||
for (const artifact of [
|
|
||||||
"skill-card.md",
|
|
||||||
"permissions.json",
|
|
||||||
"install.md",
|
|
||||||
"skillspector-report.md",
|
|
||||||
"checksums.sig",
|
|
||||||
"signing-public.pem",
|
|
||||||
]) {
|
|
||||||
assert.ok(
|
|
||||||
checksums.files[artifact] || artifact.endsWith(".sig") || artifact === "signing-public.pem",
|
|
||||||
`expected ${artifact} to be represented in the release output`,
|
|
||||||
);
|
|
||||||
const file = await readFile(path.join(releaseAssetsDir, artifact));
|
|
||||||
assert.ok(file.length > 0, `${artifact} should not be empty`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const archive = await readFile(path.join(releaseAssetsDir, `${expectedTag}.zip`));
|
|
||||||
assert.ok(archive.length > 0, "release archive should not be empty");
|
|
||||||
|
|
||||||
const install = await readFile(path.join(releaseAssetsDir, "install.md"), "utf8");
|
|
||||||
assert.match(
|
|
||||||
install,
|
|
||||||
new RegExp(
|
|
||||||
`npx skills add prompt-security/clawsec#pull-request-head --skill ${skillName} --agent ${expectedAgent} --global --yes`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
assert.match(install, new RegExp(`npx skills update ${skillName}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await writeFile(
|
|
||||||
fakeSkillspector,
|
|
||||||
`#!/usr/bin/env node
|
|
||||||
import { readdirSync, writeFileSync } from "node:fs";
|
|
||||||
import path from "node:path";
|
|
||||||
|
|
||||||
const scanIndex = process.argv.indexOf("scan");
|
|
||||||
if (scanIndex === -1 || !process.argv[scanIndex + 1]) {
|
|
||||||
console.error("missing scan target");
|
|
||||||
process.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
function containsTestDirectory(dir) {
|
|
||||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
||||||
if (!entry.isDirectory()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const lowerName = entry.name.toLowerCase();
|
|
||||||
if (lowerName === "test" || lowerName === "tests") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (containsTestDirectory(path.join(dir, entry.name))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scanTarget = process.argv[scanIndex + 1];
|
|
||||||
if (containsTestDirectory(scanTarget)) {
|
|
||||||
console.error("SkillSpector test fixture must scan the staged release payload, not source test directories.");
|
|
||||||
process.exit(42);
|
|
||||||
}
|
|
||||||
|
|
||||||
const outputIndex = process.argv.indexOf("--output");
|
|
||||||
if (outputIndex === -1 || !process.argv[outputIndex + 1]) {
|
|
||||||
console.error("missing --output");
|
|
||||||
process.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeFileSync(process.argv[outputIndex + 1], "# Fake SkillSpector Report\\n\\nNo live scan executed in unit test.\\n");
|
|
||||||
`,
|
|
||||||
{ mode: 0o700 },
|
|
||||||
);
|
|
||||||
await chmod(fakeSkillspector, 0o700);
|
|
||||||
|
|
||||||
await runSimulation({
|
|
||||||
skillDir: "skills/clawsec-suite",
|
|
||||||
outputDir: path.join(tempRoot, "stable"),
|
|
||||||
expectedOriginal: "0.1.10",
|
|
||||||
expectedSimulated: "0.1.11",
|
|
||||||
expectedAgent: "openclaw",
|
|
||||||
});
|
|
||||||
|
|
||||||
await runSimulation({
|
|
||||||
skillDir: "skills/hermes-traffic-guardian",
|
|
||||||
outputDir: path.join(tempRoot, "beta"),
|
|
||||||
expectedOriginal: "0.0.1-beta3",
|
|
||||||
expectedSimulated: "0.0.1-beta4",
|
|
||||||
expectedAgent: "hermes-agent",
|
|
||||||
});
|
|
||||||
|
|
||||||
const alphaSkillDir = await prereleaseFixture("skills/picoclaw-self-pen-testing", "0.0.3-alpha1", "alpha-fixture");
|
|
||||||
await runSimulation({
|
|
||||||
skillDir: alphaSkillDir,
|
|
||||||
outputDir: path.join(tempRoot, "alpha"),
|
|
||||||
expectedOriginal: "0.0.3-alpha1",
|
|
||||||
expectedSimulated: "0.0.3-alpha2",
|
|
||||||
expectedAgent: "openclaw",
|
|
||||||
});
|
|
||||||
|
|
||||||
const rcSkillDir = await prereleaseFixture("skills/picoclaw-security-guardian", "0.0.4-rc1", "rc-fixture");
|
|
||||||
await runSimulation({
|
|
||||||
skillDir: rcSkillDir,
|
|
||||||
outputDir: path.join(tempRoot, "rc"),
|
|
||||||
expectedOriginal: "0.0.4-rc1",
|
|
||||||
expectedSimulated: "0.0.4-rc2",
|
|
||||||
expectedAgent: "openclaw",
|
|
||||||
});
|
|
||||||
|
|
||||||
const previewSkillDir = await prereleaseFixture("skills/openclaw-traffic-guardian", "0.0.1-preview", "preview-fixture");
|
|
||||||
await runSimulation({
|
|
||||||
skillDir: previewSkillDir,
|
|
||||||
outputDir: path.join(tempRoot, "preview"),
|
|
||||||
expectedOriginal: "0.0.1-preview",
|
|
||||||
expectedSimulated: "0.0.1-preview1",
|
|
||||||
expectedAgent: "openclaw",
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
await rm(tempRoot, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
import assert from "node:assert/strict";
|
|
||||||
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
||||||
import { tmpdir } from "node:os";
|
|
||||||
import path from "node:path";
|
|
||||||
import { spawnSync } from "node:child_process";
|
|
||||||
|
|
||||||
const outputDir = await mkdtemp(path.join(tmpdir(), "clawsec-trust-packet-"));
|
|
||||||
|
|
||||||
function runTrustPacket(skillDir, targetDir, tag) {
|
|
||||||
return spawnSync(
|
|
||||||
process.execPath,
|
|
||||||
[
|
|
||||||
"scripts/ci/generate_skill_release_trust_packet.mjs",
|
|
||||||
skillDir,
|
|
||||||
targetDir,
|
|
||||||
"--repository",
|
|
||||||
"prompt-security/clawsec",
|
|
||||||
"--tag",
|
|
||||||
tag,
|
|
||||||
"--source-ref",
|
|
||||||
"main",
|
|
||||||
],
|
|
||||||
{ encoding: "utf8" },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = runTrustPacket("skills/clawsec-suite", outputDir, "clawsec-suite-v0.1.10");
|
|
||||||
|
|
||||||
assert.equal(
|
|
||||||
result.status,
|
|
||||||
0,
|
|
||||||
`trust packet generator failed\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const skillCard = await readFile(path.join(outputDir, "skill-card.md"), "utf8");
|
|
||||||
const permissions = JSON.parse(await readFile(path.join(outputDir, "permissions.json"), "utf8"));
|
|
||||||
const install = await readFile(path.join(outputDir, "install.md"), "utf8");
|
|
||||||
|
|
||||||
assert.match(skillCard, /^# Skill Card/m);
|
|
||||||
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\.10/);
|
|
||||||
|
|
||||||
assert.equal(permissions.skill, "clawsec-suite");
|
|
||||||
assert.equal(permissions.version, "0.1.10");
|
|
||||||
assert.equal(permissions.platform, "openclaw");
|
|
||||||
assert.deepEqual(
|
|
||||||
permissions.required_binaries,
|
|
||||||
["node", "npx", "openclaw", "curl", "jq", "shasum", "openssl", "unzip"],
|
|
||||||
);
|
|
||||||
assert.match(permissions.network_egress, /signed advisory feed/);
|
|
||||||
assert.match(permissions.persistence, /OpenClaw advisory hook/);
|
|
||||||
assert.ok(Array.isArray(permissions.operator_review));
|
|
||||||
assert.ok(permissions.operator_review.length > 0);
|
|
||||||
|
|
||||||
assert.match(install, /npx skills add prompt-security\/clawsec --skill clawsec-suite --agent openclaw --global --yes/);
|
|
||||||
assert.match(install, /npx skills update clawsec-suite/);
|
|
||||||
|
|
||||||
const hermesOutputDir = path.join(outputDir, "hermes");
|
|
||||||
const hermesResult = runTrustPacket(
|
|
||||||
"skills/hermes-attestation-guardian",
|
|
||||||
hermesOutputDir,
|
|
||||||
"hermes-attestation-guardian-v0.1.4",
|
|
||||||
);
|
|
||||||
assert.equal(
|
|
||||||
hermesResult.status,
|
|
||||||
0,
|
|
||||||
`Hermes trust packet generator failed\nstdout:\n${hermesResult.stdout}\nstderr:\n${hermesResult.stderr}`,
|
|
||||||
);
|
|
||||||
const hermesInstall = await readFile(path.join(hermesOutputDir, "install.md"), "utf8");
|
|
||||||
assert.match(
|
|
||||||
hermesInstall,
|
|
||||||
/npx skills add prompt-security\/clawsec --skill hermes-attestation-guardian --agent hermes-agent --global --yes/,
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
await rm(outputDir, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.0.4] - 2026-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
- Marked the release helper with top-level internal metadata so compatible installers can hide it from normal agent-facing discovery.
|
|
||||||
|
|
||||||
## [0.0.3] - 2026-05-14
|
## [0.0.3] - 2026-05-14
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
# Claw Release
|
|
||||||
|
|
||||||
Release automation for Claw skills and website. Guides through version bumping, tagging, and release verification.
|
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill claw-release -a openclaw -y
|
|
||||||
```
|
|
||||||
@@ -1,14 +1,9 @@
|
|||||||
---
|
---
|
||||||
name: claw-release
|
name: claw-release
|
||||||
version: 0.0.4
|
version: 0.0.3
|
||||||
description: Release automation for Claw skills and website. Guides through version bumping, tagging, and release verification.
|
description: Release automation for Claw skills and website. Guides through version bumping, tagging, and release verification.
|
||||||
homepage: https://clawsec.prompt.security
|
homepage: https://clawsec.prompt.security
|
||||||
metadata:
|
metadata: {"openclaw":{"emoji":"🚀","category":"utility","internal":true}}
|
||||||
internal: true
|
|
||||||
openclaw:
|
|
||||||
emoji: "🚀"
|
|
||||||
category: "utility"
|
|
||||||
internal: true
|
|
||||||
clawdis:
|
clawdis:
|
||||||
emoji: "🚀"
|
emoji: "🚀"
|
||||||
requires:
|
requires:
|
||||||
@@ -23,14 +18,6 @@ Internal tool for releasing skills and managing the ClawSec catalog.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill claw-release -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Operational Notes
|
## Operational Notes
|
||||||
|
|
||||||
- Internal maintainer workflow only.
|
- Internal maintainer workflow only.
|
||||||
@@ -39,6 +26,7 @@ npx skills add prompt-security/clawsec --skill claw-release -a openclaw -y
|
|||||||
- Side effects: creates commits, tags, pushes to remote, and publishes GitHub Releases
|
- Side effects: creates commits, tags, pushes to remote, and publishes GitHub Releases
|
||||||
- Trust model: run only from a trusted checkout with a clean working tree and maintainer approval
|
- Trust model: run only from a trusted checkout with a clean working tree and maintainer approval
|
||||||
|
|
||||||
|
|
||||||
## Release Artifact Verification
|
## Release Artifact Verification
|
||||||
|
|
||||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||||
@@ -47,7 +35,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SKILL_NAME="claw-release"
|
SKILL_NAME="claw-release"
|
||||||
VERSION="0.0.4"
|
VERSION="0.0.3"
|
||||||
REPO="prompt-security/clawsec"
|
REPO="prompt-security/clawsec"
|
||||||
TAG="${SKILL_NAME}-v${VERSION}"
|
TAG="${SKILL_NAME}-v${VERSION}"
|
||||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claw-release",
|
"name": "claw-release",
|
||||||
"version": "0.0.4",
|
"version": "0.0.3",
|
||||||
"description": "Release automation for Claw skills and website. Guides through version bumping, tagging, and release verification.",
|
"description": "Release automation for Claw skills and website. Guides through version bumping, tagging, and release verification.",
|
||||||
"author": "prompt-security",
|
"author": "prompt-security",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.0.6] - 2026-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
|
|
||||||
## [0.0.5] - 2026-06-07
|
## [0.0.5] - 2026-06-07
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|||||||
@@ -2,14 +2,6 @@
|
|||||||
|
|
||||||
A `clawsec-suite` companion skill that adds a standalone reputation gate before guarded installs.
|
A `clawsec-suite` companion skill that adds a standalone reputation gate before guarded installs.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawsec-clawhub-checker -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Operational Notes
|
## Operational Notes
|
||||||
|
|
||||||
- Required runtime: `node`, `clawhub`, `openclaw`
|
- Required runtime: `node`, `clawhub`, `openclaw`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: clawsec-clawhub-checker
|
name: clawsec-clawhub-checker
|
||||||
version: 0.0.6
|
version: 0.0.5
|
||||||
description: ClawHub reputation checker for clawsec-suite. Adds a standalone reputation gate before guarded skill installation.
|
description: ClawHub reputation checker for clawsec-suite. Adds a standalone reputation gate before guarded skill installation.
|
||||||
homepage: https://clawsec.prompt.security
|
homepage: https://clawsec.prompt.security
|
||||||
clawdis:
|
clawdis:
|
||||||
@@ -14,14 +14,6 @@ clawdis:
|
|||||||
|
|
||||||
Adds a reputation gate on top of the `clawsec-suite` guarded installer.
|
Adds a reputation gate on top of the `clawsec-suite` guarded installer.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawsec-clawhub-checker -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Operational Notes
|
## Operational Notes
|
||||||
|
|
||||||
- Required runtime: `node`, `clawhub`, `openclaw`
|
- Required runtime: `node`, `clawhub`, `openclaw`
|
||||||
@@ -53,6 +45,7 @@ Optional preflight check (validates local paths and prints recommended command):
|
|||||||
node ~/.openclaw/skills/clawsec-clawhub-checker/scripts/setup_reputation_hook.mjs
|
node ~/.openclaw/skills/clawsec-clawhub-checker/scripts/setup_reputation_hook.mjs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Release Artifact Verification
|
## Release Artifact Verification
|
||||||
|
|
||||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clawsec-clawhub-checker",
|
"name": "clawsec-clawhub-checker",
|
||||||
"version": "0.0.6",
|
"version": "0.0.5",
|
||||||
"description": "ClawHub reputation checker for clawsec-suite. Adds a standalone reputation gate before guarded skill installation.",
|
"description": "ClawHub reputation checker for clawsec-suite. Adds a standalone reputation gate before guarded skill installation.",
|
||||||
"author": "abutbul",
|
"author": "abutbul",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.0.9] - 2026-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
|
|
||||||
## [0.0.8] - 2026-05-24
|
## [0.0.8] - 2026-05-24
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -2,14 +2,6 @@
|
|||||||
|
|
||||||
Security advisory feed monitoring for AI agents. Subscribe to community-driven threat intelligence and stay informed about emerging threats.
|
Security advisory feed monitoring for AI agents. Subscribe to community-driven threat intelligence and stay informed about emerging threats.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawsec-feed -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Operational Notes
|
## Operational Notes
|
||||||
|
|
||||||
- Required runtime for standalone installation: `bash`, `curl`, `jq`, `shasum`, `unzip`
|
- Required runtime for standalone installation: `bash`, `curl`, `jq`, `shasum`, `unzip`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: clawsec-feed
|
name: clawsec-feed
|
||||||
version: 0.0.9
|
version: 0.0.8
|
||||||
description: Security advisory feed package for OpenClaw-related threats and vulnerabilities. The upstream feed is updated daily; local automation is handled by clawsec-suite or the operator.
|
description: Security advisory feed package for OpenClaw-related threats and vulnerabilities. The upstream feed is updated daily; local automation is handled by clawsec-suite or the operator.
|
||||||
homepage: https://clawsec.prompt.security
|
homepage: https://clawsec.prompt.security
|
||||||
metadata: {"openclaw":{"emoji":"📡","category":"security"}}
|
metadata: {"openclaw":{"emoji":"📡","category":"security"}}
|
||||||
@@ -16,14 +16,6 @@ Security advisory feed monitoring for AI agents. Subscribe to community-driven t
|
|||||||
|
|
||||||
The default `feed.json` is the consolidated agent feed. It includes NVD CVEs, approved community advisories, and provisional GitHub Security Advisories that do not have CVE IDs yet.
|
The default `feed.json` is the consolidated agent feed. It includes NVD CVEs, approved community advisories, and provisional GitHub Security Advisories that do not have CVE IDs yet.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawsec-feed -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Operational Notes
|
## Operational Notes
|
||||||
|
|
||||||
- Required runtime for standalone installation: `bash`, `curl`, `jq`, `shasum`, `unzip`
|
- Required runtime for standalone installation: `bash`, `curl`, `jq`, `shasum`, `unzip`
|
||||||
@@ -74,6 +66,7 @@ Standalone installation is a network download workflow. Verify the release sourc
|
|||||||
Installation Steps:
|
Installation Steps:
|
||||||
Agents should review scripts before execution!
|
Agents should review scripts before execution!
|
||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Get latest clawsec-feed release tag
|
# Get latest clawsec-feed release tag
|
||||||
LATEST_TAG=$(curl -sSL https://api.github.com/repos/prompt-security/ClawSec/releases | \
|
LATEST_TAG=$(curl -sSL https://api.github.com/repos/prompt-security/ClawSec/releases | \
|
||||||
@@ -88,6 +81,7 @@ Once you have this skill file, proceed to **[Deploy ClawSec Feed](#deploy-clawse
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
## Release Artifact Verification
|
## Release Artifact Verification
|
||||||
|
|
||||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||||
@@ -96,7 +90,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SKILL_NAME="clawsec-feed"
|
SKILL_NAME="clawsec-feed"
|
||||||
VERSION="0.0.9"
|
VERSION="0.0.8"
|
||||||
REPO="prompt-security/clawsec"
|
REPO="prompt-security/clawsec"
|
||||||
TAG="${SKILL_NAME}-v${VERSION}"
|
TAG="${SKILL_NAME}-v${VERSION}"
|
||||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||||
|
|||||||
+1519
-1211
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
|||||||
jPrlTYwicRwoQgTs5Rk3Y3g6Lz78jNRs9ZNf0R09M4jkJokZENxfvhvHphI9MH4u+7wv0sFZ+yZbQtJ42y+hCQ==
|
v+PiWmjIkY6zdIyI9xJX0l0aTy0Azp1+LoZR6qaiDZJnXFuSBX4Sw/x5tMdTb0xSbqdDTJOZwwWI8coPVepzBw==
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clawsec-feed",
|
"name": "clawsec-feed",
|
||||||
"version": "0.0.9",
|
"version": "0.0.8",
|
||||||
"description": "Security advisory feed monitoring for AI agents. Subscribe to community-driven threat intelligence.",
|
"description": "Security advisory feed monitoring for AI agents. Subscribe to community-driven threat intelligence.",
|
||||||
"author": "prompt-security",
|
"author": "prompt-security",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.0.8] - 2026-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
|
|
||||||
## [0.0.7] - 2026-06-07
|
## [0.0.7] - 2026-06-07
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|||||||
@@ -2,14 +2,6 @@
|
|||||||
|
|
||||||
ClawSec now supports NanoClaw, a containerized WhatsApp bot powered by Claude agents.
|
ClawSec now supports NanoClaw, a containerized WhatsApp bot powered by Claude agents.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawsec-nanoclaw -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## What Changed
|
## What Changed
|
||||||
|
|
||||||
### Advisory Feed Monitoring
|
### Advisory Feed Monitoring
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: clawsec-nanoclaw
|
name: clawsec-nanoclaw
|
||||||
version: 0.0.8
|
version: 0.0.7
|
||||||
description: Use when checking for security vulnerabilities in NanoClaw skills, before installing new skills, or when asked about security advisories affecting the bot
|
description: Use when checking for security vulnerabilities in NanoClaw skills, before installing new skills, or when asked about security advisories affecting the bot
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -8,14 +8,6 @@ description: Use when checking for security vulnerabilities in NanoClaw skills,
|
|||||||
|
|
||||||
Security advisory monitoring that protects your WhatsApp bot from known vulnerabilities in skills and dependencies.
|
Security advisory monitoring that protects your WhatsApp bot from known vulnerabilities in skills and dependencies.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawsec-nanoclaw -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
ClawSec provides MCP tools that check installed skills against a curated feed of security advisories. It prevents installation of vulnerable skills, includes exploitability context for triage, and alerts you to issues in existing ones.
|
ClawSec provides MCP tools that check installed skills against a curated feed of security advisories. It prevents installation of vulnerable skills, includes exploitability context for triage, and alerts you to issues in existing ones.
|
||||||
@@ -209,6 +201,7 @@ See [INSTALL.md](./INSTALL.md) for setup and [docs/](./docs/) for advanced usage
|
|||||||
- Provides actionable remediation steps
|
- Provides actionable remediation steps
|
||||||
- Zero false positives (curated feed only)
|
- Zero false positives (curated feed only)
|
||||||
|
|
||||||
|
|
||||||
## Release Artifact Verification
|
## Release Artifact Verification
|
||||||
|
|
||||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clawsec-nanoclaw",
|
"name": "clawsec-nanoclaw",
|
||||||
"version": "0.0.8",
|
"version": "0.0.7",
|
||||||
"description": "ClawSec security suite for NanoClaw - Advisory feed monitoring, MCP tools for vulnerability checking, and Ed25519 signature verification for containerized WhatsApp bot agents",
|
"description": "ClawSec security suite for NanoClaw - Advisory feed monitoring, MCP tools for vulnerability checking, and Ed25519 signature verification for containerized WhatsApp bot agents",
|
||||||
"author": "prompt-security",
|
"author": "prompt-security",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.0.5] - 2026-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
|
|
||||||
## [0.0.4] - 2026-06-07
|
## [0.0.4] - 2026-06-07
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
# Clawsec Scanner
|
|
||||||
|
|
||||||
Automated vulnerability scanner for agent platforms. Performs dependency scanning (npm audit, pip-audit), multi-database CVE lookup (OSV, NVD, GitHub Advisory), SAST analysis (Semgrep, Bandit), and agent-specific static hook inspection for OpenClaw hooks.
|
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawsec-scanner -a openclaw -y
|
|
||||||
```
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: clawsec-scanner
|
name: clawsec-scanner
|
||||||
version: 0.0.5
|
version: 0.0.4
|
||||||
description: Automated vulnerability scanner for agent platforms. Performs dependency scanning (npm audit, pip-audit), multi-database CVE lookup (OSV, NVD, GitHub Advisory), SAST analysis (Semgrep, Bandit), and agent-specific static hook inspection for OpenClaw hooks.
|
description: Automated vulnerability scanner for agent platforms. Performs dependency scanning (npm audit, pip-audit), multi-database CVE lookup (OSV, NVD, GitHub Advisory), SAST analysis (Semgrep, Bandit), and agent-specific static hook inspection for OpenClaw hooks.
|
||||||
homepage: https://clawsec.prompt.security
|
homepage: https://clawsec.prompt.security
|
||||||
clawdis:
|
clawdis:
|
||||||
@@ -20,14 +20,6 @@ Comprehensive security scanner for agent platforms that automates vulnerability
|
|||||||
- **Unified Reporting**: Consolidated vulnerability reports with severity classification and remediation guidance
|
- **Unified Reporting**: Consolidated vulnerability reports with severity classification and remediation guidance
|
||||||
- **Continuous Monitoring**: OpenClaw hook integration for automated periodic scanning
|
- **Continuous Monitoring**: OpenClaw hook integration for automated periodic scanning
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawsec-scanner -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### Multi-Engine Scanning
|
### Multi-Engine Scanning
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clawsec-scanner",
|
"name": "clawsec-scanner",
|
||||||
"version": "0.0.5",
|
"version": "0.0.4",
|
||||||
"description": "Automated vulnerability scanner for agent platforms. Performs dependency scanning (npm audit, pip-audit), multi-database CVE lookup (OSV, NVD, GitHub Advisory), SAST analysis (Semgrep, Bandit), and agent-specific static hook inspection for OpenClaw hooks.",
|
"description": "Automated vulnerability scanner for agent platforms. Performs dependency scanning (npm audit, pip-audit), multi-database CVE lookup (OSV, NVD, GitHub Advisory), SAST analysis (Semgrep, Bandit), and agent-specific static hook inspection for OpenClaw hooks.",
|
||||||
"author": "prompt-security",
|
"author": "prompt-security",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.1.10] - 2026-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
|
|
||||||
All notable changes to the ClawSec Suite will be documented in this file.
|
All notable changes to the ClawSec Suite will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
# Clawsec Suite
|
|
||||||
|
|
||||||
ClawSec suite manager with embedded advisory-feed monitoring, cryptographic signature verification, approval-gated malicious-skill response, and guided setup for additional security skills.
|
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawsec-suite -a openclaw -y
|
|
||||||
```
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: clawsec-suite
|
name: clawsec-suite
|
||||||
version: 0.1.10
|
version: 0.1.9
|
||||||
description: ClawSec suite manager with embedded advisory-feed monitoring, cryptographic signature verification, approval-gated malicious-skill response, and guided setup for additional security skills.
|
description: ClawSec suite manager with embedded advisory-feed monitoring, cryptographic signature verification, approval-gated malicious-skill response, and guided setup for additional security skills.
|
||||||
homepage: https://clawsec.prompt.security
|
homepage: https://clawsec.prompt.security
|
||||||
clawdis:
|
clawdis:
|
||||||
@@ -11,14 +11,6 @@ clawdis:
|
|||||||
|
|
||||||
# ClawSec Suite
|
# ClawSec Suite
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawsec-suite -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Operational Notes
|
## Operational Notes
|
||||||
|
|
||||||
- Required runtime: `node`, `npx`, `openclaw`, `curl`, `jq`, `shasum`, `openssl`, `unzip`
|
- Required runtime: `node`, `npx`, `openclaw`, `curl`, `jq`, `shasum`, `openssl`, `unzip`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clawsec-suite",
|
"name": "clawsec-suite",
|
||||||
"version": "0.1.10",
|
"version": "0.1.9",
|
||||||
"description": "ClawSec suite manager with embedded advisory-feed monitoring, cryptographic signature verification, approval-gated malicious-skill response, and guided setup for additional security skills.",
|
"description": "ClawSec suite manager with embedded advisory-feed monitoring, cryptographic signature verification, approval-gated malicious-skill response, and guided setup for additional security skills.",
|
||||||
"author": "prompt-security",
|
"author": "prompt-security",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
@@ -1,15 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.0.7] - 2026-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
- Marked Clawtributor as a harness-neutral global skill for OpenClaw, NanoClaw, Hermes, and Picoclaw installer grouping.
|
|
||||||
- Removed OpenClaw CLI as a declared runtime requirement because reporting is manual, approval-gated, and not tied to an OpenClaw command path.
|
|
||||||
- Documented Vercel skills installer usage alongside the OpenClaw/ClawHub install path.
|
|
||||||
- Moved local report/state guidance to `~/.clawsec/clawtributor/`.
|
|
||||||
|
|
||||||
## [0.0.6] - 2026-05-14
|
## [0.0.6] - 2026-05-14
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|||||||
@@ -2,20 +2,6 @@
|
|||||||
|
|
||||||
Community incident reporting for AI agents.
|
Community incident reporting for AI agents.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawtributor -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
Codex install is also supported:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawtributor -a codex -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Operational Notes
|
## Operational Notes
|
||||||
|
|
||||||
- Reporting is opt-in for every submission
|
- Reporting is opt-in for every submission
|
||||||
@@ -31,14 +17,6 @@ npx skills add prompt-security/clawsec --skill clawtributor -a codex -y
|
|||||||
|
|
||||||
## Quick Install
|
## Quick Install
|
||||||
|
|
||||||
Vercel skills installer:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawtributor -a codex -y
|
|
||||||
```
|
|
||||||
|
|
||||||
OpenClaw/ClawHub:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx clawhub@latest install clawtributor
|
npx clawhub@latest install clawtributor
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,44 +1,23 @@
|
|||||||
---
|
---
|
||||||
name: clawtributor
|
name: clawtributor
|
||||||
version: 0.0.7
|
version: 0.0.6
|
||||||
description: Harness-neutral community incident reporting for AI agents. Contribute to collective security by reporting threats.
|
description: Community incident reporting for AI agents. Contribute to collective security by reporting threats.
|
||||||
homepage: https://clawsec.prompt.security
|
homepage: https://clawsec.prompt.security
|
||||||
platforms:
|
metadata: {"openclaw":{"emoji":"🤝","category":"security"}}
|
||||||
- openclaw
|
|
||||||
- nanoclaw
|
|
||||||
- hermes
|
|
||||||
- picoclaw
|
|
||||||
metadata:
|
|
||||||
global: true
|
|
||||||
openclaw:
|
|
||||||
emoji: "🤝"
|
|
||||||
category: "security"
|
|
||||||
clawdis:
|
clawdis:
|
||||||
emoji: "🤝"
|
emoji: "🤝"
|
||||||
|
requires:
|
||||||
|
bins: [openclaw]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Clawtributor 🤝
|
# Clawtributor 🤝
|
||||||
|
|
||||||
Community incident reporting for AI agents. Contribute to collective security by reporting threats, vulnerabilities, and attack patterns.
|
Community incident reporting for AI agents. Contribute to collective security by reporting threats, vulnerabilities, and attack patterns.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawtributor -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
Codex install is also supported:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawtributor -a codex -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Operational Notes
|
## Operational Notes
|
||||||
|
|
||||||
- Recommended install path: harness-native skills installer; use ClawHub for OpenClaw/ClawHub environments (`npx clawhub@latest install clawtributor`)
|
- Recommended install path: ClawHub registry (`npx clawhub@latest install clawtributor`)
|
||||||
- Side effects: creates local report/state files under `~/.clawsec/clawtributor/`
|
- Side effects: creates local report/state files under `~/.openclaw/`
|
||||||
- Network behavior: none unless the user explicitly approves manual submission
|
- Network behavior: none unless the user explicitly approves manual submission
|
||||||
- Trust model: reporting is opt-in for every submission; sanitize evidence before it leaves the host
|
- Trust model: reporting is opt-in for every submission; sanitize evidence before it leaves the host
|
||||||
|
|
||||||
@@ -48,13 +27,7 @@ npx skills add prompt-security/clawsec --skill clawtributor -a codex -y
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Install with your harness-native skills installer. For the Vercel skills installer:
|
Install from the registry:
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill clawtributor -a codex -y
|
|
||||||
```
|
|
||||||
|
|
||||||
For OpenClaw/ClawHub environments, install from the registry:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx clawhub@latest install clawtributor
|
npx clawhub@latest install clawtributor
|
||||||
@@ -71,6 +44,7 @@ I will keep reports local unless you explicitly approve submission.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
## Release Artifact Verification
|
## Release Artifact Verification
|
||||||
|
|
||||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||||
@@ -79,7 +53,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SKILL_NAME="clawtributor"
|
SKILL_NAME="clawtributor"
|
||||||
VERSION="0.0.7"
|
VERSION="0.0.6"
|
||||||
REPO="prompt-security/clawsec"
|
REPO="prompt-security/clawsec"
|
||||||
TAG="${SKILL_NAME}-v${VERSION}"
|
TAG="${SKILL_NAME}-v${VERSION}"
|
||||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||||
@@ -259,7 +233,7 @@ See [reporting.md](./reporting.md) for the full report format and submission gui
|
|||||||
|
|
||||||
### Step 1: Prepare report locally
|
### Step 1: Prepare report locally
|
||||||
|
|
||||||
- Save the report JSON under `~/.clawsec/clawtributor/reports/`
|
- Save the report JSON under `~/.openclaw/clawtributor-reports/`
|
||||||
- Keep file permissions private (`chmod 600`)
|
- Keep file permissions private (`chmod 600`)
|
||||||
- Confirm the report is sanitized before sharing
|
- Confirm the report is sanitized before sharing
|
||||||
|
|
||||||
@@ -310,7 +284,7 @@ DO NOT include:
|
|||||||
|
|
||||||
## State Tracking
|
## State Tracking
|
||||||
|
|
||||||
Track submitted reports in `~/.clawsec/clawtributor/state.json`.
|
Track submitted reports in `~/.openclaw/clawtributor-state.json`.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "clawtributor",
|
"name": "clawtributor",
|
||||||
"version": "0.0.7",
|
"version": "0.0.6",
|
||||||
"description": "Harness-neutral community incident reporting for AI agents. Contribute to collective security by reporting threats.",
|
"description": "Community incident reporting for AI agents. Contribute to collective security by reporting threats.",
|
||||||
"author": "prompt-security",
|
"author": "prompt-security",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"homepage": "https://clawsec.prompt.security",
|
"homepage": "https://clawsec.prompt.security",
|
||||||
"platforms": [
|
|
||||||
"openclaw",
|
|
||||||
"nanoclaw",
|
|
||||||
"hermes",
|
|
||||||
"picoclaw"
|
|
||||||
],
|
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"security",
|
"security",
|
||||||
"reporting",
|
"reporting",
|
||||||
"community",
|
"community",
|
||||||
"agents",
|
"agents",
|
||||||
"ai",
|
"ai",
|
||||||
"global",
|
|
||||||
"harness-neutral",
|
|
||||||
"vulnerability",
|
"vulnerability",
|
||||||
"contribution"
|
"contribution"
|
||||||
],
|
],
|
||||||
@@ -44,6 +36,11 @@
|
|||||||
"openclaw": {
|
"openclaw": {
|
||||||
"emoji": "🤝",
|
"emoji": "🤝",
|
||||||
"category": "security",
|
"category": "security",
|
||||||
|
"requires": {
|
||||||
|
"bins": [
|
||||||
|
"openclaw"
|
||||||
|
]
|
||||||
|
},
|
||||||
"execution": {
|
"execution": {
|
||||||
"always": false,
|
"always": false,
|
||||||
"persistence": "Stores local report/state files only; no recurring automation is created by default.",
|
"persistence": "Stores local report/state files only; no recurring automation is created by default.",
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.1.4] - 2026-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
|
|
||||||
## [0.1.3] - 2026-05-24
|
## [0.1.3] - 2026-05-24
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -4,14 +4,6 @@ Hermes-only attestation, advisory verification, and guarded verification workflo
|
|||||||
|
|
||||||
Status: implemented (v0.1.0), Hermes-only.
|
Status: implemented (v0.1.0), Hermes-only.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill hermes-attestation-guardian -a hermes-agent -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Capabilities
|
## Capabilities
|
||||||
|
|
||||||
This skill now covers the full Hermes-side capability set expected from the clawsec-suite parity workstream:
|
This skill now covers the full Hermes-side capability set expected from the clawsec-suite parity workstream:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: hermes-attestation-guardian
|
name: hermes-attestation-guardian
|
||||||
version: 0.1.4
|
version: 0.1.3
|
||||||
description: Hermes-only runtime security attestation and drift detection skill for operator-managed Hermes infrastructure.
|
description: Hermes-only runtime security attestation and drift detection skill for operator-managed Hermes infrastructure.
|
||||||
homepage: https://clawsec.prompt.security
|
homepage: https://clawsec.prompt.security
|
||||||
hermes:
|
hermes:
|
||||||
@@ -15,13 +15,6 @@ IMPORTANT SCOPE:
|
|||||||
- This skill targets Hermes infrastructure only (CLI/Gateway/profile-managed deployments).
|
- This skill targets Hermes infrastructure only (CLI/Gateway/profile-managed deployments).
|
||||||
- This skill is not an OpenClaw runtime hook package.
|
- This skill is not an OpenClaw runtime hook package.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill hermes-attestation-guardian -a hermes-agent -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Release Artifact Verification
|
## Release Artifact Verification
|
||||||
|
|
||||||
@@ -31,7 +24,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SKILL_NAME="hermes-attestation-guardian"
|
SKILL_NAME="hermes-attestation-guardian"
|
||||||
VERSION="0.1.4"
|
VERSION="0.1.3"
|
||||||
REPO="prompt-security/clawsec"
|
REPO="prompt-security/clawsec"
|
||||||
TAG="${SKILL_NAME}-v${VERSION}"
|
TAG="${SKILL_NAME}-v${VERSION}"
|
||||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hermes-attestation-guardian",
|
"name": "hermes-attestation-guardian",
|
||||||
"version": "0.1.4",
|
"version": "0.1.3",
|
||||||
"description": "Hermes-only runtime security attestation and drift detection skill. Generates deterministic posture artifacts, verifies integrity fail-closed, and classifies baseline drift severity.",
|
"description": "Hermes-only runtime security attestation and drift detection skill. Generates deterministic posture artifacts, verifies integrity fail-closed, and classifies baseline drift severity.",
|
||||||
"author": "prompt-security",
|
"author": "prompt-security",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.0.1-beta3] - 2026-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
|
|
||||||
## [0.0.1-beta2] - 2026-05-13
|
## [0.0.1-beta2] - 2026-05-13
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|||||||
@@ -4,14 +4,6 @@ Baseline skill for Hermes runtime traffic monitoring.
|
|||||||
|
|
||||||
This package is intentionally a spec scaffold. Builders should add the Hermes-specific monitor implementation here while preserving the safety contract in `SKILL.md` and `SPEC.md`.
|
This package is intentionally a spec scaffold. Builders should add the Hermes-specific monitor implementation here while preserving the safety contract in `SKILL.md` and `SPEC.md`.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill hermes-traffic-guardian -a hermes-agent -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Intended Capability
|
## Intended Capability
|
||||||
|
|
||||||
- detect outbound secret exfiltration in Hermes HTTP/HTTPS traffic
|
- detect outbound secret exfiltration in Hermes HTTP/HTTPS traffic
|
||||||
@@ -23,3 +15,4 @@ npx skills add prompt-security/clawsec --skill hermes-traffic-guardian -a hermes
|
|||||||
## Builder Notes
|
## Builder Notes
|
||||||
|
|
||||||
Keep runtime ownership in this skill. `hermes-attestation-guardian` should only attest this skill's state, config, and output fingerprints.
|
Keep runtime ownership in this skill. `hermes-attestation-guardian` should only attest this skill's state, config, and output fingerprints.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: hermes-traffic-guardian
|
name: hermes-traffic-guardian
|
||||||
version: 0.0.1-beta3
|
version: 0.0.1-beta2
|
||||||
description: Hermes runtime traffic monitoring baseline for opt-in proxy inspection, egress detection, and attestation-aware traffic posture.
|
description: Hermes runtime traffic monitoring baseline for opt-in proxy inspection, egress detection, and attestation-aware traffic posture.
|
||||||
homepage: https://clawsec.prompt.security
|
homepage: https://clawsec.prompt.security
|
||||||
author: prompt-security
|
author: prompt-security
|
||||||
@@ -15,13 +15,6 @@ hermes:
|
|||||||
|
|
||||||
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill hermes-traffic-guardian -a hermes-agent -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Release Artifact Verification
|
## Release Artifact Verification
|
||||||
|
|
||||||
@@ -31,7 +24,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SKILL_NAME="hermes-traffic-guardian"
|
SKILL_NAME="hermes-traffic-guardian"
|
||||||
VERSION="0.0.1-beta3"
|
VERSION="0.0.1-beta2"
|
||||||
REPO="prompt-security/clawsec"
|
REPO="prompt-security/clawsec"
|
||||||
TAG="${SKILL_NAME}-v${VERSION}"
|
TAG="${SKILL_NAME}-v${VERSION}"
|
||||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||||
@@ -152,3 +145,4 @@ Read `SPEC.md` before implementing. Use the placeholder folders as follows:
|
|||||||
- default blocking
|
- default blocking
|
||||||
- sending traffic to external services
|
- sending traffic to external services
|
||||||
- collecting full request/response bodies
|
- collecting full request/response bodies
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hermes-traffic-guardian",
|
"name": "hermes-traffic-guardian",
|
||||||
"version": "0.0.1-beta3",
|
"version": "0.0.1-beta2",
|
||||||
"description": "Hermes runtime traffic monitoring baseline for opt-in proxy inspection, egress detection, and attestation-aware traffic posture.",
|
"description": "Hermes runtime traffic monitoring baseline for opt-in proxy inspection, egress detection, and attestation-aware traffic posture.",
|
||||||
"author": "prompt-security",
|
"author": "prompt-security",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.0.1-beta3] - 2026-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
|
|
||||||
## [0.0.1-beta2] - 2026-05-13
|
## [0.0.1-beta2] - 2026-05-13
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|||||||
@@ -4,14 +4,6 @@ Baseline skill for NanoClaw runtime traffic monitoring.
|
|||||||
|
|
||||||
This package is intentionally a spec scaffold. Builders should add the NanoClaw-specific host-service, IPC, and MCP implementation here while preserving the safety contract in `SKILL.md` and `SPEC.md`.
|
This package is intentionally a spec scaffold. Builders should add the NanoClaw-specific host-service, IPC, and MCP implementation here while preserving the safety contract in `SKILL.md` and `SPEC.md`.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill nanoclaw-traffic-guardian -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Intended Capability
|
## Intended Capability
|
||||||
|
|
||||||
- detect outbound secret exfiltration in NanoClaw host-managed traffic
|
- detect outbound secret exfiltration in NanoClaw host-managed traffic
|
||||||
@@ -23,3 +15,4 @@ npx skills add prompt-security/clawsec --skill nanoclaw-traffic-guardian -a open
|
|||||||
## Builder Notes
|
## Builder Notes
|
||||||
|
|
||||||
Follow the existing `clawsec-nanoclaw` pattern: host services own privileged operations, while MCP tools expose bounded requests and redacted responses.
|
Follow the existing `clawsec-nanoclaw` pattern: host services own privileged operations, while MCP tools expose bounded requests and redacted responses.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: nanoclaw-traffic-guardian
|
name: nanoclaw-traffic-guardian
|
||||||
version: 0.0.1-beta3
|
version: 0.0.1-beta2
|
||||||
description: NanoClaw runtime traffic monitoring baseline for host-side proxy inspection with container-safe MCP and IPC status surfaces.
|
description: NanoClaw runtime traffic monitoring baseline for host-side proxy inspection with container-safe MCP and IPC status surfaces.
|
||||||
homepage: https://clawsec.prompt.security
|
homepage: https://clawsec.prompt.security
|
||||||
author: prompt-security
|
author: prompt-security
|
||||||
@@ -14,13 +14,6 @@ nanoclaw:
|
|||||||
|
|
||||||
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill nanoclaw-traffic-guardian -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Release Artifact Verification
|
## Release Artifact Verification
|
||||||
|
|
||||||
@@ -30,7 +23,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SKILL_NAME="nanoclaw-traffic-guardian"
|
SKILL_NAME="nanoclaw-traffic-guardian"
|
||||||
VERSION="0.0.1-beta3"
|
VERSION="0.0.1-beta2"
|
||||||
REPO="prompt-security/clawsec"
|
REPO="prompt-security/clawsec"
|
||||||
TAG="${SKILL_NAME}-v${VERSION}"
|
TAG="${SKILL_NAME}-v${VERSION}"
|
||||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||||
@@ -153,3 +146,4 @@ Read `SPEC.md` before implementing. Use the placeholder folders as follows:
|
|||||||
- default blocking
|
- default blocking
|
||||||
- sending traffic to external services
|
- sending traffic to external services
|
||||||
- exposing raw request/response bodies to the container
|
- exposing raw request/response bodies to the container
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nanoclaw-traffic-guardian",
|
"name": "nanoclaw-traffic-guardian",
|
||||||
"version": "0.0.1-beta3",
|
"version": "0.0.1-beta2",
|
||||||
"description": "NanoClaw runtime traffic monitoring baseline for host-side proxy inspection with container-safe MCP and IPC status surfaces.",
|
"description": "NanoClaw runtime traffic monitoring baseline for host-side proxy inspection with container-safe MCP and IPC status surfaces.",
|
||||||
"author": "prompt-security",
|
"author": "prompt-security",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.1.7] - 2026-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
|
|
||||||
## [0.1.6] - 2026-05-16
|
## [0.1.6] - 2026-05-16
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -2,14 +2,6 @@
|
|||||||
|
|
||||||
Automated daily security audits for OpenClaw agents with DM delivery and optional email reporting.
|
Automated daily security audits for OpenClaw agents with DM delivery and optional email reporting.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill openclaw-audit-watchdog -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
The Audit Watchdog provides automated security monitoring for your OpenClaw agent deployments:
|
The Audit Watchdog provides automated security monitoring for your OpenClaw agent deployments:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: openclaw-audit-watchdog
|
name: openclaw-audit-watchdog
|
||||||
version: 0.1.7
|
version: 0.1.6
|
||||||
description: Automated daily security audits for OpenClaw agents with DM delivery and optional email reporting. Runs deep audits, creates or updates a recurring cron job, and sends formatted reports to configured recipients.
|
description: Automated daily security audits for OpenClaw agents with DM delivery and optional email reporting. Runs deep audits, creates or updates a recurring cron job, and sends formatted reports to configured recipients.
|
||||||
homepage: https://clawsec.prompt.security
|
homepage: https://clawsec.prompt.security
|
||||||
metadata:
|
metadata:
|
||||||
@@ -29,14 +29,6 @@ clawdis:
|
|||||||
|
|
||||||
# Prompt Security Audit (openclaw)
|
# Prompt Security Audit (openclaw)
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill openclaw-audit-watchdog -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation Options
|
## Installation Options
|
||||||
|
|
||||||
You can get openclaw-audit-watchdog in two ways:
|
You can get openclaw-audit-watchdog in two ways:
|
||||||
@@ -73,6 +65,7 @@ Continue below for standalone installation instructions.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
## Release Artifact Verification
|
## Release Artifact Verification
|
||||||
|
|
||||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||||
@@ -81,7 +74,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SKILL_NAME="openclaw-audit-watchdog"
|
SKILL_NAME="openclaw-audit-watchdog"
|
||||||
VERSION="0.1.7"
|
VERSION="0.1.6"
|
||||||
REPO="prompt-security/clawsec"
|
REPO="prompt-security/clawsec"
|
||||||
TAG="${SKILL_NAME}-v${VERSION}"
|
TAG="${SKILL_NAME}-v${VERSION}"
|
||||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "openclaw-audit-watchdog",
|
"name": "openclaw-audit-watchdog",
|
||||||
"version": "0.1.7",
|
"version": "0.1.6",
|
||||||
"description": "Automated daily security audits for OpenClaw agents with DM delivery and optional email reporting. Creates or updates an unattended cron job and sends formatted reports to configured recipients.",
|
"description": "Automated daily security audits for OpenClaw agents with DM delivery and optional email reporting. Creates or updates an unattended cron job and sends formatted reports to configured recipients.",
|
||||||
"author": "prompt-security",
|
"author": "prompt-security",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
@@ -1,15 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.0.1-beta3] - 2026-06-10
|
|
||||||
|
|
||||||
### Security
|
|
||||||
- Added the `POLICY_REVIEW` scope for approval-sensitive social-account mutation requests, contributed by @kriptoburak.
|
|
||||||
- Defined required JSONL metadata for social-account mutation findings, including source type, mutation category, approval-marker presence, and execution context.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Clarified that persistent social monitor and webhook configuration changes are review findings, while read-only social research should remain covered by no-false-positive tests.
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
|
|
||||||
## [0.0.1-beta2] - 2026-05-13
|
## [0.0.1-beta2] - 2026-05-13
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|||||||
@@ -4,19 +4,10 @@ Baseline skill for OpenClaw runtime traffic monitoring.
|
|||||||
|
|
||||||
This package is intentionally a spec scaffold. Builders should add the OpenClaw-specific monitor implementation here while preserving the safety contract in `SKILL.md` and `SPEC.md`.
|
This package is intentionally a spec scaffold. Builders should add the OpenClaw-specific monitor implementation here while preserving the safety contract in `SKILL.md` and `SPEC.md`.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill openclaw-traffic-guardian -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Intended Capability
|
## Intended Capability
|
||||||
|
|
||||||
- detect outbound secret exfiltration in agent HTTP/HTTPS traffic
|
- detect outbound secret exfiltration in agent HTTP/HTTPS traffic
|
||||||
- detect inbound command-injection and tool-abuse payloads
|
- detect inbound command-injection and tool-abuse payloads
|
||||||
- record operator-review findings for approval-sensitive social-account mutations
|
|
||||||
- write redacted local JSONL findings
|
- write redacted local JSONL findings
|
||||||
- provide explicit start, stop, status, and log-query commands
|
- provide explicit start, stop, status, and log-query commands
|
||||||
- integrate with `clawsec-suite` as an optional add-on
|
- integrate with `clawsec-suite` as an optional add-on
|
||||||
@@ -24,3 +15,4 @@ npx skills add prompt-security/clawsec --skill openclaw-traffic-guardian -a open
|
|||||||
## Builder Notes
|
## Builder Notes
|
||||||
|
|
||||||
Use `SPEC.md` as the implementation contract. Keep runtime changes opt-in and scoped to the OpenClaw process being monitored.
|
Use `SPEC.md` as the implementation contract. Keep runtime changes opt-in and scoped to the OpenClaw process being monitored.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: openclaw-traffic-guardian
|
name: openclaw-traffic-guardian
|
||||||
version: 0.0.1-beta3
|
version: 0.0.1-beta2
|
||||||
description: OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, inbound injection detection, and social-account policy review.
|
description: OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, and inbound injection detection.
|
||||||
homepage: https://clawsec.prompt.security
|
homepage: https://clawsec.prompt.security
|
||||||
author: prompt-security
|
author: prompt-security
|
||||||
license: AGPL-3.0-or-later
|
license: AGPL-3.0-or-later
|
||||||
@@ -15,13 +15,6 @@ clawdis:
|
|||||||
|
|
||||||
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill openclaw-traffic-guardian -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Release Artifact Verification
|
## Release Artifact Verification
|
||||||
|
|
||||||
@@ -31,7 +24,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SKILL_NAME="openclaw-traffic-guardian"
|
SKILL_NAME="openclaw-traffic-guardian"
|
||||||
VERSION="0.0.1-beta3"
|
VERSION="0.0.1-beta2"
|
||||||
REPO="prompt-security/clawsec"
|
REPO="prompt-security/clawsec"
|
||||||
TAG="${SKILL_NAME}-v${VERSION}"
|
TAG="${SKILL_NAME}-v${VERSION}"
|
||||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||||
@@ -110,7 +103,6 @@ Builders should use this skill as the OpenClaw landing zone for runtime traffic
|
|||||||
- optional HTTPS inspection with per-process CA trust
|
- optional HTTPS inspection with per-process CA trust
|
||||||
- outbound exfiltration detection
|
- outbound exfiltration detection
|
||||||
- inbound injection detection
|
- inbound injection detection
|
||||||
- approval-sensitive social-account mutation review
|
|
||||||
- redacted local threat logs
|
- redacted local threat logs
|
||||||
- optional OpenClaw hook/status integration
|
- optional OpenClaw hook/status integration
|
||||||
|
|
||||||
@@ -144,10 +136,8 @@ Read `SPEC.md` before implementing. Use the placeholder folders as follows:
|
|||||||
3. Scope proxy environment variables to the target OpenClaw process.
|
3. Scope proxy environment variables to the target OpenClaw process.
|
||||||
4. Inspect HTTP request/response text up to a bounded byte limit.
|
4. Inspect HTTP request/response text up to a bounded byte limit.
|
||||||
5. Support optional HTTPS MITM only when the operator supplies per-process trust configuration.
|
5. Support optional HTTPS MITM only when the operator supplies per-process trust configuration.
|
||||||
6. Flag requests matching `SPEC.md`'s Outbound POLICY_REVIEW cases as operator-review findings, including TweetClaw or other X/Twitter automation writes and scheduler/background-runner repeats without a fresh operator-approval marker.
|
6. Emit JSONL findings with redacted snippets.
|
||||||
7. Detect repeat/background-runner context from bounded request metadata such as paths, headers, user-agent, client context, tool invocation metadata, or scheduler identifiers.
|
7. Provide a `status` command that reports mode, listener, CA fingerprint if present, and last findings.
|
||||||
8. Emit JSONL findings with redacted snippets plus source type, mutation category, approval-marker presence, and direct-operator versus background-runner context.
|
|
||||||
9. Provide a `status` command that reports mode, listener, CA fingerprint if present, and last findings.
|
|
||||||
|
|
||||||
## Out of Scope for v0.0.1 Implementation
|
## Out of Scope for v0.0.1 Implementation
|
||||||
|
|
||||||
@@ -156,3 +146,4 @@ Read `SPEC.md` before implementing. Use the placeholder folders as follows:
|
|||||||
- default blocking
|
- default blocking
|
||||||
- sending traffic to external services
|
- sending traffic to external services
|
||||||
- collecting full request/response bodies
|
- collecting full request/response bodies
|
||||||
|
|
||||||
|
|||||||
@@ -45,24 +45,6 @@ Findings must be JSON objects with these fields:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`POLICY_REVIEW` findings must keep the same base schema and add these fields:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"threat_type": "POLICY_REVIEW",
|
|
||||||
"pattern": "social_account_mutation",
|
|
||||||
"source_type": "openclaw_tool_request",
|
|
||||||
"mutation_category": "post",
|
|
||||||
"approval_marker_present": false,
|
|
||||||
"execution_context": "background_runner"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- `source_type`: `http_request`, `openclaw_tool_request`, or `unknown`.
|
|
||||||
- `mutation_category`: `post`, `reply`, `repost`, `like`, `follow`, `unfollow`, `dm`, `media_upload`, `persistent_monitor`, `webhook_config`, `giveaway_draw`, or `other_social_account_mutation`.
|
|
||||||
- `approval_marker_present`: boolean; do not persist marker secrets or full approval tokens.
|
|
||||||
- `execution_context`: `direct_operator`, `scheduler`, `background_runner`, or `unknown`.
|
|
||||||
|
|
||||||
## Minimum Detection Set
|
## Minimum Detection Set
|
||||||
|
|
||||||
Outbound EXFIL:
|
Outbound EXFIL:
|
||||||
@@ -82,12 +64,6 @@ Inbound INJECTION:
|
|||||||
- destructive remove commands
|
- destructive remove commands
|
||||||
- SSH authorized-key injection shapes
|
- SSH authorized-key injection shapes
|
||||||
|
|
||||||
Outbound POLICY_REVIEW:
|
|
||||||
|
|
||||||
- social-account write requests such as post, reply, repost, like, follow, unfollow, DM, media upload, persistent monitor creation/update, webhook configuration changes, or giveaway draw actions
|
|
||||||
- OpenClaw plugin/tool requests that invoke TweetClaw or another X/Twitter automation plugin for account mutation
|
|
||||||
- scheduler or background-runner requests that would repeat social-account mutations without a fresh operator approval
|
|
||||||
|
|
||||||
## Safety Requirements
|
## Safety Requirements
|
||||||
|
|
||||||
- Default mode is detect-and-log.
|
- Default mode is detect-and-log.
|
||||||
@@ -96,7 +72,6 @@ Outbound POLICY_REVIEW:
|
|||||||
- Maximum scan bytes must be configurable and bounded.
|
- Maximum scan bytes must be configurable and bounded.
|
||||||
- CA trust must be per-process by default.
|
- CA trust must be per-process by default.
|
||||||
- System trust-store instructions must require explicit operator confirmation and must never run automatically.
|
- System trust-store instructions must require explicit operator confirmation and must never run automatically.
|
||||||
- POLICY_REVIEW findings must create an operator-review record only; they must not auto-block, auto-approve, or rewrite the requested action.
|
|
||||||
|
|
||||||
## Tests Required Before Release
|
## Tests Required Before Release
|
||||||
|
|
||||||
@@ -104,7 +79,7 @@ Outbound POLICY_REVIEW:
|
|||||||
- redaction tests proving secrets are not persisted
|
- redaction tests proving secrets are not persisted
|
||||||
- proxy fixture tests for HTTP request and response inspection
|
- proxy fixture tests for HTTP request and response inspection
|
||||||
- no-false-positive tests for common benign traffic
|
- no-false-positive tests for common benign traffic
|
||||||
- policy-review fixture tests for TweetClaw/social-account mutation examples and benign read-only social research requests
|
|
||||||
- lifecycle tests for stale PID/state cleanup
|
- lifecycle tests for stale PID/state cleanup
|
||||||
- status output tests
|
- status output tests
|
||||||
- OpenClaw hook integration tests if hook files are added
|
- OpenClaw hook integration tests if hook files are added
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "openclaw-traffic-guardian",
|
"name": "openclaw-traffic-guardian",
|
||||||
"version": "0.0.1-beta3",
|
"version": "0.0.1-beta2",
|
||||||
"description": "OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, inbound injection detection, and social-account policy review.",
|
"description": "OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, and inbound injection detection.",
|
||||||
"author": "prompt-security",
|
"author": "prompt-security",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"homepage": "https://clawsec.prompt.security/",
|
"homepage": "https://clawsec.prompt.security/",
|
||||||
@@ -15,10 +15,7 @@
|
|||||||
"injection",
|
"injection",
|
||||||
"proxy",
|
"proxy",
|
||||||
"mitm",
|
"mitm",
|
||||||
"runtime",
|
"runtime"
|
||||||
"policy-review",
|
|
||||||
"operator-review",
|
|
||||||
"social-account-mutation"
|
|
||||||
],
|
],
|
||||||
"sbom": {
|
"sbom": {
|
||||||
"files": [
|
"files": [
|
||||||
@@ -87,7 +84,6 @@
|
|||||||
"https_mitm_inspection": "planned_optional",
|
"https_mitm_inspection": "planned_optional",
|
||||||
"egress_exfiltration_detection": "planned",
|
"egress_exfiltration_detection": "planned",
|
||||||
"inbound_injection_detection": "planned",
|
"inbound_injection_detection": "planned",
|
||||||
"social_account_policy_review": "planned",
|
|
||||||
"blocking": "future_version"
|
"blocking": "future_version"
|
||||||
},
|
},
|
||||||
"execution": {
|
"execution": {
|
||||||
@@ -100,7 +96,6 @@
|
|||||||
"Default to detect-and-log mode; blocking is out of scope for v0.0.1 implementation.",
|
"Default to detect-and-log mode; blocking is out of scope for v0.0.1 implementation.",
|
||||||
"Scope HTTP_PROXY/HTTPS_PROXY to the OpenClaw process being monitored.",
|
"Scope HTTP_PROXY/HTTPS_PROXY to the OpenClaw process being monitored.",
|
||||||
"Redact secret snippets before writing logs or sending conversation alerts.",
|
"Redact secret snippets before writing logs or sending conversation alerts.",
|
||||||
"Record POLICY_REVIEW findings for approval-sensitive social-account mutations without auto-blocking, auto-approving, or rewriting requests.",
|
|
||||||
"Integrate with clawsec-suite as an optional add-on, not a default install."
|
"Integrate with clawsec-suite as an optional add-on, not a default install."
|
||||||
],
|
],
|
||||||
"triggers": [
|
"triggers": [
|
||||||
@@ -108,9 +103,7 @@
|
|||||||
"openclaw traffic monitoring",
|
"openclaw traffic monitoring",
|
||||||
"monitor openclaw egress",
|
"monitor openclaw egress",
|
||||||
"inspect openclaw http traffic",
|
"inspect openclaw http traffic",
|
||||||
"detect openclaw exfiltration",
|
"detect openclaw exfiltration"
|
||||||
"review social account mutations",
|
|
||||||
"detect tweetclaw write actions"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.0.4] - 2026-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
|
|
||||||
## [0.0.3] - 2026-05-24
|
## [0.0.3] - 2026-05-24
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -6,14 +6,6 @@ Status: implemented (v0.0.1), Picoclaw-specific.
|
|||||||
|
|
||||||
Detailed architecture/operator docs: `wiki/modules/picoclaw-security-guardian.md`.
|
Detailed architecture/operator docs: `wiki/modules/picoclaw-security-guardian.md`.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill picoclaw-security-guardian -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Support matrix mapping
|
## Support matrix mapping
|
||||||
|
|
||||||
| Skill name | supported platform | security feed | config drift | agent posture-review lane | chain of supply verification |
|
| Skill name | supported platform | security feed | config drift | agent posture-review lane | chain of supply verification |
|
||||||
@@ -56,3 +48,4 @@ test/picoclaw_security_guardian_sandbox_regression.sh
|
|||||||
```
|
```
|
||||||
|
|
||||||
It uses Docker to publish the skill through a local ClawHub-compatible registry, installs it with Picoclaw's own `find_skills` / `install_skill` flow into an isolated Picoclaw workspace, confirms Picoclaw's skill loader can list/load it, then verifies the installed copy's profile, drift, advisory, and supply-chain paths.
|
It uses Docker to publish the skill through a local ClawHub-compatible registry, installs it with Picoclaw's own `find_skills` / `install_skill` flow into an isolated Picoclaw workspace, confirms Picoclaw's skill loader can list/load it, then verifies the installed copy's profile, drift, advisory, and supply-chain paths.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: picoclaw-security-guardian
|
name: picoclaw-security-guardian
|
||||||
version: 0.0.4
|
version: 0.0.3
|
||||||
description: Picoclaw security posture skill with advisory awareness, configuration drift detection, and supply-chain verification guidance.
|
description: Picoclaw security posture skill with advisory awareness, configuration drift detection, and supply-chain verification guidance.
|
||||||
homepage: https://clawsec.prompt.security
|
homepage: https://clawsec.prompt.security
|
||||||
author: prompt-security
|
author: prompt-security
|
||||||
@@ -18,13 +18,6 @@ picoclaw:
|
|||||||
|
|
||||||
Detailed architecture/operator docs: `wiki/modules/picoclaw-security-guardian.md`.
|
Detailed architecture/operator docs: `wiki/modules/picoclaw-security-guardian.md`.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill picoclaw-security-guardian -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Release Artifact Verification
|
## Release Artifact Verification
|
||||||
|
|
||||||
@@ -34,7 +27,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SKILL_NAME="picoclaw-security-guardian"
|
SKILL_NAME="picoclaw-security-guardian"
|
||||||
VERSION="0.0.4"
|
VERSION="0.0.3"
|
||||||
REPO="prompt-security/clawsec"
|
REPO="prompt-security/clawsec"
|
||||||
TAG="${SKILL_NAME}-v${VERSION}"
|
TAG="${SKILL_NAME}-v${VERSION}"
|
||||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "picoclaw-security-guardian",
|
"name": "picoclaw-security-guardian",
|
||||||
"version": "0.0.4",
|
"version": "0.0.3",
|
||||||
"description": "Picoclaw security posture skill with advisory awareness, configuration drift detection, and supply-chain verification guidance.",
|
"description": "Picoclaw security posture skill with advisory awareness, configuration drift detection, and supply-chain verification guidance.",
|
||||||
"author": "prompt-security",
|
"author": "prompt-security",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.0.3] - 2026-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
|
|
||||||
## [0.0.2] - 2026-05-13
|
## [0.0.2] - 2026-05-13
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|||||||
@@ -4,14 +4,6 @@ Picoclaw-only local posture-review findings package for ClawSec.
|
|||||||
|
|
||||||
Status: implemented (v0.0.1), Picoclaw-specific.
|
Status: implemented (v0.0.1), Picoclaw-specific.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill picoclaw-self-pen-testing -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## What it does
|
## What it does
|
||||||
|
|
||||||
Given a generated Picoclaw posture profile, it emits severity-ranked findings and a summary count for local operator review.
|
Given a generated Picoclaw posture profile, it emits severity-ranked findings and a summary count for local operator review.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: picoclaw-self-pen-testing
|
name: picoclaw-self-pen-testing
|
||||||
version: 0.0.3
|
version: 0.0.2
|
||||||
description: Picoclaw-only local posture-review skill focused on read-only findings and safe operator remediation guidance.
|
description: Picoclaw-only local posture-review skill focused on read-only findings and safe operator remediation guidance.
|
||||||
homepage: https://clawsec.prompt.security
|
homepage: https://clawsec.prompt.security
|
||||||
author: prompt-security
|
author: prompt-security
|
||||||
@@ -18,13 +18,6 @@ picoclaw:
|
|||||||
|
|
||||||
Purpose: keep Picoclaw posture-review checks isolated from the broader guardian package so moderation-sensitive checks can be versioned/published independently.
|
Purpose: keep Picoclaw posture-review checks isolated from the broader guardian package so moderation-sensitive checks can be versioned/published independently.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill picoclaw-self-pen-testing -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Release Artifact Verification
|
## Release Artifact Verification
|
||||||
|
|
||||||
@@ -34,7 +27,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SKILL_NAME="picoclaw-self-pen-testing"
|
SKILL_NAME="picoclaw-self-pen-testing"
|
||||||
VERSION="0.0.3"
|
VERSION="0.0.2"
|
||||||
REPO="prompt-security/clawsec"
|
REPO="prompt-security/clawsec"
|
||||||
TAG="${SKILL_NAME}-v${VERSION}"
|
TAG="${SKILL_NAME}-v${VERSION}"
|
||||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "picoclaw-self-pen-testing",
|
"name": "picoclaw-self-pen-testing",
|
||||||
"version": "0.0.3",
|
"version": "0.0.2",
|
||||||
"description": "Picoclaw-only local posture-review skill focused on read-only findings and safe operator remediation guidance.",
|
"description": "Picoclaw-only local posture-review skill focused on read-only findings and safe operator remediation guidance.",
|
||||||
"author": "prompt-security",
|
"author": "prompt-security",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.0.1-beta3] - 2026-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
|
|
||||||
## [0.0.1-beta2] - 2026-05-13
|
## [0.0.1-beta2] - 2026-05-13
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|||||||
@@ -4,14 +4,6 @@ Baseline skill for Picoclaw runtime traffic monitoring.
|
|||||||
|
|
||||||
This package is intentionally a spec scaffold. Builders should add the Picoclaw-specific monitor implementation here while preserving the safety contract in `SKILL.md` and `SPEC.md`.
|
This package is intentionally a spec scaffold. Builders should add the Picoclaw-specific monitor implementation here while preserving the safety contract in `SKILL.md` and `SPEC.md`.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill picoclaw-traffic-guardian -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Intended Capability
|
## Intended Capability
|
||||||
|
|
||||||
- detect outbound secret exfiltration in Picoclaw gateway HTTP/HTTPS traffic
|
- detect outbound secret exfiltration in Picoclaw gateway HTTP/HTTPS traffic
|
||||||
@@ -23,3 +15,4 @@ npx skills add prompt-security/clawsec --skill picoclaw-traffic-guardian -a open
|
|||||||
## Builder Notes
|
## Builder Notes
|
||||||
|
|
||||||
Keep runtime ownership in this skill. `picoclaw-security-guardian` should only profile and drift-check this skill's state, config, and output fingerprints.
|
Keep runtime ownership in this skill. `picoclaw-security-guardian` should only profile and drift-check this skill's state, config, and output fingerprints.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: picoclaw-traffic-guardian
|
name: picoclaw-traffic-guardian
|
||||||
version: 0.0.1-beta3
|
version: 0.0.1-beta2
|
||||||
description: Picoclaw runtime traffic monitoring baseline for lightweight AI gateway proxy inspection, egress detection, and posture integration.
|
description: Picoclaw runtime traffic monitoring baseline for lightweight AI gateway proxy inspection, egress detection, and posture integration.
|
||||||
homepage: https://clawsec.prompt.security
|
homepage: https://clawsec.prompt.security
|
||||||
author: prompt-security
|
author: prompt-security
|
||||||
@@ -15,13 +15,6 @@ picoclaw:
|
|||||||
|
|
||||||
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
This is a baseline specification skill. It intentionally does not ship a proxy or runtime implementation yet.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill picoclaw-traffic-guardian -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Release Artifact Verification
|
## Release Artifact Verification
|
||||||
|
|
||||||
@@ -31,7 +24,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SKILL_NAME="picoclaw-traffic-guardian"
|
SKILL_NAME="picoclaw-traffic-guardian"
|
||||||
VERSION="0.0.1-beta3"
|
VERSION="0.0.1-beta2"
|
||||||
REPO="prompt-security/clawsec"
|
REPO="prompt-security/clawsec"
|
||||||
TAG="${SKILL_NAME}-v${VERSION}"
|
TAG="${SKILL_NAME}-v${VERSION}"
|
||||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||||
@@ -152,3 +145,4 @@ Read `SPEC.md` before implementing. Use the placeholder folders as follows:
|
|||||||
- default blocking
|
- default blocking
|
||||||
- sending traffic to external services
|
- sending traffic to external services
|
||||||
- collecting full request/response bodies
|
- collecting full request/response bodies
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "picoclaw-traffic-guardian",
|
"name": "picoclaw-traffic-guardian",
|
||||||
"version": "0.0.1-beta3",
|
"version": "0.0.1-beta2",
|
||||||
"description": "Picoclaw runtime traffic monitoring baseline for lightweight AI gateway proxy inspection, egress detection, and posture integration.",
|
"description": "Picoclaw runtime traffic monitoring baseline for lightweight AI gateway proxy inspection, egress detection, and posture integration.",
|
||||||
"author": "prompt-security",
|
"author": "prompt-security",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.0.7] - 2026-06-10
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Re-released skill package with updated marketplace grouping and signed release trust artifacts for Vercel-compatible skill installation.
|
|
||||||
|
|
||||||
## [0.0.6] - 2026-05-14
|
## [0.0.6] - 2026-05-14
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|||||||
@@ -2,14 +2,6 @@
|
|||||||
|
|
||||||
A small, dependency-free integrity guard for OpenClaw agent workspaces.
|
A small, dependency-free integrity guard for OpenClaw agent workspaces.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill soul-guardian -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Operational Notes
|
## Operational Notes
|
||||||
|
|
||||||
- Required runtime: `python3`
|
- Required runtime: `python3`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: soul-guardian
|
name: soul-guardian
|
||||||
version: 0.0.7
|
version: 0.0.6
|
||||||
description: Drift detection + baseline integrity guard for agent workspace files with automatic alerting support
|
description: Drift detection + baseline integrity guard for agent workspace files with automatic alerting support
|
||||||
homepage: https://clawsec.prompt.security
|
homepage: https://clawsec.prompt.security
|
||||||
metadata: {"openclaw":{"emoji":"👻","category":"security"}}
|
metadata: {"openclaw":{"emoji":"👻","category":"security"}}
|
||||||
@@ -14,14 +14,6 @@ clawdis:
|
|||||||
|
|
||||||
Protects your agent's core files (SOUL.md, AGENTS.md, etc.) from unauthorized changes with automatic detection, restoration, and **user alerting**.
|
Protects your agent's core files (SOUL.md, AGENTS.md, etc.) from unauthorized changes with automatic detection, restoration, and **user alerting**.
|
||||||
|
|
||||||
## Vercel Skills Installation
|
|
||||||
|
|
||||||
Install with the Vercel Skills CLI for this harness:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx skills add prompt-security/clawsec --skill soul-guardian -a openclaw -y
|
|
||||||
```
|
|
||||||
|
|
||||||
## Operational Notes
|
## Operational Notes
|
||||||
|
|
||||||
- Required runtime: `python3`
|
- Required runtime: `python3`
|
||||||
@@ -30,6 +22,7 @@ npx skills add prompt-security/clawsec --skill soul-guardian -a openclaw -y
|
|||||||
- Network behavior: none by default
|
- Network behavior: none by default
|
||||||
- Trust model: any scheduling is opt-in, but restore mode intentionally overwrites drifted files
|
- Trust model: any scheduling is opt-in, but restore mode intentionally overwrites drifted files
|
||||||
|
|
||||||
|
|
||||||
## Release Artifact Verification
|
## Release Artifact Verification
|
||||||
|
|
||||||
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metadata/SBOM source, and the release pipeline signs `checksums.json` with the ClawSec release key.
|
||||||
@@ -38,7 +31,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SKILL_NAME="soul-guardian"
|
SKILL_NAME="soul-guardian"
|
||||||
VERSION="0.0.7"
|
VERSION="0.0.6"
|
||||||
REPO="prompt-security/clawsec"
|
REPO="prompt-security/clawsec"
|
||||||
TAG="${SKILL_NAME}-v${VERSION}"
|
TAG="${SKILL_NAME}-v${VERSION}"
|
||||||
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
BASE="https://github.com/${REPO}/releases/download/${TAG}"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "soul-guardian",
|
"name": "soul-guardian",
|
||||||
"version": "0.0.7",
|
"version": "0.0.6",
|
||||||
"description": "Drift detection and baseline integrity guard for agent workspace prompt files. Auto-restore critical files with tamper-evident audit logging.",
|
"description": "Drift detection and baseline integrity guard for agent workspace prompt files. Auto-restore critical files with tamper-evident audit logging.",
|
||||||
"author": "prompt-security",
|
"author": "prompt-security",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user