mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 18:37:24 +00:00
feat(cloud): sign agents out before sandbox delete, scrub env-build images, recipe env warning (A9, P-4)
- Worker: run orca-agent-signout (each agent's logout, then credential files) on a running sandbox before the provider delete; bounded (20 s) and best effort, never blocks the delete. - Environment builds: scrub agent auth (--scrub-only) before the snapshot; fail closed. - New VM script registered in VM_SCRIPTS + provision loop; vm-scripts regenerated. - Main IPC ephemeralVm:forwardedEnvSummary returns names only, grouped by credential-looking. - P-4 warning component (+ fixture) shown under Settings > recipes; delete dialog line for awake Orca Cloud sandboxes.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { summarizeRecipeForwardedEnv } from './ephemeral-vm-recipe-forwarded-env'
|
||||
|
||||
describe('summarizeRecipeForwardedEnv', () => {
|
||||
it('returns names only, grouping the ones that look like credentials', () => {
|
||||
const summary = summarizeRecipeForwardedEnv({
|
||||
PATH: '/usr/bin',
|
||||
HOME: '/home/me',
|
||||
GITHUB_TOKEN: 'ghp_secret_value',
|
||||
AWS_SECRET_ACCESS_KEY: 'aws-secret-value',
|
||||
DB_PASSWORD: 'hunter2',
|
||||
ANTHROPIC_BASE_URL: 'https://gateway.example',
|
||||
openai_api_key: 'sk-lower',
|
||||
KEYCHAIN_PATH: '/tmp/k',
|
||||
UNSET: undefined
|
||||
})
|
||||
expect(summary).toEqual({
|
||||
count: 8,
|
||||
keyLike: [
|
||||
'ANTHROPIC_BASE_URL',
|
||||
'AWS_SECRET_ACCESS_KEY',
|
||||
'DB_PASSWORD',
|
||||
'GITHUB_TOKEN',
|
||||
'openai_api_key'
|
||||
],
|
||||
other: ['HOME', 'KEYCHAIN_PATH', 'PATH']
|
||||
})
|
||||
expect(JSON.stringify(summary)).not.toMatch(
|
||||
/ghp_secret_value|aws-secret-value|hunter2|sk-lower/
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,46 @@
|
||||
import { AGENT_CREDENTIAL_NAMES } from './agent-credentials/agent-credential-names'
|
||||
|
||||
/** Names only: recipe create scripts get this computer's whole env, so values never leave main. */
|
||||
export type RecipeForwardedEnvSummary = {
|
||||
count: number
|
||||
/** Sorted names that look like credentials (key/token/secret/password, or an agent credential). */
|
||||
keyLike: string[]
|
||||
/** Every other forwarded name, sorted. */
|
||||
other: string[]
|
||||
}
|
||||
|
||||
const AGENT_CREDENTIAL_NAME_SET = new Set(Object.values(AGENT_CREDENTIAL_NAMES).flat())
|
||||
const CREDENTIAL_SEGMENTS = new Set([
|
||||
'KEY',
|
||||
'KEYS',
|
||||
'APIKEY',
|
||||
'TOKEN',
|
||||
'TOKENS',
|
||||
'SECRET',
|
||||
'SECRETS',
|
||||
'CREDENTIAL',
|
||||
'CREDENTIALS',
|
||||
'PAT'
|
||||
])
|
||||
const CREDENTIAL_SUBSTRINGS = /PASSWORD|PASSWD|SECRET|TOKEN|PRIVATE_KEY|API_?KEY/
|
||||
|
||||
export function looksLikeCredentialEnvName(name: string): boolean {
|
||||
// Windows env names are case-insensitive; match them the way agents would read them.
|
||||
const upper = name.toUpperCase()
|
||||
return (
|
||||
AGENT_CREDENTIAL_NAME_SET.has(upper) ||
|
||||
CREDENTIAL_SUBSTRINGS.test(upper) ||
|
||||
upper.split(/[_\-.]/).some((segment) => CREDENTIAL_SEGMENTS.has(segment))
|
||||
)
|
||||
}
|
||||
|
||||
export function summarizeRecipeForwardedEnv(env: NodeJS.ProcessEnv): RecipeForwardedEnvSummary {
|
||||
const names = Object.keys(env)
|
||||
.filter((name) => name.length > 0 && env[name] !== undefined)
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
return {
|
||||
count: names.length,
|
||||
keyLike: names.filter(looksLikeCredentialEnvName),
|
||||
other: names.filter((name) => !looksLikeCredentialEnvName(name))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user