mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-31 01:56:02 +00:00
fix(sync): address project privacy review
This commit is contained in:
parent
33dc05bb90
commit
cc721f00a7
6 changed files with 97 additions and 20 deletions
|
|
@ -62,13 +62,14 @@ sqliteDescribe('Goose sync project provenance', () => {
|
|||
created_timestamp INTEGER
|
||||
);
|
||||
`)
|
||||
db.prepare(`
|
||||
const insertSession = db.prepare(`
|
||||
INSERT INTO sessions (
|
||||
id, name, working_dir, created_at, updated_at,
|
||||
accumulated_input_tokens, accumulated_output_tokens,
|
||||
provider_name, model_config_json
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
`)
|
||||
insertSession.run(
|
||||
'goose-session-1',
|
||||
'LLM-authored session title',
|
||||
'/Users/alice/company/private-widget',
|
||||
|
|
@ -79,18 +80,33 @@ sqliteDescribe('Goose sync project provenance', () => {
|
|||
'openai',
|
||||
JSON.stringify({ model_name: 'gpt-5.4' }),
|
||||
)
|
||||
insertSession.run(
|
||||
'goose-container-session',
|
||||
'Container session title must stay local',
|
||||
'/sessions/synthetic-customer-secret',
|
||||
'2026-08-23T11:00:00.000Z',
|
||||
'2026-08-23T11:01:00.000Z',
|
||||
50,
|
||||
10,
|
||||
'openai',
|
||||
JSON.stringify({ model_name: 'gpt-5.4' }),
|
||||
)
|
||||
db.close()
|
||||
|
||||
const provider = createGooseProvider()
|
||||
const sources = await provider.discoverSessions()
|
||||
expect(sources).toHaveLength(1)
|
||||
expect(sources).toHaveLength(2)
|
||||
const calls = []
|
||||
for await (const providerCall of provider.createSessionParser(sources[0]!, new Set()).parse()) calls.push(providerCall)
|
||||
expect(calls).toHaveLength(1)
|
||||
expect(calls[0]!.workingDirectory).toBe('/Users/alice/company/private-widget')
|
||||
for (const source of sources) {
|
||||
for await (const providerCall of provider.createSessionParser(source, new Set()).parse()) calls.push(providerCall)
|
||||
}
|
||||
expect(calls).toHaveLength(2)
|
||||
const trusted = calls.find(call => call.sessionId === 'goose-session-1')!
|
||||
const container = calls.find(call => call.sessionId === 'goose-container-session')!
|
||||
expect(trusted.workingDirectory).toBe('/Users/alice/company/private-widget')
|
||||
expect(container.workingDirectory).toBe('/sessions/synthetic-customer-secret')
|
||||
|
||||
const raw = calls[0]!
|
||||
const parsed: ParsedApiCall = {
|
||||
const toParsed = (raw: typeof trusted): ParsedApiCall => ({
|
||||
provider: raw.provider,
|
||||
model: raw.model,
|
||||
usage: {
|
||||
|
|
@ -113,19 +129,23 @@ sqliteDescribe('Goose sync project provenance', () => {
|
|||
timestamp: raw.timestamp,
|
||||
bashCommands: raw.bashCommands,
|
||||
deduplicationKey: raw.deduplicationKey,
|
||||
}
|
||||
const payload = buildOtlpPayload([{
|
||||
call: parsed,
|
||||
})
|
||||
const payload = buildOtlpPayload(calls.map(raw => ({
|
||||
call: toParsed(raw),
|
||||
sessionId: raw.sessionId,
|
||||
project: sources[0]!.project,
|
||||
workingDirectory: raw.workingDirectory,
|
||||
}])
|
||||
const span = payload.resourceSpans[0]!.scopeSpans[0]!.spans[0]!
|
||||
const attributes = Object.fromEntries(span.attributes.map(attribute => [attribute.key, attribute.value]))
|
||||
})))
|
||||
const spans = payload.resourceSpans[0]!.scopeSpans[0]!.spans
|
||||
const trustedSpan = spans.find(span => span.spanId === deriveSpanId(trusted.deduplicationKey))!
|
||||
const containerSpan = spans.find(span => span.spanId === deriveSpanId(container.deduplicationKey))!
|
||||
const trustedAttributes = Object.fromEntries(trustedSpan.attributes.map(attribute => [attribute.key, attribute.value]))
|
||||
const containerAttributes = Object.fromEntries(containerSpan.attributes.map(attribute => [attribute.key, attribute.value]))
|
||||
|
||||
expect(attributes['ai.project']).toEqual({ stringValue: 'private-widget' })
|
||||
expect(trustedAttributes['ai.project']).toEqual({ stringValue: 'private-widget' })
|
||||
expect(containerAttributes['ai.project']).toBeUndefined()
|
||||
expect(JSON.stringify(payload)).not.toContain('/Users/alice')
|
||||
expect(JSON.stringify(payload)).not.toContain('synthetic-customer-secret')
|
||||
expect(JSON.stringify(payload)).not.toContain('LLM-authored session title')
|
||||
expect(span.spanId).toBe(deriveSpanId(raw.deduplicationKey))
|
||||
expect(JSON.stringify(payload)).not.toContain('Container session title must stay local')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ describe('sync project privacy boundary', () => {
|
|||
'/mnt/c/Users/alice',
|
||||
'/var/home/alice',
|
||||
'/net/home/alice',
|
||||
'/sessions/synthetic-customer-secret',
|
||||
'\\\\server\\Users\\alice',
|
||||
'D:\\Profiles\\alice',
|
||||
])('omits ai.project when cwd provenance is absent or unsafe: %s', cwd => {
|
||||
|
|
@ -116,6 +117,26 @@ describe('sync identifier privacy boundary', () => {
|
|||
expect(wire).not.toContain('synthetic-secret')
|
||||
expect(wire).not.toContain('alice@example.com')
|
||||
})
|
||||
|
||||
it('preserves real routed provider, model, and tool identifiers', () => {
|
||||
const payload = buildOtlpPayload([usage('/workspace/widget', {
|
||||
provider: 'openrouter',
|
||||
model: 'anthropic/claude-sonnet-4.6',
|
||||
tools: ['orcarouter/openai-compatible', 'mcp__github__search'],
|
||||
})])
|
||||
const span = payload.resourceSpans[0]!.scopeSpans[0]!.spans[0]!
|
||||
const attrs = Object.fromEntries(span.attributes.map(attribute => [attribute.key, attribute.value]))
|
||||
|
||||
expect(attrs['ai.provider']).toEqual({ stringValue: 'openrouter' })
|
||||
expect(attrs['ai.model']).toEqual({ stringValue: 'anthropic/claude-sonnet-4.6' })
|
||||
expect(attrs['ai.tools']).toEqual({
|
||||
arrayValue: { values: [
|
||||
{ stringValue: 'orcarouter/openai-compatible' },
|
||||
{ stringValue: 'mcp__github__search' },
|
||||
] },
|
||||
})
|
||||
expect(span.name).toBe('openrouter/anthropic/claude-sonnet-4.6')
|
||||
})
|
||||
})
|
||||
|
||||
describe('sync attribution project privacy boundary', () => {
|
||||
|
|
@ -150,4 +171,19 @@ describe('sync attribution project privacy boundary', () => {
|
|||
expect(attrs['ai.project']).toBeUndefined()
|
||||
expect(JSON.stringify(payload)).not.toContain('/Users/alice/secret')
|
||||
})
|
||||
|
||||
it('accepts owner/repo identities and rejects credential-directory repo labels', () => {
|
||||
const [legitimate, credentialShaped] = flattenAttributionRecords([
|
||||
record({ repo: 'acme/widget' }),
|
||||
record({ sessionId: 'privacy-session-2', repo: 'acme/.ssh' }),
|
||||
])
|
||||
|
||||
expect(legitimate?.project).toBe('widget')
|
||||
expect(credentialShaped?.project).toBeUndefined()
|
||||
const payload = buildAttributionOtlpPayload([legitimate!, credentialShaped!])
|
||||
const spans = payload.resourceSpans[0]!.scopeSpans[0]!.spans
|
||||
const credentialAttributes = Object.fromEntries(spans[1]!.attributes
|
||||
.map(attribute => [attribute.key, attribute.value]))
|
||||
expect(credentialAttributes['ai.project']).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue