@mintlify/cli 4.0.906 → 4.0.907
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__test__/init.test.ts +13 -8
- package/__test__/openApiCheck.test.ts +0 -17
- package/__test__/pathValidation.test.ts +25 -0
- package/bin/helpers.js +2 -8
- package/bin/init.js +2 -11
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/helpers.tsx +2 -8
- package/src/init.tsx +2 -12
package/__test__/init.test.ts
CHANGED
|
@@ -12,11 +12,16 @@ vi.mock('@mintlify/previewing', () => ({
|
|
|
12
12
|
removeLastLog: vi.fn(),
|
|
13
13
|
}));
|
|
14
14
|
|
|
15
|
-
vi.mock('@mintlify/validation', () =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
vi.mock('@mintlify/validation', async () => {
|
|
16
|
+
const original =
|
|
17
|
+
await vi.importActual<typeof import('@mintlify/validation')>('@mintlify/validation');
|
|
18
|
+
return {
|
|
19
|
+
...original,
|
|
20
|
+
docsConfigSchema: {
|
|
21
|
+
options: [{ shape: { theme: { _def: { value: 'quill' } } } }],
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
});
|
|
20
25
|
|
|
21
26
|
vi.mock('fs-extra', () => ({
|
|
22
27
|
default: {
|
|
@@ -48,19 +53,19 @@ describe('init', () => {
|
|
|
48
53
|
describe('path traversal prevention', () => {
|
|
49
54
|
it('rejects path traversal with ../', async () => {
|
|
50
55
|
await expect(init('../outside', false, 'quill', 'Test')).rejects.toThrow(
|
|
51
|
-
'Access denied: path
|
|
56
|
+
'Access denied: path: ../outside is outside the current directory'
|
|
52
57
|
);
|
|
53
58
|
});
|
|
54
59
|
|
|
55
60
|
it('rejects deep path traversal', async () => {
|
|
56
61
|
await expect(init('../../../etc', false, 'quill', 'Test')).rejects.toThrow(
|
|
57
|
-
'Access denied: path
|
|
62
|
+
'Access denied: path: ../../../etc is outside the current directory'
|
|
58
63
|
);
|
|
59
64
|
});
|
|
60
65
|
|
|
61
66
|
it('rejects absolute paths outside cwd', async () => {
|
|
62
67
|
await expect(init('/etc/test', false, 'quill', 'Test')).rejects.toThrow(
|
|
63
|
-
'Access denied: path
|
|
68
|
+
'Access denied: path: /etc/test is outside the current directory'
|
|
64
69
|
);
|
|
65
70
|
});
|
|
66
71
|
|
|
@@ -125,20 +125,3 @@ describe('openApiCheck', () => {
|
|
|
125
125
|
expect(processExitMock).toHaveBeenCalledWith(1);
|
|
126
126
|
});
|
|
127
127
|
});
|
|
128
|
-
|
|
129
|
-
describe('readLocalOpenApiFile', () => {
|
|
130
|
-
it('rejects path traversal attempts', async () => {
|
|
131
|
-
const { readLocalOpenApiFile } =
|
|
132
|
-
await vi.importActual<typeof import('../src/helpers.js')>('../src/helpers.js');
|
|
133
|
-
|
|
134
|
-
await expect(readLocalOpenApiFile('../etc/passwd')).rejects.toThrow(
|
|
135
|
-
'Access denied: invalid path'
|
|
136
|
-
);
|
|
137
|
-
await expect(readLocalOpenApiFile('/etc/passwd')).rejects.toThrow(
|
|
138
|
-
'Access denied: invalid path'
|
|
139
|
-
);
|
|
140
|
-
await expect(readLocalOpenApiFile('../../secret.yaml')).rejects.toThrow(
|
|
141
|
-
'Access denied: invalid path'
|
|
142
|
-
);
|
|
143
|
-
});
|
|
144
|
-
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { readLocalOpenApiFile } from '../src/helpers.js';
|
|
4
|
+
|
|
5
|
+
describe('readLocalOpenApiFile', () => {
|
|
6
|
+
describe('path traversal prevention', () => {
|
|
7
|
+
it('rejects path traversal with ../', async () => {
|
|
8
|
+
await expect(readLocalOpenApiFile('../etc/passwd')).rejects.toThrow(
|
|
9
|
+
'path: ../etc/passwd is outside the current directory'
|
|
10
|
+
);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('rejects absolute paths', async () => {
|
|
14
|
+
await expect(readLocalOpenApiFile('/etc/passwd')).rejects.toThrow(
|
|
15
|
+
'path: /etc/passwd is outside the current directory'
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('rejects deep path traversal', async () => {
|
|
20
|
+
await expect(readLocalOpenApiFile('../../secret.yaml')).rejects.toThrow(
|
|
21
|
+
'path: ../../secret.yaml is outside the current directory'
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|
package/bin/helpers.js
CHANGED
|
@@ -11,7 +11,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
11
11
|
import { getConfigPath } from '@mintlify/prebuild';
|
|
12
12
|
import { MintConfigUpdater } from '@mintlify/prebuild';
|
|
13
13
|
import { addLog, ErrorLog, getClientVersion, SuccessLog, InfoLog, SpinnerLog, removeLastLog, LOCAL_LINKED_CLI_VERSION, } from '@mintlify/previewing';
|
|
14
|
-
import { upgradeToDocsConfig } from '@mintlify/validation';
|
|
14
|
+
import { upgradeToDocsConfig, validatePathWithinCwd } from '@mintlify/validation';
|
|
15
15
|
import detect from 'detect-port';
|
|
16
16
|
import fse from 'fs-extra';
|
|
17
17
|
import fs from 'fs/promises';
|
|
@@ -143,13 +143,7 @@ export const suppressConsoleWarnings = () => {
|
|
|
143
143
|
};
|
|
144
144
|
};
|
|
145
145
|
export const readLocalOpenApiFile = (filename) => __awaiter(void 0, void 0, void 0, function* () {
|
|
146
|
-
const
|
|
147
|
-
// const pathname = path.resolve(process.cwd(), filename);
|
|
148
|
-
const resolvedPath = path.resolve(baseDir, filename);
|
|
149
|
-
const relative = path.relative(baseDir, resolvedPath);
|
|
150
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
151
|
-
throw new Error('Access denied: invalid path');
|
|
152
|
-
}
|
|
146
|
+
const { resolvedPath } = validatePathWithinCwd(filename, CMD_EXEC_PATH);
|
|
153
147
|
const file = yield fs.readFile(resolvedPath, 'utf-8');
|
|
154
148
|
const document = yaml.load(file);
|
|
155
149
|
return document;
|
package/bin/init.js
CHANGED
|
@@ -10,19 +10,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
11
|
import { select, input } from '@inquirer/prompts';
|
|
12
12
|
import { addLogs, addLog, SpinnerLog, removeLastLog } from '@mintlify/previewing';
|
|
13
|
-
import { docsConfigSchema } from '@mintlify/validation';
|
|
13
|
+
import { docsConfigSchema, validatePathWithinCwd } from '@mintlify/validation';
|
|
14
14
|
import AdmZip from 'adm-zip';
|
|
15
15
|
import fse from 'fs-extra';
|
|
16
16
|
import { Box, Text } from 'ink';
|
|
17
|
-
import path from 'path';
|
|
18
|
-
const validatePathWithinCwd = (inputPath) => {
|
|
19
|
-
const baseDir = process.cwd();
|
|
20
|
-
const resolvedPath = path.resolve(baseDir, inputPath);
|
|
21
|
-
const relative = path.relative(baseDir, resolvedPath);
|
|
22
|
-
if (relative.startsWith(`..${path.sep}`) || relative === '..' || path.isAbsolute(relative)) {
|
|
23
|
-
throw new Error(`Access denied: path "${inputPath}" is outside the current directory`);
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
17
|
const sendOnboardingMessage = (installDir) => {
|
|
27
18
|
addLogs(_jsx(Text, { bold: true, children: "Documentation Setup!" }), _jsx(Text, { children: "To see your docs run" }), _jsxs(Box, { children: [_jsx(Text, { color: "blue", children: "cd" }), _jsxs(Text, { children: [" ", installDir] })] }), _jsx(Text, { color: "blue", children: "mint dev" }));
|
|
28
19
|
};
|
|
@@ -74,7 +65,7 @@ export function init(installDir, force, theme, name) {
|
|
|
74
65
|
}
|
|
75
66
|
installDir = installDir === '.' ? subdir : `${installDir}/${subdir}`;
|
|
76
67
|
// Re-validate after subdirectory is appended
|
|
77
|
-
validatePathWithinCwd(installDir);
|
|
68
|
+
validatePathWithinCwd(installDir, process.cwd());
|
|
78
69
|
}
|
|
79
70
|
}
|
|
80
71
|
if (!isAI && (!selectedTheme || !projectName)) {
|