mirror of
https://github.com/Ryder-Sun/Meldwork.git
synced 2026-11-07 06:56:03 -10:00
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:
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
|
||||
|
||||
Reference in new issue
Block a user