Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Resolve Manifest File Reference

- **Status:** Approved
- **Domain:** Manifest security
- **Owner:** Microsoft 365 Agents Toolkit maintainers
- **Requirement source:** Private security report reviewed on August 5, 2026
- **Product impact:** Existing manifest packaging remains supported, while file references are
restricted to their manifest directory.

## Purpose

Resolve `$[file()]` references without allowing a manifest to read files outside its own
directory tree.

## Inputs

| Input | Type | Required | Description |
| -------------- | ------ | -------: | ------------------------------------------------------------------ |
| file reference | string | yes | Relative or absolute `.txt` or `.md` path supplied to `$[file()]`. |
| manifest path | path | yes | Manifest containing the reference and defining the trusted root. |

## Outputs

The operation returns the referenced text when the real file is contained by the manifest
directory. Invalid references return an `FxError` and no package artifact is produced.

## Acceptance Criteria

| ID | Runtime | Purpose | Gate | Harness | Given | When | Then |
| ---------- | ------- | --------------------- | -------- | -------------- | ----------------------------------------------------------------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| FILE-AC-01 | L1 | operation-integration | required | TempDirRuntime | A relative `.txt` or `.md` reference resolves inside the manifest directory | The reference is expanded | The file content is returned. |
| FILE-AC-02 | L1 | operation-integration | required | TempDirRuntime | A relative reference resolves through `..` outside the manifest directory | The reference is expanded | A `FileReferenceOutsideManifestDirectory` error is returned. |
| FILE-AC-03 | L1 | operation-integration | required | TempDirRuntime | An absolute `.txt` or `.md` reference resolves inside the manifest directory | The reference is expanded | The file content is returned. |
| FILE-AC-04 | L1 | operation-integration | required | TempDirRuntime | A path inside the manifest directory resolves through a symbolic link to an external file | The reference is expanded | A `FileReferenceOutsideManifestDirectory` error is returned. |
| FILE-AC-05 | L1 | operation-integration | required | TempDirRuntime | A `.txt` or `.md` reference resolves to a canonical target with another extension | The reference is expanded | An `UnsupportedFileFormat` error is returned. |
| FILE-AC-06 | L1 | operation-integration | required | TempDirRuntime | An environment variable or nested `file()` produces an external reference | The reference is expanded | The same containment policy rejects the external reference. |
| FILE-AC-07 | L1 | scenario | required | TempDirRuntime | A package contains an external `$[file()]` reference | Package creation runs | Creation fails and neither resolved JSON nor a ZIP containing external content is produced. |
| FILE-AC-08 | L2 | scenario | tracked | CLI E2E | A scaffolded Declarative Agent references an external `.txt` file through `../` traversal | `atk package` runs | The command reports the reference, resolved target, manifest directory, and remediation without exposing file content, and produces no resolved JSON or ZIP. |
| FILE-AC-09 | L1 | operation-integration | required | TempDirRuntime | An absolute reference resolves outside the manifest directory or across Windows drives | The reference is expanded | A `FileReferenceOutsideManifestDirectory` error is returned without reading the target. |
| FILE-AC-10 | L1 | operation-integration | required | TempDirRuntime | A file reference resolves lexically or canonically outside the manifest directory | The error is surfaced locally | Local lifecycle summaries and logs identify the reference, resolved target, manifest directory, and remediation without an Output-panel redirect, while the telemetry-facing error omits paths. |
| ZIP-AC-01 | L1 | operation-integration | required | TempDirRuntime | Any manifest asset resolves lexically or canonically outside the app package directory | Package creation runs | An `InvalidFileOutsideOfTheDirectotryError` is returned before the source is added. |
| ZIP-AC-02 | L1 | scenario | required | TempDirRuntime | An agent skill contains a symbolic link or junction to an external file or directory | Package creation runs | The linked entry and its external contents are omitted from the package. |
| ZIP-AC-03 | L1 | scenario | required | TempDirRuntime | Package validation or output publication fails | Package creation runs | No new final ZIP or partially published resolved JSON remains. |
| ZIP-AC-04 | L1 | operation-integration | required | TempDirRuntime | A package source is rejected for leaving the trusted directory | The error is surfaced | The local display message identifies the reference, resolved target, manifest directory, and remediation, while the telemetry-facing error message omits paths. |
| ZIP-AC-05 | L1 | scenario | required | TempDirRuntime | Publishing staged package outputs fails | The error is surfaced | The local display message identifies the output path, the telemetry-facing message omits absolute paths, and prior outputs are restored. |
| ZIP-AC-06 | L1 | operation-integration | required | TempDirRuntime | Canonicalizing a package source fails | The error is surfaced | The local display message identifies the source path and the telemetry-facing message omits absolute paths. |

## Flow

```mermaid
flowchart TD
A[Receive file reference and manifest path] --> B[Resolve relative or absolute candidate]
B --> C{Lexical candidate is contained?}
C -- no --> X[Return containment error]
C -- yes --> D{Requested extension supported?}
D -- no --> Y[Return unsupported-format error]
D -- yes --> E[Resolve real manifest directory and candidate]
E --> F{Canonical candidate is contained?}
F -- no --> X
F -- yes --> G{Canonical extension supported?}
G -- no --> Y
G -- yes --> H[Read and expand file content]
```

## Boundary

This operation does not broaden supported file formats, allow references relative to the
project root, execute lifecycle actions, or upload package artifacts. The ZIP package boundary
also covers files named by the Teams manifest, Declarative Agent manifest, API plugin manifest,
embedded knowledge capabilities, agent skills, and agent connectors.

Containment is evaluated against the filesystem state at canonicalization time. Concurrent
filesystem mutation between canonicalization and reading requires platform-specific handle
APIs that Node.js does not expose consistently and is outside this operation's threat model.

## Invariants

1. Relative and absolute references are accepted only when their lexical and canonical targets
remain inside the manifest directory.
2. A candidate whose canonical path is outside the canonical manifest directory at resolution
time is never read.
3. Containment handles parent traversal, sibling-prefix paths, symbolic links, and Windows
cross-drive paths.
4. Telemetry-facing error messages do not expose resolved local absolute paths. Local display
messages and logs may include user-actionable paths.
5. Relative and absolute `.txt` and `.md` references inside the manifest directory remain valid.
6. Every filesystem source added to an app package is lexically and canonically contained by the
app package directory.
7. Directory packaging never follows symbolic links or junctions.
8. The ZIP and every resolved JSON file are staged before any existing final output is modified.
9. A failed package operation does not publish a new final ZIP or partial resolved JSON output.
8 changes: 7 additions & 1 deletion packages/fx-core/resource/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,9 @@
"core.common.removeShareAccess.success": "Shared access successfully removed from users: %s.",
"core.envFunc.unsupportedFile.errorLog": "\"%s\" is an invalid file. Supported format: %s.",
"core.envFunc.unsupportedFile.errorMessage": "Invalid file. %s",
"core.envFunc.fileReferenceOutsideManifestDirectory.errorLog.local": "The file reference resolves outside the manifest directory.\nReference: %s\nResolved path: %s\nManifest directory: %s\nMove the file into the manifest directory, then update the file reference in %s.",
"core.envFunc.fileReferenceOutsideManifestDirectory.errorMessage": "The file reference must point to a file inside the manifest directory.",
"core.envFunc.fileReferenceOutsideManifestDirectory.errorMessage.local": "The file reference \"%s\" resolves to \"%s\", outside the manifest directory \"%s\". Move the file into the manifest directory, then update the reference in %s.",
"core.envFunc.unsupportedFunction.errorLog": "\"%s\" is an invalid function. Supported function: \"%s\".",
"core.envFunc.unsupportedFunction.errorMessage": "Invalid function. %s",
"core.envFunc.invalidFunctionParameter.errorLog": "The parameter \"%s\" of function \"%s\" is invalid. Please provide a valid file path wrapped by '' or an environment variable name in \"${{}}\" format.",
Expand Down Expand Up @@ -895,8 +898,11 @@
"error.teamsApp.validate.details": "File path: %s, title: %s",
"error.teamsApp.AppIdNotExistError": "App with ID %s does not exist in Developer Portal.",
"error.teamsApp.InvalidAppIdError": "App ID %s is invalid, must be a GUID.",
"error.teamsApp.createAppPackage.invalidFile": "%s is invalid, it should be in the same directory as manifest.json or a subdirectory of it.",
"error.teamsApp.createAppPackage.invalidFile": "A referenced file is invalid. It must be in the same directory as manifest.json or a subdirectory of it.",
"error.teamsApp.createAppPackage.invalidFile.local": "The file reference \"%s\" resolves to \"%s\", outside the manifest directory \"%s\". Move the file into the manifest directory, then update this reference in the manifest that contains it.",
"error.teamsApp.createAppPackage.packageSizeExceeded": "The generated app package (%s MB) exceeds the maximum allowed size of %s MB. Reduce the package contents before building.",
"error.teamsApp.createAppPackage.fileSystemError.telemetry": "The app package file operation failed. File system error: %s.",
"error.teamsApp.createAppPackage.fileSystemError.display": "The app package file operation failed for '%s'. File system error: %s.",
"error.m365.packageService.packageSizeExceeded": "The app package (%s MB) exceeds the maximum supported size (%s MB). Reduce the package contents before uploading.",
"driver.botFramework.description": "creates or updates the bot registration on dev.botframework.com",
"driver.botFramework.summary.create": "The bot registration has been created successfully (%s).",
Expand Down
9 changes: 8 additions & 1 deletion packages/fx-core/src/component/configManager/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ function resolveDriverDef(
}
}

function getDisplayMessage(error: FxError): string | undefined {
return "displayMessage" in error && typeof error.displayMessage === "string"
? error.displayMessage
: undefined;
}

// Replace placeholders in the driver definitions' `with` field inplace
// and returns resolved and unresolved placeholders
function resolvePlaceHolders(
Expand Down Expand Up @@ -303,7 +309,8 @@ export class Lifecycle implements ILifecycle {
const summary = r.summaries.map((s) => `${SummaryConstant.Succeeded} ${s}`);
summaries.push(summary);
if (result.isErr()) {
summary.push(`${SummaryConstant.Failed} ${result.error.message}`);
const errorMessage = getDisplayMessage(result.error) ?? result.error.message;
summary.push(`${SummaryConstant.Failed} ${errorMessage}`);
return {
result: err({
kind: "PartialSuccess",
Expand Down
Loading
Loading