Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/core/src/claustrum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,14 +408,16 @@ function isValidCustodyCredentialId(value: unknown): value is string {
// NOT enumerate or constrain it: enumerating the kinds would reject live
// credentials the moment a new one ships. The third-and-later segments are
// the label, which is never consulted here — the label is the lookup key
// elsewhere, not an authorization check.
// elsewhere, not an authorization check. Empty segments are invalid because
// the co-tenant parser rejects the whole manifest for a malformed row, while
// ours can otherwise isolate it to the row that contains it.
function isScopedCustodyCredentialId(
value: unknown,
provider: string,
): value is string {
if (typeof value !== 'string' || value.length === 0) return false
const segments = value.split(':')
return segments[1] === provider
return segments[1] === provider && segments.every((segment) => segment !== '')
}

function legacyOrUnresolved(
Expand Down
80 changes: 78 additions & 2 deletions packages/core/src/tests/claustrum.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { describe, expect, test } from 'bun:test'
import { basename, win32 } from 'node:path'
import { afterEach, describe, expect, test } from 'bun:test'
import { exists, mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { basename, join, win32 } from 'node:path'

import {
__deriveCustodyManifestStaleLockPrefix,
Expand All @@ -8,8 +10,19 @@ import {
custodyCredentialIdFromResolution,
readCustodyHandles,
resolveCustodyHandle,
writeCustodyHandleManifestEntry,
} from '../claustrum.ts'

const temporaryDirectories: string[] = []

afterEach(async () => {
await Promise.all(
temporaryDirectories
.splice(0)
.map((directory) => rm(directory, { recursive: true, force: true })),
)
})

describe('custody manifest stale-lock prefix', () => {
test('derives prefixes from POSIX and Windows path basenames', () => {
expect(
Expand Down Expand Up @@ -311,4 +324,67 @@ describe('readCustodyHandles provider scope', () => {
expect(parsed.accounts[0]?.credentialId).toBe('antigravity:google')
expect(parsed.accounts[0]?.label).toBe('main')
})

test('resolves an `apikey:deepseek:main` id inside a deepseek block', () => {
const doc = makeManifest('deepseek', 'deepseek-auth')
doc.providers[0]!.accounts.push({
label: 'main',
handle: anthropicHandle,
credential_id: 'apikey:deepseek:main',
})

const parsed = readCustodyHandles(doc, 'deepseek', 'deepseek-auth')
expect(parsed.corruptLabels).toEqual(new Set())
expect(parsed.accounts).toHaveLength(1)
expect(parsed.accounts[0]?.credentialId).toBe('apikey:deepseek:main')
expect(parsed.accounts[0]?.label).toBe('main')
})

test('rejects credential ids with any empty colon-separated segment', () => {
const parseMalformed = (credentialId: string, label: string) => {
const doc = makeManifest('anthropic', 'anthropic-auth')
doc.providers[0]!.accounts.push({
label,
handle: anthropicHandle,
credential_id: credentialId,
})

const parsed = readCustodyHandles(doc, 'anthropic', 'anthropic-auth')
return parsed
}

expect(parseMalformed(':anthropic:x', 'empty-kind').corruptLabels).toEqual(
new Set(['empty-kind']),
)
expect(parseMalformed('oauth::x', 'empty-provider').corruptLabels).toEqual(
new Set(['empty-provider']),
)
expect(
parseMalformed('oauth:anthropic:', 'empty-label').corruptLabels,
).toEqual(new Set(['empty-label']))
expect(
parseMalformed('oauth:anthropic::y', 'empty-middle-label').corruptLabels,
).toEqual(new Set(['empty-middle-label']))
expect(parseMalformed('', 'empty-id').corruptLabels).toEqual(
new Set(['empty-id']),
)
})

test('writer refuses empty-segment credential ids before they reach disk', async () => {
const directory = await mkdtemp(join(tmpdir(), 'claustrum-manifest-'))
temporaryDirectories.push(directory)
const path = join(directory, 'handles.json')

await expect(
writeCustodyHandleManifestEntry({
path,
entry: {
label: 'main',
handle: anthropicHandle,
credentialId: 'oauth:anthropic:',
},
}),
).resolves.toEqual({ status: 'refused', reason: 'invalid entry' })
expect(await exists(path)).toBe(false)
})
})
Loading