Preserve management of legacy Connector packages

Reject unsafe endpoint paths for new packages without blocking the package store from reading and disabling older packages. Checks: 231 Connector conformance tests.
This commit is contained in:
Ryder Sun committed 2026-11-07 22:10:26 +08:00
1 parent 72ff8c273b
commit 953ba18bfa
2 files changed
+31 -2

No files matched your search

@@ -52,9 +52,8 @@ function safeTextSchema(max) {
const httpsEndpointSchema = z.string().min(9).max(2048).superRefine((value, context) => {
try {
const parsed = new URL(value)
const decodedPath = decodeURIComponent(parsed.pathname)
if (parsed.protocol !== 'https:' || parsed.username || parsed.password
|| parsed.search || parsed.hash || redactSecrets(decodedPath) !== decodedPath) {
|| parsed.search || parsed.hash) {
context.addIssue({ code: 'custom', message: 'HTTPS endpoint required' })
}
} catch {
@@ -62,6 +61,15 @@ const httpsEndpointSchema = z.string().min(9).max(2048).superRefine((value, cont
}
})
function assertSafePackageEndpoint(provider) {
if (provider.id !== SDK_HTTP_JSON_RECIPE_ID) return
try {
const pathname = decodeURIComponent(new URL(provider.config.endpoint).pathname)
if (redactSecrets(pathname) === pathname) return
} catch { /* Invalid encoding cannot be safely checked. */ }
fail('AGENT_CONNECTOR_PACKAGE_SCHEMA_INVALID')
}
const publisherSchema = z.strictObject({
id: z.string().min(1).max(120).regex(PUBLIC_ID),
name: safeTextSchema(120),
@@ -160,6 +168,7 @@ function canonicalPackage(record) {
function createAgentConnectorPackage(input) {
const result = packageContentSchema.safeParse(input)
if (!result.success) fail('AGENT_CONNECTOR_PACKAGE_SCHEMA_INVALID')
assertSafePackageEndpoint(result.data.provider)
if (!supportsSdkCapabilities(parseAgentConnectorManifest(result.data.manifest))) {
fail('AGENT_CONNECTOR_PACKAGE_SCHEMA_INVALID')
}
@@ -475,6 +484,7 @@ class AgentConnectorPackageStore {
import(input, originFilename = 'connector-package.json') {
this.requireAvailable()
const packageRecord = parseAgentConnectorPackage(input)
assertSafePackageEndpoint(packageRecord.provider)
if (!supportsSdkCapabilities(packageRecord.manifest)) fail('AGENT_CONNECTOR_PACKAGE_SCHEMA_INVALID')
const existing = this.states.get(packageRecord.packageId)
if (existing && existing.state !== 'removed') return this.inspect(packageRecord.packageId)
@@ -4,6 +4,8 @@ const os = require('node:os')
const path = require('node:path')
const test = require('node:test')
const http = require('node:http')
const { createHash } = require('node:crypto')
const { canonicalJson } = require('../../../src/collaboration/outcome-records.cjs')
const { outboundWirePayloadBytes } = require('../../../src/collaboration/outbound-payload.cjs')
const { LocalWorkspace } = require('../../../src/workspace/local-workspace.cjs')
const { fixture: workspaceFixture } = require('../../support/local-workspace-test-helpers.cjs')
@@ -115,6 +117,23 @@ test('HTTP connector packages reject recognizable credentials in endpoint paths'
assert.doesNotThrow(() => create('https://connector.example.com/v1/run'))
})
test('older Connector packages remain manageable while unsafe new imports are rejected', t => {
const { packageStore } = fixture(t)
const original = httpPackage()
const { packageId: _packageId, ...body } = original
body.provider = { ...body.provider, config: { ...body.provider.config,
endpoint: `https://connector.example.com/v1/sk-${'a'.repeat(32)}` } }
const legacy = { ...body, packageId: `connector-package-${createHash('sha256').update(canonicalJson(body)).digest('hex')}` }
const serialized = canonicalJson(legacy)
assert.throws(() => packageStore.import(serialized), { message: 'AGENT_CONNECTOR_PACKAGE_SCHEMA_INVALID' })
fs.writeFileSync(packageStore.packagePath(legacy.packageId), serialized, { mode: 0o600 })
packageStore.append('imported', legacy.packageId, { originFilename: 'legacy.json', originSha256: 'a'.repeat(64) })
packageStore.append('approved', legacy.packageId, {})
packageStore.append('installed', legacy.packageId, {})
assert.equal(packageStore.list()[0].packageId, legacy.packageId)
assert.equal(packageStore.disable(legacy.packageId).state, 'disabled')
})
test('concurrent HTTP Connector accounts use only their own credentials at the transport boundary', async t => {
const requests = []
const previous = process.env.OPENAI_API_KEY