@@ -111,22 +111,42 @@ async function runWorkflow(workflowId: string, inputs: Record<string, unknown> =
111111 return runId ;
112112}
113113
114- async function createSecret ( name : string , value : string ) {
115- const res = await fetch ( `${ API_BASE } /secrets` , {
116- method : 'POST' ,
117- headers : HEADERS ,
118- body : JSON . stringify ( { name, value } ) ,
119- } ) ;
114+ async function listSecrets ( ) : Promise < Array < { id : string ; name : string } > > {
115+ const res = await fetch ( `${ API_BASE } /secrets` , { headers : HEADERS } ) ;
120116 if ( ! res . ok ) {
121117 const text = await res . text ( ) ;
122- throw new Error ( `Failed to create secret : ${ res . status } ${ text } ` ) ;
118+ throw new Error ( `Failed to list secrets : ${ res . status } ${ text } ` ) ;
123119 }
124- const secret = await res . json ( ) ;
125- return secret . id as string ;
120+ return res . json ( ) ;
126121}
127122
128- async function deleteSecret ( secretId : string ) {
129- await fetch ( `${ API_BASE } /secrets/${ secretId } ` , { method : 'DELETE' , headers : HEADERS } ) ;
123+ async function createOrRotateSecret ( name : string , value : string ) : Promise < string > {
124+ const secrets = await listSecrets ( ) ;
125+ const existing = secrets . find ( ( s ) => s . name === name ) ;
126+ if ( ! existing ) {
127+ const res = await fetch ( `${ API_BASE } /secrets` , {
128+ method : 'POST' ,
129+ headers : HEADERS ,
130+ body : JSON . stringify ( { name, value } ) ,
131+ } ) ;
132+ if ( ! res . ok ) {
133+ const text = await res . text ( ) ;
134+ throw new Error ( `Failed to create secret: ${ res . status } ${ text } ` ) ;
135+ }
136+ const secret = await res . json ( ) ;
137+ return secret . id as string ;
138+ }
139+
140+ const res = await fetch ( `${ API_BASE } /secrets/${ existing . id } /rotate` , {
141+ method : 'PUT' ,
142+ headers : HEADERS ,
143+ body : JSON . stringify ( { value } ) ,
144+ } ) ;
145+ if ( ! res . ok ) {
146+ const text = await res . text ( ) ;
147+ throw new Error ( `Failed to rotate secret: ${ res . status } ${ text } ` ) ;
148+ }
149+ return existing . id ;
130150}
131151
132152function loadGuardDutySample ( ) {
@@ -145,18 +165,17 @@ e2eDescribe('ENG-104: End-to-End Alert Investigation Workflow', () => {
145165 e2eTest ( 'triage workflow runs end-to-end with MCP tools + OpenCode agent' , { timeout : 480000 } , async ( ) => {
146166 const now = Date . now ( ) ;
147167
148- const abuseSecretId = await createSecret ( `ENG104_ABUSE_${ now } ` , ABUSEIPDB_API_KEY ! ) ;
149- const vtSecretId = await createSecret ( `ENG104_VT_${ now } ` , VIRUSTOTAL_API_KEY ! ) ;
150- const zaiSecretId = await createSecret ( `ENG104_ZAI_${ now } ` , ZAI_API_KEY ! ) ;
168+ const abuseSecretName = `ENG104_ABUSE_${ now } ` ;
169+ const vtSecretName = `ENG104_VT_${ now } ` ;
170+ const zaiSecretName = `ENG104_ZAI_${ now } ` ;
171+ const awsAccessKeyName = `ENG104_AWS_ACCESS_${ now } ` ;
172+ const awsSecretKeyName = `ENG104_AWS_SECRET_${ now } ` ;
151173
152- // For AWS MCP components, credentials are contract-based (object, not secret reference)
153- // So we pass the credential object directly instead of a secret ID
154- const awsCredentials = {
155- accessKeyId : AWS_ACCESS_KEY_ID ,
156- secretAccessKey : AWS_SECRET_ACCESS_KEY ,
157- sessionToken : AWS_SESSION_TOKEN ,
158- region : AWS_REGION ,
159- } ;
174+ await createOrRotateSecret ( abuseSecretName , ABUSEIPDB_API_KEY ! ) ;
175+ await createOrRotateSecret ( vtSecretName , VIRUSTOTAL_API_KEY ! ) ;
176+ await createOrRotateSecret ( zaiSecretName , ZAI_API_KEY ! ) ;
177+ await createOrRotateSecret ( awsAccessKeyName , AWS_ACCESS_KEY_ID ! ) ;
178+ await createOrRotateSecret ( awsSecretKeyName , AWS_SECRET_ACCESS_KEY ! ) ;
160179
161180 const guardDutyAlert = loadGuardDutySample ( ) ;
162181
@@ -178,34 +197,6 @@ e2eDescribe('ENG-104: End-to-End Alert Investigation Workflow', () => {
178197 } ,
179198 } ,
180199 } ,
181- {
182- id : 'parse' ,
183- type : 'core.logic.script' ,
184- position : { x : 250 , y : 0 } ,
185- data : {
186- label : 'Parse Alert' ,
187- config : {
188- params : {
189- variables : [
190- { name : 'alert' , type : 'json' } ,
191- ] ,
192- returns : [
193- { name : 'suspiciousIp' , type : 'string' } ,
194- { name : 'publicIp' , type : 'string' } ,
195- { name : 'instanceId' , type : 'string' } ,
196- ] ,
197- code : `export async function script(input: Input): Promise<Output> {
198- const alert = input.alert || {};
199- const portProbe = alert?.service?.action?.portProbeAction?.portProbeDetails || [];
200- const suspiciousIp = portProbe[0]?.remoteIpDetails?.ipAddressV4 || alert?.intel?.ip || '';
201- const publicIp = alert?.resource?.instanceDetails?.publicIp || '';
202- const instanceId = alert?.resource?.instanceDetails?.instanceId || '';
203- return { suspiciousIp, publicIp, instanceId };
204- }` ,
205- } ,
206- } ,
207- } ,
208- } ,
209200 {
210201 id : 'abuseipdb' ,
211202 type : 'security.abuseipdb.check' ,
@@ -216,7 +207,7 @@ e2eDescribe('ENG-104: End-to-End Alert Investigation Workflow', () => {
216207 mode : 'tool' ,
217208 params : { maxAgeInDays : 90 } ,
218209 inputOverrides : {
219- apiKey : abuseSecretId ,
210+ apiKey : abuseSecretName ,
220211 ipAddress : '' ,
221212 } ,
222213 } ,
@@ -232,12 +223,28 @@ e2eDescribe('ENG-104: End-to-End Alert Investigation Workflow', () => {
232223 mode : 'tool' ,
233224 params : { type : 'ip' } ,
234225 inputOverrides : {
235- apiKey : vtSecretId ,
226+ apiKey : vtSecretName ,
236227 indicator : '' ,
237228 } ,
238229 } ,
239230 } ,
240231 } ,
232+ {
233+ id : 'aws-creds' ,
234+ type : 'core.credentials.aws' ,
235+ position : { x : 520 , y : 200 } ,
236+ data : {
237+ label : 'AWS Credentials Bundle' ,
238+ config : {
239+ params : { } ,
240+ inputOverrides : {
241+ accessKeyId : awsAccessKeyName ,
242+ secretAccessKey : awsSecretKeyName ,
243+ region : AWS_REGION ,
244+ } ,
245+ } ,
246+ } ,
247+ } ,
241248 {
242249 id : 'cloudtrail' ,
243250 type : 'security.aws-cloudtrail-mcp' ,
@@ -250,9 +257,7 @@ e2eDescribe('ENG-104: End-to-End Alert Investigation Workflow', () => {
250257 image : AWS_CLOUDTRAIL_MCP_IMAGE ,
251258 region : AWS_REGION ,
252259 } ,
253- inputOverrides : {
254- credentials : awsCredentials ,
255- } ,
260+ inputOverrides : { } ,
256261 } ,
257262 } ,
258263 } ,
@@ -268,9 +273,7 @@ e2eDescribe('ENG-104: End-to-End Alert Investigation Workflow', () => {
268273 image : AWS_CLOUDWATCH_MCP_IMAGE ,
269274 region : AWS_REGION ,
270275 } ,
271- inputOverrides : {
272- credentials : awsCredentials ,
273- } ,
276+ inputOverrides : { } ,
274277 } ,
275278 } ,
276279 } ,
@@ -302,16 +305,15 @@ e2eDescribe('ENG-104: End-to-End Alert Investigation Workflow', () => {
302305 } ,
303306 ] ,
304307 edges : [
305- { id : 'e1' , source : 'start' , target : 'parse' , sourceHandle : 'alert' , targetHandle : 'alert' } ,
306308 { id : 'e2' , source : 'start' , target : 'agent' } ,
307309
308310 { id : 't1' , source : 'abuseipdb' , target : 'agent' , sourceHandle : 'tools' , targetHandle : 'tools' } ,
309311 { id : 't2' , source : 'virustotal' , target : 'agent' , sourceHandle : 'tools' , targetHandle : 'tools' } ,
310312 { id : 't3' , source : 'cloudtrail' , target : 'agent' , sourceHandle : 'tools' , targetHandle : 'tools' } ,
311313 { id : 't4' , source : 'cloudwatch' , target : 'agent' , sourceHandle : 'tools' , targetHandle : 'tools' } ,
312314
313- { id : 'd1 ' , source : 'parse ' , target : 'abuseipdb ' , sourceHandle : 'suspiciousIp ' , targetHandle : 'ipAddress ' } ,
314- { id : 'd2 ' , source : 'parse ' , target : 'virustotal ' , sourceHandle : 'suspiciousIp ' , targetHandle : 'indicator ' } ,
315+ { id : 'a1 ' , source : 'aws-creds ' , target : 'cloudtrail ' , sourceHandle : 'credentials ' , targetHandle : 'credentials ' } ,
316+ { id : 'a2 ' , source : 'aws-creds ' , target : 'cloudwatch ' , sourceHandle : 'credentials ' , targetHandle : 'credentials ' } ,
315317 ] ,
316318 } ;
317319
@@ -340,8 +342,6 @@ e2eDescribe('ENG-104: End-to-End Alert Investigation Workflow', () => {
340342 }
341343 }
342344
343- await deleteSecret ( abuseSecretId ) ;
344- await deleteSecret ( vtSecretId ) ;
345- await deleteSecret ( zaiSecretId ) ;
345+ // Leave secrets for reuse across runs; rotation already updated values.
346346 } ) ;
347347} ) ;
0 commit comments