feat(plugins): define marketplace trust contracts

This commit is contained in:
Neil
2026-07-13 03:33:44 -07:00
parent 7eef2bd8ed
commit 4558e5d46c
4 changed files with 501 additions and 0 deletions
@@ -0,0 +1,89 @@
import { describe, expect, it } from 'vitest'
import {
PLUGIN_KILL_LIST_ENTRY_LIMIT,
findKilledPlugin,
killedPluginKeys,
pluginKillListSchema
} from './plugin-kill-list'
function entry(pluginKey = 'community.unsafe'): Record<string, unknown> {
return {
pluginKey,
reason: 'Known malicious release',
advisoryUrl: 'https://orca.example/security/unsafe'
}
}
describe('pluginKillListSchema', () => {
it('parses a strict versioned revocation document', () => {
const parsed = pluginKillListSchema.parse({
version: 1,
generatedAt: '2026-07-12T20:00:00Z',
plugins: [entry()]
})
expect(findKilledPlugin(parsed, 'community.unsafe')).toEqual(entry())
expect(killedPluginKeys(parsed)).toEqual(new Set(['community.unsafe']))
})
it.each([
{
version: 1,
generatedAt: '2026-07-12T20:00:00Z',
plugins: [],
unexpected: true
},
{
version: 1,
generatedAt: '2026-07-12T20:00:00Z',
plugins: [{ ...entry(), unexpected: true }]
},
{
version: 1,
generatedAt: 'not-a-date',
plugins: []
},
{
version: 1,
generatedAt: '2026-07-12T20:00:00Z',
plugins: [{ ...entry(), pluginKey: 'unsafe' }]
},
{
version: 1,
generatedAt: '2026-07-12T20:00:00Z',
plugins: [{ ...entry(), advisoryUrl: 'http://orca.example/advisory' }]
}
])('rejects malformed or untrusted fields', (killList) => {
expect(pluginKillListSchema.safeParse(killList).success).toBe(false)
})
it('rejects duplicate killed plugin identities', () => {
const parsed = pluginKillListSchema.safeParse({
version: 1,
generatedAt: '2026-07-12T20:00:00Z',
plugins: [entry(), entry()]
})
expect(parsed.success).toBe(false)
if (!parsed.success) {
expect(parsed.error.issues).toEqual(
expect.arrayContaining([
expect.objectContaining({ message: 'duplicate killed plugin: community.unsafe' })
])
)
}
})
it('caps the revocation document size by entry count', () => {
const plugins = Array.from({ length: PLUGIN_KILL_LIST_ENTRY_LIMIT + 1 }, (_, index) =>
entry(`publisher.plugin-${index}`)
)
expect(
pluginKillListSchema.safeParse({
version: 1,
generatedAt: '2026-07-12T20:00:00Z',
plugins
}).success
).toBe(false)
})
})
+50
View File
@@ -0,0 +1,50 @@
import { z } from 'zod'
import { isQualifiedPluginKey } from './plugin-manifest'
export const PLUGIN_KILL_LIST_ENTRY_LIMIT = 4_096
const advisoryUrlSchema = z
.string()
.url()
.max(2_048)
.refine((value) => new URL(value).protocol === 'https:', 'advisory URL must use HTTPS')
export const pluginKillListEntrySchema = z.strictObject({
pluginKey: z.string().refine(isQualifiedPluginKey, 'invalid qualified plugin key'),
reason: z.string().min(1).max(1_024),
advisoryUrl: advisoryUrlSchema.optional()
})
export const pluginKillListSchema = z
.strictObject({
version: z.literal(1),
generatedAt: z.string().datetime({ offset: true }),
plugins: z.array(pluginKillListEntrySchema).max(PLUGIN_KILL_LIST_ENTRY_LIMIT)
})
.superRefine((killList, context) => {
const seen = new Set<string>()
for (const [index, plugin] of killList.plugins.entries()) {
if (seen.has(plugin.pluginKey)) {
context.addIssue({
code: z.ZodIssueCode.custom,
path: ['plugins', index, 'pluginKey'],
message: `duplicate killed plugin: ${plugin.pluginKey}`
})
}
seen.add(plugin.pluginKey)
}
})
export type PluginKillList = z.infer<typeof pluginKillListSchema>
export type PluginKillListEntry = z.infer<typeof pluginKillListEntrySchema>
export function killedPluginKeys(killList: PluginKillList): ReadonlySet<string> {
return new Set(killList.plugins.map((plugin) => plugin.pluginKey))
}
export function findKilledPlugin(
killList: PluginKillList,
pluginKey: string
): PluginKillListEntry | null {
return killList.plugins.find((plugin) => plugin.pluginKey === pluginKey) ?? null
}
@@ -0,0 +1,178 @@
import { describe, expect, it } from 'vitest'
import {
OFFICIAL_MARKETPLACE_REPOSITORY,
PLUGIN_MARKETPLACE_CATEGORY_LIMIT,
PLUGIN_MARKETPLACE_ENTRY_LIMIT,
isOfficialMarketplaceGitSource,
isOfficialOrganizationGitSource,
isOfficialPluginIdentity,
isReservedPluginIdentity,
parseGitRepositoryIdentity,
pluginMarketplaceSchema,
pluginMarketplaceTrustMetadataSchema
} from './plugin-marketplace'
function listing(id = 'community.nord'): Record<string, unknown> {
return {
id,
source: { kind: 'git', url: 'https://github.com/community/nord.git', ref: 'v1.0.0' },
description: 'A theme pack',
categories: ['themes']
}
}
describe('pluginMarketplaceSchema', () => {
it('parses a bounded, pinned Git marketplace index', () => {
expect(
pluginMarketplaceSchema.parse({
name: 'Community plugins',
owner: 'community',
plugins: [listing()]
})
).toEqual({
name: 'Community plugins',
owner: 'community',
plugins: [listing()]
})
})
it.each([
['root', { name: 'Plugins', owner: 'team', plugins: [], extra: true }],
['entry', { name: 'Plugins', owner: 'team', plugins: [{ ...listing(), official: true }] }],
[
'source',
{
name: 'Plugins',
owner: 'team',
plugins: [
{
...listing(),
source: {
kind: 'git',
url: 'https://github.com/community/nord.git',
ref: 'main',
depth: 1
}
}
]
}
]
])('rejects unknown keys at the %s boundary', (_boundary, marketplace) => {
expect(pluginMarketplaceSchema.safeParse(marketplace).success).toBe(false)
})
it.each([
['an unqualified id', { ...listing(), id: 'nord' }],
[
'a missing ref',
{
...listing(),
source: { kind: 'git', url: 'https://github.com/community/nord.git', ref: '' }
}
],
[
'an executable Git transport',
{
...listing(),
source: { kind: 'git', url: 'ext::sh -c exploit', ref: 'main' }
}
],
['a duplicate category', { ...listing(), categories: ['themes', 'themes'] }],
[
'too many categories',
{
...listing(),
categories: Array.from(
{ length: PLUGIN_MARKETPLACE_CATEGORY_LIMIT + 1 },
(_, index) => `category-${index}`
)
}
]
])('rejects %s', (_label, entry) => {
expect(
pluginMarketplaceSchema.safeParse({ name: 'Plugins', owner: 'team', plugins: [entry] })
.success
).toBe(false)
})
it('rejects duplicate plugin identities', () => {
const parsed = pluginMarketplaceSchema.safeParse({
name: 'Plugins',
owner: 'team',
plugins: [listing(), listing()]
})
expect(parsed.success).toBe(false)
if (!parsed.success) {
expect(parsed.error.issues).toEqual(
expect.arrayContaining([
expect.objectContaining({ message: 'duplicate plugin id: community.nord' })
])
)
}
})
it('caps the marketplace entry count', () => {
const plugins = Array.from({ length: PLUGIN_MARKETPLACE_ENTRY_LIMIT + 1 }, (_, index) =>
listing(`publisher.plugin-${index}`)
)
expect(
pluginMarketplaceSchema.safeParse({ name: 'Plugins', owner: 'team', plugins }).success
).toBe(false)
})
})
describe('marketplace provenance contracts', () => {
it.each([
['stablyai.orca-skills', true, true],
['stablyai.skills', true, false],
['community.orca-skills', true, false],
['community.skills', false, false],
['invalid', false, false]
])('classifies %s', (pluginKey, reserved, official) => {
expect(isReservedPluginIdentity(pluginKey)).toBe(reserved)
expect(isOfficialPluginIdentity(pluginKey)).toBe(official)
})
it.each([
'https://github.com/stablyai/orca-skills.git',
'ssh://git@github.com/stablyai/orca-skills.git',
'git@github.com:stablyai/orca-skills.git'
])('accepts official organization source %s', (source) => {
expect(isOfficialOrganizationGitSource(source)).toBe(true)
})
it('does not trust lookalike organizations or hosts', () => {
expect(isOfficialOrganizationGitSource('https://github.com/stablyai-fakes/orca-skills')).toBe(
false
)
expect(isOfficialOrganizationGitSource('https://gitlab.com/stablyai/orca-skills')).toBe(false)
})
it('recognizes only the canonical official marketplace repository', () => {
expect(
isOfficialMarketplaceGitSource(
`git@github.com:stablyai/${OFFICIAL_MARKETPLACE_REPOSITORY}.git`
)
).toBe(true)
expect(isOfficialMarketplaceGitSource('git@github.com:stablyai/plugins.git')).toBe(false)
})
it('parses nested repository paths without confusing the repository name', () => {
expect(parseGitRepositoryIdentity('https://gitlab.com/team/subgroup/plugin.git')).toEqual({
host: 'gitlab.com',
owner: 'team',
repository: 'plugin'
})
})
it('prevents an untrusted listing from self-awarding trust metadata', () => {
expect(pluginMarketplaceTrustMetadataSchema.parse({ official: true, bundled: true })).toEqual({
official: true,
bundled: true
})
expect(
pluginMarketplaceTrustMetadataSchema.safeParse({ official: false, bundled: true }).success
).toBe(false)
})
})
+184
View File
@@ -0,0 +1,184 @@
import { z } from 'zod'
import { isAllowedPluginGitUrl } from './plugin-install-lockfile'
import { isQualifiedPluginKey } from './plugin-manifest'
export const PLUGIN_MARKETPLACE_FILENAME = 'orca-marketplace.json'
export const PLUGIN_MARKETPLACE_ENTRY_LIMIT = 2_048
export const PLUGIN_MARKETPLACE_CATEGORY_LIMIT = 16
export const OFFICIAL_PLUGIN_PUBLISHER = 'stablyai'
export const OFFICIAL_PLUGIN_ID_PREFIX = 'orca-'
export const OFFICIAL_MARKETPLACE_OWNER = 'stablyai'
export const OFFICIAL_MARKETPLACE_REPOSITORY = 'orca-plugins'
const marketplaceOwnerSchema = z
.string()
.min(1)
.max(128)
.regex(/^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/, 'invalid marketplace owner')
const marketplaceCategorySchema = z
.string()
.min(1)
.max(64)
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'categories must be lowercase slugs')
export const pluginMarketplaceGitSourceSchema = z.strictObject({
kind: z.literal('git'),
url: z
.string()
.trim()
.min(1)
.max(32 * 1024)
.refine(isAllowedPluginGitUrl, 'git URL must use HTTPS or SSH'),
// Why: marketplace installs must first resolve a named ref to an exact
// commit; an omitted remote default would make the listing irreproducible.
ref: z.string().trim().min(1).max(4_096)
})
export const pluginMarketplaceEntrySchema = z
.strictObject({
/** Canonical `<publisher>.<id>` identity expected in the source manifest. */
id: z.string().refine(isQualifiedPluginKey, 'invalid qualified plugin key'),
source: pluginMarketplaceGitSourceSchema,
description: z.string().min(1).max(4_096).optional(),
categories: z
.array(marketplaceCategorySchema)
.max(PLUGIN_MARKETPLACE_CATEGORY_LIMIT)
.default([])
})
.superRefine((entry, context) => {
const seen = new Set<string>()
for (const [index, category] of entry.categories.entries()) {
if (seen.has(category)) {
context.addIssue({
code: z.ZodIssueCode.custom,
path: ['categories', index],
message: `duplicate category: ${category}`
})
}
seen.add(category)
}
})
export const pluginMarketplaceSchema = z
.strictObject({
name: z.string().min(1).max(256),
owner: marketplaceOwnerSchema,
plugins: z.array(pluginMarketplaceEntrySchema).max(PLUGIN_MARKETPLACE_ENTRY_LIMIT)
})
.superRefine((marketplace, context) => {
const seen = new Set<string>()
for (const [index, plugin] of marketplace.plugins.entries()) {
if (seen.has(plugin.id)) {
context.addIssue({
code: z.ZodIssueCode.custom,
path: ['plugins', index, 'id'],
message: `duplicate plugin id: ${plugin.id}`
})
}
seen.add(plugin.id)
}
})
/** Host-derived trust metadata. Marketplace JSON cannot self-award either bit. */
export const pluginMarketplaceTrustMetadataSchema = z
.strictObject({
official: z.boolean(),
bundled: z.boolean()
})
.refine((metadata) => !metadata.bundled || metadata.official, {
message: 'bundled plugins must be official',
path: ['bundled']
})
export type PluginMarketplace = z.infer<typeof pluginMarketplaceSchema>
export type PluginMarketplaceEntry = z.infer<typeof pluginMarketplaceEntrySchema>
export type PluginMarketplaceGitSource = z.infer<typeof pluginMarketplaceGitSourceSchema>
export type PluginMarketplaceTrustMetadata = z.infer<typeof pluginMarketplaceTrustMetadataSchema>
export function splitQualifiedPluginKey(pluginKey: string): {
publisher: string
id: string
} | null {
if (!isQualifiedPluginKey(pluginKey)) {
return null
}
const separator = pluginKey.indexOf('.')
return {
publisher: pluginKey.slice(0, separator),
id: pluginKey.slice(separator + 1)
}
}
export function isReservedPluginIdentity(pluginKey: string): boolean {
const identity = splitQualifiedPluginKey(pluginKey)
return (
identity !== null &&
(identity.publisher === OFFICIAL_PLUGIN_PUBLISHER ||
identity.id.startsWith(OFFICIAL_PLUGIN_ID_PREFIX))
)
}
export function isOfficialPluginIdentity(pluginKey: string): boolean {
const identity = splitQualifiedPluginKey(pluginKey)
return (
identity !== null &&
identity.publisher === OFFICIAL_PLUGIN_PUBLISHER &&
identity.id.startsWith(OFFICIAL_PLUGIN_ID_PREFIX)
)
}
type GitRepositoryIdentity = {
host: string
owner: string
repository: string
}
/** Extracts the host and first owner segment from the HTTPS/SSH Git forms the
* installer accepts. It is intentionally only a provenance parser. */
export function parseGitRepositoryIdentity(url: string): GitRepositoryIdentity | null {
const trimmed = url.trim()
const scp = /^[^\s@/:]+@([^\s:]+):(.+)$/.exec(trimmed)
if (scp) {
return repositoryIdentity(scp[1]!, scp[2]!)
}
try {
const parsed = new URL(trimmed)
if (parsed.protocol !== 'https:' && parsed.protocol !== 'ssh:') {
return null
}
return repositoryIdentity(parsed.hostname, parsed.pathname)
} catch {
return null
}
}
function repositoryIdentity(host: string, repositoryPath: string): GitRepositoryIdentity | null {
const segments = repositoryPath
.replace(/^\/+|\/+$/g, '')
.split('/')
.filter(Boolean)
if (segments.length < 2) {
return null
}
const repository = segments.at(-1)!.replace(/\.git$/i, '')
if (!repository) {
return null
}
return { host: host.toLowerCase(), owner: segments[0]!, repository }
}
export function isOfficialOrganizationGitSource(url: string): boolean {
const source = parseGitRepositoryIdentity(url)
return source?.host === 'github.com' && source.owner.toLowerCase() === OFFICIAL_PLUGIN_PUBLISHER
}
export function isOfficialMarketplaceGitSource(url: string): boolean {
const source = parseGitRepositoryIdentity(url)
return (
source?.host === 'github.com' &&
source.owner.toLowerCase() === OFFICIAL_MARKETPLACE_OWNER &&
source.repository.toLowerCase() === OFFICIAL_MARKETPLACE_REPOSITORY
)
}