Hacktoberfest 2026:維護者為十月標記出來的 issue,仍然開放、適合新手。 瀏覽 Hacktoberfest issue

Angular CLI can copy files from outside the workspace root through symlinked asset directories

未關閉
#34,164 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

評估

難度
4/5
預估耗時
3-5 天
新手友好度
52/100
Issue 類型
缺陷
描述清晰度
基本清楚
活躍度
活躍
技術堆疊
angular, typescript

研究方向

Start by reproducing the six-command example with Angular CLI 22.2.0, then inspect the application builder's asset handling and schema.json, including the schema registry's Ajv setup. Check how symlinked asset directories and the followSymlinks option are passed to globbing. Done means the documented default prevents copying files outside the workspace while explicitly configured behavior remains covered by tests.

由索引模型根據 Issue 內容生成。

描述

area: @angular/build gemini-triaged
Command

build

Is this a regression?
  • Yes, this behavior used to work in the previous version
The previous version in which this bug was not present was

No response

Description

ng build can copy files from outside the workspace into the build output when an asset directory is a symbolic link.

The application builder checks that every asset path stays inside the workspace root before globbing it:

if (!isSubDirectory(root, entry.input)) {
  throw new Error(`The ${entry.input} asset path must be within the workspace root.`);
}

const cwd = path.resolve(root, entry.input);

const files = await glob(entry.glob, {
  cwd,
  dot: true,
  ignore: entry.ignore ? defaultIgnore.concat(entry.ignore) : defaultIgnore,
  followSymbolicLinks: entry.followSymlinks,
});

That check is lexical. isSubDirectory() resolves both operands with resolve(), compares them with relative(), and never calls realpath:

function isSubDirectory(parent, child) {
  const resolvedParent = resolve(parent);
  const resolvedChild = resolve(parent, child);
  const relativePath = toPosixPath(relative(resolvedParent, resolvedChild));
  return relativePath !== '..' && !relativePath.startsWith('../') && !isAbsolute(relativePath);
}

A symlink inside the project resolves inside the workspace root, passes the check, and is then used as the globber's cwd, which reads through it to the real target.

Whether the globber walks through it depends on followSymlinks, and the value that arrives there is not the documented one. schema.json declares:

"followSymlinks": {
  "type": "boolean",
  "default": false,
  "description": "Allow glob patterns to follow symlink directories. This allows subdirectories of the symlink to be searched."
}

The schema registry builds Ajv without useDefaults:

this._ajv = new ajv.default({
  strict: false,
  loadSchema: (uri) => this._fetch(uri),
  passContext: true,
  verbose: true,
});

so schema defaults are never written into the options object. When the option is not set, entry.followSymlinks is undefined, and tinyglobby treats undefined the same as true:

followSymbolicLinks: undefined  ->  [ 'link/secret.txt' ]
followSymbolicLinks: false      ->  [ ]
followSymbolicLinks: true       ->  [ 'link/secret.txt' ]

The documented default is false. The effective default is true.

The containment check does hold for the direct form, which is what shows it is meant to hold:

assets: [{ "glob": "**/*", "input": "../../../outside" }]

An unhandled exception occurred: The ../../../outside asset path must be within the workspace root.

The same target reached through a symlink is copied with no warning and a successful build.

The configuration ng new generates already globs the whole public/ directory, so angular.json does not need to change:

"assets": [{ "glob": "**/*", "input": "public" }]

A symbolic link is a git object of mode 120000 whose content is a path string, so it survives clone. Nothing is executed during the build, so npm ci --ignore-scripts does not change the outcome.

Minimal Reproduction

See https://github.com/SkyZeroZx/angular-cli-symlink-asset-escape-poc

The same thing reproduces on a stock application in six commands:

npx @angular/[email protected] new repro-app --defaults --skip-git
cd repro-app

mkdir -p /tmp/outside-the-workspace
echo "this file is outside the Angular workspace" > /tmp/outside-the-workspace/secret.txt

ln -s /tmp/outside-the-workspace public/docs

ng build
find dist -name 'secret.txt'

The build completes normally and the file from outside the workspace is in the output:

dist/repro-app/browser/docs/secret.txt

Setting the option to the value the schema already documents as its default stops it:

assets: [{ "glob": "**/*", "input": "public", "followSymlinks": false }]
Exception or Error

Your Environment
Angular CLI 22.2.0
@angular/build 22.2.0
Node.js 24.21.0
npm 10.9.7
Linux x64
tinyglobby 0.2.17
Anything else relevant?

The symlink target can be an absolute path or a relative one such as ../../../.., so it does not need to know the layout of the machine that runs the build.

主要語言
TypeScript
星號
27k
分支
11.8k
平均合併
17 小時 47 分鐘
30 天內合併 PR
181

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

angular/angular-cli 的其他 Issue

查看 angular/angular-cli 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。