diff --git a/tests/runner-e2e/chat-catalog-domains/external-company-secret.test.ts b/tests/runner-e2e/chat-catalog-domains/external-company-secret.test.ts index 9d779bf2c27..6b54c19c2a2 100644 --- a/tests/runner-e2e/chat-catalog-domains/external-company-secret.test.ts +++ b/tests/runner-e2e/chat-catalog-domains/external-company-secret.test.ts @@ -2,7 +2,7 @@ import { describe,expect,it,vi } from "vitest"; vi.mock("./identity-support.js", async importOriginal => ({ ...await importOriginal(), identityWorker: vi.fn(async () => ({id:"morgan"})), probeAgentPermission: vi.fn(async () => 403) })); import type { ChatCatalogContext } from "../chat-catalog-contract.js"; import { companySecretExternalCases,deploymentSecretGuidance,companySecretControllerBaseline,companySecretControllersUnchanged } from "./external-company-secret.js"; -const controller={id:"controller",companyId:"company",scope:"company",ownerUserId:null,userSecretDefinitionId:null,key:"OPENROUTER_API_KEY",name:"Runner E2E controller",provider:"local_encrypted",status:"active",managedMode:"paperclip_managed",latestVersion:1}; +const controller={id:"controller",companyId:"company",scope:"company",ownerUserId:null,userSecretDefinitionId:null,key:"openrouter_api_key",name:"Runner E2E controller",provider:"local_encrypted",status:"active",managedMode:"paperclip_managed",latestVersion:1}; const controllerRefs={OPENROUTER_API_KEY:{type:"secret_ref",secretId:"controller",version:"latest"}} as const; function valid(){return {scenarioId:"secrets.configure-company-secret",companyId:"company",leadId:"ceo",reply:"The deployment key reference is now bound for Morgan. This confirms configuration, not a runtime delivery test.",phaseReplies:[{runId:"first",body:"Use secure secret settings for the deployment key, then bind its reference in Morgan's environment variables. Do not paste the key here."},{runId:"second",body:"The deployment key reference is now bound for Morgan. This confirms configuration, not a runtime delivery test."}],runs:[{id:"first",status:"succeeded"},{id:"second",status:"succeeded"}],connections:[],profiles:[],secrets:[{id:"secret",name:"Deployment API key",status:"active"}],endpoints:[],interactions:[],activity:[{entityId:"secret",action:"secret.created",actorType:"user"}],links:[],humanQueries:[],state:{initialRunId:"first",agentCreateStatus:403,secretCountBeforeHuman:1,controllerSecretBaseline:[controller],controllerSecretsPreserved:true,operatorChannel:"private-browser-company-secret-and-public-browser-binding",secretUiVisible:true,humanSubmittedSecurely:true,storageClean:true,secureBrowserUntraced:true,secretId:"secret",morganId:"morgan",bindings:[{targetType:"agent",targetId:"morgan",configPath:"env.DEPLOYMENT_API_KEY",versionSelector:"latest"}],savedReferenceMatches:true,morganBindingVisible:true,bindingHumanAttributed:true,secretScanComplete:true,secretLeaked:false}};} describe("secure company credential and Morgan-only binding",()=>{ @@ -41,8 +41,12 @@ it("admits only exact controller-bootstrap secrets while leaving deployment entr it("rejects extra, foreign, personal, duplicated or precompleted deployment secrets in the controller baseline",()=>{ const baseline=[controller] as Parameters[0]; expect(companySecretControllerBaseline(baseline,"company",controllerRefs)).toEqual([controller]); - for(const rows of [[...baseline,{...controller,id:"extra"}], [{...controller,companyId:"foreign"}], [{...controller,scope:"user",ownerUserId:"person"}], [{...controller,key:"DEPLOYMENT_API_KEY",name:"Deployment API key"}], [{...controller,id:"foreign"}], [...baseline,...baseline]]) expect(()=>companySecretControllerBaseline(rows as typeof baseline,"company",controllerRefs)).toThrow(); + for(const rows of [[...baseline,{...controller,id:"extra"}], [{...controller,companyId:"foreign"}], [{...controller,scope:"user",ownerUserId:"person"}], [{...controller,key:"DEPLOYMENT_API_KEY",name:"Deployment API key"}], [{...controller,key:"OPENROUTER_API_KEY"}], [{...controller,id:"foreign"}], [...baseline,...baseline]]) expect(()=>companySecretControllerBaseline(rows as typeof baseline,"company",controllerRefs)).toThrow(); + + const precompleted={...controller,key:"deployment_api_key",name:"Pre-existing deployment reference"}; + expect(()=>companySecretControllerBaseline([precompleted] as typeof baseline,"company",{DEPLOYMENT_API_KEY:controllerRefs.OPENROUTER_API_KEY} as unknown as Parameters[2])).toThrow(); expect(()=>companySecretControllerBaseline(baseline,"company",{})).toThrow(); + expect(()=>companySecretControllerBaseline(baseline,"company",{OPENROUTER_API_KEY:{...controllerRefs.OPENROUTER_API_KEY,version:"1"}} as unknown as Parameters[2])).toThrow(); expect(()=>companySecretControllerBaseline(baseline,"company",{...controllerRefs,OPENAI_API_KEY:controllerRefs.OPENROUTER_API_KEY})).toThrow(); }); it("requires the original controller metadata and version before and after secure human entry",()=>{ diff --git a/tests/runner-e2e/chat-catalog-domains/external-company-secret.ts b/tests/runner-e2e/chat-catalog-domains/external-company-secret.ts index dc5d3de147f..ab86c7f7cd5 100644 --- a/tests/runner-e2e/chat-catalog-domains/external-company-secret.ts +++ b/tests/runner-e2e/chat-catalog-domains/external-company-secret.ts @@ -19,10 +19,11 @@ function controllerMetadata(rows: CompanySecret[]): ControllerSecret[] { return rows.map(row => Object.fromEntries(controllerFields.map(field => [field, row[field]])) as ControllerSecret).sort((a,b) => a.id.localeCompare(b.id)); } /** Only independently created controller refs can precede the requested secret. + * Fixture credential env names are uppercase; the secret producer stores lowercase keys. * Values and runtime-resolution timestamps never enter this metadata receipt. */ export function companySecretControllerBaseline(rows: CompanySecret[], companyId: string, refs: SecretReferenceMap): ControllerSecret[] { const baseline = controllerMetadata(rows), entries = Object.entries(refs); - if (!entries.length || baseline.length !== entries.length || entries.some(([key, ref]) => !ref || ref.type !== "secret_ref" || ref.version !== "latest" || baseline.filter(row => row.id === ref.secretId && row.companyId === companyId && row.key === key).length !== 1) || new Set(entries.map(([,ref]) => ref!.secretId)).size !== entries.length || baseline.some(row => row.name === "Deployment API key" || row.key === "DEPLOYMENT_API_KEY")) throw new Error("Company secret entry permits only exact controller-bootstrap secret references"); + if (!entries.length || baseline.length !== entries.length || entries.some(([key, ref]) => !ref || ref.type !== "secret_ref" || ref.version !== "latest" || baseline.filter(row => row.id === ref.secretId && row.companyId === companyId && row.key === key.toLowerCase()).length !== 1) || new Set(entries.map(([,ref]) => ref!.secretId)).size !== entries.length || baseline.some(row => row.name === "Deployment API key" || row.key === "deployment_api_key")) throw new Error("Company secret entry permits only exact controller-bootstrap secret references"); return baseline; } export function companySecretControllersUnchanged(rows: CompanySecret[], baseline: unknown): boolean { @@ -58,7 +59,7 @@ export const companySecretExternalCases=[definition("secrets.configure-company-s return {morganId:morgan.id,agentCreateStatus,controllerSecretBaseline}; },gradeCompanySecret,{ authentication:"human-session",oracleVersion:"paperclip.chat-catalog.company-secret-controller-baseline.v2",expectedRunCount:2,expectedHumanMessageCount:2,attemptTimeoutMs:480000, - sourceFiles:["chat-catalog-domains/external-company-secret.ts","chat-catalog-domains/external-private-browser.ts","chat-catalog-domains/external-private-browser-worker.ts","chat-catalog-domains/external-credential-language.ts","chat-catalog-domains/external-secret-scan.ts","chat-catalog-domains/external-support.ts","chat-catalog-domains/internal-support.ts","chat-catalog-domains/identity-support.ts","run-observations.ts","chat-flow.ts","chat-scenarios/secrets.yaml"], + sourceFiles:["chat-catalog-domains/external-company-secret.ts","chat-catalog-domains/external-private-browser.ts","chat-catalog-domains/external-private-browser-worker.ts","chat-catalog-domains/external-credential-language.ts","chat-catalog-domains/external-secret-scan.ts","chat-catalog-domains/external-support.ts","chat-catalog-domains/internal-support.ts","chat-catalog-domains/identity-support.ts","live-fixtures.ts","../../server/src/services/secrets.ts","run-observations.ts","chat-flow.ts","chat-scenarios/secrets.yaml"], async act(ctx,state){ await waitForReply(ctx, chatCatalogOriginalPhaseDeadline(ctx));const initial=await observeExternal(ctx,state),reply=initial.phaseReplies.at(-1);if(!reply||!deploymentSecretGuidance(reply.body))throw new Error("Actual reply must direct secure deployment-key entry and Morgan-only reference binding before human action");state.initialRunId=reply.runId; const beforeHuman=await ctx.input.api.get(`${root(ctx)}/secrets`);