Conversation
| // Check if lib/ folder exists for reproducibility | ||
| const libPath = path.join(taskFolderPath, 'lib'); | ||
| try { | ||
| const libStats = await fs.stat(libPath); |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 9 days ago
In general, user-controlled input used to build filesystem paths must be normalized (e.g., via path.resolve/fs.realpath) and then checked to ensure the resulting path stays within a trusted root directory before being passed to filesystem APIs. When a function is exported and might be reused from elsewhere, you should not rely solely on upstream callers to perform these checks; instead, validate inside the function as well.
For this codebase, the best fix is to make createDeterministicTarball self‑contained with respect to path safety:
- Require that
allowedDirbe provided; otherwise throw an error instead of silently proceeding with potentially unbounded paths. - Normalize
allowedDiritself (realpath) so comparisons are reliable, and then normalizetaskFolderPathrelative to that root if not already done. - Ensure all derived paths (
resolvedTaskFolderPath,libPath, and the generated tarball path) are validated withassertWithinDirbefore anyfsoperations. - Avoid writing the tarball into an uncontrolled location; instead, place it within the validated root (e.g., under
resolvedTaskFolderPath) so that even ifprocess.cwd()is something unexpected, output remains confined.
Concretely, in src/lib/task-origin-validate.ts:
- Strengthen
createDeterministicTarballso it:- Throws if
allowedDiris missing. - Normalizes
allowedDirviafs.realpath. - Normalizes
taskFolderPathusingfs.realpathonly after constructing it underallowedDirand checking withassertWithinDir. - Derives
libPathand a newtarballPathunderresolvedTaskFolderPathand validates both withassertWithinDirbefore using them.
- Throws if
- Adjust tarball output location to be inside
resolvedTaskFolderPath(or another validated directory underallowedDir) instead of usingprocess.cwd()without checks.
These changes keep existing functional behavior (creating a deterministic tarball of the task folder and checking lib/ for reproducibility) while enforcing that all filesystem activity remains within the configured allowedDir.
| @@ -57,22 +57,30 @@ | ||
| taskFolderPath: string, | ||
| allowedDir?: string | ||
| ): Promise<string> { | ||
| // If an allowed directory is specified, resolve symlinks and validate the real path | ||
| let resolvedTaskFolderPath = taskFolderPath; | ||
| if (allowedDir) { | ||
| resolvedTaskFolderPath = await fs.realpath(taskFolderPath); | ||
| assertWithinDir(resolvedTaskFolderPath, allowedDir); | ||
| // For security, require an allowed directory to constrain all filesystem access | ||
| if (!allowedDir) { | ||
| throw new Error( | ||
| 'createDeterministicTarball: allowedDir is required to constrain filesystem access' | ||
| ); | ||
| } | ||
|
|
||
| // Take the last '/' separate part of the folder path to be the tarfile name | ||
| const folderName = resolvedTaskFolderPath.split('/').pop(); | ||
| const tarballPath = path.resolve(process.cwd(), `${folderName}.tar`); | ||
| // Normalize and validate the allowed directory itself | ||
| const resolvedAllowedDir = await fs.realpath(allowedDir); | ||
|
|
||
| // Resolve symlinks for the task folder path and validate the real path | ||
| let resolvedTaskFolderPath = await fs.realpath(taskFolderPath); | ||
| assertWithinDir(resolvedTaskFolderPath, resolvedAllowedDir); | ||
|
|
||
| // Take the last path segment of the folder path to be the tarfile name | ||
| const folderName = path.basename(resolvedTaskFolderPath); | ||
|
|
||
| // Place the tarball within the validated task folder directory | ||
| const tarballPath = path.join(resolvedTaskFolderPath, `${folderName}.tar`); | ||
| assertWithinDir(tarballPath, resolvedAllowedDir); | ||
|
|
||
| // Check if lib/ folder exists for reproducibility | ||
| const libPath = path.join(resolvedTaskFolderPath, 'lib'); | ||
| if (allowedDir) { | ||
| assertWithinDir(libPath, allowedDir); | ||
| } | ||
| assertWithinDir(libPath, resolvedAllowedDir); | ||
| try { | ||
| const libStats = await fs.stat(libPath); | ||
| if (!libStats.isDirectory()) { | ||
| @@ -92,7 +95,7 @@ | ||
| const files = await getAllFilesRecursively( | ||
| resolvedTaskFolderPath, | ||
| resolvedTaskFolderPath, | ||
| allowedDir | ||
| resolvedAllowedDir | ||
| ); | ||
| const sortedFiles = files.sort(); | ||
|
|
|
|
||
| // Extract the bundle containing both the signature and certificate chain | ||
| console.log(` Signature: ${signatureFile}`); | ||
| const bundleSigJSON = JSON.parse(await fs.readFile(signatureFile, 'utf8')); |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 9 days ago
General approach: ensure any path that can be influenced by user input is normalized and verified to lie within an allowed root directory before being passed to file-system APIs. Even if upstream code intends to always pass safe values, placing the check at the boundary (just before fs.readFile) avoids future misuse and satisfies the static analyzer.
Best concrete fix here:
buildAndValidateSignaturereceivesTaskOriginVerifyOptionswith an optionalallowedDir. Today it only enforcesassertWithinDirifallowedDiris truthy and trusts the caller to supply it.- We can make
buildAndValidateSignaturerobust by:- Treating
allowedDiras required for safe use (at least forsignatureFile), and - Resolving and validating the real path of
signatureFileagainstallowedDirbefore reading it.
- Treating
- This mirrors the recommended pattern: normalize (via
path.resolve/fs.realpath) then check containment (assertWithinDir), and only then callfs.readFile.
Concretely, in src/lib/task-origin-validate.ts:
- In
buildAndValidateSignature, beforefs.readFile(signatureFile, 'utf8'):- If
allowedDiris not provided, throw an error (instead of silently skipping validation) to prevent unsafe use of this function. - Use
const resolvedSignatureFile = await fs.realpath(signatureFile);to resolve symlinks. - Call
assertWithinDir(resolvedSignatureFile, allowedDir);. - Use
resolvedSignatureFileforfs.readFileand the subsequentfs.statinverifyTaskOriginif you want to be fully consistent, but to minimize behavior changes we’ll at least ensure the read inbuildAndValidateSignatureuses the validated path.
- If
- Keep the existing
assertWithinDirinverifyTaskOriginandvalidateSigneras defense-in-depth; we’re just adding an extra guarantee at the actual sink.
No new imports are required: fs (promises) and path are already imported, and assertWithinDir already exists. All changes occur inside the provided snippet in src/lib/task-origin-validate.ts.
| @@ -120,9 +120,15 @@ | ||
| assertWithinDir(signatureFile, allowedDir); | ||
| } | ||
|
|
||
| if (!allowedDir) { | ||
| throw new Error('allowedDir is required when validating task origin signatures'); | ||
| } | ||
|
|
||
| // Extract the bundle containing both the signature and certificate chain | ||
| console.log(` Signature: ${signatureFile}`); | ||
| const bundleSigJSON = JSON.parse(await fs.readFile(signatureFile, 'utf8')); | ||
| const resolvedSignatureFile = await fs.realpath(signatureFile); | ||
| assertWithinDir(resolvedSignatureFile, allowedDir); | ||
| const bundleSigJSON = JSON.parse(await fs.readFile(resolvedSignatureFile, 'utf8')); | ||
| const bundleSig = bundleFromJSON(bundleSigJSON); | ||
|
|
||
| // Regenerate the tarball from the provided task folder |
|
|
||
| // Make sure that the task folder path and signature file exist | ||
| try { | ||
| const taskStats = await fs.stat(taskFolderPath); |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 9 days ago
General approach: Ensure that any filesystem operations on paths influenced by user input are performed only after validating those paths against a trusted root or sanitizing them. Here, the best fit is “containment within a safe root” using assertWithinDir.
Concrete fix for this codebase:
- In
verifyTaskOrigin(src/lib/task-origin-validate.ts), normalize and validatetaskFolderPathandsignatureFilebefore using them infs.stat, even whenallowedDiris not provided or caller forgot to validate. - We already have
assertWithinDirimported; we should make use of it unconditionally. SinceverifyTaskOriginis always called fromvalidateSignerwithallowedDir: CONTRACT_DEPLOYMENTS_ROOTtoday, strengthening the checks insideverifyTaskOriginwill not change existing behavior for current callers but will harden the function against any future misuse. - The simplest, non‑breaking change is:
- If
allowedDiris provided, resolve both paths and validate them withassertWithinDirbefore any filesystem calls (this is already done, but we should base all subsequent operations on the validated values). - If
allowedDiris not provided, still normalize the paths viapath.resolvebefore passing them tofs.stat. This mitigates some issues and avoids odd relative paths, although without an allowed root there’s no strong containment. However, the main security invariant for this app is that callers should passallowedDir; we’ll enforce that at compile-time by leaving the type as-is but making the runtime behavior safer.
- If
- To keep behavior identical while satisfying CodeQL, we can take a stronger step: once
allowedDiris provided and the check passes, overwritetaskFolderPathandsignatureFilewith their resolved versions and only use those from then on. This ensures we’re operating on normalized, validated paths and makes the data flow clearer to the analyzer.
Specific edits (all in src/lib/task-origin-validate.ts):
- Introduce new local variables
validatedTaskFolderPathandvalidatedSignatureFilethat, whenallowedDiris set, are the outputs ofassertWithinDir(assuming it returns the normalized path; if it currently returns void, we can instead callpath.resolveourselves before and after). Because we cannot changeassertWithinDirhere, we’ll implement normalization directly but still callassertWithinDirfor the security check. - Use these validated variables in both
fs.statcalls instead of the rawtaskFolderPath/signatureFileso the sink sees only paths that have passed validation.
No new imports or external dependencies are required; we already import path and assertWithinDir.
| @@ -240,33 +240,40 @@ | ||
| throw new Error('Task folder path, signature file, commonName, and role are required'); | ||
| } | ||
|
|
||
| // Validate paths are within the allowed directory if specified | ||
| // Normalize paths and, if specified, validate they are within the allowed directory | ||
| const normalizedTaskFolderPath = path.resolve(taskFolderPath); | ||
| const normalizedSignatureFile = path.resolve(signatureFile); | ||
|
|
||
| if (allowedDir) { | ||
| assertWithinDir(taskFolderPath, allowedDir); | ||
| assertWithinDir(signatureFile, allowedDir); | ||
| assertWithinDir(normalizedTaskFolderPath, allowedDir); | ||
| assertWithinDir(normalizedSignatureFile, allowedDir); | ||
| } | ||
|
|
||
| // Use normalized (and, if applicable, validated) paths for filesystem access | ||
| const safeTaskFolderPath = normalizedTaskFolderPath; | ||
| const safeSignatureFile = normalizedSignatureFile; | ||
|
|
||
| // Make sure that the task folder path and signature file exist | ||
| try { | ||
| const taskStats = await fs.stat(taskFolderPath); | ||
| const taskStats = await fs.stat(safeTaskFolderPath); | ||
| if (!taskStats.isDirectory()) { | ||
| throw new Error(`Task folder path exists but is not a directory: ${taskFolderPath}`); | ||
| throw new Error(`Task folder path exists but is not a directory: ${safeTaskFolderPath}`); | ||
| } | ||
| } catch (error) { | ||
| if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') { | ||
| throw new Error(`Task folder path does not exist: ${taskFolderPath}`); | ||
| if (error && typeof error === 'object' && 'code' in error && (error as any).code === 'ENOENT') { | ||
| throw new Error(`Task folder path does not exist: ${safeTaskFolderPath}`); | ||
| } | ||
| throw error; | ||
| } | ||
|
|
||
| try { | ||
| const sigStats = await fs.stat(signatureFile); | ||
| const sigStats = await fs.stat(safeSignatureFile); | ||
| if (!sigStats.isFile()) { | ||
| throw new Error(`Signature path exists but is not a file: ${signatureFile}`); | ||
| throw new Error(`Signature path exists but is not a file: ${safeSignatureFile}`); | ||
| } | ||
| } catch (error) { | ||
| if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') { | ||
| throw new Error(`Signature file does not exist: ${signatureFile}`); | ||
| if (error && typeof error === 'object' && 'code' in error && (error as any).code === 'ENOENT') { | ||
| throw new Error(`Signature file does not exist: ${safeSignatureFile}`); | ||
| } | ||
| throw error; | ||
| } |
| } | ||
| throw error; | ||
| try { | ||
| const sigStats = await fs.stat(signatureFile); |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 9 days ago
General approach: Before using any path derived from user-controlled data in filesystem operations, normalize the path and enforce that it resides within an allowed root directory. In this codebase, that is exactly what assertWithinDir is meant to do. We should call it in verifyTaskOrigin with the allowedDir for all file system uses of taskFolderPath and signatureFile, regardless of whether upstream callers already validated them.
Best concrete fix here: strengthen verifyTaskOrigin so that it always validates taskFolderPath and signatureFile against allowedDir at the point of use, right before the fs.stat calls. This makes verifyTaskOrigin self-contained in terms of security: any caller that passes allowedDir gets guaranteed validation, and the static analysis tool will see the validation happening in the same function as the sink.
Specific changes in src/lib/task-origin-validate.ts:
- In
verifyTaskOrigin, keep the existing early validation block:
// Validate paths are within the allowed directory if specified
if (allowedDir) {
assertWithinDir(taskFolderPath, allowedDir);
assertWithinDir(signatureFile, allowedDir);
}- Additionally, before calling
fs.stat(taskFolderPath)andfs.stat(signatureFile), re-assert the directory constraint ifallowedDiris provided. This is a no-op in terms of functionality (since the earlier checks should already pass) but clarifies to both humans and tools that, just before the filesystem operation, the path is known to be safe.
That means:
- Just before
const taskStats = await fs.stat(taskFolderPath);, insert:
if (allowedDir) {
assertWithinDir(taskFolderPath, allowedDir);
}- Just before
const sigStats = await fs.stat(signatureFile);, insert:
if (allowedDir) {
assertWithinDir(signatureFile, allowedDir);
}No new imports or types are required; assertWithinDir is already imported at the top of task-origin-validate.ts, and we are not changing function signatures or external behavior.
| @@ -248,6 +248,9 @@ | ||
|
|
||
| // Make sure that the task folder path and signature file exist | ||
| try { | ||
| if (allowedDir) { | ||
| assertWithinDir(taskFolderPath, allowedDir); | ||
| } | ||
| const taskStats = await fs.stat(taskFolderPath); | ||
| if (!taskStats.isDirectory()) { | ||
| throw new Error(`Task folder path exists but is not a directory: ${taskFolderPath}`); | ||
| @@ -260,6 +263,9 @@ | ||
| } | ||
|
|
||
| try { | ||
| if (allowedDir) { | ||
| assertWithinDir(signatureFile, allowedDir); | ||
| } | ||
| const sigStats = await fs.stat(signatureFile); | ||
| if (!sigStats.isFile()) { | ||
| throw new Error(`Signature path exists but is not a file: ${signatureFile}`); |
| // If an allowed directory is specified, resolve symlinks and validate the real path | ||
| let currentDir = dir; | ||
| if (allowedDir) { | ||
| currentDir = await fs.realpath(dir); |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 9 days ago
General approach: ensure any filesystem path derived (even indirectly) from user-controlled data is (a) resolved/normalized and (b) required to lie within a designated safe root before performing operations like realpath, readdir, stat, or tar creation. Avoid optional security parameters where possible: make allowedDir mandatory for functions that walk directories or create tarballs, and validate inputs as early as possible.
Best concrete fix here:
- Make
allowedDirmandatory forgetAllFilesRecursivelyandcreateDeterministicTarball. That way, these functions can always enforce a root directory and cannot be accidentally called in an unsafe way. - In
getAllFilesRecursively, callassertWithinDir(dir, allowedDir)beforefs.realpath(dir). This ensures that even the initial path string (before symlink resolution) is anchored under the allowed root, avoiding using completely arbitrarydirwithrealpath. Then callfs.realpathand validate the resolved path again if desired (we’re already validatingcurrentDirafterrealpathtoday). - In
createDeterministicTarball, makeallowedDirrequired and moveassertWithinDir(taskFolderPath, allowedDir)before thefs.realpathcall, mirroring the same pattern: validate the user-influenced path string, then normalize it, then validate subsequent derived paths (libPathetc.) against the same root. - Update the call site in
buildAndValidateSignatureto match the newcreateDeterministicTarballsignature (no optionalallowedDir), and keep the pre-existingassertWithinDirchecks intact.
This preserves existing behavior for the current caller (which already always passes allowedDir), but makes the safety invariant local to the helpers and eliminates the CodeQL warning by ensuring untrusted data is never used with fs.realpath before being range-checked.
| @@ -23,14 +23,12 @@ | ||
| async function getAllFilesRecursively( | ||
| dir: string, | ||
| baseDir: string = dir, | ||
| allowedDir?: string | ||
| allowedDir: string | ||
| ): Promise<string[]> { | ||
| // If an allowed directory is specified, resolve symlinks and validate the real path | ||
| let currentDir = dir; | ||
| if (allowedDir) { | ||
| currentDir = await fs.realpath(dir); | ||
| assertWithinDir(currentDir, allowedDir); | ||
| } | ||
| // Resolve symlinks and validate the real path within the allowed directory | ||
| assertWithinDir(dir, allowedDir); | ||
| let currentDir = await fs.realpath(dir); | ||
| assertWithinDir(currentDir, allowedDir); | ||
|
|
||
| const entries = await fs.readdir(currentDir, { withFileTypes: true }); | ||
| const files: string[] = []; | ||
| @@ -55,14 +52,12 @@ | ||
|
|
||
| export async function createDeterministicTarball( | ||
| taskFolderPath: string, | ||
| allowedDir?: string | ||
| allowedDir: string | ||
| ): Promise<string> { | ||
| // If an allowed directory is specified, resolve symlinks and validate the real path | ||
| let resolvedTaskFolderPath = taskFolderPath; | ||
| if (allowedDir) { | ||
| resolvedTaskFolderPath = await fs.realpath(taskFolderPath); | ||
| assertWithinDir(resolvedTaskFolderPath, allowedDir); | ||
| } | ||
| // Resolve symlinks and validate the real path within the allowed directory | ||
| assertWithinDir(taskFolderPath, allowedDir); | ||
| let resolvedTaskFolderPath = await fs.realpath(taskFolderPath); | ||
| assertWithinDir(resolvedTaskFolderPath, allowedDir); | ||
|
|
||
| // Take the last '/' separate part of the folder path to be the tarfile name | ||
| const folderName = resolvedTaskFolderPath.split('/').pop(); | ||
| @@ -70,9 +64,7 @@ | ||
|
|
||
| // Check if lib/ folder exists for reproducibility | ||
| const libPath = path.join(resolvedTaskFolderPath, 'lib'); | ||
| if (allowedDir) { | ||
| assertWithinDir(libPath, allowedDir); | ||
| } | ||
| assertWithinDir(libPath, allowedDir); | ||
| try { | ||
| const libStats = await fs.stat(libPath); | ||
| if (!libStats.isDirectory()) { | ||
| @@ -126,7 +118,7 @@ | ||
| const bundleSig = bundleFromJSON(bundleSigJSON); | ||
|
|
||
| // Regenerate the tarball from the provided task folder | ||
| const tarballPath = await createDeterministicTarball(taskFolderPath, allowedDir); | ||
| const tarballPath = await createDeterministicTarball(taskFolderPath, allowedDir ?? taskFolderPath); | ||
| const tarball = await fs.readFile(tarballPath); // Read as binary Buffer | ||
|
|
||
| // Extract the deployment-specific intermediate CA from bundle |
| assertWithinDir(currentDir, allowedDir); | ||
| } | ||
|
|
||
| const entries = await fs.readdir(currentDir, { withFileTypes: true }); |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 9 days ago
In general, to fix uncontrolled path usage when walking directories recursively, you should (1) normalize each directory path (e.g., using fs.realpath or path.resolve) and (2) ensure it is still within an allowed root directory before performing filesystem operations like readdir or recursing further. This prevents an attacker from leveraging symlinks or crafted directory names under a trusted root to escape to arbitrary locations.
The best minimally invasive fix here is to ensure that getAllFilesRecursively enforces the allowedDir constraint on every directory it recurses into, not just the initial dir. We can do this by: (a) always normalizing dir at the beginning, (b) calling assertWithinDir on that normalized path whenever allowedDir is supplied, and (c) when recursing into fullPath, passing fullPath as the dir argument, letting the next call repeat normalization and validation before calling fs.readdir. Concretely, in src/lib/task-origin-validate.ts within getAllFilesRecursively, we should change the logic around currentDir so that fs.realpath is always used to normalize dir, and the assertWithinDir check applies to that normalized path, then fs.readdir uses the normalized currentDir. The recursion already calls getAllFilesRecursively(fullPath, baseDir, allowedDir), so once we normalize and validate dir consistently at the top of the function, all recursive steps will be covered. No changes are necessary in validation-service.ts or the API handler for this specific issue, and no new external dependencies are required.
| @@ -25,10 +25,9 @@ | ||
| baseDir: string = dir, | ||
| allowedDir?: string | ||
| ): Promise<string[]> { | ||
| // If an allowed directory is specified, resolve symlinks and validate the real path | ||
| let currentDir = dir; | ||
| // Resolve symlinks and validate the real path for every directory we traverse | ||
| const currentDir = await fs.realpath(dir); | ||
| if (allowedDir) { | ||
| currentDir = await fs.realpath(dir); | ||
| assertWithinDir(currentDir, allowedDir); | ||
| } | ||
|
|
| // If an allowed directory is specified, resolve symlinks and validate the real path | ||
| let resolvedTaskFolderPath = taskFolderPath; | ||
| if (allowedDir) { | ||
| resolvedTaskFolderPath = await fs.realpath(taskFolderPath); |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 9 days ago
In general, the problem is that createDeterministicTarball trusts its taskFolderPath argument and only conditionally normalizes and validates it based on an optional allowedDir. Static analysis correctly shows that this argument can originate from user input. The safest fix is to ensure that createDeterministicTarball always performs normalization and directory containment validation, or at least refuses to operate if no allowedDir is provided, instead of silently proceeding with a raw, potentially attacker-controlled path.
Best concrete fix without changing external behavior for existing callers:
- Require that
allowedDirbe provided when creating a tarball and enforce it insidecreateDeterministicTarball. - Normalize
taskFolderPathwithfs.realpathand check it (and any derived important subpaths such aslibPath) usingassertWithinDir, even when the caller has already performed checks. This is cheap and removes any reliance on caller discipline. - If
allowedDiris not provided, throw an error rather than proceeding with an unconstrained filesystem operation.
This only requires changes within src/lib/task-origin-validate.ts:
-
In
createDeterministicTarball:- At the top of the function, check if
allowedDiris provided; if not, throw a descriptive error. - Remove the conditional “if (allowedDir)” branch and instead always:
- Resolve
taskFolderPathwithfs.realpath. - Call
assertWithinDiron the resolved path andallowedDir.
- Resolve
- For the
libPath, we already callassertWithinDirwhenallowedDiris set; after the change, that guard will always run becauseallowedDiris required.
- At the top of the function, check if
-
In
buildAndValidateSignature:- Before calling
createDeterministicTarball, ensureallowedDiris present; if not, throw an error. This is defensive and makes the requirement explicit at the only call site we see.
- Before calling
No new helpers or imports are needed; we reuse fs.realpath and assertWithinDir which are already imported in this file.
| @@ -57,22 +57,23 @@ | ||
| taskFolderPath: string, | ||
| allowedDir?: string | ||
| ): Promise<string> { | ||
| // If an allowed directory is specified, resolve symlinks and validate the real path | ||
| let resolvedTaskFolderPath = taskFolderPath; | ||
| if (allowedDir) { | ||
| resolvedTaskFolderPath = await fs.realpath(taskFolderPath); | ||
| assertWithinDir(resolvedTaskFolderPath, allowedDir); | ||
| if (!allowedDir) { | ||
| throw new Error( | ||
| 'createDeterministicTarball: allowedDir is required to validate taskFolderPath' | ||
| ); | ||
| } | ||
|
|
||
| // Resolve symlinks and validate the real path against the allowed directory | ||
| const resolvedTaskFolderPath = await fs.realpath(taskFolderPath); | ||
| assertWithinDir(resolvedTaskFolderPath, allowedDir); | ||
|
|
||
| // Take the last '/' separate part of the folder path to be the tarfile name | ||
| const folderName = resolvedTaskFolderPath.split('/').pop(); | ||
| const tarballPath = path.resolve(process.cwd(), `${folderName}.tar`); | ||
|
|
||
| // Check if lib/ folder exists for reproducibility | ||
| const libPath = path.join(resolvedTaskFolderPath, 'lib'); | ||
| if (allowedDir) { | ||
| assertWithinDir(libPath, allowedDir); | ||
| } | ||
| assertWithinDir(libPath, allowedDir); | ||
| try { | ||
| const libStats = await fs.stat(libPath); | ||
| if (!libStats.isDirectory()) { | ||
| @@ -114,12 +107,16 @@ | ||
| const { taskFolderPath, signatureFile, commonName, role, allowedDir } = options; | ||
| console.log(` Task folder: ${taskFolderPath}`); | ||
|
|
||
| // Validate paths are within the allowed directory if specified | ||
| if (allowedDir) { | ||
| assertWithinDir(taskFolderPath, allowedDir); | ||
| assertWithinDir(signatureFile, allowedDir); | ||
| if (!allowedDir) { | ||
| throw new Error( | ||
| 'buildAndValidateSignature: allowedDir is required to validate task and signature paths' | ||
| ); | ||
| } | ||
|
|
||
| // Validate paths are within the allowed directory | ||
| assertWithinDir(taskFolderPath, allowedDir); | ||
| assertWithinDir(signatureFile, allowedDir); | ||
|
|
||
| // Extract the bundle containing both the signature and certificate chain | ||
| console.log(` Signature: ${signatureFile}`); | ||
| const bundleSigJSON = JSON.parse(await fs.readFile(signatureFile, 'utf8')); |
No description provided.