codeburn/app/scripts/snap-grants.test.ts
iamtoruk c73d8ff6c8 snap: scope the entries main gained after the tightening
The ported declaration was written against main as of c9e6e2ec. Main has since
added dsh and carries five entries that commit never saw, all of them still bare
tool roots, so the port dropped them rather than reintroduce what the store
rejected. Each is restored at the path its provider opens:

  .dsh/sessions                          dsh.ts reads <DSH_HOME>/sessions only
  .kiro/sessions                         CLI store at sessions/cli, v2 IDE store
                                         at sessions/<hash> — siblings
  .quickwork/{profiles.json,sessions,metrics}
                                         profiles.json names the profile bases;
                                         the legacy layout is sessions/sessions.db
                                         plus metrics/
  .config/Claude/local-agent-mode-sessions
                                         Claude Desktop's local-agent-mode store
  .config/Open Design/{runs,data/runs,namespaces}
                                         the three discovery roots open-design.ts
                                         probes under its data dir

.lingtai and .lingtai-tui stay dropped. A LingTai ledger lives at
.lingtai/<agent>/logs/token_ledger.jsonl, and personal-files has no wildcard for
the agent segment; .lingtai-tui only exists to enumerate project homes that could
not be read anyway. Goose is narrowed to .local/share/goose/sessions, which holds
the only file it opens.

Four entries stay whole roots because the provider reads a file sitting directly
in the root: .config/github-copilot (JetBrains stores nest under a variable
<ide>/<kind>/<storeId>) and .local/share/{opencode,crush,kilo}. The new
app/scripts/snap-grants.test.ts asserts every other entry is at least one level
below its tool root, so a bare root cannot come back unnoticed.
2026-08-21 03:42:09 -07:00

45 lines
2.2 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { readFileSync } from 'fs'
import { join } from 'path'
// Snap Store review rejected the first submission because every entry was a
// tool's whole root, and personal-files read is recursive: granting $HOME/.claude
// granted .claude/.credentials.json with it. Each entry must name the log
// directory (or file) the provider actually opens, never the root above it.
// The exceptions below are roots only because the provider reads a file sitting
// directly in them, so no narrower path exists without wildcards.
const ROOT_GRANTS_WITH_NO_NARROWER_FORM = new Set([
'$HOME/.config/github-copilot', // JetBrains stores nest under a variable <ide>/<kind>/<storeId>
'$HOME/.local/share/opencode', // opencode*.db sits in the data dir itself
'$HOME/.local/share/crush', // projects.json sits in the data dir itself
'$HOME/.local/share/kilo', // kilo*.db sits in the data dir itself
])
const XDG_PARENTS = ['.config', '.local']
function readGrants(): string[] {
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'))
const plug = pkg.build.snap.plugs.find((p: unknown) => typeof p === 'object')
return plug['ai-agent-session-logs'].read
}
describe('snap personal-files declaration', () => {
it('names a log path under each tool root, never the root itself', () => {
const bare: string[] = []
for (const entry of readGrants()) {
if (ROOT_GRANTS_WITH_NO_NARROWER_FORM.has(entry)) continue
const segments = entry.replace('$HOME/', '').split('/')
const depth = XDG_PARENTS.includes(segments[0] ?? '') ? 3 : 2
if (segments.length < depth) bare.push(entry)
}
expect(bare).toEqual([])
})
it('requests read only, and one credential file explicitly', () => {
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'))
const plug = pkg.build.snap.plugs.find((p: unknown) => typeof p === 'object')['ai-agent-session-logs']
expect(Object.keys(plug).sort()).toEqual(['interface', 'read'])
expect(readGrants().filter(e => e.includes('credential') || e.includes('auth.json')))
.toEqual(['$HOME/.claude/.credentials.json'])
})
})