11import { createHash } from "node:crypto"
22import { mkdtemp , rm , mkdir , writeFile } from "node:fs/promises"
33import { tmpdir } from "node:os"
4- import { join } from "node:path"
4+ import { isAbsolute , join , relative , resolve } from "node:path"
55import { spawn } from "node:child_process"
66
77const REPOSITORY = / ^ [ A - Z a - z 0 - 9 _ . - ] + \/ [ A - Z a - z 0 - 9 _ . - ] + $ /
@@ -178,6 +178,7 @@ export async function materializeRuntimeSources(sources, options = {}) {
178178 const descriptors = normalizeRuntimeSources ( sources , options . policy )
179179 const root = await mkdtemp ( join ( options . tempRoot ?? tmpdir ( ) , "wp-codebox-runtime-sources-" ) )
180180 try {
181+ assertPrivateRuntimeRoot ( root , options . forbiddenRoots )
181182 const lowered = [ ]
182183 for ( const [ index , descriptor ] of descriptors . entries ( ) ) {
183184 const checkout = join ( root , `source-${ index } ` )
@@ -189,25 +190,42 @@ export async function materializeRuntimeSources(sources, options = {}) {
189190 await run ( "git" , [ "-c" , "credential.helper=" , "-c" , "http.extraHeader=" , "fetch" , "--depth=1" , "origin" , descriptor . revision ] , { cwd : checkout , env : environment } )
190191 const commit = ( await run ( "git" , [ "rev-parse" , "FETCH_HEAD^{commit}" ] , { cwd : checkout , env : environment } ) ) . toString ( "utf8" ) . trim ( ) . toLowerCase ( )
191192 if ( commit !== descriptor . revision ) throw new Error ( "Runtime source revision did not resolve to the requested immutable commit." )
192- const objectType = ( await run ( "git" , [ "cat-file" , "-t" , `${ descriptor . revision } :${ descriptor . path } ` ] , { cwd : checkout , env : environment } ) ) . toString ( "utf8" ) . trim ( )
193+ const sourceObject = descriptor . path === "." ? `${ descriptor . revision } ^{tree}` : `${ descriptor . revision } :${ descriptor . path } `
194+ const objectType = ( await run ( "git" , [ "cat-file" , "-t" , sourceObject ] , { cwd : checkout , env : environment } ) ) . toString ( "utf8" ) . trim ( )
193195 if ( objectType !== "tree" ) throw new Error ( "Runtime source path must identify a directory." )
194- const entries = ( await run ( "git" , [ "ls-tree" , "-r" , "-z" , descriptor . revision , "--" , descriptor . path ] , { cwd : checkout , env : environment } ) ) . toString ( "utf8" ) . split ( "\0" ) . filter ( Boolean )
196+ const entries = ( await run ( "git" , descriptor . path === "." ? [ "ls-tree" , "-r" , "-z" , descriptor . revision ] : [ "ls-tree" , "-r" , "-z" , descriptor . revision , "--" , descriptor . path ] , { cwd : checkout , env : environment } ) ) . toString ( "utf8" ) . split ( "\0" ) . filter ( Boolean )
195197 if ( entries . length === 0 || entries . some ( ( entry ) => ! / ^ 1 0 0 (?: 6 4 4 | 7 5 5 ) \s + b l o b \s + [ 0 - 9 a - f ] { 40 } \t / . test ( entry ) ) ) throw new Error ( "Runtime source must contain only regular files; symlinks and special files are rejected." )
196- const archive = await run ( "git" , [ "archive" , "--format=tar" , descriptor . revision , descriptor . path ] , { cwd : checkout , env : environment } )
198+ const archive = await run ( "git" , descriptor . path === "." ? [ "archive" , "--format=tar" , descriptor . revision ] : [ "archive" , "--format=tar" , descriptor . revision , descriptor . path ] , { cwd : checkout , env : environment } )
197199 if ( descriptor . digest && sha256GitArchiveV1 ( archive ) !== descriptor . digest ) throw new Error ( "Runtime source archive digest does not match the trusted descriptor." )
198200 await mkdir ( source , { recursive : true } )
199201 const archivePath = join ( checkout , "source.tar" )
200202 await writeFile ( archivePath , archive )
201203 await run ( "tar" , [ "-xf" , archivePath , "-C" , source ] , { cwd : checkout , env : environment } )
202- const materializedPath = join ( source , descriptor . path )
204+ const materializedPath = descriptor . path === "." ? source : join ( source , descriptor . path )
203205 lowered . push ( lowerRuntimeSource ( descriptor , materializedPath ) )
204206 }
205- return { root, descriptors, lowered }
207+ return { root, descriptors : descriptors . map ( runtimeSourceProvenance ) , lowered }
206208 } catch ( error ) { await rm ( root , { recursive : true , force : true } ) ; throw error }
207209}
208210
211+ export function runtimeSourceProvenance ( descriptor ) {
212+ return { role : descriptor . role , repository : descriptor . repository , revision : descriptor . revision , path : descriptor . path , ...( descriptor . digest ? { digest : descriptor . digest } : { } ) }
213+ }
214+
215+ export function assertPrivateRuntimeRoot ( root , forbiddenRoots = [ ] ) {
216+ const privateRoot = resolve ( root )
217+ for ( const forbiddenRoot of forbiddenRoots ) {
218+ if ( ! forbiddenRoot ) continue
219+ const boundary = resolve ( forbiddenRoot )
220+ const path = relative ( boundary , privateRoot )
221+ if ( privateRoot === boundary || ( ! path . startsWith ( `..${ String . fromCharCode ( 47 ) } ` ) && path !== ".." && ! isAbsolute ( path ) ) ) {
222+ throw new Error ( "Runtime sources must be materialized outside target workspaces and artifacts." )
223+ }
224+ }
225+ }
226+
209227export function lowerRuntimeSource ( descriptor , source ) {
210- const provenance = { role : descriptor . role , repository : descriptor . repository , revision : descriptor . revision , path : descriptor . path , ... ( descriptor . digest ? { digest : descriptor . digest } : { } ) }
228+ const provenance = runtimeSourceProvenance ( descriptor )
211229 if ( descriptor . role === "component" ) return { component_contracts : [ { path : source , ...descriptor . metadata , metadata : { runtime_source : provenance } } ] }
212230 if ( descriptor . role === "provider_plugin" ) return { provider_plugin_paths : [ source ] , provider_plugins : [ { source, ...descriptor . metadata , metadata : { runtime_source : provenance } } ] }
213231 return { runtime_overlays : [ { kind : "bundled-library" , source, ...descriptor . metadata , metadata : { runtime_source : provenance } } ] }
0 commit comments