Skip to content

Python: feat(core): add session-scoped file access isolation - #8542

Merged
Eduard van Valkenburg (eavanvalkenburg) merged 4 commits into
microsoft:mainfrom
JHf0912:fix/8539-file-access-session-scoped
Sep 21, 2026
Merged

Eduard van Valkenburg (eavanvalkenburg) merged 4 commits into
microsoft:mainfrom
JHf0912:fix/8539-file-access-session-scoped

Conversation

@JHf0912

@JHf0912 JHf0912 commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Motivation & Context

create_harness_agent's file_access_store expects a caller-supplied AgentFileStore whose root is already the desired working directory. Unlike FileMemoryProvider, FileAccessProvider provides no session isolation, so hosts that compile the harness agent before a session exists (AG-UI entrypoints, workflow executors restored from checkpoints) cannot give each session an isolated workspace without baking the session id into the path, recreating the agent per session, or mutating private store fields. Sessions therefore share one workspace, or hosts rely on private-API workarounds. This PR adds an opt-in session-scoped mode so per-session/tenant workspaces (downloads, uploads, sandbox-adjacent artifacts) are a first-class framework feature, mirroring how file memory already isolates by session.

Description & Review Guide

  • What are the major changes?
    • FileAccessProvider gains session_scoped: bool = False and scope: str | None = None. When enabled (or when a non-empty scope is passed), before_run derives a working folder from scope or context.session_id via the shared _storage_key_segment derivation inside a fixed ~access- namespace directory, fails closed (ValueError) when neither is available, and creates the folder. All eight tools route store calls through a single _session_path helper; ls/grep normalize their directory argument with _normalize_relative_path(..., is_directory=True) in scoped mode and treat an empty directory as the session root; grep results stay session-relative.
    • create_harness_agent exposes the mode via file_access_session_scoped (assembly pass-through, .pyi stub, docstring), and packages/core/AGENTS.md documents it.
    • Tests: isolation and collision coverage using the COLLIDING_IDENTIFIERS attack strings, fail-closed behavior, scope override and scope-only activation, parent-traversal rejection, cross-component namespace separation from FileMemoryProvider on a shared store root, and a shared-store default regression.
  • What is the impact of these changes?
    • Default mode is byte-for-byte unchanged; isolation is strictly opt-in. The provider now enforces its workspace boundary independently of the store implementation (a permissive custom store can no longer let .. escape the session folder), and file-access working folders can no longer collide with file-memory folders when both providers share one store root. The only full-suite failure is the pre-existing test_feature_stage.py::test_feature_id_allows_lowercase_values, which also fails on a clean checkout of main (verified via stash).
  • What do you want reviewers to focus on?
    • The namespace design: _storage_key_segment returns literal-safe identifiers unchanged, so the fixed ~access- directory is what separates file access from file memory on a shared store root.
    • Directory normalization is applied only in scoped mode; default mode passes the raw target through unchanged.
    • An empty-string scope is treated as unconfigured (falsy), matching FileMemoryProvider's resolution semantics.
    • The scoped-mode instruction suffix exists because the default instructions describe the store as shared, which would mislead the model when isolation is on.

Related Issue

Fixes #8539 — no other open PR covers this issue.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Add an opt-in session_scoped mode (with an optional explicit scope) that
confines file-access tool operations to a working folder derived from the
session id via the shared _storage_key_segment derivation under the
file-access-specific "~access-" prefix, mirroring FileMemoryProvider. The
mode fails closed (ValueError) when neither a scope nor a session id is
available, creates the session folder in before_run, appends a
session-isolation note to the tool instructions, and logs the derived
folder at debug level. All eight tools route store calls through a single
_session_path helper; ls/grep treat an empty directory as the session root
and grep results stay session-relative. Default mode is byte-for-byte
unchanged.

Adds seven regression tests, including the COLLIDING_IDENTIFIERS attack
strings parametrization, fail-closed coverage, scope override, and
shared-store default regression. All harness file-access tests pass; the
only full-suite failure is the pre-existing
test_feature_stage.py::test_feature_id_allows_lowercase_values on main.

Addresses microsoft#8539.
Expose the FileAccessProvider session-scoped mode from
create_harness_agent via a new file_access_session_scoped flag (default
False, preserving shared-store semantics), threaded through
_assemble_context_providers and synchronized across the .pyi stub and the
package AGENTS.md file-access section.

Adds a pass-through regression test. Addresses microsoft#8539.
Copilot AI balanced review requested due to automatic review settings September 19, 2026 08:30
@agent-framework-automation agent-framework-automation Bot added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python labels Sep 19, 2026
@github-actions github-actions Bot changed the title feat(core): add session-scoped file access isolation Python: feat(core): add session-scoped file access isolation Sep 19, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Scoped instructions and directory validation are inconsistent with the new API’s documented behavior.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Low severity

Open (1)
What changed in this PR

Adds opt-in session-scoped isolation for Python harness file-access tools.

Changes:

  • Derives per-session or explicit-scope workspace paths.
  • Exposes session scoping through create_harness_agent.
  • Adds documentation and isolation tests.
File Description
python/​packages/​core/​agent_framework/​_harness/​_file_access.py Implements scoped file-access routing.
python/​packages/​core/​agent_framework/​_harness/​_agent.py Wires the new harness option.
python/​packages/​core/​agent_framework/​_harness/​_agent.pyi Updates the public signature stub.
python/​packages/​core/​tests/​core/​test_harness_file_access.py Tests isolation and compatibility.
python/​packages/​core/​tests/​core/​test_harness_agent.py Tests harness option wiring.
python/​packages/​core/​AGENTS.md Documents scoped file access.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

…kspace

The session-scoped instruction suffix claimed files are never visible to
other sessions, which is wrong when an explicit scope intentionally shares
a workspace across sessions. Rephrase the suffix, the class docstring, and
the session_scoped parameter docs to describe isolation relative to the
resolved workspace (session or configured scope).

Addresses review feedback on PR microsoft#8542.
Address review feedback on PR microsoft#8542:
- Normalize ls/grep directories with _normalize_relative_path(...,
  is_directory=True) in scoped mode so a ".." directory cannot escape the
  session workspace through a permissive custom store.
- Place every scoped working folder under a fixed ~access- namespace
  directory, so file access and file memory working folders cannot collide
  when both providers share one store root and a literal-safe identifier.
- Treat a non-empty explicit scope as enabling scoped mode by itself,
  aligning with FileMemoryProvider(scope=...).

Adds parent-traversal, namespace-collision, and scope-only regression
tests and updates layout assertions to the namespaced folder shape. All
harness suites pass; ruff clean.
@eavanvalkenburg

Copy link
Copy Markdown
Member

JHf0912, thanks for addressing the review feedback. The remaining blocker is the PR body: it does not retain and complete the current template’s four required sections, all three Description & Review Guide prompts, a valid closing reference to #8539, or the contribution checklist. Please update the body to the current template and re-request review.

@JHf0912

JHf0912 commented Sep 21, 2026

Copy link
Copy Markdown
Contributor Author

Eduard van Valkenburg (@eavanvalkenburg) thanks — I've rewritten the PR body to the current template: Motivation & Context, Description & Review Guide (all three prompts), Related Issue with a closing Fixes #8539 reference, and the Contribution Checklist. Requesting re-review.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JHf0912, the review feedback is addressed on the current head, and the complete change looks good to me.

Merged via the queue into microsoft:main with commit 87d4d0a Sep 21, 2026
44 checks passed
ZedingZhang (ZedingZhang) pushed a commit to ZedingZhang/agent-framework that referenced this pull request Sep 29, 2026
…ft#8542)

* feat(core): add session-scoped mode to FileAccessProvider

Add an opt-in session_scoped mode (with an optional explicit scope) that
confines file-access tool operations to a working folder derived from the
session id via the shared _storage_key_segment derivation under the
file-access-specific "~access-" prefix, mirroring FileMemoryProvider. The
mode fails closed (ValueError) when neither a scope nor a session id is
available, creates the session folder in before_run, appends a
session-isolation note to the tool instructions, and logs the derived
folder at debug level. All eight tools route store calls through a single
_session_path helper; ls/grep treat an empty directory as the session root
and grep results stay session-relative. Default mode is byte-for-byte
unchanged.

Adds seven regression tests, including the COLLIDING_IDENTIFIERS attack
strings parametrization, fail-closed coverage, scope override, and
shared-store default regression. All harness file-access tests pass; the
only full-suite failure is the pre-existing
test_feature_stage.py::test_feature_id_allows_lowercase_values on main.

Addresses microsoft#8539.

* feat(core): wire file_access_session_scoped through create_harness_agent

Expose the FileAccessProvider session-scoped mode from
create_harness_agent via a new file_access_session_scoped flag (default
False, preserving shared-store semantics), threaded through
_assemble_context_providers and synchronized across the .pyi stub and the
package AGENTS.md file-access section.

Adds a pass-through regression test. Addresses microsoft#8539.

* docs(core): describe scoped file access isolation relative to the workspace

The session-scoped instruction suffix claimed files are never visible to
other sessions, which is wrong when an explicit scope intentionally shares
a workspace across sessions. Rephrase the suffix, the class docstring, and
the session_scoped parameter docs to describe isolation relative to the
resolved workspace (session or configured scope).

Addresses review feedback on PR microsoft#8542.

* fix(core): harden session-scoped file access boundaries

Address review feedback on PR microsoft#8542:
- Normalize ls/grep directories with _normalize_relative_path(...,
  is_directory=True) in scoped mode so a ".." directory cannot escape the
  session workspace through a permissive custom store.
- Place every scoped working folder under a fixed ~access- namespace
  directory, so file access and file memory working folders cannot collide
  when both providers share one store root and a literal-safe identifier.
- Treat a non-empty explicit scope as enabling scoped mode by itself,
  aligning with FileMemoryProvider(scope=...).

Adds parent-traversal, namespace-collision, and scope-only regression
tests and updates layout assertions to the namespaced folder shape. All
harness suites pass; ruff clean.

This branch was previously deployed

1 inactive deployment
github-app-auth — 00211e83 Deployed Sep 21, 2026 by JHf0912 via add_label #23408
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: Harness: session-scoped file_access_store option when session_id is unknown at create_harness_agent time

4 participants