feat(skills): validate manifest permissions
This commit is contained in:
@@ -10,6 +10,7 @@ import type { AuditEvent } from '../audit/types.js';
|
||||
import type { Config } from '../config/schema.js';
|
||||
import type { Skill } from '../skills/index.js';
|
||||
import { loadAllSkills, SkillInstaller, buildInstallerPlan, loadSkill } from '../skills/index.js';
|
||||
import type { SkillPermissions } from '../skills/types.js';
|
||||
import { loadConfigSafe } from './shared.js';
|
||||
|
||||
export interface SkillListRow {
|
||||
@@ -31,6 +32,7 @@ export interface SkillInstallerPlanView {
|
||||
name: string;
|
||||
tier: Skill['manifest']['tier'];
|
||||
version: string;
|
||||
permissions?: SkillPermissions;
|
||||
};
|
||||
mode: 'dry-run';
|
||||
steps: Array<{ installerType: string; command: string }>;
|
||||
@@ -45,6 +47,39 @@ export interface SkillInstallPreflightView {
|
||||
skipped: SkillInstallerPlanView['skipped'];
|
||||
}
|
||||
|
||||
function renderSkillPermissions(perms?: SkillPermissions): string[] {
|
||||
if (!perms) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const lines: string[] = ['Permissions:'];
|
||||
if (perms.tool_groups && perms.tool_groups.length > 0) {
|
||||
lines.push(`- tool_groups: ${perms.tool_groups.join(', ')}`);
|
||||
}
|
||||
if (perms.tools && perms.tools.length > 0) {
|
||||
lines.push(`- tools: ${perms.tools.join(', ')}`);
|
||||
}
|
||||
if (perms.fs?.read && perms.fs.read.length > 0) {
|
||||
lines.push(`- fs.read: ${perms.fs.read.join(', ')}`);
|
||||
}
|
||||
if (perms.fs?.write && perms.fs.write.length > 0) {
|
||||
lines.push(`- fs.write: ${perms.fs.write.join(', ')}`);
|
||||
}
|
||||
if (perms.net && perms.net.length > 0) {
|
||||
lines.push(`- net: ${perms.net.map((n) => `${n.host}${n.ports && n.ports.length > 0 ? `:${n.ports.join('|')}` : ''}`).join(', ')}`);
|
||||
}
|
||||
if (perms.secrets && perms.secrets.length > 0) {
|
||||
lines.push(`- secrets: ${perms.secrets.join(', ')}`);
|
||||
}
|
||||
if (perms.execution_environment) {
|
||||
lines.push(`- execution_environment: ${perms.execution_environment}`);
|
||||
}
|
||||
if (lines.length === 1) {
|
||||
return ['Permissions: (none declared)'];
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
export interface SkillInstallerExecutionStubView {
|
||||
skill: SkillInstallerPlanView['skill'];
|
||||
execution: 'stub';
|
||||
@@ -667,6 +702,10 @@ export function renderSkillInfo(skill: Skill): string {
|
||||
lines.push(`Tools: ${skill.manifest.tools.join(', ')}`);
|
||||
}
|
||||
|
||||
if (skill.manifest.permissions) {
|
||||
lines.push(...renderSkillPermissions(skill.manifest.permissions));
|
||||
}
|
||||
|
||||
if (skill.unavailableReasons && skill.unavailableReasons.length > 0) {
|
||||
lines.push(`Unavailable reasons: ${skill.unavailableReasons.join('; ')}`);
|
||||
}
|
||||
@@ -698,6 +737,7 @@ export function toSkillInstallerPlanView(skill: Skill): SkillInstallerPlanView {
|
||||
name: skill.manifest.name,
|
||||
tier: skill.manifest.tier,
|
||||
version: skill.manifest.version,
|
||||
permissions: skill.manifest.permissions,
|
||||
},
|
||||
mode: plan.mode,
|
||||
steps: plan.steps,
|
||||
@@ -711,6 +751,10 @@ export function renderSkillInstallerPlan(view: SkillInstallerPlanView): string {
|
||||
`Mode: ${view.mode}`,
|
||||
];
|
||||
|
||||
if (view.skill.permissions) {
|
||||
lines.push(...renderSkillPermissions(view.skill.permissions));
|
||||
}
|
||||
|
||||
if (view.steps.length === 0) {
|
||||
lines.push('Planned steps: none');
|
||||
} else {
|
||||
@@ -754,6 +798,10 @@ export function renderSkillInstallPreflight(view: SkillInstallPreflightView): st
|
||||
`Mode: ${view.mode}`,
|
||||
];
|
||||
|
||||
if (view.skill.permissions) {
|
||||
lines.push(...renderSkillPermissions(view.skill.permissions));
|
||||
}
|
||||
|
||||
if (view.steps.length === 0) {
|
||||
lines.push('Planned installer steps: none');
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user