镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Commit dd5662e

Browse files
RafaelGSSaduh95
authored andcommitted
fs: do not descend into symlinks for ** unless following symlinks
Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com> PR-URL: #65435 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
1 parent ac4cf54 commit dd5662e

3 files changed

Lines changed: 86 additions & 20 deletions

File tree

‎lib/internal/fs/glob.js‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -616,9 +616,9 @@ class Glob {
616616
subPatterns.add(index + 2);
617617
}
618618
if ((nextMatches || pattern.at(0) === '.') &&
619-
(entryIsDirectory || entry.isSymbolicLink()) && !fromSymlink) {
619+
entryIsDirectory && !fromSymlink) {
620620
// If pattern after ** matches, or pattern starts with "."
621-
// and entry is a directory or symlink, add to potential patterns
621+
// and entry is a directory, add to potential patterns
622622
subPatterns.add(nextIndex);
623623
}
624624

@@ -834,9 +834,9 @@ class Glob {
834834
subPatterns.add(index + 2);
835835
}
836836
if ((nextMatches || pattern.at(0) === '.') &&
837-
(entryIsDirectory || entry.isSymbolicLink()) && !fromSymlink) {
837+
entryIsDirectory && !fromSymlink) {
838838
// If pattern after ** matches, or pattern starts with "."
839-
// and entry is a directory or symlink, add to potential patterns
839+
// and entry is a directory, add to potential patterns
840840
subPatterns.add(nextIndex);
841841
}
842842

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import * as common from '../common/index.mjs';
2+
import tmpdir from '../common/tmpdir.js';
3+
import assert from 'node:assert';
4+
import path from 'node:path';
5+
import { spawnSync } from 'node:child_process';
6+
import { glob as globPromise, mkdir, symlink, writeFile } from 'node:fs/promises';
7+
import { globSync, glob as asyncGlob } from 'node:fs';
8+
9+
if (common.isWindows) {
10+
// Directory symlinks require elevated privileges on Windows.
11+
common.skip('symlinks are unreliable on Windows');
12+
}
13+
14+
tmpdir.refresh();
15+
16+
// Layout:
17+
// root/visible.txt
18+
// root/link -> outside (outside/ holds files that must NOT be listed)
19+
const base = tmpdir.resolve('globstar-symlink');
20+
const root = path.resolve(base, 'workspace');
21+
const outside = path.resolve(base, 'outside');
22+
const outsideNested = path.resolve(outside, 'nested');
23+
24+
await mkdir(root, { recursive: true });
25+
await mkdir(outsideNested, { recursive: true });
26+
await writeFile(path.resolve(root, 'visible.txt'), 'visible');
27+
await writeFile(path.resolve(outside, 'secret-one.txt'), 'secret one');
28+
await writeFile(path.resolve(outsideNested, 'secret-two.txt'), 'secret two');
29+
await symlink(outside, path.resolve(root, 'link'), 'dir');
30+
31+
const expected = ['link', 'visible.txt'].map((e) => e.replaceAll('/', path.sep)).sort();
32+
33+
// `**/*` must not descend into the symlinked directory, neither by default nor
34+
// with followSymlinks explicitly disabled.
35+
assert.deepStrictEqual(globSync('**/*', { cwd: root }).sort(), expected);
36+
assert.deepStrictEqual(
37+
globSync('**/*', { cwd: root, followSymlinks: false }).sort(),
38+
expected,
39+
);
40+
41+
const promiseMatches = [];
42+
for await (const entry of globPromise('**/*', { cwd: root, followSymlinks: false })) {
43+
promiseMatches.push(entry);
44+
}
45+
assert.deepStrictEqual(promiseMatches.sort(), expected);
46+
47+
const callbackMatches = await new Promise((resolve, reject) => {
48+
asyncGlob('**/*', { cwd: root, followSymlinks: false }, (err, matches) => {
49+
if (err) reject(err);
50+
else resolve(matches);
51+
});
52+
});
53+
assert.deepStrictEqual(callbackMatches.sort(), expected);
54+
55+
// Under the permission model, `**/*` must not expose entries from a symlink
56+
// target that is outside the granted read set.
57+
if (common.hasCrypto) {
58+
const child = `
59+
const assert = require('node:assert');
60+
const fs = require('node:fs');
61+
const path = require('node:path');
62+
const root = ${JSON.stringify(root)};
63+
const outside = ${JSON.stringify(outside)};
64+
const expected = ['link', 'visible.txt'].map((e) => e.replaceAll('/', path.sep)).sort();
65+
assert.strictEqual(process.permission.has('fs.read', root), true);
66+
assert.strictEqual(process.permission.has('fs.read', outside), false);
67+
assert.throws(() => fs.readdirSync(outside), { code: 'ERR_ACCESS_DENIED' });
68+
assert.deepStrictEqual(fs.globSync('**/*', { cwd: root }).sort(), expected);
69+
assert.deepStrictEqual(
70+
fs.globSync('**/*', { cwd: root, followSymlinks: false }).sort(),
71+
expected,
72+
);
73+
`;
74+
const { status, stdout, stderr } = spawnSync(process.execPath, [
75+
'--permission',
76+
`--allow-fs-read=${root}`,
77+
'-e',
78+
child,
79+
], { encoding: 'utf8' });
80+
assert.strictEqual(status, 0, stderr || stdout);
81+
}

‎test/parallel/test-fs-glob.mjs‎

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ const patterns = {
119119
'a/x',
120120
'a/z',
121121
],
122-
'./**/a': common.isWindows ? ['a'] : ['a', 'a/symlink/a', 'a/symlink/a/b/c/a'],
122+
'./**/a': common.isWindows ? ['a'] : ['a', 'a/symlink/a'],
123123
'./**/a/**/': [
124124
'a',
125125
'a/abcdef',
@@ -139,10 +139,6 @@ const patterns = {
139139
'a/symlink',
140140
'a/symlink/a',
141141
'a/symlink/a/b',
142-
'a/symlink/a/b/c',
143-
'a/symlink/a/b/c/a',
144-
'a/symlink/a/b/c/a/b',
145-
'a/symlink/a/b/c/a/b/c',
146142
]),
147143
'a/x',
148144
'a/z',
@@ -173,23 +169,13 @@ const patterns = {
173169
'a/symlink/a',
174170
'a/symlink/a/b',
175171
'a/symlink/a/b/c',
176-
'a/symlink/a/b/c/a',
177-
'a/symlink/a/b/c/a/b',
178-
'a/symlink/a/b/c/a/b/c',
179172
]),
180173
'a/x',
181174
'a/z',
182175
],
183176
'./**/a/**/a/**/': common.isWindows ? [] : [
184177
'a/symlink/a',
185178
'a/symlink/a/b',
186-
'a/symlink/a/b/c',
187-
'a/symlink/a/b/c/a',
188-
'a/symlink/a/b/c/a/b',
189-
'a/symlink/a/b/c/a/b/c',
190-
'a/symlink/a/b/c/a/b/c/a',
191-
'a/symlink/a/b/c/a/b/c/a/b',
192-
'a/symlink/a/b/c/a/b/c/a/b/c',
193179
],
194180
'+(a|b|c)/a{/,bc*}/**': [
195181
'a/abcdef',
@@ -268,7 +254,6 @@ const patterns = {
268254
'a/symlink/a/**/*': common.isWindows ? [] : [
269255
'a/symlink/a/b',
270256
'a/symlink/a/b/c',
271-
'a/symlink/a/b/c/a',
272257
],
273258
'a/!(symlink)/**/..': [
274259
'a',

0 commit comments

Comments
 (0)