fix: enable only curated agent skills

This commit is contained in:
2026-07-11 12:25:52 +08:00
parent 96e120e02c
commit 17c0536958
6 changed files with 14 additions and 9 deletions
@@ -125,7 +125,8 @@ The boundary is enforced by the Claude Code SDK's built-in sandbox
- Platform-curated Agent skills are immutable Hub release assets, loaded as a - Platform-curated Agent skills are immutable Hub release assets, loaded as a
programmatic local plugin from a release-owned path. The sandbox exposes that programmatic local plugin from a release-owned path. The sandbox exposes that
path read-only, and the SDK receives only plugin-qualified allowlist names path read-only, and the SDK receives only plugin-qualified allowlist names
through its `skills` option. Filesystem setting sources remain disabled, so a through its `skills` option; SDK-bundled skills are disabled. Filesystem
setting sources remain disabled, so a
project cannot register another skill or widen its tools through `.claude` project cannot register another skill or widen its tools through `.claude`
settings. Requested skill ids are recorded on `run.created`; SDK settings. Requested skill ids are recorded on `run.created`; SDK
initialization/results remain the authoritative evidence that loading initialization/results remain the authoritative evidence that loading
@@ -1,5 +1,5 @@
{ {
"name": "cph-curated", "name": "cph-curated",
"description": "Reviewed curriculum-production skills shipped with the Curriculum Project Hub.", "description": "Reviewed curriculum-production skills shipped with the Curriculum Project Hub.",
"version": "0.0.1" "version": "0.0.2"
} }
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@paradigm/hub", "name": "@paradigm/hub",
"version": "0.0.8", "version": "0.0.9",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@paradigm/hub", "name": "@paradigm/hub",
"version": "0.0.8", "version": "0.0.9",
"dependencies": { "dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.3.202", "@anthropic-ai/claude-agent-sdk": "^0.3.202",
"@fastify/cookie": "^11.0.2", "@fastify/cookie": "^11.0.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@paradigm/hub", "name": "@paradigm/hub",
"version": "0.0.8", "version": "0.0.9",
"private": true, "private": true,
"type": "module", "type": "module",
"engines": { "engines": {
+4 -1
View File
@@ -151,7 +151,9 @@ export async function runAgent(req: RunRequest): Promise<RunResult> {
type QueryOptions = NonNullable<Parameters<typeof query>[0]["options"]>; type QueryOptions = NonNullable<Parameters<typeof query>[0]["options"]>;
const options: QueryOptions = { const options: QueryOptions = {
cwd: security.cwd, cwd: security.cwd,
tools: [...toolConfig.tools], // `skills` controls discovery/allowlisting, but an explicit `tools`
// list still has to expose the Skill dispatcher itself.
tools: [...toolConfig.tools, "Skill"],
allowedTools: [...toolConfig.allowedTools], allowedTools: [...toolConfig.allowedTools],
maxTurns: cap, maxTurns: cap,
includePartialMessages: true, includePartialMessages: true,
@@ -169,6 +171,7 @@ export async function runAgent(req: RunRequest): Promise<RunResult> {
// The project workspace is untrusted input. Do not load user/project // The project workspace is untrusted input. Do not load user/project
// settings that could widen tools, hooks, MCP servers, or sandbox paths. // settings that could widen tools, hooks, MCP servers, or sandbox paths.
settingSources: [], settingSources: [],
settings: { disableBundledSkills: true },
plugins: [{ type: "local", path: security.skillPluginRoot, skipMcpDiscovery: true }], plugins: [{ type: "local", path: security.skillPluginRoot, skipMcpDiscovery: true }],
skills: [...security.skillIds], skills: [...security.skillIds],
strictMcpConfig: true, strictMcpConfig: true,
+4 -3
View File
@@ -112,6 +112,7 @@ describe("runAgent", () => {
permissionMode: "bypassPermissions", permissionMode: "bypassPermissions",
allowDangerouslySkipPermissions: true, allowDangerouslySkipPermissions: true,
settingSources: [], settingSources: [],
settings: { disableBundledSkills: true },
plugins: [expect.objectContaining({ type: "local", skipMcpDiscovery: true })], plugins: [expect.objectContaining({ type: "local", skipMcpDiscovery: true })],
skills: ["cph-curated:outline", "cph-curated:lesson-project", "cph-curated:data-processing-spec"], skills: ["cph-curated:outline", "cph-curated:lesson-project", "cph-curated:data-processing-spec"],
strictMcpConfig: true, strictMcpConfig: true,
@@ -182,13 +183,13 @@ describe("runAgent", () => {
expect(queryMock.mock.calls[0]?.[0]).toMatchObject({ expect(queryMock.mock.calls[0]?.[0]).toMatchObject({
options: { options: {
tools: ["Read", "Bash"], tools: ["Read", "Bash", "Skill"],
allowedTools: ["Read", "Bash", "mcp__cph_hub__send_file"], allowedTools: ["Read", "Bash", "mcp__cph_hub__send_file"],
}, },
}); });
}); });
it("disables all SDK tools for an empty role tool whitelist", async () => { it("keeps only the curated Skill dispatcher for an empty role tool whitelist", async () => {
queryMock.mockReturnValue(messages(assistantMessage("ok"), resultMessage("sdk-session-1"))); queryMock.mockReturnValue(messages(assistantMessage("ok"), resultMessage("sdk-session-1")));
await runAgent({ await runAgent({
@@ -204,7 +205,7 @@ describe("runAgent", () => {
expect(queryMock.mock.calls[0]?.[0]).toMatchObject({ expect(queryMock.mock.calls[0]?.[0]).toMatchObject({
options: { options: {
tools: [], tools: ["Skill"],
allowedTools: [], allowedTools: [],
}, },
}); });