fix(skills): address Copilot review nits (safety + UI guards)
This commit is contained in:
@@ -176,6 +176,9 @@ async def delete_gateway(
|
|||||||
await service.clear_agent_foreign_keys(agent_id=agent.id)
|
await service.clear_agent_foreign_keys(agent_id=agent.id)
|
||||||
await session.delete(agent)
|
await session.delete(agent)
|
||||||
|
|
||||||
|
# NOTE: The migration declares `ondelete="CASCADE"` for gateway_installed_skills.gateway_id,
|
||||||
|
# but some backends/test environments (e.g. SQLite without FK pragma) may not
|
||||||
|
# enforce cascades. Delete rows explicitly to guarantee cleanup semantics.
|
||||||
installed_skills = await GatewayInstalledSkill.objects.filter_by(
|
installed_skills = await GatewayInstalledSkill.objects.filter_by(
|
||||||
gateway_id=gateway.id,
|
gateway_id=gateway.id,
|
||||||
).all(session)
|
).all(session)
|
||||||
|
|||||||
@@ -243,6 +243,15 @@ def _collect_pack_skills_from_index(
|
|||||||
if not index_file.is_file():
|
if not index_file.is_file():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
stat = index_file.stat()
|
||||||
|
except OSError as exc:
|
||||||
|
raise RuntimeError("unable to read skills_index.json") from exc
|
||||||
|
|
||||||
|
# Hard cap to avoid loading an arbitrarily large file into memory.
|
||||||
|
if stat.st_size > 256 * 1024:
|
||||||
|
raise RuntimeError("skills_index.json is too large")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
payload = json.loads(index_file.read_text(encoding="utf-8"))
|
payload = json.loads(index_file.read_text(encoding="utf-8"))
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
@@ -382,7 +391,10 @@ def _collect_pack_skills(source_url: str) -> list[PackSkillCandidate]:
|
|||||||
raise RuntimeError("timed out cloning pack repository") from exc
|
raise RuntimeError("timed out cloning pack repository") from exc
|
||||||
except subprocess.CalledProcessError as exc:
|
except subprocess.CalledProcessError as exc:
|
||||||
stderr = (exc.stderr or "").strip()
|
stderr = (exc.stderr or "").strip()
|
||||||
detail = stderr or "unable to clone pack repository"
|
# Avoid reflecting arbitrary stderr to callers; keep details minimal.
|
||||||
|
detail = "unable to clone pack repository"
|
||||||
|
if stderr:
|
||||||
|
detail = f"{detail}: {stderr.splitlines()[0][:200]}"
|
||||||
raise RuntimeError(detail) from exc
|
raise RuntimeError(detail) from exc
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -397,6 +409,9 @@ def _collect_pack_skills(source_url: str) -> list[PackSkillCandidate]:
|
|||||||
branch = "main"
|
branch = "main"
|
||||||
|
|
||||||
branch = branch or "main"
|
branch = branch or "main"
|
||||||
|
# Fail closed on weird output to avoid constructing malformed URLs.
|
||||||
|
if any(ch in branch for ch in {"\n", "\r", "\t"}):
|
||||||
|
branch = "main"
|
||||||
|
|
||||||
return _collect_pack_skills_from_repo(
|
return _collect_pack_skills_from_repo(
|
||||||
repo_dir=repo_dir,
|
repo_dir=repo_dir,
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ export default function SkillsPacksPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSyncPack = async (pack: SkillPackRead) => {
|
const handleSyncPack = async (pack: SkillPackRead) => {
|
||||||
|
if (syncingPackIds.has(pack.id)) return;
|
||||||
|
|
||||||
setSyncingPackIds((previous) => {
|
setSyncingPackIds((previous) => {
|
||||||
const next = new Set(previous);
|
const next = new Set(previous);
|
||||||
next.add(pack.id);
|
next.add(pack.id);
|
||||||
|
|||||||
@@ -9,7 +9,15 @@ export const repoBaseFromSkillSourceUrl = (skillSourceUrl: string): string | nul
|
|||||||
const marker = "/tree/";
|
const marker = "/tree/";
|
||||||
const markerIndex = parsed.pathname.indexOf(marker);
|
const markerIndex = parsed.pathname.indexOf(marker);
|
||||||
if (markerIndex <= 0) return null;
|
if (markerIndex <= 0) return null;
|
||||||
return normalizeRepoSourceUrl(`${parsed.origin}${parsed.pathname.slice(0, markerIndex)}`);
|
|
||||||
|
// Reject unexpected structures (e.g. multiple /tree/ markers).
|
||||||
|
if (parsed.pathname.indexOf(marker, markerIndex + marker.length) !== -1) return null;
|
||||||
|
|
||||||
|
const repoPath = parsed.pathname.slice(0, markerIndex);
|
||||||
|
if (!repoPath || repoPath === "/") return null;
|
||||||
|
if (repoPath.endsWith("/tree")) return null;
|
||||||
|
|
||||||
|
return normalizeRepoSourceUrl(`${parsed.origin}${repoPath}`);
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user