mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
* plugins: download Git sources without checkout, within a protocol allowlist and size cap Plugin and marketplace sources were a real clone/checkout, so user hooks, smudge filters and core.hooksPath ran, insteadOf rules could reach any transport, and nothing bounded the download. Now a --no-checkout depth-1 clone (or exact-commit fetch) runs with an empty hooks path, only the https and ssh transports, and Orca writes the commit's exact blobs from ls-tree + cat-file --batch. Symlinks, submodules, .git aliases, colliding portable paths and SHA-256 repositories are refused. A size watcher stops the whole Git process tree once the pack outgrows an approximate cap. The exact argv lives in shared code and joins the real-Git compatibility matrix, so the Git 2.25 lane runs it. Tests reach fixtures over an SSH shim, since file transports are now refused. * plugins: manifest v2 declares an execution profile data < instructional < sandboxed-ui < trusted-worker. A v2 manifest is strict and fails when it carries contributions beyond its profile; v1 manifests keep loading with the least profile inferred from what they declare. * plugins: pin exact catalog releases in orca-marketplace.v2.json A v2 catalog entry carries the release it vouches for: commit, content hash, version, profile, minimum Orca and contribution types. Preview and install fetch that commit directly and refuse bytes that differ. The v1 file stays strict and frozen for shipped clients; v2 entries strip unknown keys, and entries this client cannot install are dropped at parse time. Catalog fetches read only the index blobs under their own download cap, so a catalog repository is not held to one plugin's limits. Release-bearing snapshots are cached under a new key, so an older build never reads them and simply refetches. readMarketplaceJson and parseMarketplaceFile from #30612 are replaced by parsePluginMarketplaceIndex: catalogs are now read as Git blobs, so the on-disk JSON read has no caller left. * test(plugins): serve marketplace e2e fixtures over SSH Plugin downloads now refuse the file transport, even through an insteadOf rewrite, so the fresh-profile marketplace journey reaches its fixture repositories through the GIT_SSH_COMMAND shim the unit tests use. * test(plugins): give the Git compatibility plugin source its own identity * test(plugins): record the plugin fixture's executable bit in the Git index on every platform
67 lines
2.9 KiB
TypeScript
67 lines
2.9 KiB
TypeScript
import { mkdir, writeFile } from 'node:fs/promises'
|
|
import { join } from 'node:path'
|
|
import { expect, it } from 'vitest'
|
|
import {
|
|
pluginGitCloneArgs,
|
|
pluginGitConfigArgs,
|
|
pluginGitFetchCommitArgs,
|
|
pluginGitTreeArgs
|
|
} from './plugins/plugin-git-arguments'
|
|
|
|
export function registerPluginGitDownloadBinaryCompatibilityCase(
|
|
runGit: (
|
|
args: string[],
|
|
env?: NodeJS.ProcessEnv,
|
|
stdin?: string
|
|
) => Promise<{ stdout: string; stderr: string }>,
|
|
getRepoPath: () => string
|
|
): void {
|
|
// Why pin this: plugin downloads pass every `-c` key and flag below to the user's Git, and a
|
|
// local path counts as the file transport, so the protocol policy is observable offline.
|
|
it('downloads a plugin commit with the exact no-checkout arguments', async () => {
|
|
const source = 'compat-plugin-source'
|
|
await mkdir(join(getRepoPath(), source, 'bin'), { recursive: true })
|
|
await writeFile(join(getRepoPath(), source, 'bin', 'run'), 'plugin\n', { mode: 0o755 })
|
|
await runGit(['init', '-q', source])
|
|
await runGit(['-C', source, 'add', '--all'])
|
|
// Why: Windows ignores the 0o755 file mode, so record the executable bit in the index directly.
|
|
await runGit(['-C', source, 'update-index', '--chmod=+x', 'bin/run'])
|
|
// Why inline identity: a fresh repository does not inherit the fixture repository's config.
|
|
const identity = [
|
|
'-c',
|
|
'user.name=Compatibility Test',
|
|
'-c',
|
|
'user.email=compatibility@example.invalid'
|
|
]
|
|
await runGit(['-C', source, ...identity, 'commit', '-qm', 'plugin'])
|
|
await runGit(['-C', source, 'tag', 'v1'])
|
|
const commit = (await runGit(['-C', source, 'rev-parse', 'HEAD'])).stdout.trim()
|
|
const config = pluginGitConfigArgs('compat-no-hooks')
|
|
const allowLocal = ['-c', 'protocol.file.allow=always']
|
|
|
|
await expect(
|
|
runGit([...config, ...pluginGitCloneArgs(source, 'v1', 'refused')])
|
|
).rejects.toThrow("transport 'file' not allowed")
|
|
|
|
await runGit([...config, ...allowLocal, ...pluginGitCloneArgs(source, 'v1', 'compat-clone')])
|
|
await runGit([...config, 'init', '--quiet', '--template=', 'compat-fetch'])
|
|
await runGit(['-C', 'compat-fetch', ...config, 'remote', 'add', 'origin', `../${source}`])
|
|
await runGit([
|
|
'-C',
|
|
'compat-fetch',
|
|
...config,
|
|
...allowLocal,
|
|
...pluginGitFetchCommitArgs(commit)
|
|
])
|
|
for (const repository of ['compat-clone', 'compat-fetch']) {
|
|
const tree = await runGit(['-C', repository, ...pluginGitTreeArgs(commit)])
|
|
const [directory, file] = tree.stdout.split('\0')
|
|
expect(directory).toMatch(/^040000 tree [0-9a-f]{40} +-\tbin$/)
|
|
expect(file).toMatch(/^100755 blob [0-9a-f]{40} +7\tbin\/run$/)
|
|
const object = file?.slice(12, 52) ?? ''
|
|
const blob = await runGit(['-C', repository, 'cat-file', '--batch'], {}, `${object}\n`)
|
|
expect(blob.stdout).toBe(`${object} blob 7\nplugin\n\n`)
|
|
}
|
|
})
|
|
}
|