mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 00:04:01 +00:00
refactor(profiles): validate untrusted profile JSON instead of asserting it
Removes the last introduced cast and the unsound pre-existing ones in the
file this change already touches.
The test cast is gone because narrowing normalizeLoadedProjectCatalog to the
two fields it reads made `{}` assignable on its own.
arrayOrEmpty and recordOrEmpty checked the shape and then asserted the
element type, which is the same "declared type is a promise the data does not
keep" problem this change exists to fix. Array.isArray already narrows on its
own, and a generic isRecord narrows the value part, so both assertions delete
outright. JSON.parse returns any, so annotating the binding beats asserting
its result.
The two remaining `as const` in project-host-setup-actions are untouched and
deliberate: a literal assertion narrows a type rather than overriding it.
This commit is contained in:
@@ -3,7 +3,6 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from '
|
||||
import { homedir } from 'node:os'
|
||||
import { dirname } from 'node:path'
|
||||
import { getDefaultPersistedState, getDefaultWorkspaceSession } from '../../shared/constants'
|
||||
import type { ExecutionHostId } from '../../shared/execution-host'
|
||||
import { projectHostSetupProjectionFromRepos } from '../../shared/project-host-setup-projection'
|
||||
import {
|
||||
normalizeProjectHostSetupRows,
|
||||
@@ -20,16 +19,16 @@ import { getOrcaProfileDataFile } from './profile-index-store'
|
||||
|
||||
export type TransferProfileState = PersistedState
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
function isRecord<T>(value: unknown): value is Record<string, T> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function arrayOrEmpty<T>(value: unknown): T[] {
|
||||
return Array.isArray(value) ? (value as T[]) : []
|
||||
return Array.isArray(value) ? value : []
|
||||
}
|
||||
|
||||
function recordOrEmpty<T>(value: unknown): Record<string, T> {
|
||||
return isRecord(value) ? (value as Record<string, T>) : {}
|
||||
return isRecord<T>(value) ? value : {}
|
||||
}
|
||||
|
||||
export function readProfileState(profileId: string, userDataPath: string): TransferProfileState {
|
||||
@@ -38,7 +37,7 @@ export function readProfileState(profileId: string, userDataPath: string): Trans
|
||||
if (!existsSync(dataFile)) {
|
||||
return structuredClone(defaults)
|
||||
}
|
||||
const parsed = JSON.parse(readFileSync(dataFile, 'utf-8')) as Partial<PersistedState>
|
||||
const parsed: Partial<PersistedState> = JSON.parse(readFileSync(dataFile, 'utf-8'))
|
||||
return rebuildRepoBackedProjectState({
|
||||
...defaults,
|
||||
...parsed,
|
||||
@@ -67,17 +66,15 @@ export function readProfileState(profileId: string, userDataPath: string): Trans
|
||||
ui: isRecord(parsed.ui) ? { ...defaults.ui, ...parsed.ui } : defaults.ui,
|
||||
githubCache: isRecord(parsed.githubCache)
|
||||
? {
|
||||
pr: recordOrEmpty((parsed.githubCache as PersistedState['githubCache']).pr),
|
||||
issue: recordOrEmpty((parsed.githubCache as PersistedState['githubCache']).issue)
|
||||
pr: recordOrEmpty(parsed.githubCache.pr),
|
||||
issue: recordOrEmpty(parsed.githubCache.issue)
|
||||
}
|
||||
: defaults.githubCache,
|
||||
workspaceSession: isRecord(parsed.workspaceSession)
|
||||
? { ...getDefaultWorkspaceSession(), ...parsed.workspaceSession }
|
||||
: defaults.workspaceSession,
|
||||
workspaceSessionsByHostId: isRecord(parsed.workspaceSessionsByHostId)
|
||||
? (parsed.workspaceSessionsByHostId as Partial<
|
||||
Record<ExecutionHostId, WorkspaceSessionState>
|
||||
>)
|
||||
workspaceSessionsByHostId: isRecord<WorkspaceSessionState>(parsed.workspaceSessionsByHostId)
|
||||
? parsed.workspaceSessionsByHostId
|
||||
: {},
|
||||
sshTargets: arrayOrEmpty(parsed.sshTargets),
|
||||
sshRemotePtyLeases: arrayOrEmpty(parsed.sshRemotePtyLeases),
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('normalizeLoadedProjectCatalog', () => {
|
||||
})
|
||||
|
||||
it('tolerates missing or non-array collections', () => {
|
||||
const result = normalizeLoadedProjectCatalog({} as PersistedState, vi.fn())
|
||||
const result = normalizeLoadedProjectCatalog({}, vi.fn())
|
||||
expect(result.projects).toEqual([])
|
||||
expect(result.projectHostSetups).toEqual([])
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user