diff --git a/desktop/src/main.cjs b/desktop/src/main.cjs index e5c1e61..68f0c23 100644 --- a/desktop/src/main.cjs +++ b/desktop/src/main.cjs @@ -4,6 +4,7 @@ const fs = require('node:fs') const path = require('node:path') const { redactSecrets } = require('./security/secret-redaction.cjs') const { ensurePrivateDirectory } = require('./security/private-file.cjs') +const { projectVerificationIdentity } = require('./workspace/agent-verification-identity.cjs') const { NATIVE_CONFIG_DIRECTORIES, NATIVE_CONFIG_IDENTITY_DIRECTORY, NATIVE_ENVIRONMENT_IDENTITY, NATIVE_AUTH_IDENTITY_FILES, NATIVE_PROJECT_CONFIG_PATHS, nativeConfigEnvironment, nativeIdentityDirectory, nativeEnvironmentIdentity, nativeAuthIdentityFiles, nativeProjectConfigPaths, nativeNetworkIdentityFiles, validateNativeConfigDirectory } = require('./agents/native-config-directory.cjs') const { AttachmentStore } = require('./attachments/attachment-store.cjs') @@ -414,11 +415,18 @@ async function resolveProjectEnvironmentIdentities(workdir, kinds) { } const keys = nativeDeclaredCredentialKeys(kind, captured) const env = nativeConfigEnvironment(kind, workspace?.state.agentPreferences, captured.env) - result[kind] = nativeEnvironmentIdentity({ + const environmentIdentity = nativeEnvironmentIdentity({ ...(env.PATH ? { PATH: env.PATH } : {}), ...networkEnvironment(env), ...configDirectoryEnvironment(env), ...nativeCredentialEnvironment(kind, env, keys), }) + const projectPaths = nativeProjectConfigPaths(kind, env) + if (projectPaths.length) { + const agent = { [NATIVE_PROJECT_CONFIG_PATHS]: projectPaths } + result[kind] = projectVerificationIdentity(kind, workdir, agent, environmentIdentity) + } else { + result[kind] = environmentIdentity + } } return result } diff --git a/desktop/test/security/main-security.test.cjs b/desktop/test/security/main-security.test.cjs index 1399725..39bc9b1 100644 --- a/desktop/test/security/main-security.test.cjs +++ b/desktop/test/security/main-security.test.cjs @@ -908,6 +908,24 @@ test('native project identity changes when the CLI tool search path changes', as assert.doesNotMatch(JSON.stringify([first, second]), /synthetic|tool-bin/) }) +test('native project identity follows relative credential files captured for the project', async t => { + const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-native-relative-identity-')) + t.after(() => fs.rmSync(directory, { recursive: true, force: true })) + const credentialFile = path.join(directory, 'credentials.json') + fs.writeFileSync(credentialFile, 'first synthetic credential') + const { harness } = loadMain(directory, { + nativeShellEnvironment: () => ({ source: 'native-shell', env: { GOOGLE_APPLICATION_CREDENTIALS: 'credentials.json' } }), + }) + await harness.ready() + const resolve = harness.workspaceInstances[0].input.resolveProjectEnvironmentIdentities + const first = await resolve(directory, ['hermes']) + fs.writeFileSync(credentialFile, 'replacement synthetic credential') + const second = await resolve(directory, ['hermes']) + assert.match(first.hermes, /^[a-f0-9]{64}$/) + assert.notEqual(first.hermes, second.hermes) + assert.doesNotMatch(JSON.stringify([first, second]), /credentials|synthetic/) +}) + test('Codex and Hermes project identities track only their own native credentials', async t => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-native-project-identities-')) t.after(() => fs.rmSync(directory, { recursive: true, force: true }))