mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
fix: restore release gate checks
Keep provider ordering compatible with older desktop store state, keep workspace traversal blocked even when external changed-file roots are registered, and align the quality-contract test with the current AGENTS wording. Tested: bun test scripts/pr/quality-contract.test.ts Tested: cd desktop && bun run test -- src/__tests__/generalSettings.test.tsx src/__tests__/skillsSettings.test.tsx src/__tests__/pluginsSettings.test.tsx src/__tests__/diagnosticsSettings.test.tsx --run Tested: bun run check:policy Tested: bun run check:desktop Tested: bun test src/server/__tests__/workspace-service.test.ts -t 'does not allow relative traversal' Tested: bun test src/server/__tests__/sessions.test.ts -t 'workspace/file and tree should reject traversal|workspace/diff should reject traversal' --timeout=20000 Confidence: high Scope-risk: narrow
This commit is contained in:
parent
7c37384ebc
commit
84bcdb5f52
@ -1547,6 +1547,20 @@ describe('Settings > Providers tab', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('falls back to the default provider order when stored order is missing', () => {
|
||||
providerStoreState.providerOrder = undefined as unknown as string[]
|
||||
|
||||
render(<Settings />)
|
||||
|
||||
const rows = screen.getAllByRole('button', { name: 'Drag to reorder' })
|
||||
.map((handle) => handle.closest('[data-testid]')?.getAttribute('data-testid'))
|
||||
expect(rows).toEqual([
|
||||
'provider-provider-1',
|
||||
'claude-official-provider',
|
||||
'openai-official-provider',
|
||||
])
|
||||
})
|
||||
|
||||
it('requires confirmation before deleting a provider', async () => {
|
||||
render(<Settings />)
|
||||
|
||||
|
||||
@ -270,12 +270,16 @@ function defaultProviderOrder(providers: SavedProvider[]): string[] {
|
||||
]
|
||||
}
|
||||
|
||||
function normalizeProviderOrder(providerOrder: string[], providers: SavedProvider[]): string[] {
|
||||
function normalizeProviderOrder(providerOrder: string[] | undefined, providers: SavedProvider[]): string[] {
|
||||
const knownIds = new Set<string>(defaultProviderOrder(providers))
|
||||
const seen = new Set<string>()
|
||||
const order: string[] = []
|
||||
|
||||
for (const id of providerOrder.length > 0 ? providerOrder : defaultProviderOrder(providers)) {
|
||||
const source = providerOrder && providerOrder.length > 0
|
||||
? providerOrder
|
||||
: defaultProviderOrder(providers)
|
||||
|
||||
for (const id of source) {
|
||||
if (!knownIds.has(id) || seen.has(id)) continue
|
||||
seen.add(id)
|
||||
order.push(id)
|
||||
@ -292,7 +296,7 @@ function normalizeProviderOrder(providerOrder: string[], providers: SavedProvide
|
||||
|
||||
function buildProviderListItems(
|
||||
providers: SavedProvider[],
|
||||
providerOrder: string[],
|
||||
providerOrder: string[] | undefined,
|
||||
): ProviderListItem[] {
|
||||
const savedItems = new Map(
|
||||
providers.map((provider) => [
|
||||
|
||||
@ -11,12 +11,12 @@ describe('feature quality contract', () => {
|
||||
expect(agents).toContain('`~/.claude/settings.json` is user-owned shared state')
|
||||
expect(agents).toContain('persistence upgrade gate')
|
||||
expect(agents).toContain('Production code changes under `desktop/src`, `src/server`, `src/tools`, `src/utils`, or `adapters` must include a same-area test file')
|
||||
expect(agents).toContain('Coverage is part of the feature, not an afterthought.')
|
||||
expect(agents).toContain('Coverage is part of PR readiness, not an afterthought.')
|
||||
expect(agents).toContain('changed executable production line must meet the changed-line coverage gate')
|
||||
expect(agents).toContain('E2E is required when the feature crosses process boundaries')
|
||||
expect(agents).toContain('AI agents must include this evidence')
|
||||
expect(agents).toContain('Unified local entrypoint: `bun run verify`')
|
||||
expect(agents).toContain('If `bun run verify` fails, do not stop at reporting the failure')
|
||||
expect(agents).toContain('If `bun run verify` is intentionally run and fails, do not stop at reporting the failure')
|
||||
})
|
||||
|
||||
test('keeps PR authors accountable for tests, coverage, E2E, and risk', () => {
|
||||
|
||||
@ -115,6 +115,19 @@ describe('WorkspaceService outside-workspace preview', () => {
|
||||
|
||||
await expect(service.readFile('session-1', unrelatedFile)).rejects.toThrow(/outside workspace/)
|
||||
})
|
||||
|
||||
it('does not allow relative traversal through a registered outside dir', async () => {
|
||||
const baseDir = await makeTempDir('workspace-service-base-')
|
||||
const workDir = path.join(baseDir, 'work')
|
||||
const outsideFile = path.join(baseDir, 'outside.txt')
|
||||
await fs.mkdir(workDir)
|
||||
await fs.writeFile(outsideFile, 'secret\n')
|
||||
registerFilesystemAccessRoot(baseDir)
|
||||
|
||||
const service = new WorkspaceService(async (sessionId) => sessionId === 'session-1' ? workDir : null)
|
||||
|
||||
await expect(service.readFile('session-1', '../outside.txt')).rejects.toThrow(/outside workspace/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('WorkspaceService', () => {
|
||||
|
||||
@ -1065,7 +1065,7 @@ export class WorkspaceService {
|
||||
// registered as access roots when the turn checkpoint is built. Preview
|
||||
// those by absolute path — they have no workspace-relative form — instead
|
||||
// of rejecting them as out-of-sandbox.
|
||||
if (isWithinRegisteredFilesystemRoot(absolutePath)) {
|
||||
if (this.isAbsoluteRequestPath(requestedPath) && isWithinRegisteredFilesystemRoot(absolutePath)) {
|
||||
return this.resolveOutsideWorkspacePath(absolutePath, requestedPath)
|
||||
}
|
||||
throw new Error(`Path is outside workspace: ${requestedPath}`)
|
||||
@ -1192,6 +1192,11 @@ export class WorkspaceService {
|
||||
return isSameOrInsidePathForPlatform(targetPath, rootPath)
|
||||
}
|
||||
|
||||
private isAbsoluteRequestPath(requestedPath: string): boolean {
|
||||
const pathApi = process.platform === 'win32' ? path.win32 : path
|
||||
return pathApi.isAbsolute(normalizeDriveRootPathForPlatform(requestedPath))
|
||||
}
|
||||
|
||||
private normalizeRelativePath(filePath: string): string {
|
||||
if (!filePath || filePath === '.') return ''
|
||||
return filePath.split(path.sep).join('/')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user