070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
83 lines
2.9 KiB
TypeScript
83 lines
2.9 KiB
TypeScript
// Plan §3.A3 / spec §9: connector tool-token capability gate.
|
|
//
|
|
// Token-level §5.3 enforcement (no SQLite reads needed at execute time):
|
|
//
|
|
// 1. Non-plugin runs: grant has no plugin context; gate is bypassed.
|
|
// 2. Trusted plugins: implicit `connector:*`; any connector id passes.
|
|
// 3. Restricted plugins: must list `connector:<id>` in
|
|
// pluginCapabilitiesGranted; otherwise the call is rejected so a
|
|
// replayed token can't reach a connector that was never granted.
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
checkConnectorAccess,
|
|
ToolTokenRegistry,
|
|
type ToolTokenGrant,
|
|
} from '../src/tool-tokens.js';
|
|
|
|
function mintGrant(registry: ToolTokenRegistry, overrides: Partial<{
|
|
pluginSnapshotId: string;
|
|
pluginTrust: 'trusted' | 'restricted' | 'bundled';
|
|
pluginCapabilitiesGranted: string[];
|
|
}> = {}): ToolTokenGrant {
|
|
return registry.mint({
|
|
runId: 'run-1',
|
|
projectId: 'project-1',
|
|
...overrides,
|
|
});
|
|
}
|
|
|
|
describe('checkConnectorAccess', () => {
|
|
it('lets non-plugin runs through (no snapshot id on the grant)', () => {
|
|
const registry = new ToolTokenRegistry();
|
|
const grant = mintGrant(registry);
|
|
expect(checkConnectorAccess(grant, 'slack')).toEqual({ ok: true });
|
|
});
|
|
|
|
it('lets trusted plugins through (implicit connector:*)', () => {
|
|
const registry = new ToolTokenRegistry();
|
|
const grant = mintGrant(registry, {
|
|
pluginSnapshotId: 'snap-1',
|
|
pluginTrust: 'trusted',
|
|
pluginCapabilitiesGranted: ['prompt:inject'],
|
|
});
|
|
expect(checkConnectorAccess(grant, 'slack')).toEqual({ ok: true });
|
|
});
|
|
|
|
it('rejects restricted plugins missing connector:<id>', () => {
|
|
const registry = new ToolTokenRegistry();
|
|
const grant = mintGrant(registry, {
|
|
pluginSnapshotId: 'snap-2',
|
|
pluginTrust: 'restricted',
|
|
pluginCapabilitiesGranted: ['prompt:inject', 'fs:read'],
|
|
});
|
|
const result = checkConnectorAccess(grant, 'slack');
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) {
|
|
expect(result.reason).toMatch(/connector:slack/);
|
|
}
|
|
});
|
|
|
|
it('accepts restricted plugins with the explicit connector:<id> grant', () => {
|
|
const registry = new ToolTokenRegistry();
|
|
const grant = mintGrant(registry, {
|
|
pluginSnapshotId: 'snap-3',
|
|
pluginTrust: 'restricted',
|
|
pluginCapabilitiesGranted: ['prompt:inject', 'connector:notion'],
|
|
});
|
|
expect(checkConnectorAccess(grant, 'notion')).toEqual({ ok: true });
|
|
expect(checkConnectorAccess(grant, 'slack').ok).toBe(false);
|
|
});
|
|
|
|
it('accepts the coarse `connector` grant for any id', () => {
|
|
const registry = new ToolTokenRegistry();
|
|
const grant = mintGrant(registry, {
|
|
pluginSnapshotId: 'snap-4',
|
|
pluginTrust: 'restricted',
|
|
pluginCapabilitiesGranted: ['prompt:inject', 'connector'],
|
|
});
|
|
expect(checkConnectorAccess(grant, 'slack')).toEqual({ ok: true });
|
|
expect(checkConnectorAccess(grant, 'notion')).toEqual({ ok: true });
|
|
});
|
|
});
|