Compare commits

..

11 Commits

Author SHA1 Message Date
davida-ps 6573ee9ecf chore(release): bump all public skills (#283)
* chore(skill): bump clawhub checker release

* chore(release): bump all public skills

* fix(release): require skillspector PR comments

* fix(release): align skill verification versions

* fix(release): checksum standalone release assets

* fix(release): narrow skillspector comment permissions
2026-06-23 11:12:42 +03:00
davida-ps 2a76509fcf fix(release): preserve republish slug helper dependency (#282) 2026-06-23 10:15:08 +03:00
davida-ps 4a1cf246eb fix(release): install pinned clawhub CLI from npm (#281)
* fix(release): install pinned clawhub CLI from npm

* test(release): assert public clawhub lockfile source
2026-06-23 10:08:29 +03:00
davida-ps 4c26671dc3 chore(release): bump skill metadata for republish (#278)
* chore(release): bump skill metadata for republish

* fix(release): keep skillspector PR comments non-blocking

* fix(release): resolve metadata review comments
2026-06-23 09:25:50 +03:00
davida-ps de28dadd39 fix(release): update ClawHub slug pipeline deps (#277)
* fix(release): update clawhub slug pipeline deps

* fix(release): bump clawhub cli undici lock

* fix(release): guard clawhub publish slug

* fix(release): keep suite slug releasable

* fix(release): harden clawhub slug guard

* fix(release): pass clawhub registry to slug guard
2026-06-22 23:45:24 +03:00
github-actions[bot] f937384104 chore: update NVD/GHSA advisories - 0 NVD new, 27 NVD updated (#276)
Automated update from NVD CVE and GHSA advisory feeds.
Keywords: openclaw, nanoclaw, hermes, picoclaw
Poll window: 2026-06-17T07:45:48Z to 2026-06-21T07:34:32.000Z

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-21 14:32:23 +03:00
github-actions[bot] 8648aad6d7 chore: update NVD/GHSA advisories - 27 NVD new, 20 NVD updated (#274)
* chore: update NVD/GHSA advisories - 27 NVD new, 20 NVD updated

Automated update from NVD CVE and GHSA advisory feeds.
Keywords: openclaw, nanoclaw, hermes, picoclaw
Poll window: 2026-06-14T07:33:37Z to 2026-06-17T07:44:37.000Z

* fix(skill-release): ignore generated advisory mirror updates

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: David Abutbul <David.a@prompt.security>
2026-06-17 17:24:25 +03:00
davida-ps 4a4b547b92 ci(skills): pin clawhub CLI by hash via committed lockfile (#268)
* ci(skills): pin clawhub CLI by hash via committed lockfile

Scorecard flags the skill-release workflow's npm install of the clawhub
CLI (code-scanning alerts #25/#26): version pinning alone carries no
integrity guarantee. Install it with npm ci from a committed
package-lock.json instead, so every package (clawhub + 35 transitive
deps) is verified against its sha512 hash at install time.

The publish-payload patch step now resolves the module from the local
node_modules instead of npm root -g.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(skill-release): authenticate pinned clawhub install

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 18:12:36 +03:00
davida-ps 6f51e53cdd fix(skill-release): report SkillSpector scans on PRs (#267)
* fix(skill-release): report skillspector scans on PRs

* fix(skill-release): address PR report review comments

* fix(deps): update vite audit chain

* docs(wiki): document skillspector release evidence

* docs(wiki): centralize skillspector release details
2026-06-14 19:28:11 +03:00
github-actions[bot] d8dec965a8 chore: update NVD/GHSA advisories - 34 NVD new, 0 NVD updated (#270)
Automated update from NVD CVE and GHSA advisory feeds.
Keywords: openclaw, nanoclaw, hermes, picoclaw
Poll window: 2026-06-10T08:30:16Z to 2026-06-14T07:32:26.000Z

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-14 13:23:30 +03:00
davida-ps 9fd3059271 fix(traffic): require a traffic-capable PAT for the archive workflow (#265)
* fix(traffic): use a traffic-capable PAT for the archive workflow

The daily Archive GitHub Traffic run has failed since creation: the
TRAFFIC_ARCHIVE_TOKEN secret was never provisioned, so the workflow fell
back to github.token, which GitHub categorically rejects on traffic
endpoints (403 "Resource not accessible by integration").

- Fall back to the existing POLL_NVD_CVES_PAT automation token instead
  of github.token, keeping TRAFFIC_ARCHIVE_TOKEN as the preferred
  override once provisioned.
- Fail fast with an actionable error when no traffic-capable token is
  configured.
- Explain token requirements in the script's 401/403 errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(traffic): require dedicated TRAFFIC_ARCHIVE_TOKEN, drop expired PAT fallback

A live dispatch confirmed POLL_NVD_CVES_PAT is expired (401 Bad
credentials), so falling back to it only trades one daily failure for
another. Require the dedicated secret and fail fast with setup
instructions instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 08:25:56 +03:00
77 changed files with 6993 additions and 7002 deletions
+406
View File
@@ -0,0 +1,406 @@
{
"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://registry.npmjs.org/@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://registry.npmjs.org/@ark/util/-/util-0.56.0.tgz",
"integrity": "sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA=="
},
"node_modules/@clack/core": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@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://registry.npmjs.org/@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://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
"integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
"engines": {
"node": ">=20"
}
},
"node_modules/fflate": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
"integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="
},
"node_modules/get-east-asian-width": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/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://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"engines": {
"node": ">= 4"
}
},
"node_modules/is-interactive": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"node_modules/restore-cursor": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
},
"node_modules/stdin-discarder": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/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://registry.npmjs.org/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://registry.npmjs.org/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.28.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
"integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/yoctocolors": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz",
"integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"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"
}
}
+14 -2
View File
@@ -53,9 +53,21 @@ jobs:
- name: Collect traffic
env:
GH_TRAFFIC_TOKEN: ${{ secrets.TRAFFIC_ARCHIVE_TOKEN || github.token }}
# Traffic endpoints reject the Actions GITHUB_TOKEN ("Resource not
# 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 }}
run: node scripts/archive-github-traffic.mjs --archive-dir "${TRAFFIC_ARCHIVE_DIR}"
run: |
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
run: |
+265 -104
View File
@@ -7,6 +7,8 @@ on:
pull_request:
paths:
- 'skills/**'
- '!skills/clawsec-feed/advisories/feed.json'
- '!skills/clawsec-feed/advisories/feed.json.sig'
- '.github/workflows/skill-release.yml'
- 'scripts/ci/**'
- 'scripts/test-skill-*.mjs'
@@ -19,8 +21,8 @@ on:
permissions: read-all
env:
CLAWHUB_CLI_VERSION: 0.7.0
# The ClawHub CLI version is pinned (with integrity hashes) in
# .github/clawhub-cli/package-lock.json — bump it there.
concurrency:
group: skill-release-${{ github.ref }}
@@ -88,9 +90,19 @@ jobs:
touched_skills_file="$(mktemp)"
git diff --name-only "${BASE_SHA}...${HEAD_SHA}" -- \
'skills/*/**' \
':(exclude)skills/clawsec-feed/advisories/feed.json' \
':(exclude)skills/clawsec-feed/advisories/feed.json.sig' \
':(exclude)skills/*/test/**' \
':(exclude)skills/*/tests/**' \
| awk -F/ 'NF >= 3 {print $1 "/" $2}' \
| awk -F/ '
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}"
if [ ! -s "${touched_skills_file}" ]; then
@@ -400,12 +412,17 @@ jobs:
}
touched_skills_file="$(mktemp)"
git diff --name-only "${BASE_SHA}...${HEAD_SHA}" -- 'skills/*/skill.json' 'skills/*/SKILL.md' \
git diff --name-only "${BASE_SHA}...${HEAD_SHA}" -- \
'skills/*/**' \
':(exclude)skills/clawsec-feed/advisories/feed.json' \
':(exclude)skills/clawsec-feed/advisories/feed.json.sig' \
':(exclude)skills/*/test/**' \
':(exclude)skills/*/tests/**' \
| awk -F/ 'NF >= 3 {print $1 "/" $2}' \
| sort -u > "${touched_skills_file}"
if [ ! -s "${touched_skills_file}" ]; then
echo "No skill metadata files changed in this PR."
echo "No release-relevant skill package files changed in this PR."
rm -f "${touched_skills_file}"
exit 0
fi
@@ -431,7 +448,8 @@ jobs:
is_test_release_path() {
local lower="${1,,}"
[[ "$lower" == test/* || "$lower" == tests/* || "$lower" == */test/* || "$lower" == */tests/* ]]
local name="${lower##*/}"
[[ "$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() {
@@ -526,11 +544,6 @@ jobs:
md_version_changed=true
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
echo "::error file=${skill_dir}::Version metadata is invalid for dry-run. Ensure validate-pr-version-sync passes."
failures=$((failures + 1))
@@ -619,9 +632,9 @@ jobs:
# --- Create zip preserving directory structure ---
zip_name="${skill_name}-v${version}.zip"
(cd "${staging_dir}" && zip -qr "${OLDPWD}/${out_assets}/${zip_name}" .)
if unzip -Z1 "${out_assets}/${zip_name}" | grep -Eiq '(^|/)(test|tests)/'; then
if unzip -Z1 "${out_assets}/${zip_name}" | grep -Eiq '(^|/)(__tests__|test|tests)/|(^|/)(test|spec)[_-]|(^|/).*\.(test|spec)\.'; then
echo "::error::Dry-run release archive contains test-only files: ${zip_name}"
unzip -Z1 "${out_assets}/${zip_name}" | grep -Ei '(^|/)(test|tests)/' || true
unzip -Z1 "${out_assets}/${zip_name}" | grep -Ei '(^|/)(__tests__|test|tests)/|(^|/)(test|spec)[_-]|(^|/).*\.(test|spec)\.' || true
failures=$((failures + 1))
fi
@@ -762,6 +775,35 @@ jobs:
cp "${skill_dir}/README.md" "${out_assets}/README.md"
fi
if ! add_release_asset_checksum "${out_assets}" "skill.json"; then
failures=$((failures + 1))
rm -rf "${staging_dir}"
echo "::endgroup::"
continue
fi
if ! add_release_asset_checksum "${out_assets}" "SKILL.md"; then
failures=$((failures + 1))
rm -rf "${staging_dir}"
echo "::endgroup::"
continue
fi
if [ -f "${out_assets}/README.md" ] && ! add_release_asset_checksum "${out_assets}" "README.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 standalone release assets."
failures=$((failures + 1))
rm -rf "${staging_dir}"
echo "::endgroup::"
continue
fi
rm -rf "${staging_dir}"
echo "Prepared dry-run assets for ${tag}:"
@@ -777,12 +819,174 @@ jobs:
fi
if [ "${dry_run_count}" -eq 0 ]; then
echo "No version bumps detected in changed skill metadata files."
echo "No changed skill directories required dry-run assets."
exit 0
fi
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
permissions:
actions: read
contents: read
issues: write
steps:
- name: Download SkillSpector reports
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
@@ -1065,7 +1269,8 @@ jobs:
is_test_release_path() {
local lower="${1,,}"
[[ "$lower" == test/* || "$lower" == tests/* || "$lower" == */test/* || "$lower" == */tests/* ]]
local name="${lower##*/}"
[[ "$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() {
@@ -1146,9 +1351,9 @@ jobs:
# --- Create zip preserving directory structure ---
ZIP_NAME="${SKILL_NAME}-v${VERSION}.zip"
(cd "$STAGING_DIR" && zip -qr "$OLDPWD/release-assets/$ZIP_NAME" .)
if unzip -Z1 "release-assets/$ZIP_NAME" | grep -Eiq '(^|/)(test|tests)/'; then
if unzip -Z1 "release-assets/$ZIP_NAME" | grep -Eiq '(^|/)(__tests__|test|tests)/|(^|/)(test|spec)[_-]|(^|/).*\.(test|spec)\.'; then
echo "::error::Release archive contains test-only files: $ZIP_NAME"
unzip -Z1 "release-assets/$ZIP_NAME" | grep -Ei '(^|/)(test|tests)/' || true
unzip -Z1 "release-assets/$ZIP_NAME" | grep -Ei '(^|/)(__tests__|test|tests)/|(^|/)(test|spec)[_-]|(^|/).*\.(test|spec)\.' || true
exit 1
fi
@@ -1247,6 +1452,17 @@ jobs:
cp "$SKILL_PATH/README.md" release-assets/
fi
add_release_asset_checksum "skill.json"
add_release_asset_checksum "SKILL.md"
if [ -f release-assets/README.md ]; then
add_release_asset_checksum "README.md"
fi
if ! jq -e . "release-assets/checksums.json" >/dev/null 2>&1; then
echo "::error::Generated checksums.json is invalid JSON after adding standalone release assets."
exit 1
fi
rm -rf "$STAGING_DIR"
echo "=== checksums.json ==="
@@ -1424,8 +1640,6 @@ jobs:
"",
process.env.QUICK_INSTALL || "",
"",
"### SkillSpector Security Report",
"",
report,
"",
`Download the generated release-payload scan: [skillspector-report.md](https://github.com/${process.env.REPO}/releases/download/${process.env.TAG}/skillspector-report.md)`,
@@ -1533,51 +1747,12 @@ jobs:
- name: Install clawhub CLI
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
run: npm install -g clawhub@${CLAWHUB_CLI_VERSION}
run: bash scripts/ci/install_clawhub_cli.sh
- name: Patch clawhub publish payload workaround
# Temporary: clawhub@0.7.0 publish payload is missing acceptLicenseTerms.
# Idempotent compatibility guard: older clawhub@0.7.0 builds omitted acceptLicenseTerms.
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
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
run: node scripts/ci/patch_clawhub_publish_payload.mjs
- name: Login to ClawHub
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
@@ -1590,6 +1765,18 @@ jobs:
CLAWHUB_DISABLE_TELEMETRY=1 CLAWHUB_SITE="$SITE" CLAWHUB_REGISTRY="$REGISTRY" \
clawhub login --token "$CLAWHUB_TOKEN" --site "$SITE" --no-input
- name: Guard ClawHub slug ownership
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
run: |
set -euo pipefail
SITE=${CLAWHUB_SITE:-https://clawhub.ai}
REGISTRY=${CLAWHUB_REGISTRY:-$SITE}
export CLAWHUB_CONFIG_PATH="$HOME/.clawhub-ci/config.json"
export CLAWHUB_SITE="$SITE"
export CLAWHUB_REGISTRY="$REGISTRY"
bash scripts/ci/guard_clawhub_slug_owner.sh \
"${{ needs.release-tag.outputs.clawhub_slug }}"
- name: Guard duplicate ClawHub version
if: needs.release-tag.outputs.publish_clawhub == 'true' && env.CLAWHUB_TOKEN != ''
run: |
@@ -1679,7 +1866,9 @@ jobs:
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"
run: |
cp scripts/ci/resolve_clawhub_slug.mjs "$RUNNER_TEMP/resolve_clawhub_slug.mjs"
cp scripts/ci/skill_platforms.mjs "$RUNNER_TEMP/skill_platforms.mjs"
- name: Checkout tag
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -1723,50 +1912,11 @@ jobs:
run: node scripts/ci/validate_skill_install_docs.mjs --skills "${{ steps.parse.outputs.skill_path }}"
- name: Install clawhub CLI
run: npm install -g clawhub@${CLAWHUB_CLI_VERSION}
run: bash scripts/ci/install_clawhub_cli.sh
- name: Patch clawhub publish payload workaround
# Temporary: clawhub@0.7.0 publish payload is missing acceptLicenseTerms.
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
# Idempotent compatibility guard: older clawhub@0.7.0 builds omitted acceptLicenseTerms.
run: node scripts/ci/patch_clawhub_publish_payload.mjs
- name: Login to ClawHub
run: |
@@ -1783,6 +1933,17 @@ jobs:
CLAWHUB_DISABLE_TELEMETRY=1 CLAWHUB_SITE="$SITE" CLAWHUB_REGISTRY="$REGISTRY" \
clawhub login --token "$CLAWHUB_TOKEN" --site "$SITE" --no-input
- name: Guard ClawHub slug ownership
run: |
set -euo pipefail
SITE=${CLAWHUB_SITE:-https://clawhub.ai}
REGISTRY=${CLAWHUB_REGISTRY:-$SITE}
export CLAWHUB_CONFIG_PATH="$HOME/.clawhub-ci/config.json"
export CLAWHUB_SITE="$SITE"
export CLAWHUB_REGISTRY="$REGISTRY"
bash scripts/ci/guard_clawhub_slug_owner.sh \
"${{ steps.publishable.outputs.clawhub_slug }}"
- name: Publish to ClawHub
run: |
set -euo pipefail
+2194 -2708
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
agiAAFvzM1vNHxH2+bGtyeKqFScLWJHnNreBcPpTODUqD0xqFi0cnyP/ZaZX+Rsw1Y9uZ7pGdFdA93pD4lh2BQ==
K19pfVfv7qB1cqFPFTu69+sKLHIMIrmS7GeK4BZIlHzRvrLfRUuq/KftC8/CIWwvixVlBBm/iZlyfJ5sutoDDw==
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
q1EyZ75QcdG2X6FVDkUoAyBtQE3ONA+7k9cmNFmXFgOOuGRPOpSDFUtbSvy86HPqnii26DMoeFJ1hatWJ0lBCQ==
pmw3QutYARGuNH2evzHY/slVqxsrIGU+JrtS1hr1kOSqo1Md1aVBEA0tsNoQ+SkVjNohwGVk/61CcUxeW6WAAA==
+573 -1019
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -31,13 +31,13 @@
"@types/node": "^25.8.0",
"@typescript-eslint/eslint-plugin": "^8.55.0",
"@typescript-eslint/parser": "^8.58.1",
"@vitejs/plugin-react": "^5.1.4",
"@vitejs/plugin-react": "^6.0.2",
"eslint": "^9.39.4",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"fast-check": "^4.7.0",
"typescript": "~5.9.3",
"vite": "^7.3.2"
"vite": "^8.0.16"
},
"overrides": {
"ajv": "6.14.0",
+8 -1
View File
@@ -321,7 +321,14 @@ const fetchJson = async ({ repo, token, pathname, fetchImpl }) => {
if (!response.ok) {
const body = await response.text().catch(() => '');
const suffix = body ? ` ${body.slice(0, 500)}` : '';
throw new Error(`GitHub traffic API request failed for ${repo}: ${url.pathname}${url.search} returned ${response.status}.${suffix}`);
const lacksPushAccess = response.status === 403
&& /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();
+104
View File
@@ -0,0 +1,104 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
echo "Usage: $0 <target-clawhub-slug>" >&2
}
if [ "$#" -ne 1 ]; then
usage
exit 2
fi
TARGET_SLUG="$1"
SITE="${CLAWHUB_SITE:-https://clawhub.ai}"
REGISTRY="${CLAWHUB_REGISTRY:-$SITE}"
CONFIG_PATH="${CLAWHUB_CONFIG_PATH:-$HOME/.clawhub-ci/config.json}"
if [[ ! "$TARGET_SLUG" =~ ^[a-z0-9-]+$ ]]; then
echo "::error::Invalid ClawHub slug for ownership guard: ${TARGET_SLUG}"
exit 1
fi
if [ ! -f "$CONFIG_PATH" ]; then
echo "::error::ClawHub config not found at ${CONFIG_PATH}. Run clawhub login before ownership guard."
exit 1
fi
TOKEN="$(jq -r '.token // empty' "$CONFIG_PATH")"
if [ -z "$TOKEN" ]; then
echo "::error::ClawHub token missing from ${CONFIG_PATH}. Run clawhub login before ownership guard."
exit 1
fi
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
api_get() {
local path="$1"
local output_path="$2"
local url="${REGISTRY%/}${path}"
local http_status
local curl_status
set +e
http_status="$(
curl --silent --show-error --location --max-time 15 \
--header "Accept: application/json" \
--header "Authorization: Bearer ${TOKEN}" \
--output "$output_path" \
--write-out "%{http_code}" \
"$url"
)"
curl_status=$?
set -e
if [ "$curl_status" -ne 0 ]; then
echo "::error::Failed to call ClawHub API: ${url}"
return 1
fi
printf '%s\n' "$http_status"
}
whoami_json="$TMP_DIR/whoami.json"
whoami_status="$(api_get "/api/v1/whoami" "$whoami_json")"
if [ "$whoami_status" != "200" ]; then
echo "::error::Failed to verify authenticated ClawHub publisher. HTTP ${whoami_status}."
cat "$whoami_json"
exit 1
fi
publisher_handle="$(jq -r '.user.handle // empty' "$whoami_json")"
if [ -z "$publisher_handle" ]; then
echo "::error::Could not determine authenticated ClawHub publisher handle."
cat "$whoami_json"
exit 1
fi
target_json="$TMP_DIR/target.json"
target_status="$(api_get "/api/v1/skills/${TARGET_SLUG}" "$target_json")"
if [ "$target_status" = "404" ]; then
echo "Target ClawHub slug ${TARGET_SLUG} is not currently published; authenticated publisher ${publisher_handle} may create it."
exit 0
fi
if [ "$target_status" != "200" ]; then
echo "::error::Failed to inspect target ClawHub slug ${TARGET_SLUG}. HTTP ${target_status}."
cat "$target_json"
exit 1
fi
target_owner="$(jq -r '.owner.handle // .owner.displayName // empty' "$target_json")"
if [ -z "$target_owner" ]; then
echo "::error::Could not determine owner for existing ClawHub slug ${TARGET_SLUG}."
echo "target owner: ${target_owner:-unknown}"
exit 1
fi
if [ "$target_owner" != "$publisher_handle" ]; then
echo "::error::Resolved ClawHub slug ${TARGET_SLUG} is already owned by ${target_owner}, but the authenticated publisher is ${publisher_handle}. Transfer or alias the registry slug before publishing."
exit 1
fi
echo "ClawHub slug ownership guard passed: ${TARGET_SLUG} owned by authenticated publisher ${publisher_handle}."
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
CLI_PREFIX="${CLAWHUB_CLI_PREFIX:-.github/clawhub-cli}"
npm ci --prefix "$CLI_PREFIX"
if [ -n "${GITHUB_PATH:-}" ]; then
workspace="${GITHUB_WORKSPACE:-$(pwd)}"
echo "${workspace}/${CLI_PREFIX}/node_modules/.bin" >> "$GITHUB_PATH"
fi
@@ -0,0 +1,35 @@
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}`);
+4 -4
View File
@@ -43,14 +43,14 @@ export function resolveClawHubSlug({ name, platforms = [] }) {
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 (name.startsWith("clawsec-")) {
return name;
}
if (PLATFORM_KEYS.some((platform) => name.startsWith(`${platform}-`))) {
return `clawsec-${name}`;
}
@@ -484,6 +484,13 @@ async function main() {
await cp(path.join(tempSkillDir, "README.md"), path.join(releaseAssetsDir, "README.md"));
}
for (const artifact of ["skill.json", "SKILL.md", "README.md"]) {
if (existsSync(path.join(releaseAssetsDir, artifact))) {
await addReleaseAssetChecksum({ releaseAssetsDir, manifest, asset: artifact });
}
}
await writeJson(path.join(releaseAssetsDir, "checksums.json"), manifest);
const { privateKeyPath, publicKeyPath } = await createSigningKeyPair(tempRoot);
await signFileBase64({
keyPath: privateKeyPath,
@@ -143,6 +143,8 @@ function changedSkillDirs({ root, base, head }) {
`${base}...${head}`,
"--",
"skills/*/**",
":(exclude)skills/clawsec-feed/advisories/feed.json",
":(exclude)skills/clawsec-feed/advisories/feed.json.sig",
":(exclude)skills/*/test/**",
":(exclude)skills/*/tests/**",
],
+17
View File
@@ -141,6 +141,23 @@ if [ -f "$SKILL_PATH/SKILL.md" ]; then
echo " ✓ Version updated to $VERSION"
echo "Updating release verification VERSION assignments in SKILL.md..."
VERSION_ASSIGNMENT_PATTERN='^VERSION="[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?"$'
if grep -qE "$VERSION_ASSIGNMENT_PATTERN" "$TEMP_DIR/SKILL.md"; then
sed -E "s|$VERSION_ASSIGNMENT_PATTERN|VERSION=\"$VERSION\"|g" "$TEMP_DIR/SKILL.md" > "$TEMP_DIR/SKILL.md.tmp"
if ! grep -qF "VERSION=\"$VERSION\"" "$TEMP_DIR/SKILL.md.tmp"; then
echo "Warning: VERSION assignment found but substitution may have failed" >&2
else
VERSION_ASSIGNMENT_COUNT=$(grep -cF "VERSION=\"$VERSION\"" "$TEMP_DIR/SKILL.md.tmp")
echo " ✓ Updated $VERSION_ASSIGNMENT_COUNT VERSION assignment(s)"
fi
mv "$TEMP_DIR/SKILL.md.tmp" "$TEMP_DIR/SKILL.md"
else
echo " No hardcoded release verification VERSION assignments found"
fi
echo "Updating hardcoded version URLs in SKILL.md to use tag $TAG..."
# Replace all hardcoded version URLs: download/SKILLNAME-vX.Y.Z(-prerelease)?/ -> download/TAG/
# This handles patterns like: download/clawsec-feed-v1.0.0/ or download/prompt-agent-v1.0.0-beta1/
+36 -1
View File
@@ -76,6 +76,40 @@ test('fetchGitHubTraffic requests the daily GitHub traffic endpoints with auth',
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', () => {
const archive = mergeTrafficArchive(
{
@@ -232,7 +266,8 @@ test('traffic archive workflow uses a daily schedule and a dedicated archive bra
assert.match(workflow, /cron:\s+'17 3 \* \* \*'/);
assert.match(workflow, /TRAFFIC_ARCHIVE_BRANCH:\s+traffic-archive/);
assert.match(workflow, /TRAFFIC_ARCHIVE_TOKEN/);
assert.match(workflow, /GH_TRAFFIC_TOKEN:\s*\$\{\{\s*secrets\.TRAFFIC_ARCHIVE_TOKEN\b/);
assert.doesNotMatch(workflow, /GH_TRAFFIC_TOKEN:[^\n]*github\.token/);
assert.match(workflow, /node scripts\/archive-github-traffic\.mjs/);
assert.match(workflow, /git add traffic\/archive\.json traffic\/summary\.json/);
assert.match(workflow, /git rm --ignore-unmatch traffic\/README\.md/);
+288 -6
View File
@@ -3,8 +3,20 @@ import { readFile } from 'node:fs/promises';
const workflowPath = new URL('../.github/workflows/skill-release.yml', import.meta.url);
const ciWorkflowPath = new URL('../.github/workflows/ci.yml', import.meta.url);
const clawhubLockPath = new URL('../.github/clawhub-cli/package-lock.json', import.meta.url);
const validateSkillInstallDocsPath = new URL('./ci/validate_skill_install_docs.mjs', 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 guardClawhubSlugOwnerPath = new URL('./ci/guard_clawhub_slug_owner.sh', import.meta.url);
const releaseSkillScriptPath = new URL('./release-skill.sh', import.meta.url);
const workflow = await readFile(workflowPath, 'utf8');
const ciWorkflow = await readFile(ciWorkflowPath, 'utf8');
const clawhubLock = JSON.parse(await readFile(clawhubLockPath, 'utf8'));
const validateSkillInstallDocs = await readFile(validateSkillInstallDocsPath, 'utf8');
const installClawhubCli = await readFile(installClawhubCliPath, 'utf8');
const patchClawhubPayload = await readFile(patchClawhubPayloadPath, 'utf8');
const guardClawhubSlugOwner = await readFile(guardClawhubSlugOwnerPath, 'utf8');
const releaseSkillScript = await readFile(releaseSkillScriptPath, 'utf8');
assert.match(
workflow,
@@ -12,6 +24,16 @@ assert.match(
'Skill release workflow must run when any skill package file changes',
);
for (const generatedFeedPath of [
'skills/clawsec-feed/advisories/feed.json',
'skills/clawsec-feed/advisories/feed.json.sig',
]) {
assert.ok(
workflow.includes(` - '!${generatedFeedPath}'`),
`Skill release workflow must not run for generated advisory mirror-only changes to ${generatedFeedPath}`,
);
}
assert.match(
workflow,
/pull_request:[\s\S]*paths:[\s\S]*- '\.github\/workflows\/skill-release\.yml'[\s\S]*- 'scripts\/ci\/\*\*'/,
@@ -30,8 +52,25 @@ assert.ok(
assert.match(
workflow,
/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',
/git diff --name-only "\$\{BASE_SHA\}\.\.\.\$\{HEAD_SHA\}" --[\s\S]*'skills\/\*\/\*\*'[\s\S]*':\(exclude\)skills\/clawsec-feed\/advisories\/feed\.json'[\s\S]*':\(exclude\)skills\/clawsec-feed\/advisories\/feed\.json\.sig'[\s\S]*':\(exclude\)skills\/\*\/test\/\*\*'[\s\S]*':\(exclude\)skills\/\*\/tests\/\*\*'/,
'Skill release validation must ignore generated clawsec-feed advisory mirror and test-only changes while inspecting release-relevant skill files',
);
for (const generatedFeedPath of [
':(exclude)skills/clawsec-feed/advisories/feed.json',
':(exclude)skills/clawsec-feed/advisories/feed.json.sig',
]) {
assert.ok(
validateSkillInstallDocs.includes(`"${generatedFeedPath}"`),
`Install-doc validation changed-skill detection must ignore generated advisory mirror-only changes to ${generatedFeedPath}`,
);
}
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(
@@ -88,10 +127,16 @@ assert.match(
'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,
/### SkillSpector Security Report[\s\S]*\[skillspector-report\.md\]\(https:\/\/github\.com\/\$\{process\.env\.REPO\}\/releases\/download\/\$\{process\.env\.TAG\}\/skillspector-report\.md\)/,
'GitHub release notes must include a direct SkillSpector report link',
/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(
@@ -118,6 +163,20 @@ assert.match(
'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\/clawsec-feed\/advisories\/feed\.json'[\s\S]*':\(exclude\)skills\/clawsec-feed\/advisories\/feed\.json\.sig'[\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 except generated advisory mirror files',
);
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"/,
@@ -163,6 +222,29 @@ for (const artifact of ['skill-card.md', 'permissions.json', 'install.md', 'skil
);
}
for (const artifact of ['skill.json', 'SKILL.md']) {
assert.match(
workflow,
new RegExp(
String.raw`cp [\s\S]*? "\$\{out_assets\}/${escapeRegExp(artifact)}"[\s\S]*?` +
String.raw`if ! add_release_asset_checksum "\$\{out_assets\}" "${escapeRegExp(artifact)}"; then`,
),
`PR dry-run validation must checksum standalone downloadable ${artifact} after copying it to release assets`,
);
}
assert.match(
workflow,
/if \[ -f "\$\{out_assets\}\/README\.md" \] && ! add_release_asset_checksum "\$\{out_assets\}" "README\.md"; then/,
'PR dry-run validation must checksum standalone downloadable README.md when it is shipped',
);
assert.match(
workflow,
/cp "\$SKILL_PATH\/skill\.json" release-assets\/skill\.json[\s\S]*add_release_asset_checksum "skill\.json"[\s\S]*add_release_asset_checksum "SKILL\.md"[\s\S]*add_release_asset_checksum "README\.md"/,
'Tag release validation must checksum standalone downloadable skill files before signing checksums.json',
);
assert.match(
workflow,
/add_release_asset_checksum "skill-card\.md"/,
@@ -187,6 +269,73 @@ assert.match(
'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 issue-comment permissions',
);
const commentJob = workflow.match(/[ ]{2}comment-skillspector-report:[\s\S]*?\n[ ]{2}[a-z][^:\n]*:/)?.[0] || "";
assert.match(
commentJob,
/issues: write/,
'SkillSpector PR comment publishing must request issues write permissions so report comments can be created',
);
assert.doesNotMatch(
commentJob,
/pull-requests: write/,
'SkillSpector PR comment publishing must not broaden the token with pull-requests write permissions',
);
assert.match(
workflow,
/comment-skillspector-report:[\s\S]*if: always\(\) && github\.event_name == 'pull_request' && needs\.release\.result != 'cancelled'[\s\S]*Download SkillSpector reports/,
'SkillSpector PR comments must still run when the release dry-run produced reports but the release job failed later',
);
assert.match(
workflow,
/Comment SkillSpector reports[\s\S]*actions\/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9\.0\.0/,
'SkillSpector PR comment publishing must use the pinned GitHub script action',
);
assert.doesNotMatch(
commentJob,
/continue-on-error: true/,
'SkillSpector PR comment publishing must fail visibly when report artifacts or PR comments cannot be created',
);
assert.match(
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/,
@@ -204,6 +353,16 @@ assert.ok(
'Skill release workflow must accept every prerelease version format that release-skill.sh accepts',
);
assert.ok(
releaseSkillScript.includes(`VERSION_ASSIGNMENT_PATTERN='^VERSION="[0-9]+\\.[0-9]+\\.[0-9]+(-[a-zA-Z0-9.]+)?"$'`),
'release-skill.sh must detect hardcoded release verification VERSION assignments in SKILL.md',
);
assert.ok(
releaseSkillScript.includes('sed -E "s|$VERSION_ASSIGNMENT_PATTERN|VERSION=\\"$VERSION\\"|g"'),
'release-skill.sh must update hardcoded release verification VERSION assignments when bumping a skill',
);
assert.match(
workflow,
/clawhub_slug: \$\{\{ steps\.publishable\.outputs\.clawhub_slug \}\}/,
@@ -218,8 +377,8 @@ assert.match(
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',
/cp scripts\/ci\/resolve_clawhub_slug\.mjs "\$RUNNER_TEMP\/resolve_clawhub_slug\.mjs"[\s\S]*cp scripts\/ci\/skill_platforms\.mjs "\$RUNNER_TEMP\/skill_platforms\.mjs"/,
'Manual ClawHub republish must preserve the current slug helper and its local module dependency before checking out an older release tag',
);
assert.match(
@@ -246,6 +405,129 @@ assert.match(
'ClawHub publish must use the resolved ClawHub slug',
);
assert.match(
workflow,
/clawhub publish "\$SKILL_PATH"[\s\S]*--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.equal(
workflow.match(/bash scripts\/ci\/guard_clawhub_slug_owner\.sh/g)?.length,
2,
'ClawHub publish and republish jobs must guard mapped slug ownership before publishing',
);
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',
);
assert.match(
installClawhubCli,
/npm ci --prefix "\$CLI_PREFIX"/,
'ClawHub CLI installer must install from the committed lockfile prefix',
);
assert.doesNotMatch(
installClawhubCli,
/aws codeartifact login|AWS credentials are required/,
'ClawHub CLI installer must not require AWS secrets that are not configured for release workflows',
);
const clawhubLockResolvedUrls = Object.values(clawhubLock.packages ?? {})
.map((entry) => entry.resolved)
.filter(Boolean);
assert.ok(clawhubLockResolvedUrls.length > 0, 'ClawHub CLI lockfile must contain resolved tarball URLs');
assert.ok(
clawhubLockResolvedUrls.every((url) => url.startsWith('https://registry.npmjs.org/')),
'ClawHub CLI lockfile must use public npm tarballs because release workflows do not have AWS CodeArtifact secrets',
);
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.match(
patchClawhubPayload,
/Already patched/,
'ClawHub payload patch helper must stay idempotent when the pinned CLI already includes acceptLicenseTerms',
);
assert.match(
guardClawhubSlugOwner,
/api_get "\/api\/v1\/whoami" "\$whoami_json"/,
'ClawHub slug ownership guard must verify the authenticated publisher through the ClawHub API',
);
assert.match(
guardClawhubSlugOwner,
/api_get "\/api\/v1\/skills\/\$\{TARGET_SLUG\}" "\$target_json"/,
'ClawHub slug ownership guard must inspect the resolved publish slug through the ClawHub API',
);
assert.match(
guardClawhubSlugOwner,
/\[ "\$target_status" = "404" \]/,
'ClawHub slug ownership guard must treat HTTP 404 as the structured unpublished-slug signal',
);
assert.match(
guardClawhubSlugOwner,
/\[ "\$target_owner" != "\$publisher_handle" \]/,
'ClawHub slug ownership guard must reject slugs owned by a different authenticated registry publisher',
);
assert.doesNotMatch(
guardClawhubSlugOwner,
/SOURCE_SLUG|source_owner|grep -Eqi[\s\S]*Skill not found/,
'ClawHub slug ownership guard must not inspect raw source names or depend on stderr wording',
);
assert.match(
workflow,
/SITE=\$\{CLAWHUB_SITE:-https:\/\/clawhub\.ai\}[\s\S]*REGISTRY=\$\{CLAWHUB_REGISTRY:-\$SITE\}[\s\S]*export CLAWHUB_CONFIG_PATH="\$HOME\/\.clawhub-ci\/config\.json"[\s\S]*export CLAWHUB_SITE="\$SITE"[\s\S]*export CLAWHUB_REGISTRY="\$REGISTRY"[\s\S]*bash scripts\/ci\/guard_clawhub_slug_owner\.sh[\s\S]*\$\{\{ needs\.release-tag\.outputs\.clawhub_slug \}\}/,
'ClawHub publish job must guard the resolved publish slug with the authenticated ClawHub config path',
);
assert.match(
workflow,
/SITE=\$\{CLAWHUB_SITE:-https:\/\/clawhub\.ai\}[\s\S]*REGISTRY=\$\{CLAWHUB_REGISTRY:-\$SITE\}[\s\S]*export CLAWHUB_CONFIG_PATH="\$HOME\/\.clawhub-ci\/config\.json"[\s\S]*export CLAWHUB_SITE="\$SITE"[\s\S]*export CLAWHUB_REGISTRY="\$REGISTRY"[\s\S]*bash scripts\/ci\/guard_clawhub_slug_owner\.sh[\s\S]*\$\{\{ steps\.publishable\.outputs\.clawhub_slug \}\}/,
'ClawHub republish job must guard the resolved publish slug with the authenticated ClawHub config path',
);
assert.doesNotMatch(
workflow,
/clawhub inspect "\$SKILL_NAME" --version "\$VERSION" --json/,
+28 -4
View File
@@ -1,5 +1,7 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { chmod, cp, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { existsSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { spawnSync } from "node:child_process";
@@ -7,6 +9,10 @@ import { spawnSync } from "node:child_process";
const tempRoot = await mkdtemp(path.join(tmpdir(), "clawsec-tag-release-sim-"));
const fakeSkillspector = path.join(tempRoot, "skillspector");
function sha256(buffer) {
return createHash("sha256").update(buffer).digest("hex");
}
async function prereleaseFixture(sourceSkillDir, version, fixtureGroup) {
const fixtureDir = path.join(tempRoot, fixtureGroup, path.basename(sourceSkillDir));
await cp(sourceSkillDir, fixtureDir, { recursive: true });
@@ -77,6 +83,24 @@ async function runSimulation({ skillDir, outputDir, expectedOriginal, expectedSi
assert.ok(file.length > 0, `${artifact} should not be empty`);
}
for (const artifact of ["skill.json", "SKILL.md", "skillspector-report.md"]) {
const file = await readFile(path.join(releaseAssetsDir, artifact));
assert.equal(
checksums.files[artifact]?.sha256,
sha256(file),
`${artifact} must be downloadable and covered by checksums.json`,
);
}
if (existsSync(path.join(releaseAssetsDir, "README.md"))) {
const file = await readFile(path.join(releaseAssetsDir, "README.md"));
assert.equal(
checksums.files["README.md"]?.sha256,
sha256(file),
"README.md must be downloadable and covered by checksums.json when shipped",
);
}
const archive = await readFile(path.join(releaseAssetsDir, `${expectedTag}.zip`));
assert.ok(archive.length > 0, "release archive should not be empty");
@@ -140,16 +164,16 @@ writeFileSync(process.argv[outputIndex + 1], "# Fake SkillSpector Report\\n\\nNo
await runSimulation({
skillDir: "skills/clawsec-suite",
outputDir: path.join(tempRoot, "stable"),
expectedOriginal: "0.1.10",
expectedSimulated: "0.1.11",
expectedOriginal: "0.1.12",
expectedSimulated: "0.1.13",
expectedAgent: "openclaw",
});
await runSimulation({
skillDir: "skills/hermes-traffic-guardian",
outputDir: path.join(tempRoot, "beta"),
expectedOriginal: "0.0.1-beta3",
expectedSimulated: "0.0.1-beta4",
expectedOriginal: "0.0.1-beta5",
expectedSimulated: "0.0.1-beta6",
expectedAgent: "hermes-agent",
});
+4 -4
View File
@@ -25,7 +25,7 @@ function runTrustPacket(skillDir, targetDir, tag) {
}
try {
const result = runTrustPacket("skills/clawsec-suite", outputDir, "clawsec-suite-v0.1.10");
const result = runTrustPacket("skills/clawsec-suite", outputDir, "clawsec-suite-v0.1.12");
assert.equal(
result.status,
@@ -41,10 +41,10 @@ try {
assert.match(skillCard, /## License\/Terms of Use/);
assert.match(skillCard, /AGPL-3\.0-or-later/);
assert.match(skillCard, /skillspector-report\.md/);
assert.match(skillCard, /clawsec-suite-v0\.1\.10/);
assert.match(skillCard, /clawsec-suite-v0\.1\.12/);
assert.equal(permissions.skill, "clawsec-suite");
assert.equal(permissions.version, "0.1.10");
assert.equal(permissions.version, "0.1.12");
assert.equal(permissions.platform, "openclaw");
assert.deepEqual(
permissions.required_binaries,
@@ -62,7 +62,7 @@ try {
const hermesResult = runTrustPacket(
"skills/hermes-attestation-guardian",
hermesOutputDir,
"hermes-attestation-guardian-v0.1.4",
"hermes-attestation-guardian-v0.1.6",
);
assert.equal(
hermesResult.status,
@@ -1,5 +1,17 @@
# Changelog
## [0.0.8] - 2026-06-23
### Changed
- Re-released skill metadata to run through the corrected normal tag publish pipeline without runtime changes.
## [0.0.7] - 2026-06-22
### Changed
- Re-released skill metadata to publish through the updated ClawHub pipeline without runtime changes.
## [0.0.6] - 2026-06-10
### Changed
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: clawsec-clawhub-checker
version: 0.0.6
version: 0.0.8
description: ClawHub reputation checker for clawsec-suite. Adds a standalone reputation gate before guarded skill installation.
homepage: https://clawsec.prompt.security
clawdis:
@@ -61,7 +61,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
set -euo pipefail
SKILL_NAME="clawsec-clawhub-checker"
VERSION="0.0.4"
VERSION="0.0.8"
REPO="prompt-security/clawsec"
TAG="${SKILL_NAME}-v${VERSION}"
BASE="https://github.com/${REPO}/releases/download/${TAG}"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "clawsec-clawhub-checker",
"version": "0.0.6",
"version": "0.0.8",
"description": "ClawHub reputation checker for clawsec-suite. Adds a standalone reputation gate before guarded skill installation.",
"author": "abutbul",
"license": "AGPL-3.0-or-later",
+12
View File
@@ -1,5 +1,17 @@
# Changelog
## [0.0.11] - 2026-06-23
### Changed
- Re-released skill metadata to run through the corrected normal tag publish pipeline without runtime changes.
## [0.0.10] - 2026-06-22
### Changed
- Re-released skill metadata to publish through the updated ClawHub pipeline without runtime changes.
## [0.0.9] - 2026-06-10
### Changed
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: clawsec-feed
version: 0.0.9
version: 0.0.11
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
metadata: {"openclaw":{"emoji":"📡","category":"security"}}
@@ -96,7 +96,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
set -euo pipefail
SKILL_NAME="clawsec-feed"
VERSION="0.0.9"
VERSION="0.0.11"
REPO="prompt-security/clawsec"
TAG="${SKILL_NAME}-v${VERSION}"
BASE="https://github.com/${REPO}/releases/download/${TAG}"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
agiAAFvzM1vNHxH2+bGtyeKqFScLWJHnNreBcPpTODUqD0xqFi0cnyP/ZaZX+Rsw1Y9uZ7pGdFdA93pD4lh2BQ==
K19pfVfv7qB1cqFPFTu69+sKLHIMIrmS7GeK4BZIlHzRvrLfRUuq/KftC8/CIWwvixVlBBm/iZlyfJ5sutoDDw==
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "clawsec-feed",
"version": "0.0.9",
"version": "0.0.11",
"description": "Security advisory feed monitoring for AI agents. Subscribe to community-driven threat intelligence.",
"author": "prompt-security",
"license": "AGPL-3.0-or-later",
+12
View File
@@ -1,5 +1,17 @@
# Changelog
## [0.0.10] - 2026-06-23
### Changed
- Re-released skill metadata to run through the corrected normal tag publish pipeline without runtime changes.
## [0.0.9] - 2026-06-22
### Changed
- Re-released skill metadata to publish through the updated ClawHub pipeline without runtime changes.
## [0.0.8] - 2026-06-10
### Changed
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: clawsec-nanoclaw
version: 0.0.8
version: 0.0.10
description: Use when checking for security vulnerabilities in NanoClaw skills, before installing new skills, or when asked about security advisories affecting the bot
---
@@ -217,7 +217,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
set -euo pipefail
SKILL_NAME="clawsec-nanoclaw"
VERSION="0.0.6"
VERSION="0.0.10"
REPO="prompt-security/clawsec"
TAG="${SKILL_NAME}-v${VERSION}"
BASE="https://github.com/${REPO}/releases/download/${TAG}"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "clawsec-nanoclaw",
"version": "0.0.8",
"version": "0.0.10",
"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",
"license": "AGPL-3.0-or-later",
+8 -2
View File
@@ -1,10 +1,16 @@
# Changelog
## [0.0.6] - 2026-06-11
## [0.0.7] - 2026-06-23
### Changed
- Updated shipped documentation (hook HOOK.md, dast_runner.mjs header, SKILL.md roadmap heading) to describe the static hook source inspection model introduced in 0.0.4, removing stale references to executing target hook handlers.
- Re-released skill metadata to run through the corrected normal tag publish pipeline without runtime changes.
## [0.0.6] - 2026-06-22
### Changed
- Re-released skill metadata to publish through the updated ClawHub pipeline without runtime changes.
## [0.0.5] - 2026-06-10
+3 -3
View File
@@ -1,6 +1,6 @@
---
name: clawsec-scanner
version: 0.0.6
version: 0.0.7
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
clawdis:
@@ -50,7 +50,7 @@ The scanner orchestrates four complementary scan types to provide comprehensive
- **Bandit** for Python: Leverages existing `pyproject.toml` configuration
- Identifies: hardcoded secrets (API keys, tokens), command injection (`eval`, `exec`), path traversal, unsafe deserialization
4. **DAST (Static Hook Inspection)**
4. **Dynamic Analysis (DAST)**
- Static hook inspection for OpenClaw hook handlers discovered from `HOOK.md` metadata
- Verifies coverage and source-level risk signals without importing, transpiling, or invoking target handlers
- Note: Traditional web DAST tools (ZAP, Burp) do not apply to agent platforms - this provides agent-specific testing
@@ -464,7 +464,7 @@ npx clawhub@latest install clawsec-suite
## Roadmap
### v0.0.6 (Current)
### v0.0.4 (Current)
- [x] Dependency scanning (npm audit, pip-audit)
- [x] CVE database integration (OSV, NVD, GitHub Advisory)
- [x] SAST analysis (Semgrep, Bandit)
@@ -20,7 +20,7 @@ The hook orchestrates four independent scanning engines:
1. **Dependency Scanning**: Executes `npm audit` and `pip-audit` to detect known vulnerabilities in JavaScript and Python dependencies
2. **SAST (Static Analysis)**: Runs Semgrep (JS/TS) and Bandit (Python) to detect security issues like hardcoded secrets, command injection, and path traversal
3. **CVE Database Lookup**: Queries OSV API (primary), NVD 2.0 (optional), and GitHub Advisory Database (optional) for vulnerability enrichment
4. **DAST (Static Hook Inspection)**: Reads OpenClaw hook handler source in an isolated helper process and pattern-matches source-level risk signals (subprocess execution, dynamic imports, `eval`, environment access) without importing, transpiling, or executing target handler code
4. **DAST (Dynamic Analysis)**: Executes real OpenClaw hook handlers in an isolated harness and tests malicious-input resilience, timeout behavior, output bounds, and event mutation safety
## Safety Contract
@@ -48,7 +48,7 @@ The hook orchestrates four independent scanning engines:
- `CLAWSEC_SKIP_DEPENDENCY_SCAN`: Set to `1` to disable dependency scanning (npm audit, pip-audit).
- `CLAWSEC_SKIP_SAST`: Set to `1` to disable static analysis (Semgrep, Bandit).
- `CLAWSEC_SKIP_DAST`: Set to `1` to disable static hook inspection (DAST hook source checks).
- `CLAWSEC_SKIP_DAST`: Set to `1` to disable dynamic analysis (hook security tests).
- `CLAWSEC_SKIP_CVE_LOOKUP`: Set to `1` to disable CVE database enrichment.
### Advanced Options
@@ -1,13 +1,13 @@
#!/usr/bin/env node
/**
* DAST Runner for ClawSec Scanner (static OpenClaw hook inspection).
* DAST (Dynamic Application Security Testing) Runner for ClawSec Scanner.
*
* Scope:
* - Discover OpenClaw hooks from target directories
* - Inspect hook handler source in an isolated helper process without
* importing, transpiling, or invoking target handler code
* - Report coverage and source-level risk signals as DAST-STATIC-* findings
* - Execute real hook handlers in an isolated harness process
* - Validate malicious-input resilience, timeout behavior, output bounds,
* and event mutation safety
*/
import fs from "node:fs/promises";
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "clawsec-scanner",
"version": "0.0.6",
"version": "0.0.7",
"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",
"license": "AGPL-3.0-or-later",
+12
View File
@@ -1,5 +1,17 @@
# Changelog
## [0.1.12] - 2026-06-23
### Changed
- Re-released skill metadata to run through the corrected normal tag publish pipeline without runtime changes.
## [0.1.11] - 2026-06-22
### Changed
- Re-released skill metadata to publish through the updated ClawHub pipeline without runtime changes.
## [0.1.10] - 2026-06-10
### Changed
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: clawsec-suite
version: 0.1.10
version: 0.1.12
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
clawdis:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "clawsec-suite",
"version": "0.1.10",
"version": "0.1.12",
"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",
"license": "AGPL-3.0-or-later",
+12
View File
@@ -1,5 +1,17 @@
# Changelog
## [0.0.9] - 2026-06-23
### Changed
- Re-released skill metadata to run through the corrected normal tag publish pipeline without runtime changes.
## [0.0.8] - 2026-06-22
### Changed
- Re-released skill metadata to publish through the updated ClawHub pipeline without runtime changes.
## [0.0.7] - 2026-06-10
### Changed
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: clawtributor
version: 0.0.7
version: 0.0.9
description: Harness-neutral community incident reporting for AI agents. Contribute to collective security by reporting threats.
homepage: https://clawsec.prompt.security
platforms:
@@ -79,7 +79,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
set -euo pipefail
SKILL_NAME="clawtributor"
VERSION="0.0.7"
VERSION="0.0.9"
REPO="prompt-security/clawsec"
TAG="${SKILL_NAME}-v${VERSION}"
BASE="https://github.com/${REPO}/releases/download/${TAG}"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "clawtributor",
"version": "0.0.7",
"version": "0.0.9",
"description": "Harness-neutral community incident reporting for AI agents. Contribute to collective security by reporting threats.",
"author": "prompt-security",
"license": "AGPL-3.0-or-later",
@@ -1,5 +1,17 @@
# Changelog
## [0.1.6] - 2026-06-23
### Changed
- Re-released skill metadata to run through the corrected normal tag publish pipeline without runtime changes.
## [0.1.5] - 2026-06-22
### Changed
- Re-released skill metadata to publish through the updated ClawHub pipeline without runtime changes.
## [0.1.4] - 2026-06-10
### Changed
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: hermes-attestation-guardian
version: 0.1.4
version: 0.1.6
description: Hermes-only runtime security attestation and drift detection skill for operator-managed Hermes infrastructure.
homepage: https://clawsec.prompt.security
hermes:
@@ -31,7 +31,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
set -euo pipefail
SKILL_NAME="hermes-attestation-guardian"
VERSION="0.1.4"
VERSION="0.1.6"
REPO="prompt-security/clawsec"
TAG="${SKILL_NAME}-v${VERSION}"
BASE="https://github.com/${REPO}/releases/download/${TAG}"
@@ -1,6 +1,6 @@
{
"name": "hermes-attestation-guardian",
"version": "0.1.4",
"version": "0.1.6",
"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",
"license": "AGPL-3.0-or-later",
@@ -1,5 +1,17 @@
# Changelog
## [0.0.1-beta5] - 2026-06-23
### Changed
- Re-released skill metadata to run through the corrected normal tag publish pipeline without runtime changes.
## [0.0.1-beta4] - 2026-06-22
### Changed
- Re-released skill metadata to publish through the updated ClawHub pipeline without runtime changes.
## [0.0.1-beta3] - 2026-06-10
### Changed
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: hermes-traffic-guardian
version: 0.0.1-beta3
version: 0.0.1-beta5
description: Hermes runtime traffic monitoring baseline for opt-in proxy inspection, egress detection, and attestation-aware traffic posture.
homepage: https://clawsec.prompt.security
author: prompt-security
@@ -31,7 +31,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
set -euo pipefail
SKILL_NAME="hermes-traffic-guardian"
VERSION="0.0.1-beta3"
VERSION="0.0.1-beta5"
REPO="prompt-security/clawsec"
TAG="${SKILL_NAME}-v${VERSION}"
BASE="https://github.com/${REPO}/releases/download/${TAG}"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hermes-traffic-guardian",
"version": "0.0.1-beta3",
"version": "0.0.1-beta5",
"description": "Hermes runtime traffic monitoring baseline for opt-in proxy inspection, egress detection, and attestation-aware traffic posture.",
"author": "prompt-security",
"license": "AGPL-3.0-or-later",
@@ -1,5 +1,17 @@
# Changelog
## [0.0.1-beta5] - 2026-06-23
### Changed
- Re-released skill metadata to run through the corrected normal tag publish pipeline without runtime changes.
## [0.0.1-beta4] - 2026-06-22
### Changed
- Re-released skill metadata to publish through the updated ClawHub pipeline without runtime changes.
## [0.0.1-beta3] - 2026-06-10
### Changed
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: nanoclaw-traffic-guardian
version: 0.0.1-beta3
version: 0.0.1-beta5
description: NanoClaw runtime traffic monitoring baseline for host-side proxy inspection with container-safe MCP and IPC status surfaces.
homepage: https://clawsec.prompt.security
author: prompt-security
@@ -30,7 +30,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
set -euo pipefail
SKILL_NAME="nanoclaw-traffic-guardian"
VERSION="0.0.1-beta3"
VERSION="0.0.1-beta5"
REPO="prompt-security/clawsec"
TAG="${SKILL_NAME}-v${VERSION}"
BASE="https://github.com/${REPO}/releases/download/${TAG}"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "nanoclaw-traffic-guardian",
"version": "0.0.1-beta3",
"version": "0.0.1-beta5",
"description": "NanoClaw runtime traffic monitoring baseline for host-side proxy inspection with container-safe MCP and IPC status surfaces.",
"author": "prompt-security",
"license": "AGPL-3.0-or-later",
@@ -1,5 +1,17 @@
# Changelog
## [0.1.9] - 2026-06-23
### Changed
- Re-released skill metadata to run through the corrected normal tag publish pipeline without runtime changes.
## [0.1.8] - 2026-06-22
### Changed
- Re-released skill metadata to publish through the updated ClawHub pipeline without runtime changes.
## [0.1.7] - 2026-06-10
### Changed
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: openclaw-audit-watchdog
version: 0.1.7
version: 0.1.9
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
metadata:
@@ -81,7 +81,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
set -euo pipefail
SKILL_NAME="openclaw-audit-watchdog"
VERSION="0.1.7"
VERSION="0.1.9"
REPO="prompt-security/clawsec"
TAG="${SKILL_NAME}-v${VERSION}"
BASE="https://github.com/${REPO}/releases/download/${TAG}"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "openclaw-audit-watchdog",
"version": "0.1.7",
"version": "0.1.9",
"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",
"license": "AGPL-3.0-or-later",
@@ -1,5 +1,17 @@
# Changelog
## [0.0.1-beta5] - 2026-06-23
### Changed
- Re-released skill metadata to run through the corrected normal tag publish pipeline without runtime changes.
## [0.0.1-beta4] - 2026-06-22
### Changed
- Re-released skill metadata to publish through the updated ClawHub pipeline without runtime changes.
## [0.0.1-beta3] - 2026-06-10
### Security
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: openclaw-traffic-guardian
version: 0.0.1-beta3
version: 0.0.1-beta5
description: OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, inbound injection detection, and social-account policy review.
homepage: https://clawsec.prompt.security
author: prompt-security
@@ -31,7 +31,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
set -euo pipefail
SKILL_NAME="openclaw-traffic-guardian"
VERSION="0.0.1-beta3"
VERSION="0.0.1-beta5"
REPO="prompt-security/clawsec"
TAG="${SKILL_NAME}-v${VERSION}"
BASE="https://github.com/${REPO}/releases/download/${TAG}"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "openclaw-traffic-guardian",
"version": "0.0.1-beta3",
"version": "0.0.1-beta5",
"description": "OpenClaw runtime traffic monitoring baseline for opt-in HTTP/HTTPS proxy inspection, egress detection, inbound injection detection, and social-account policy review.",
"author": "prompt-security",
"license": "AGPL-3.0-or-later",
@@ -1,5 +1,17 @@
# Changelog
## [0.0.6] - 2026-06-23
### Changed
- Re-released skill metadata to run through the corrected normal tag publish pipeline without runtime changes.
## [0.0.5] - 2026-06-22
### Changed
- Re-released skill metadata to publish through the updated ClawHub pipeline without runtime changes.
## [0.0.4] - 2026-06-10
### Changed
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: picoclaw-security-guardian
version: 0.0.4
version: 0.0.6
description: Picoclaw security posture skill with advisory awareness, configuration drift detection, and supply-chain verification guidance.
homepage: https://clawsec.prompt.security
author: prompt-security
@@ -34,7 +34,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
set -euo pipefail
SKILL_NAME="picoclaw-security-guardian"
VERSION="0.0.4"
VERSION="0.0.6"
REPO="prompt-security/clawsec"
TAG="${SKILL_NAME}-v${VERSION}"
BASE="https://github.com/${REPO}/releases/download/${TAG}"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "picoclaw-security-guardian",
"version": "0.0.4",
"version": "0.0.6",
"description": "Picoclaw security posture skill with advisory awareness, configuration drift detection, and supply-chain verification guidance.",
"author": "prompt-security",
"license": "AGPL-3.0-or-later",
@@ -1,5 +1,17 @@
# Changelog
## [0.0.5] - 2026-06-23
### Changed
- Re-released skill metadata to run through the corrected normal tag publish pipeline without runtime changes.
## [0.0.4] - 2026-06-22
### Changed
- Re-released skill metadata to publish through the updated ClawHub pipeline without runtime changes.
## [0.0.3] - 2026-06-10
### Changed
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: picoclaw-self-pen-testing
version: 0.0.3
version: 0.0.5
description: Picoclaw-only local posture-review skill focused on read-only findings and safe operator remediation guidance.
homepage: https://clawsec.prompt.security
author: prompt-security
@@ -34,7 +34,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
set -euo pipefail
SKILL_NAME="picoclaw-self-pen-testing"
VERSION="0.0.3"
VERSION="0.0.5"
REPO="prompt-security/clawsec"
TAG="${SKILL_NAME}-v${VERSION}"
BASE="https://github.com/${REPO}/releases/download/${TAG}"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "picoclaw-self-pen-testing",
"version": "0.0.3",
"version": "0.0.5",
"description": "Picoclaw-only local posture-review skill focused on read-only findings and safe operator remediation guidance.",
"author": "prompt-security",
"license": "AGPL-3.0-or-later",
@@ -1,5 +1,17 @@
# Changelog
## [0.0.1-beta5] - 2026-06-23
### Changed
- Re-released skill metadata to run through the corrected normal tag publish pipeline without runtime changes.
## [0.0.1-beta4] - 2026-06-22
### Changed
- Re-released skill metadata to publish through the updated ClawHub pipeline without runtime changes.
## [0.0.1-beta3] - 2026-06-10
### Changed
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: picoclaw-traffic-guardian
version: 0.0.1-beta3
version: 0.0.1-beta5
description: Picoclaw runtime traffic monitoring baseline for lightweight AI gateway proxy inspection, egress detection, and posture integration.
homepage: https://clawsec.prompt.security
author: prompt-security
@@ -31,7 +31,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
set -euo pipefail
SKILL_NAME="picoclaw-traffic-guardian"
VERSION="0.0.1-beta3"
VERSION="0.0.1-beta5"
REPO="prompt-security/clawsec"
TAG="${SKILL_NAME}-v${VERSION}"
BASE="https://github.com/${REPO}/releases/download/${TAG}"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "picoclaw-traffic-guardian",
"version": "0.0.1-beta3",
"version": "0.0.1-beta5",
"description": "Picoclaw runtime traffic monitoring baseline for lightweight AI gateway proxy inspection, egress detection, and posture integration.",
"author": "prompt-security",
"license": "AGPL-3.0-or-later",
+12
View File
@@ -1,5 +1,17 @@
# Changelog
## [0.0.9] - 2026-06-23
### Changed
- Re-released skill metadata to run through the corrected normal tag publish pipeline without runtime changes.
## [0.0.8] - 2026-06-22
### Changed
- Re-released skill metadata to publish through the updated ClawHub pipeline without runtime changes.
## [0.0.7] - 2026-06-10
### Changed
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: soul-guardian
version: 0.0.7
version: 0.0.9
description: Drift detection + baseline integrity guard for agent workspace files with automatic alerting support
homepage: https://clawsec.prompt.security
metadata: {"openclaw":{"emoji":"👻","category":"security"}}
@@ -38,7 +38,7 @@ For standalone installs, verify the signed release manifest before trusting `SKI
set -euo pipefail
SKILL_NAME="soul-guardian"
VERSION="0.0.7"
VERSION="0.0.9"
REPO="prompt-security/clawsec"
TAG="${SKILL_NAME}-v${VERSION}"
BASE="https://github.com/${REPO}/releases/download/${TAG}"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "soul-guardian",
"version": "0.0.7",
"version": "0.0.9",
"description": "Drift detection and baseline integrity guard for agent workspace prompt files. Auto-restore critical files with tamper-evident audit logging.",
"author": "prompt-security",
"license": "AGPL-3.0-or-later",
+3
View File
@@ -35,6 +35,7 @@
| GitHub API | Deploy/release workflows | Discover releases, download assets, publish outputs. |
| GitHub Pages | Deploy workflow | Serve static site and mirrored artifacts. |
| ClawHub CLI/registry | Install scripts + optional publish jobs | Install and publish skills. |
| [NVIDIA SkillSpector](https://github.com/NVIDIA/SkillSpector) | Skill release workflow | Scan staged skill release payloads and produce Markdown release evidence. |
| Optional local SMTP/sendmail | `openclaw-audit-watchdog` scripts | Deliver audit reports by email. |
## Development Tools
@@ -46,6 +47,7 @@
| Bandit | `bandit -r utils/ -ll` | Python security checks. |
| Trivy | Workflow + optional local run | FS/config vulnerability scans. |
| Gitleaks | `scripts/prepare-to-push.sh` optional local run | Secret leak detection before push. |
| SkillSpector | `.github/workflows/skill-release.yml` | Release-payload scanner used for PR comments and signed release artifacts. |
## Example Snippets
```json
@@ -83,6 +85,7 @@ skips = ["B101"]
- PR validation enforces version parity between `skill.json` and `SKILL.md` frontmatter for bumped skills.
- The public skills index keeps latest discovered version per skill for UI display.
- Signed artifact manifests (`checksums.json`) are versioned per release and include file hashes and URLs.
- SkillSpector reports are generated per release payload and included in signed artifact manifests.
## Source References
- package.json
+2 -1
View File
@@ -15,6 +15,7 @@
| --- | --- |
| Skill Tag | Git tag formatted as `<skill>-v<semver>` used by release automation. |
| Release Assets | Files attached to GitHub release (zip, `skill.json`, checksums, signatures). |
| SkillSpector Report | Markdown security scan evidence generated from a staged skill release payload. |
| Catalog Index | `public/skills/index.json`, generated list consumed by web catalog. |
| Embedded Components | Capability bundle from one skill included in another (for example feed embedded in suite). |
@@ -39,7 +40,7 @@
| --- | --- |
| Poll NVD CVEs Workflow | Scheduled workflow that fetches and transforms NVD CVEs into advisories. |
| Community Advisory Workflow | Issue-label-triggered workflow that publishes approved community advisories. |
| Skill Release Workflow | Tag-triggered packaging/signing/publishing pipeline for skills. |
| Skill Release Workflow | PR and tag-triggered packaging/signing/publishing pipeline for skills. |
| Deploy Pages Workflow | Workflow that builds site assets and mirrors release/advisory artifacts. |
## Source References
+7 -1
View File
@@ -2,7 +2,7 @@
Track translation coverage and freshness versus English source docs.
_Last updated: 2026-04-27_
_Last updated: 2026-06-14_
## README Coverage
@@ -24,6 +24,12 @@ _Last updated: 2026-04-27_
| `wiki/testing.md` | — | pending |
| `wiki/workflow.md` | — | pending |
## English Source Freshness Notes
| Date | Changed pages | Translation impact |
| --- | --- | --- |
| 2026-06-14 | `wiki/workflow.md`, `wiki/modules/automation-release.md`, `wiki/security-signing-runbook.md`, `wiki/dependencies.md`, `wiki/glossary.md` | Added SkillSpector release-pipeline documentation, signed-report behavior, and PR comment behavior. Translation refresh pending. |
## Wiki Coverage (KO)
| Source page | Korean page | Status |
+40 -5
View File
@@ -19,11 +19,38 @@ This module intentionally focuses on automation/release-specific workflow behavi
When a skill is tagged (for example, `soul-guardian-v1.0.0`), the pipeline:
1. Validates `skill.json` version/tag alignment.
2. Enforces signing-key consistency against canonical repo key material.
3. Generates `checksums.json` for SBOM files.
4. Signs and verifies release checksum artifacts.
5. Publishes GitHub Release assets.
6. Supersedes older releases within the same major version (tags remain).
7. Triggers website catalog refresh.
3. Stages the release payload from SBOM-scoped files and root skill docs.
4. Generates release trust packet files, install instructions, and a SkillSpector security report.
5. Generates `checksums.json` for the archive and release assets.
6. Signs and verifies release checksum artifacts.
7. Publishes GitHub Release assets.
8. Supersedes older releases within the same major version (tags remain).
9. Triggers website catalog refresh.
### PR dry-run behavior
PRs that touch skill packages run the release workflow in validation mode:
- `validate-pr-version-sync` checks changed skill metadata and documentation parity.
- `release` builds dry-run release assets for changed release-relevant skill files.
- `comment-skillspector-report` posts a sanitized SkillSpector summary back to the PR when reports are available.
- `simulate-tag-release-build` exercises the tag-release builder across skills without publishing.
The PR path exists to catch packaging, signing, and release-evidence regressions before a maintainer pushes a real release tag.
### SkillSpector release evidence
The pipeline installs [NVIDIA SkillSpector](https://github.com/NVIDIA/SkillSpector) inside GitHub Actions and runs:
```bash
skillspector scan <staged-release-payload> --no-llm --format markdown --output skillspector-report.md
```
The scan target is the staged payload, not the raw `skills/<name>/` source directory. That matters because release evidence should describe what users install, while source-only tests and fixtures stay outside the packaged payload.
SkillSpector output is used in three places:
- PR dry-run artifact: `skillspector-pr-reports`
- GitHub release asset: `skillspector-report.md`
- Signed checksum manifest: `checksums.json` includes the SkillSpector report hash
PR comments intentionally use a sanitized summary. Raw code blocks, inline snippets, emails, and token-like values are omitted from the comment body, and reviewers can download the workflow artifact when they need the full report.
### Signing-key consistency guardrails
Guardrail script:
@@ -40,9 +67,16 @@ Enforced in:
### Release artifacts
Each skill release includes:
- `<skill>-v<version>.zip`
- `checksums.json`
- `checksums.sig`
- `signing-public.pem`
- `skill.json`
- `SKILL.md`
- `skill-card.md`
- `permissions.json`
- `install.md`
- `skillspector-report.md`
- Additional SBOM-scoped files
Operational docs:
@@ -58,6 +92,7 @@ Operational docs:
- `.github/workflows/deploy-pages.yml`: site build + asset mirroring to GitHub Pages.
- `.github/workflows/wiki-sync.yml`: syncs repository `wiki/` into GitHub Wiki.
- `.github/actions/sign-and-verify/action.yml`: shared Ed25519 sign/verify composite action.
- `https://github.com/NVIDIA/SkillSpector`: upstream SkillSpector scanner installed by the release workflow.
- `scripts/prepare-to-push.sh`: local CI-like quality gate.
- `scripts/release-skill.sh`: manual helper for version bump + tag workflow.
+8 -1
View File
@@ -141,11 +141,18 @@ Current behavior:
Current release generator:
- `.github/workflows/skill-release.yml`
Current behavior:
Detailed packaging and SkillSpector behavior lives in [Automation and Release Pipelines](modules/automation-release.md). This runbook only records the signing controls operators must verify.
Signing controls:
- creates `checksums.json`, signs it as `checksums.sig`, and verifies signature before publish
- includes `signing-public.pem` in release assets
- validates generated public-key fingerprint against canonical key material
Operator review points:
- verify `checksums.json` includes the release-evidence files documented in `wiki/modules/automation-release.md`
- verify `checksums.sig` validates against `signing-public.pem`
- review the release workflow run and PR evidence links before pushing or approving follow-up release tags
## 8) Rotation policy and runbook
### Rotation cadence
+7 -1
View File
@@ -32,11 +32,16 @@
## Release Workflow Details
- Version bump and docs parity are enforced for PR/tag paths.
- Skill packaging includes SBOM-declared files and integrity manifests.
- PR runs validate changed skill packages with a dry-run build before anything is published.
- Tag pushes matching `<skill>-v<semver>` build the real release payload, sign `checksums.json`, verify the signature, and publish GitHub Release assets.
- Skill packaging includes SBOM-declared files, release trust packet files, install instructions, security scan evidence, and integrity manifests.
- `checksums.json` is signed and immediately verified in workflow execution.
- Optional publish-to-ClawHub job runs after successful GitHub release when configured.
- Older releases within same major line can be superseded/deleted by automation.
## SkillSpector Release Evidence
Detailed SkillSpector release behavior lives in [Automation and Release Pipelines](modules/automation-release.md). Keep the detailed scanner command, staged-payload rules, PR comment behavior, and release-asset list there so scanner changes have one primary documentation owner.
## Advisory Workflow Details
- NVD workflow determines incremental window from previous feed `updated` timestamp.
- Transform phase maps CVE metrics to severity/type and normalizes affected targets.
@@ -74,6 +79,7 @@ on:
- .github/workflows/poll-nvd-cves.yml
- .github/workflows/community-advisory.yml
- .github/workflows/skill-release.yml
- https://github.com/NVIDIA/SkillSpector
- .github/workflows/deploy-pages.yml
- .github/workflows/pages-verify.yml
- .github/workflows/wiki-sync.yml