feat(skills): validate manifest permissions
This commit is contained in:
@@ -53,6 +53,84 @@ function hasValidInstallers(manifest: unknown): boolean {
|
||||
});
|
||||
}
|
||||
|
||||
function isNumberArray(value: unknown): value is number[] {
|
||||
return Array.isArray(value) && value.every((item) => typeof item === 'number' && Number.isFinite(item));
|
||||
}
|
||||
|
||||
function hasValidPermissions(manifest: unknown): boolean {
|
||||
if (!manifest || typeof manifest !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const candidate = manifest as { permissions?: unknown };
|
||||
if (candidate.permissions === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!candidate.permissions || typeof candidate.permissions !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const perms = candidate.permissions as {
|
||||
tool_groups?: unknown;
|
||||
tools?: unknown;
|
||||
fs?: unknown;
|
||||
net?: unknown;
|
||||
secrets?: unknown;
|
||||
execution_environment?: unknown;
|
||||
};
|
||||
|
||||
if (perms.tool_groups !== undefined && !isStringArray(perms.tool_groups)) {
|
||||
return false;
|
||||
}
|
||||
if (perms.tools !== undefined && !isStringArray(perms.tools)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (perms.fs !== undefined) {
|
||||
if (!perms.fs || typeof perms.fs !== 'object') {
|
||||
return false;
|
||||
}
|
||||
const fsPerms = perms.fs as { read?: unknown; write?: unknown };
|
||||
if (fsPerms.read !== undefined && !isStringArray(fsPerms.read)) {
|
||||
return false;
|
||||
}
|
||||
if (fsPerms.write !== undefined && !isStringArray(fsPerms.write)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (perms.net !== undefined) {
|
||||
if (!Array.isArray(perms.net)) {
|
||||
return false;
|
||||
}
|
||||
for (const entry of perms.net) {
|
||||
if (!entry || typeof entry !== 'object') {
|
||||
return false;
|
||||
}
|
||||
const e = entry as { host?: unknown; ports?: unknown };
|
||||
if (typeof e.host !== 'string' || e.host.trim().length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (e.ports !== undefined && !isNumberArray(e.ports)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (perms.secrets !== undefined && !isStringArray(perms.secrets)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (perms.execution_environment !== undefined) {
|
||||
if (perms.execution_environment !== 'sandbox' && perms.execution_environment !== 'host') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a skill's system requirements are met.
|
||||
*
|
||||
@@ -136,6 +214,10 @@ export function loadSkill(directory: string, tier: SkillTier): Skill | null {
|
||||
console.warn(`Skill manifest at ${manifestPath} has invalid installers specification`);
|
||||
return null;
|
||||
}
|
||||
if (!hasValidPermissions(raw)) {
|
||||
console.warn(`Skill manifest at ${manifestPath} has invalid permissions specification`);
|
||||
return null;
|
||||
}
|
||||
|
||||
manifest = {
|
||||
...raw,
|
||||
|
||||
Reference in New Issue
Block a user