Skip to content

Commit 187b013

Browse files
committed
fix: directly copy path validator code over
1 parent eb9b744 commit 187b013

File tree

4 files changed

+89
-21
lines changed

4 files changed

+89
-21
lines changed

package-lock.json

Lines changed: 3 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
"@octokit/rest": "^20.1.1",
4242
"@opengovsg/formsg-sdk": "^0.11.0",
4343
"@opengovsg/sgid-client": "^2.0.0",
44-
"@opengovsg/starter-kitty-validators": "^1.2.11",
4544
"@slack/bolt": "^3.19.0",
4645
"auto-bind": "^4.0.0",
4746
"aws-lambda": "^1.0.7",
@@ -113,7 +112,9 @@
113112
"validator": "^13.12.0",
114113
"winston": "^3.13.0",
115114
"winston-cloudwatch": "^6.3.0",
116-
"yaml": "^2.4.2"
115+
"yaml": "^2.4.2",
116+
"zod": "^3.25.76",
117+
"zod-validation-error": "^3.5.3"
117118
},
118119
"devDependencies": {
119120
"@octokit/types": "^6.35.0",

src/services/db/GitFileSystemService.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import fs from "fs"
22
import path from "path"
33

4-
import { createPathSchema } from "@opengovsg/starter-kitty-validators"
54
import { err, errAsync, ok, okAsync, Result, ResultAsync } from "neverthrow"
65
import {
76
CleanOptions,
@@ -21,6 +20,8 @@ import { NotFoundError } from "@errors/NotFoundError"
2120

2221
import tracer from "@utils/tracer"
2322

23+
import { createPathSchema } from "@validators/path"
24+
2425
import {
2526
EFS_VOL_PATH_STAGING,
2627
EFS_VOL_PATH_STAGING_LITE,

src/validators/path.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const path = require("path")
2+
3+
const { z } = require("zod")
4+
const { fromError } = require("zod-validation-error")
5+
6+
class OptionsError extends Error {
7+
constructor(message) {
8+
super(message)
9+
this.name = "OptionsError"
10+
}
11+
}
12+
13+
const optionsSchema = z.object({
14+
basePath: z
15+
.string()
16+
.refine(
17+
(basePath) =>
18+
basePath === path.resolve(basePath) && path.isAbsolute(basePath),
19+
"The base path must be an absolute path"
20+
),
21+
})
22+
23+
const isSafePath = (absPath, basePath) => {
24+
// check for poison null bytes
25+
if (absPath.indexOf("\0") !== -1) {
26+
return false
27+
}
28+
// check for backslashes
29+
if (absPath.indexOf("\\") !== -1) {
30+
return false
31+
}
32+
33+
// check for dot segments, even if they don't normalize to anything
34+
if (absPath.includes("..")) {
35+
return false
36+
}
37+
38+
// check if the normalized path is within the provided 'safe' base path
39+
if (path.resolve(basePath, path.relative(basePath, absPath)) !== absPath) {
40+
return false
41+
}
42+
if (absPath.indexOf(basePath) !== 0) {
43+
return false
44+
}
45+
return true
46+
}
47+
48+
const createValidationSchema = (options) =>
49+
z
50+
.string()
51+
// resolve the path relative to the Node process's current working directory
52+
// since that's what fs operations will be relative to
53+
.transform((untrustedPath) => path.resolve(untrustedPath))
54+
// resolvedPath is now an absolute path
55+
.refine((resolvedPath) => isSafePath(resolvedPath, options.basePath), {
56+
message: "The provided path is unsafe.",
57+
})
58+
59+
const toSchema = (options) =>
60+
z.string().trim().pipe(createValidationSchema(options))
61+
62+
/**
63+
* Create a schema that validates user-supplied pathnames for filesystem operations.
64+
*
65+
* @param options - The options to use for validation
66+
* @throws {@link OptionsError} If the options are invalid
67+
* @returns A Zod schema that validates paths.
68+
*
69+
* @public
70+
*/
71+
const createPathSchema = (options) => {
72+
const result = optionsSchema.safeParse(options)
73+
if (result.success) {
74+
return toSchema(result.data)
75+
}
76+
throw new OptionsError(fromError(result.error).toString())
77+
}
78+
79+
module.exports = {
80+
createPathSchema,
81+
}

0 commit comments

Comments
 (0)