73 lines
2.9 KiB
JavaScript
73 lines
2.9 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { chmod, lstat, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import test from 'node:test'
|
|
import { UserSkillManager } from '../../control-plane/services/management/user-skills.mjs'
|
|
import { LocalApiClient } from '../../control-plane/modules/updater/lib/client.mjs'
|
|
import { createUserSkillServer, listenUserSkills } from '../stage0/lib/user-skill-server.mjs'
|
|
|
|
async function rootOwnedFixture() {
|
|
const root = await mkdtemp(join(tmpdir(), 'dsh-user-skill-server-'))
|
|
const dshHome = join(root, 'dsh')
|
|
const agentsHome = join(root, 'agents')
|
|
const skill = join(dshHome, 'skills', 'root-owned')
|
|
await mkdir(skill, { recursive: true })
|
|
await mkdir(join(agentsHome, 'skills'), { recursive: true })
|
|
await writeFile(join(skill, 'SKILL.md'), '---\nname: root-owned\ndescription: Root-owned fixture\n---\n')
|
|
await chmod(skill, 0o755)
|
|
await chmod(join(skill, 'SKILL.md'), 0o644)
|
|
const manager = new UserSkillManager({ dshHome, agentsHome })
|
|
const server = createUserSkillServer({ manager })
|
|
const socket = join(root, 'run', 'user-skills.sock')
|
|
await listenUserSkills(server, socket)
|
|
return { root, dshHome, manager, server, socket, skill }
|
|
}
|
|
|
|
test('privileged User Skill broker validates and configures root-owned entries', async t => {
|
|
const value = await rootOwnedFixture()
|
|
t.after(async () => {
|
|
await new Promise(resolve => value.server.close(resolve))
|
|
await rm(value.root, { recursive: true, force: true })
|
|
})
|
|
const client = new LocalApiClient(value.socket)
|
|
const initial = await value.manager.list()
|
|
const entry = initial.skills[0]
|
|
const disabled = await client.request('POST', '/v1/action', {
|
|
entryId: entry.entryId,
|
|
revision: initial.revision,
|
|
action: 'disable',
|
|
})
|
|
assert.equal(disabled.skills[0].enabled, false)
|
|
assert.equal((await lstat(join(value.dshHome, 'skills/.disabled/root-owned'))).isDirectory(), true)
|
|
|
|
await assert.rejects(client.request('POST', '/v1/action', {
|
|
entryId: entry.entryId,
|
|
revision: initial.revision,
|
|
action: 'delete',
|
|
}), error => error.statusCode === 409)
|
|
|
|
const deleted = await client.request('POST', '/v1/action', {
|
|
entryId: disabled.skills[0].entryId,
|
|
revision: disabled.revision,
|
|
action: 'delete',
|
|
})
|
|
assert.deepEqual(deleted.skills, [])
|
|
})
|
|
|
|
test('privileged User Skill broker exposes no arbitrary path operation', async t => {
|
|
const value = await rootOwnedFixture()
|
|
t.after(async () => {
|
|
await new Promise(resolve => value.server.close(resolve))
|
|
await rm(value.root, { recursive: true, force: true })
|
|
})
|
|
const client = new LocalApiClient(value.socket)
|
|
await assert.rejects(client.request('POST', '/v1/action', {
|
|
entryId: `sha256:${'a'.repeat(64)}`,
|
|
revision: `sha256:${'b'.repeat(64)}`,
|
|
action: 'delete',
|
|
path: '/root',
|
|
}), error => error.statusCode === 400)
|
|
assert.equal((await lstat(value.skill)).isDirectory(), true)
|
|
})
|