11import { rmSync } from "node:fs"
22import { appendFile , mkdir , readFile , rm , writeFile } from "node:fs/promises"
3- import { isAbsolute , join , relative , resolve } from "node:path"
3+ import { join , resolve } from "node:path"
44import { spawn } from "node:child_process"
55import { materializeExternalNativePackage , materializeRuntimeSources , normalizeExternalPackageSource , normalizeRuntimeSources , parseExternalPackageSourcePolicy , validateRuntimeSourceModel } from "./materialize-external-native-package.mjs"
66import { readNativeResult } from "./native-result-file.mjs"
7+ import { assertNoRuntimeSourcePaths , sanitizeRuntimeSourceJson , sanitizeRuntimeSourceText , sanitizeRuntimeSourceValue } from "./runtime-source-sanitizer.mjs"
78
89const requestPath = process . env . AGENT_TASK_REQUEST_PATH || ".codebox/agent-task-request.json"
910const workspace = resolve ( process . env . AGENT_TASK_WORKSPACE || process . cwd ( ) )
@@ -13,7 +14,6 @@ const outputPath = process.env.GITHUB_OUTPUT
1314const MAX_CAPTURE_BYTES = 32768
1415const MAX_OUTPUT_CHARS = 8192
1516const secretValues = [ "OPENAI_API_KEY" , "MODEL_PROVIDER_SECRET_1" , "MODEL_PROVIDER_SECRET_2" , "MODEL_PROVIDER_SECRET_3" , "MODEL_PROVIDER_SECRET_4" , "MODEL_PROVIDER_SECRET_5" , "GITHUB_TOKEN" , "GH_TOKEN" , "ACCESS_TOKEN" , "EXTERNAL_PACKAGE_SOURCE_POLICY" ] . map ( ( name ) => process . env [ name ] ) . filter ( Boolean )
16- const PRIVATE_RUNTIME_PATH_FIELDS = new Set ( [ "source" , "path" , "sourceRoot" , "originalSource" , "preparedPath" , "requestedPath" , "source_package_root" , "artifacts_path" , "runtime_input_path" , "task_path" , "result_path" , "event_stream_path" , "materialization_result_path" ] )
1717function redact ( value ) {
1818 if ( typeof value === "string" ) return secretValues . reduce ( ( output , secret ) => output . split ( secret ) . join ( "[REDACTED]" ) , value )
1919 if ( Array . isArray ( value ) ) return value . map ( redact )
@@ -22,7 +22,7 @@ function redact(value) {
2222}
2323
2424function bounded ( value , limit = MAX_CAPTURE_BYTES ) {
25- const safe = redact ( value )
25+ const safe = sanitizeRuntimeSourceText ( redact ( value ) , privateRuntimeSourceRoot )
2626 if ( typeof safe !== "string" ) return safe
2727 return safe . length > limit ? `${ safe . slice ( 0 , limit ) } \n[TRUNCATED ${ safe . length - limit } characters]` : safe
2828}
@@ -96,28 +96,6 @@ function record(value) {
9696 return value && typeof value === "object" && ! Array . isArray ( value ) ? value : { }
9797}
9898
99- function isPrivateRuntimePath ( value ) {
100- if ( ! privateRuntimeSourceRoot || typeof value !== "string" ) return false
101- const path = resolve ( value )
102- const contained = relative ( privateRuntimeSourceRoot , path )
103- return path === privateRuntimeSourceRoot || ( contained !== ".." && ! contained . startsWith ( `..${ String . fromCharCode ( 47 ) } ` ) && ! isAbsolute ( contained ) )
104- }
105-
106- function omitPrivateRuntimeSourcePaths ( value ) {
107- if ( Array . isArray ( value ) ) return value . map ( omitPrivateRuntimeSourcePaths )
108- if ( ! value || typeof value !== "object" ) return value
109- return Object . fromEntries ( Object . entries ( value ) . flatMap ( ( [ key , entry ] ) => {
110- if ( PRIVATE_RUNTIME_PATH_FIELDS . has ( key ) && isPrivateRuntimePath ( entry ) ) return [ ]
111- return [ [ key , omitPrivateRuntimeSourcePaths ( entry ) ] ]
112- } ) )
113- }
114-
115- function assertNoPrivateRuntimePaths ( value ) {
116- if ( privateRuntimeSourceRoot && JSON . stringify ( value ) . includes ( privateRuntimeSourceRoot ) ) {
117- throw new Error ( "Runtime source paths must never be persisted in workflow results or artifacts." )
118- }
119- }
120-
12199function string ( value ) {
122100 return typeof value === "string" ? value . trim ( ) : ""
123101}
@@ -200,9 +178,13 @@ async function redactArtifactFiles(directory) {
200178 for ( const entry of await readdir ( directory , { withFileTypes : true } ) ) {
201179 const path = join ( directory , entry . name )
202180 if ( entry . isDirectory ( ) ) await redactArtifactFiles ( path )
203- if ( entry . isFile ( ) && ( await stat ( path ) ) . size <= 4 * 1024 * 1024 ) {
181+ if ( entry . isFile ( ) && path . endsWith ( ".json" ) && ( await stat ( path ) ) . size <= 4 * 1024 * 1024 ) {
204182 const contents = await readFile ( path , "utf8" ) . catch ( ( ) => null )
205- if ( contents !== null ) await writeFile ( path , bounded ( contents , 4 * 1024 * 1024 ) )
183+ if ( contents !== null ) {
184+ const sanitized = sanitizeRuntimeSourceJson ( bounded ( contents , 4 * 1024 * 1024 ) , privateRuntimeSourceRootForSanitization )
185+ assertNoRuntimeSourcePaths ( sanitized , privateRuntimeSourceRootForSanitization )
186+ await writeFile ( path , sanitized )
187+ }
206188 }
207189 }
208190}
@@ -221,6 +203,7 @@ const resultPath = join(workspace, ".codebox", "agent-task-workflow-result.json"
221203const controlledCodeboxPath = resolve ( requestPath , ".." )
222204const nativeResultPath = join ( controlledCodeboxPath , "native-agent-task-result.json" )
223205let privateRuntimeSourceRoot = ""
206+ let privateRuntimeSourceRootForSanitization = ""
224207let cleaningPrivateRuntimeSources = false
225208async function cleanupPrivateRuntimeSources ( ) {
226209 if ( cleaningPrivateRuntimeSources || ! privateRuntimeSourceRoot ) return
@@ -263,6 +246,7 @@ const materializedRuntimeSources = request.run_agent && !request.dry_run
263246 ? await materializeRuntimeSources ( runtimeSources , { policy : externalPackagePolicy , forbiddenRoots : [ workspace , artifactsPath ] } )
264247 : undefined
265248privateRuntimeSourceRoot = materializedRuntimeSources ?. root ?? ""
249+ privateRuntimeSourceRootForSanitization = privateRuntimeSourceRoot
266250await output ( "runtime_source_root" , privateRuntimeSourceRoot )
267251const runtimeSourceInputs = ( materializedRuntimeSources ?. lowered ?? [ ] ) . reduce ( ( input , lowered ) => {
268252 for ( const [ key , entries ] of Object . entries ( lowered ) ) input [ key ] = [ ...( input [ key ] ?? [ ] ) , ...entries ]
@@ -340,11 +324,10 @@ const nativeRuntimeResult = request.run_agent && !request.dry_run
340324 ? await readNativeResult ( nativeResultPath , controlledCodeboxPath , secretValues , redact )
341325 : { }
342326await rm ( nativeResultPath , { force : true } )
343- assertNoPrivateRuntimePaths ( nativeRuntimeResult )
344- const runtimeResult = omitPrivateRuntimeSourcePaths ( nativeRuntimeResult )
345- assertNoPrivateRuntimePaths ( runtimeResult )
327+ const runtimeResult = sanitizeRuntimeSourceValue ( nativeRuntimeResult , privateRuntimeSourceRootForSanitization )
328+ assertNoRuntimeSourcePaths ( runtimeResult , privateRuntimeSourceRootForSanitization )
346329await cleanupPrivateRuntimeSources ( )
347- assertNoPrivateRuntimePaths ( runtimeResult )
330+ assertNoRuntimeSourcePaths ( runtimeResult , privateRuntimeSourceRootForSanitization )
348331
349332await redactArtifactFiles ( artifactsPath )
350333
@@ -408,7 +391,9 @@ const result = {
408391 ...( projectionError ? { projection_error : projectionError } : { } ) ,
409392}
410393
411- await writeFile ( resultPath , `${ JSON . stringify ( redact ( result ) , null , 2 ) } \n` )
394+ const sanitizedResult = sanitizeRuntimeSourceValue ( redact ( result ) , privateRuntimeSourceRootForSanitization )
395+ assertNoRuntimeSourcePaths ( sanitizedResult , privateRuntimeSourceRootForSanitization )
396+ await writeFile ( resultPath , `${ JSON . stringify ( sanitizedResult , null , 2 ) } \n` )
412397await output ( "job_status" , status )
413398await output ( "transcript_json" , JSON . stringify ( agentResult . refs ?. transcripts || [ ] ) )
414399await output ( "transcript_summary" , `${ request . workload ?. label || "Run Agent Task" } : ${ status } ` )
0 commit comments