[eric] swarm: a zip is judged by what its manifest contains, not by the fact that one is named manifest.json

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012G8kyALnPjsA7aJFmMBq3R
This commit is contained in:
ciregenz
2026-09-02 15:32:09 -07:00
co-authored by Claude Opus 5
parent e6f84c3412
commit f57e9509f8
4 changed files with 80 additions and 419 deletions
+18
View File
@@ -197,6 +197,16 @@ def stage_upload(raw: bytes, filename: str) -> tuple[str, Manifest, list[str]]:
sandbox = unpack(raw)
try:
raw_manifest = read_manifest(sandbox)
except BundleError:
shutil.rmtree(sandbox, ignore_errors=True)
raise
# "manifest.json" is a name a dozen other tools use; a skill zip that happens to carry
# one is not a broken bundle, it is not a bundle at all. Decide on the CONTENT, so a
# tampered bundle of ours still fails loudly on the strict path below.
if not looks_like_our_manifest(raw_manifest):
shutil.rmtree(sandbox, ignore_errors=True)
return stage_skill_from_zip(raw, filename, warnings)
try:
verify_checksum(sandbox, raw_manifest)
manifest = Manifest(**raw_manifest)
validate_manifest(manifest)
@@ -217,6 +227,14 @@ def stage_upload(raw: bytes, filename: str) -> tuple[str, Manifest, list[str]]:
return p_stage_skill_from_markdown(raw, filename, warnings)
def looks_like_our_manifest(raw_manifest: object) -> bool:
"""Ours, by the three keys only a .swarm manifest has. A tampered or truncated bundle of ours
still passes this and goes on to fail its checksum, which is the half that must not get lost."""
if not isinstance(raw_manifest, dict):
return False
return all(key in raw_manifest for key in ("bundle_id", "root", "entities"))
def p_name_from_filename(filename: str) -> str:
base = os.path.splitext(os.path.basename(filename or "skill"))[0]
return base.replace("-", " ").replace("_", " ").strip().title() or "Imported Skill"
@@ -0,0 +1,62 @@
"""A zip is judged by what its manifest CONTAINS, not by the fact that one is named manifest.json.
Found live 2026-09-02 on a package published to the marketplace: a plain skill zip carrying its own
unrelated `manifest.json` (name/kind/version) was rejected as "bundle manifest is invalid", because
staging keyed on the filename alone. The half that must not get lost while fixing that is the
strict path: anything shaped like OUR manifest still has to fail loudly when it has been tampered
with, rather than being quietly reinterpreted as a skill.
"""
import io
import json
import shutil
import zipfile
import pytest
from backend.apps.swarm import closure
from backend.apps.swarm.models import EntityType
from backend.apps.swarm.ziputil import BundleError
FOREIGN_MANIFEST = {"name": "hello-world", "kind": "skill", "version": "1.0.0", "description": "hi"}
SKILL_MD = "---\nname: hello-world\ndescription: Says hello\n---\n\nSay hello.\n"
def p_zip(members: dict) -> bytes:
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
for name, body in members.items():
z.writestr(name, body)
return buf.getvalue()
def test_a_skill_zip_with_someone_elses_manifest_installs_as_a_skill():
raw = p_zip({"manifest.json": json.dumps(FOREIGN_MANIFEST), "SKILL.md": SKILL_MD})
sandbox, manifest, warnings = closure.stage_upload(raw, "hello-world.swarm")
try:
assert manifest.root.type == EntityType.skill
assert closure.summarize(manifest).root.type == EntityType.skill
finally:
shutil.rmtree(sandbox, ignore_errors=True)
def test_a_manifest_shaped_like_ours_still_fails_loudly_when_it_is_broken():
"""The negative control for the fallthrough: a damaged bundle of ours must never be salvaged
into a skill install, because that would turn a tamper check into a silent downgrade."""
ours = {"bundle_id": "abc", "root": {"type": "app", "bundle_id": "abc", "name": "X", "path": "entities/abc"},
"entities": [], "checksum": "0" * 64, "format_version": 1}
raw = p_zip({"manifest.json": json.dumps(ours), "SKILL.md": SKILL_MD})
with pytest.raises(BundleError):
closure.stage_upload(raw, "tampered.swarm")
def test_the_discriminator_reads_the_three_keys_only_our_manifest_has():
assert closure.looks_like_our_manifest({"bundle_id": "a", "root": {}, "entities": []}) is True
assert closure.looks_like_our_manifest(FOREIGN_MANIFEST) is False
assert closure.looks_like_our_manifest(["not", "a", "dict"]) is False
def test_a_zip_that_is_neither_says_so_rather_than_installing_something():
raw = p_zip({"manifest.json": json.dumps(FOREIGN_MANIFEST), "README.txt": "nothing to install"})
with pytest.raises(BundleError):
closure.stage_upload(raw, "empty.swarm")
@@ -1,299 +0,0 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import CircularProgress from '@mui/material/CircularProgress';
import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
import AddIcon from '@mui/icons-material/Add';
import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined';
import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
import { fetchSkills } from '@/shared/state/skillsSlice';
import {
fetchAllRegistrySkills,
installCuratedSkill,
INSTALL_COMMAND_RE,
parseInstallCommand,
searchCommunitySkills,
CommunitySkill,
RegistrySkill,
} from '@/shared/state/skillRegistrySlice';
import DirectoryFilterBar from './DirectoryFilterBar';
import CommunityInstallConfirm from './dialogs/CommunityInstallConfirm';
interface Props {
onOpenInstalled?: (skillId: string) => void;
}
interface SkillCardModel {
key: string;
slug: string;
publisher: string;
description: string;
installs: number | null;
isCommunity: boolean;
curated?: RegistrySkill;
community?: CommunitySkill;
}
export function formatInstallCount(n: number): string {
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`;
if (n >= 1_000) return `${(n / 1_000).toFixed(1).replace(/\.0$/, '')}K`;
return String(n);
}
const DirectorySkillsTab: React.FC<Props> = ({ onOpenInstalled }) => {
const c = useClaudeTokens();
const dispatch = useAppDispatch();
const { skills: curated, loading: curatedLoading } = useAppSelector((s) => s.skillRegistry);
const localSkills = useAppSelector((s) => s.skills.items);
const [query, setQuery] = useState('');
// claude.ai grammar: Filter by is checkable sections. Community starts unchecked so the landing view is the Anthropic set, same as claude.ai's.
const [filterSelected, setFilterSelected] = useState<string[]>(['installed', 'not-installed', 'anthropic']);
const [sort, setSort] = useState('popular');
const toggleFilter = (value: string) => setFilterSelected((p) => (p.includes(value) ? p.filter((v) => v !== value) : [...p, value]));
const [community, setCommunity] = useState<CommunitySkill[]>([]);
const [communityLoading, setCommunityLoading] = useState(false);
const [installingKey, setInstallingKey] = useState<string | null>(null);
const [confirmTarget, setConfirmTarget] = useState<CommunitySkill | null>(null);
const [snackbar, setSnackbar] = useState<{ open: boolean; message: string; severity: 'success' | 'error' }>({ open: false, message: '', severity: 'success' });
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const searchSeq = useRef(0);
useEffect(() => {
if (curated.length === 0) dispatch(fetchAllRegistrySkills());
dispatch(fetchSkills());
}, [dispatch, curated.length]);
// skills.sh is the npx-installable community registry; its results carry install counts.
useEffect(() => {
if (debounceRef.current) clearTimeout(debounceRef.current);
debounceRef.current = setTimeout(async () => {
const seq = ++searchSeq.current;
setCommunityLoading(true);
try {
// A pasted README install command ("npx skills add pdf-filler", a skills.sh URL) resolves to
// its skill id server-side first, so the box accepts the grammar people actually copy (ENG-217).
let effective = query.trim();
if (INSTALL_COMMAND_RE.test(effective)) {
const skillId = await parseInstallCommand(effective).catch(() => null);
if (skillId) effective = skillId;
}
const res = await searchCommunitySkills(effective);
if (seq === searchSeq.current) setCommunity(res);
} catch {
if (seq === searchSeq.current) setCommunity([]);
} finally {
if (seq === searchSeq.current) setCommunityLoading(false);
}
}, 300);
return () => { if (debounceRef.current) clearTimeout(debounceRef.current); };
}, [query]);
const installedNames = useMemo(
() => new Set(Object.values(localSkills).map((s) => s.name.trim().toLowerCase())),
[localSkills],
);
const localIdByName = useMemo(() => {
const m: Record<string, string> = {};
for (const s of Object.values(localSkills)) m[s.name.trim().toLowerCase()] = s.id;
return m;
}, [localSkills]);
const cards = useMemo((): SkillCardModel[] => {
// A pasted install command already resolved server-side into the community results; filtering
// those hits against the raw paste string would hide the exact skill the user asked for.
const q = INSTALL_COMMAND_RE.test(query.trim()) ? '' : query.trim().toLowerCase();
// Join curated skills to their skills.sh twin (source anthropics/skills) so Anthropic cards get real install counts.
const communityByName = new Map(community.map((cs) => [cs.name.trim().toLowerCase(), cs]));
const out: SkillCardModel[] = [];
if (filterSelected.includes('anthropic')) {
for (const sk of curated) {
if (q && !sk.name.toLowerCase().includes(q) && !sk.description.toLowerCase().includes(q)) continue;
const twin = communityByName.get(sk.name.trim().toLowerCase());
out.push({
key: `curated:${sk.folder}`,
slug: sk.name.toLowerCase().replace(/\s+/g, '-'),
publisher: 'Anthropic',
description: sk.description,
installs: twin && /anthropic/i.test(twin.source) ? twin.installs : null,
isCommunity: false,
curated: sk,
});
}
}
if (filterSelected.includes('community')) {
const curatedNames = new Set(curated.map((sk) => sk.name.trim().toLowerCase()));
for (const cs of community) {
// Anthropic-sourced twins already render as curated cards; listing them twice reads as duplicates.
if (/anthropic/i.test(cs.source) && curatedNames.has(cs.name.trim().toLowerCase())) continue;
if (q && !cs.name.toLowerCase().includes(q) && !cs.source.toLowerCase().includes(q)) continue;
out.push({
key: `community:${cs.source}/${cs.skillId}`,
slug: cs.skillId.toLowerCase().replace(/\s+/g, '-'),
publisher: cs.source,
// skills.sh sometimes echoes the install count as the description; the meta line already carries it.
description: /^[\d,.]+ installs?$/.test(cs.description.trim()) ? '' : cs.description,
installs: cs.installs,
isCommunity: true,
community: cs,
});
}
}
const statusOk = (installed: boolean): boolean => (installed ? filterSelected.includes('installed') : filterSelected.includes('not-installed'));
const withStatus = out.filter((card) => statusOk(installedNames.has((card.curated?.name ?? card.community?.name ?? '').trim().toLowerCase())));
if (sort === 'name') withStatus.sort((a, b) => a.slug.localeCompare(b.slug));
else {
// claude.ai's landing order: installed first, then popularity descending.
const rank = (card: SkillCardModel): number => (installedNames.has((card.curated?.name ?? card.community?.name ?? '').trim().toLowerCase()) ? 1 : 0);
withStatus.sort((a, b) => rank(b) - rank(a) || (b.installs ?? -1) - (a.installs ?? -1));
}
return withStatus;
}, [curated, community, query, filterSelected, sort, installedNames]);
const handleInstallCurated = async (card: SkillCardModel) => {
if (!card.curated) return;
setInstallingKey(card.key);
try {
await dispatch(installCuratedSkill(card.curated.folder)).unwrap();
await dispatch(fetchSkills());
setSnackbar({ open: true, message: `Installed "${card.curated.name}"`, severity: 'success' });
} catch (e) {
const msg = (e as { message?: string })?.message || 'unknown error';
setSnackbar({ open: true, message: `Install failed: ${msg}`, severity: 'error' });
} finally {
setInstallingKey(null);
}
};
const isInstalled = (card: SkillCardModel): boolean => {
const name = (card.curated?.name ?? card.community?.name ?? '').trim().toLowerCase();
return installedNames.has(name);
};
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, height: '100%', minHeight: 0 }}>
<DirectoryFilterBar
searchPlaceholder="Search skills..."
query={query}
onQuery={setQuery}
filterSections={[
{ label: 'Status', options: [{ value: 'installed', label: 'Installed' }, { value: 'not-installed', label: 'Not installed' }] },
{ label: 'Source', options: [{ value: 'anthropic', label: 'Anthropic' }, { value: 'community', label: 'Community' }] },
]}
filterSelected={filterSelected}
onToggleFilter={toggleFilter}
sortOptions={[
{ value: 'popular', label: 'Most popular' },
{ value: 'name', label: 'Name A-Z' },
]}
sortValue={sort}
onSort={setSort}
/>
<Box sx={{
flex: 1, minHeight: 0, overflow: 'auto', pr: 0.5,
display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1.75, alignContent: 'start',
'&::-webkit-scrollbar': { width: 6 },
'&::-webkit-scrollbar-thumb': { background: c.border.medium, borderRadius: 3 },
}}>
{(curatedLoading && curated.length === 0) || (communityLoading && cards.length === 0) ? (
<Box sx={{ gridColumn: '1 / -1', display: 'flex', justifyContent: 'center', pt: 8 }}>
<CircularProgress size={24} sx={{ color: c.accent.primary }} />
</Box>
) : cards.length === 0 ? (
<Box sx={{ gridColumn: '1 / -1', display: 'flex', justifyContent: 'center', pt: 8 }}>
<Typography sx={{ fontSize: '0.875rem', color: c.text.ghost }}>No skills match your search.</Typography>
</Box>
) : cards.map((card) => {
const installed = isInstalled(card);
return (
<Box
key={card.key}
sx={{
border: `1px solid ${c.border.subtle}`, borderRadius: '14px', p: 2.25,
bgcolor: c.bg.surface, display: 'flex', flexDirection: 'column', gap: 0.5,
transition: 'border-color 0.12s, box-shadow 0.12s',
'&:hover': { borderColor: c.border.medium, boxShadow: c.shadow.sm },
}}
>
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 1 }}>
<Typography noWrap sx={{ fontSize: '1rem', fontWeight: 700, color: c.text.primary }}>
/{card.slug}
</Typography>
{installingKey === card.key ? (
<CircularProgress size={18} sx={{ color: c.text.tertiary, m: 0.5 }} />
) : installed ? (
<IconButton
size="small"
onClick={() => {
const id = localIdByName[(card.curated?.name ?? card.community?.name ?? '').trim().toLowerCase()];
if (id && onOpenInstalled) onOpenInstalled(id);
}}
sx={{ color: c.text.tertiary, '&:hover': { color: c.text.primary } }}
>
<SettingsOutlinedIcon sx={{ fontSize: 19 }} />
</IconButton>
) : (
<IconButton
size="small"
onClick={() => (card.isCommunity ? setConfirmTarget(card.community ?? null) : void handleInstallCurated(card))}
sx={{ color: c.text.secondary, '&:hover': { color: c.text.primary, bgcolor: c.bg.secondary } }}
>
<AddIcon sx={{ fontSize: 20 }} />
</IconButton>
)}
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mt: -0.5 }}>
<Typography noWrap sx={{ fontSize: '0.8125rem', color: c.text.tertiary }}>{card.publisher}</Typography>
{card.installs !== null && (
<>
<Box sx={{ width: 3, height: 3, borderRadius: '50%', bgcolor: c.text.ghost, flexShrink: 0 }} />
<Box sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.25, color: c.text.tertiary }}>
<FileDownloadOutlinedIcon sx={{ fontSize: 14 }} />
<Typography sx={{ fontSize: '0.8125rem' }}>{formatInstallCount(card.installs)}</Typography>
</Box>
</>
)}
</Box>
{card.description && (
<Typography sx={{
fontSize: '0.875rem', color: c.text.secondary, lineHeight: 1.5,
display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden',
}}>
{card.description}
</Typography>
)}
</Box>
);
})}
</Box>
<CommunityInstallConfirm
skill={confirmTarget}
onClose={() => setConfirmTarget(null)}
onInstalled={(name) => {
setConfirmTarget(null);
void dispatch(fetchSkills());
setSnackbar({ open: true, message: `Installed "${name}"`, severity: 'success' });
}}
/>
<Snackbar
open={snackbar.open}
autoHideDuration={3000}
onClose={() => setSnackbar((p) => ({ ...p, open: false }))}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<Alert severity={snackbar.severity} onClose={() => setSnackbar((p) => ({ ...p, open: false }))} sx={{ fontSize: '0.8125rem' }}>
{snackbar.message}
</Alert>
</Snackbar>
</Box>
);
};
export default DirectorySkillsTab;
@@ -1,120 +0,0 @@
import React, { useEffect, useRef, useState } from 'react';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import CircularProgress from '@mui/material/CircularProgress';
import Alert from '@mui/material/Alert';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { installCommunitySkill, CommunitySkill, InstallDisclosure } from '@/shared/state/skillRegistrySlice';
interface Props {
skill: CommunitySkill | null;
onClose: () => void;
onInstalled: (name: string) => void;
}
// The Directory's community-install gate: skills.sh code is unvetted, so the + never installs blind.
// Same disclosure contract as the old CommunitySkillsDialog: files + scripts shown before anything lands.
const CommunityInstallConfirm: React.FC<Props> = ({ skill, onClose, onInstalled }) => {
const c = useClaudeTokens();
const [disclosure, setDisclosure] = useState<InstallDisclosure | null>(null);
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const previewSeq = useRef(0);
useEffect(() => {
setDisclosure(null);
setError(null);
if (!skill) return;
const seq = ++previewSeq.current;
setBusy(true);
void installCommunitySkill(skill.source, skill.skillId, false)
.then((res) => { if (seq === previewSeq.current) setDisclosure(res.disclosure); })
.catch((e: unknown) => { if (seq === previewSeq.current) setError(e instanceof Error ? e.message : 'Could not load skill'); })
.finally(() => { if (seq === previewSeq.current) setBusy(false); });
}, [skill]);
const confirmInstall = async () => {
if (!skill) return;
setBusy(true);
setError(null);
try {
await installCommunitySkill(skill.source, skill.skillId, true);
onInstalled(disclosure?.name || skill.name);
} catch (e) {
setError(e instanceof Error ? e.message : 'Install failed');
} finally {
setBusy(false);
}
};
return (
<Dialog open={!!skill} onClose={onClose} maxWidth="sm" fullWidth
PaperProps={{ sx: { bgcolor: c.bg.surface, backgroundImage: 'none', borderRadius: '14px', border: `1px solid ${c.border.subtle}` } }}>
<DialogTitle sx={{ color: c.text.primary, fontSize: '1.0625rem', fontWeight: 700, pb: 0.5 }}>
Install community skill
</DialogTitle>
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 1, minHeight: 180 }}>
{error && <Alert severity="error" sx={{ fontSize: '0.8125rem' }}>{error}</Alert>}
{busy && !disclosure && <Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}><CircularProgress size={22} /></Box>}
{disclosure && (
<>
<Typography sx={{ fontSize: '1rem', fontWeight: 700, color: c.text.primary }}>{disclosure.name}</Typography>
{disclosure.description && (
<Typography sx={{ fontSize: '0.8125rem', color: c.text.secondary }}>{disclosure.description}</Typography>
)}
<Box
component="a" href={disclosure.repo_url} target="_blank" rel="noreferrer"
sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.5, fontSize: '0.75rem', color: c.accent.primary, textDecoration: 'none', fontFamily: c.font.mono }}>
{skill?.source} <OpenInNewIcon sx={{ fontSize: 13 }} />
</Box>
<Alert severity="info" icon={<WarningAmberIcon fontSize="small" />} sx={{ fontSize: '0.75rem', py: 0 }}>
This is an unvetted community skill. Its SKILL.md becomes instructions your agent will follow, and it can use your agent's tools (files, browser, settings). Only install from a source you trust, read it below first.
</Alert>
{disclosure.secret_findings.length > 0 && (
<Alert severity="error" icon={<WarningAmberIcon fontSize="small" />} sx={{ fontSize: '0.75rem', py: 0 }}>
{disclosure.secret_findings.length} file{disclosure.secret_findings.length === 1 ? '' : 's'} contain secret-shaped text ({disclosure.secret_findings.slice(0, 3).join(', ')}{disclosure.secret_findings.length > 3 ? '' : ''}). A trustworthy skill shouldn't ship credentials; treat this as a red flag.
</Alert>
)}
{disclosure.has_scripts && (
<Alert severity="warning" icon={<WarningAmberIcon fontSize="small" />} sx={{ fontSize: '0.75rem', py: 0 }}>
Includes {disclosure.scripts.length} script file{disclosure.scripts.length === 1 ? '' : 's'} that can run code when an agent uses this skill. Installing only writes the files; nothing runs until an agent does, and that still goes through normal command approval.
</Alert>
)}
<Typography sx={{ fontSize: '0.75rem', color: c.text.tertiary, mt: 0.5 }}>
{disclosure.files.length} file{disclosure.files.length === 1 ? '' : 's'} will be installed:
</Typography>
<Box sx={{ maxHeight: 120, overflow: 'auto', border: `1px solid ${c.border.subtle}`, borderRadius: `${c.radius.sm}px`, p: 1 }}>
{disclosure.files.map((f) => (
<Typography key={f} sx={{ fontSize: '0.75rem', fontFamily: c.font.mono, color: disclosure.scripts.includes(f) ? c.status.warning : c.text.secondary }}>
{disclosure.scripts.includes(f) ? '⚙ ' : ''}{f}
</Typography>
))}
</Box>
</>
)}
</DialogContent>
<DialogActions sx={{ px: 3, pb: 2 }}>
<Button onClick={onClose} sx={{ textTransform: 'none', color: c.text.tertiary }}>Cancel</Button>
{disclosure && (
<Button onClick={() => { void confirmInstall(); }} disabled={busy} variant="contained"
sx={{ textTransform: 'none', bgcolor: c.accent.primary, '&:hover': { bgcolor: c.accent.pressed } }}>
{busy ? 'Installing…' : 'Install skill'}
</Button>
)}
</DialogActions>
</Dialog>
);
};
export default CommunityInstallConfirm;