Skip to content

Commit 041ac8b

Browse files
JasonVMoCopilot
andauthored
Tighten Storybook manifest provenance and credential isolation (#4490)
* Harden Storybook generated manifest trust handoff Keep bridge and attachment credentials in OS-private records, validate centralized generated contracts, and pin the spawning child's helper to the independent parent handoff. Load runtime identity from a private data-only module. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Await the shared workload claim before Storybook startup Prepare the Storybook-owned call site for F10-M's native process identity verification without relocating channel or lease ownership. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Preserve resolved input metadata in generated manifests Accept the extractor's declared/derived inputSource metadata while keeping authored case options strict. Cover realistic resolved descriptors and reject unsupported metadata; this fixes the consumer smoke's publication failure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Record F6 provenance and platform qualification evidence Document portable and signed-native contract coverage, successful Fabric/Paper preparation, explicit Accessibility-blocked consumer outcomes, cleanup, final aggregate validation, and Windows CI sufficiency without claiming unrun native passes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix v3 smoke consumers and canonical private state paths Route Windows smoke lease publication and cleanup through verified private storage, use the shared v3 control schema, and resolve generated directory aliases before creating private records. Cover receipt fencing, stock macOS paths, and symlink replacement; keep Windows qualification explicitly pending. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Record reviewed provenance validation and pending native lanes Replace superseded local temporary-directory qualification with default-TMPDIR package and aggregate evidence. Keep macOS consumer permission blocks and unobserved Windows/Win32 coverage explicit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix cross-platform Storybook private storage publication Forward injected storage through the CLI factory and select host adapters explicitly. Derive protected Windows namespaces from the helper-selected per-user root, preserve Metro access outside the workspace, and report bounded refusal reasons without weakening native ACL checks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Record cross-platform storage CI fixes and qualification limits Record default-TMPDIR package results, the locked 178-task aggregate, and the unconfirmed Windows refusal and timeout diagnoses. Keep native Windows qualification explicitly pending. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Keep private Storybook projections out of cross-volume Metro file maps Use a pinned-buffer transformer on the workspace stub for Windows and Win32 while preserving protected per-user storage and macOS resolution. Normalize Windows path identities and cover cross-drive, cache, delegation, and tamper cases. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Record cross-volume Metro qualification and pending Windows lanes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Record same-branch Windows Storybook trust qualification Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 505a218 commit 041ac8b

49 files changed

Lines changed: 2295 additions & 215 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.changeset/curvy-dragons-fry.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@fluentui-react-native/storybook-desktop': patch
3+
'@fluentui-react-native/storybook-desktop-runtime': patch
4+
---
5+
6+
Separate Storybook bridge and attachment credentials in owner-private storage, validate versioned generated manifests, and pin native helper provenance through the trusted parent handoff before child processes can spawn helpers.
7+
8+
Use helper-selected per-user namespaces on Windows and forward private-storage adapters into platform-neutral CLI contracts. Resolve protected Windows projections through a digest-keyed Metro transformer on the constant workspace stub, avoiding cross-volume file-map paths and public secret copies. Normalize Windows namespace path identities while preserving macOS behavior.

‎.changeset/goofy-corners-create.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@fluentui-react-native/desktop-test': patch
3+
---
4+
5+
Record F6 Storybook manifest provenance, independent credential purposes, and platform qualification evidence in the lifecycle submission ledger.

‎.changeset/tidy-boats-smile.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@fluentui-react-native/desktop-driver': patch
3+
---
4+
5+
Identify the unsafe ancestor when macOS private storage rejects directory aliases, permissions, or ACLs.
6+
7+
Expose the Windows helper's validated storage root so consumers can create protected namespaces beneath its OS-selected per-user location.

‎packages/agentic/desktop-driver/references/contributing.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ specifiers, so keep it that way.
7878
- Propagate `AbortSignal` to every host operation.
7979
- Do not release queues or input locks until a timed-out native command settles.
8080
- Preserve attached apps and clean only exact owned resources.
81+
- Callers resolve existing private-storage parent directories before opening them.
82+
macOS storage keeps rejecting symlink ancestors on subsequent operations and
83+
identifies the failing ancestor in ownership/permissions diagnostics.
8184
- Keep native events advisory and queries authoritative.
8285
- Use opaque public window/element IDs and native liveness checks.
8386
- A Storybook preview reset invalidates preview elements, not app chrome or

‎packages/agentic/desktop-driver/references/security.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,10 @@ process recovery, automatic discovery, or supervisor wiring.
175175
`PrivateStorage` contract through the verified Windows helper's one-shot
176176
`--private-storage` mode, so the operating system judges ownership from open
177177
handles and Node never trusts a path check. The default root is a per-user
178-
folder under the local application data folder. An override must be a canonical
178+
folder under the local application data folder, resolved through the OS
179+
known-folder API rather than an environment variable. The returned `root`
180+
identifies that validated directory, allowing consumers to open independently
181+
protected child namespaces without guessing the platform location. An override must be a canonical
179182
absolute local-drive path of at most 240 characters whose parent exists. A
180183
missing root is created atomically with an owner-only protected DACL (one allow
181184
entry giving the current user full control); an existing root is validated and

‎packages/agentic/desktop-driver/src/authority/macosPrivateStorage.ts‎

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,16 @@ async function validateRoot(root: string, uid: number): Promise<Stats> {
154154

155155
async function validateAncestors(directory: string, uid: number): Promise<void> {
156156
for (let current = directory; ; current = path.dirname(current)) {
157-
const info = await lstat(current);
158-
const rootOwnedSticky = info.uid === 0 && (info.mode & 0o1000) !== 0;
159-
if (!info.isDirectory() || (info.uid !== uid && info.uid !== 0) || ((info.mode & 0o022) !== 0 && !rootOwnedSticky)) {
160-
throw unsafeStorage();
157+
try {
158+
const info = await lstat(current);
159+
const rootOwnedSticky = info.uid === 0 && (info.mode & 0o1000) !== 0;
160+
if (!info.isDirectory() || (info.uid !== uid && info.uid !== 0) || ((info.mode & 0o022) !== 0 && !rootOwnedSticky)) {
161+
throw unsafeStorage();
162+
}
163+
await rejectACL(current, true);
164+
} catch {
165+
throw unsafeStorage(current);
161166
}
162-
await rejectACL(current, true);
163167
if (current === path.dirname(current)) break;
164168
}
165169
}
@@ -194,6 +198,11 @@ function hasCode(error: unknown, code: string): boolean {
194198
return typeof error === 'object' && error !== null && 'code' in error && error.code === code;
195199
}
196200

197-
function unsafeStorage(): Error {
198-
return new Error('Desktop Driver private storage is unsafe or inaccessible.');
201+
function unsafeStorage(ancestor?: string): Error {
202+
return new Error(
203+
'Desktop Driver private storage is unsafe or inaccessible.' +
204+
(ancestor === undefined
205+
? ''
206+
: ` Check ancestor "${ancestor}": resolve directory aliases and require owner/root ownership without writable shared permissions or ACL grants.`),
207+
);
199208
}

‎packages/agentic/desktop-driver/src/authority/privateStorage.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ macosDescribe('owner-validated macOS private storage', () => {
110110
await mkdir(real, { mode: 0o700 });
111111
await symlink(real, root);
112112
await expect(createMacOSPrivateStorage(root)).rejects.toThrow();
113-
await expect(createMacOSPrivateStorage(path.join(root, 'nested'))).rejects.toThrow();
113+
await expect(createMacOSPrivateStorage(path.join(root, 'nested'))).rejects.toThrow(`Check ancestor "${root}"`);
114114
expect(await readdir(real)).toEqual([]);
115115
});
116116

‎packages/agentic/desktop-driver/src/authority/windowsPrivateStorage.test.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import path from 'node:path';
2+
13
import type { NativeDriverArtifact } from '../native/types';
24
import { FakeAuthorityHelper } from '../native/windows/fakeAuthorityHelper';
35
import { createWindowsPrivateStorage } from './windowsPrivateStorage';
@@ -98,6 +100,22 @@ describe('creating Windows private storage', () => {
98100
expect(requests()).toEqual([{ operation: 'open' }, { identity, name: 'connection.json', operation: 'read', root: defaultRoot }]);
99101
});
100102

103+
test('opens a protected child namespace beneath the root selected by the helper', async () => {
104+
const defaultRoot = 'C:\\Users\\agent\\AppData\\Local\\com.microsoft.fluentui-react-native.desktop-driver';
105+
const childRoot = path.win32.join(defaultRoot, 'storybook-fixture');
106+
helper.answer({ identity, ok: true, root: defaultRoot }).answer({ identity, ok: true, root: childRoot }).answer({ ok: true });
107+
108+
const parent = await createWindowsPrivateStorage({ artifact });
109+
const child = await createWindowsPrivateStorage({ artifact, root: path.win32.join(parent.root, 'storybook-fixture') });
110+
await child.publish('record', 'protected');
111+
112+
expect(requests()).toEqual([
113+
{ operation: 'open' },
114+
{ operation: 'open', root: childRoot },
115+
{ contents: base64('protected'), identity, name: 'record', operation: 'publish', root: childRoot },
116+
]);
117+
});
118+
101119
test('maps a helper refusal to the unsafe-storage failure', async () => {
102120
helper.answer({ detail: 'C:\\Users\\agent\\secret', ok: false, reason: 'dacl' });
103121

‎packages/agentic/desktop-driver/src/authority/windowsPrivateStorage.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@ const publicationFailed = (reason: string): Error =>
3535

3636
/**
3737
* Opens the Windows owner-only state root through the Windows helper. The parent must already exist. Existing roots are
38-
* validated, never repaired. Failures carry a fixed message and a non-enumerable `reason` for diagnostics only.
38+
* validated, never repaired. The returned root is the canonical directory validated by the helper, including when the
39+
* helper selected its per-user default. Failures carry a fixed message and a non-enumerable `reason` for diagnostics only.
3940
*/
40-
export async function createWindowsPrivateStorage(options: WindowsPrivateStorageOptions): Promise<PrivateStorage> {
41+
export async function createWindowsPrivateStorage(
42+
options: WindowsPrivateStorageOptions,
43+
): Promise<PrivateStorage & { readonly root: string }> {
4144
if (process.platform !== 'win32') {
4245
throw new Error('Windows private storage is unsupported on this platform; use a validated native ownership adapter.');
4346
}
@@ -77,6 +80,7 @@ export async function createWindowsPrivateStorage(options: WindowsPrivateStorage
7780
}
7881

7982
return {
83+
root,
8084
async read(name) {
8185
assertPrivateRecordName(name);
8286
const answer = await call({ name, operation: 'read' }, readAnswer, unsafeStorage);

0 commit comments

Comments
 (0)