From f42252c293df5af2e20b74b43aaa4ba4b3597405 Mon Sep 17 00:00:00 2001 From: Kyle Brown Date: Fri, 7 Aug 2026 00:40:50 +0000 Subject: [PATCH 1/3] [codex-security] Stabilize Windows credential ACL verification --- sdk/typescript/src/runtime.ts | 38 ++++++---- sdk/typescript/tests-ts/runtime.test.ts | 94 ++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 18 deletions(-) diff --git a/sdk/typescript/src/runtime.ts b/sdk/typescript/src/runtime.ts index bf6c96e0..aad407da 100644 --- a/sdk/typescript/src/runtime.ts +++ b/sdk/typescript/src/runtime.ts @@ -577,23 +577,31 @@ export async function verifyStableWindowsCredentialDescendants( for (let attempt = 0; attempt < 3; attempt += 1) { let descendants = 0; const pending = [path]; - while (pending.length !== 0) { - const current = pending.pop()!; - const directory = await opendir(current); - for await (const entry of directory) { - const child = join(current, entry.name); - const metadata = await lstat(child); - if (metadata.isSymbolicLink()) { - throw new Error( - "Windows credential home contains a symbolic link or junction", - ); - } - if (!metadata.isDirectory() && !metadata.isFile()) { - throw new Error("Windows credential home contains an unsafe entry"); + try { + while (pending.length !== 0) { + const current = pending.pop()!; + const directory = await opendir(current); + for await (const entry of directory) { + const child = join(current, entry.name); + const metadata = await lstat(child); + if (metadata.isSymbolicLink()) { + throw new Error( + "Windows credential home contains a symbolic link or junction", + ); + } + if (!metadata.isDirectory() && !metadata.isFile()) { + throw new Error("Windows credential home contains an unsafe entry"); + } + descendants += 1; + if (metadata.isDirectory()) pending.push(child); } - descendants += 1; - if (metadata.isDirectory()) pending.push(child); } + } catch (error) { + const failure = error as NodeJS.ErrnoException; + if (failure.code === "ENOENT" && failure.path !== path) { + continue; + } + throw error; } if (descendants === 0) return; diff --git a/sdk/typescript/tests-ts/runtime.test.ts b/sdk/typescript/tests-ts/runtime.test.ts index 00f6e96f..9310d68c 100644 --- a/sdk/typescript/tests-ts/runtime.test.ts +++ b/sdk/typescript/tests-ts/runtime.test.ts @@ -1,4 +1,4 @@ -import { spawnSync } from "node:child_process"; +import { execFile, spawnSync } from "node:child_process"; import { existsSync, renameSync, symlinkSync } from "node:fs"; import { chmod, @@ -29,6 +29,7 @@ import { sep, } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; +import { promisify } from "node:util"; import { brotliDecompressSync } from "node:zlib"; import { afterEach, describe, expect, mock, test } from "bun:test"; import { strToU8, zipSync } from "fflate"; @@ -1937,6 +1938,94 @@ describe("runtime directories and plugin Python boundary", () => { expect(attempts).toBe(2); }); + test("retries Windows credential verification when a descendant disappears", async () => { + const root = await temporaryDirectory(); + const home = join(root, "home"); + const temporary = join(home, ".auth-temporary"); + await mkdir(home); + await writeFile(join(home, "auth.json"), "credential\n"); + await writeFile(temporary, "temporary credential\n"); + const originalLstat = fsPromises.lstat; + let removed = false; + let inspections = 0; + mock.module("node:fs/promises", () => ({ + ...fsPromises, + lstat: async (path: Parameters[0]) => { + if (path === temporary && !removed) { + removed = true; + await rm(temporary); + } + return originalLstat(path); + }, + })); + + try { + await verifyStableWindowsCredentialDescendants(home, async () => { + inspections += 1; + return 1; + }); + } finally { + mock.module("node:fs/promises", () => ({ + ...fsPromises, + lstat: originalLstat, + })); + } + + expect(removed).toBe(true); + expect(inspections).toBe(1); + }); + + test("rejects Windows credential descendants that repeatedly disappear", async () => { + const root = await temporaryDirectory(); + const home = join(root, "home"); + const credential = join(home, "auth.json"); + await mkdir(home); + await writeFile(credential, "credential\n"); + const originalLstat = fsPromises.lstat; + let attempts = 0; + mock.module("node:fs/promises", () => ({ + ...fsPromises, + lstat: async (path: Parameters[0]) => { + if (path === credential) { + attempts += 1; + throw Object.assign(new Error("credential disappeared"), { + code: "ENOENT", + path, + }); + } + return originalLstat(path); + }, + })); + + try { + await expect( + verifyStableWindowsCredentialDescendants(home, async () => 1), + ).rejects.toThrow("Windows credential descendants could not be verified"); + } finally { + mock.module("node:fs/promises", () => ({ + ...fsPromises, + lstat: originalLstat, + })); + } + + expect(attempts).toBe(3); + }); + + test("does not retry a missing Windows credential home", async () => { + const root = await temporaryDirectory(); + const home = join(root, "missing-home"); + let inspections = 0; + + await expect( + verifyStableWindowsCredentialDescendants(home, async () => { + inspections += 1; + return 0; + }), + ).rejects.toMatchObject({ code: "ENOENT", path: home }); + + expect(inspections).toBe(0); + }); + test("rejects Windows credential descendants that never stabilize", async () => { const root = await temporaryDirectory(); const home = join(root, "home"); @@ -2477,7 +2566,7 @@ describe("runtime directories and plugin Python boundary", () => { "$unexpected = @($acl.Access | Where-Object { $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Allow -and $trusted -notcontains $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value })", "[pscustomobject]@{ unexpected = $unexpected.Count } | ConvertTo-Json -Compress", ].join("; "); - const result = spawnSync( + const result = await promisify(execFile)( powershell, ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", command], { @@ -2488,7 +2577,6 @@ describe("runtime directories and plugin Python boundary", () => { }, ); - expect(result.status).toBe(0); expect(JSON.parse(result.stdout)).toEqual({ unexpected: 0 }); }, ); From b4d22e45299c3b5662923244b37fed613d500ca6 Mon Sep 17 00:00:00 2001 From: Kyle Brown Date: Fri, 7 Aug 2026 00:52:36 +0000 Subject: [PATCH 2/3] [codex-security] Allow slower Windows credential integration tests --- .github/workflows/node-ci.yml | 2 +- sdk/typescript/tests-ts/skeleton.test.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml index c7a1e08e..f1bd263f 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/node-ci.yml @@ -90,7 +90,7 @@ jobs: TMP: ${{ steps.windows-temp.outputs.path || runner.temp }} TMPDIR: ${{ steps.windows-temp.outputs.path || runner.temp }} CODEX_SECURITY_ALLOW_MACHINE_POLICY_TEST: ${{ runner.environment == 'github-hosted' && runner.os == 'Windows' && 'true' || 'false' }} - run: pnpm --dir sdk/typescript run test + run: pnpm --dir sdk/typescript run test ${{ runner.os == 'Windows' && '--timeout 60000' || '' }} - name: Check formatting run: pnpm --dir sdk/typescript run format diff --git a/sdk/typescript/tests-ts/skeleton.test.ts b/sdk/typescript/tests-ts/skeleton.test.ts index 7aa952a6..7eed1c1c 100644 --- a/sdk/typescript/tests-ts/skeleton.test.ts +++ b/sdk/typescript/tests-ts/skeleton.test.ts @@ -70,6 +70,23 @@ describe("TypeScript package skeleton", () => { } }); + test("gives Windows credential integration tests a larger CI timeout", async () => { + const packageJson = JSON.parse( + await readFile(new URL("../package.json", import.meta.url), "utf8"), + ); + const ciWorkflow = await readFile( + new URL("../../../.github/workflows/node-ci.yml", import.meta.url), + "utf8", + ); + + expect(packageJson.scripts.test).toBe( + "bun test --timeout 30000 ./tests-ts", + ); + expect(ciWorkflow).toContain( + "run: pnpm --dir sdk/typescript run test ${{ runner.os == 'Windows' && '--timeout 60000' || '' }}", + ); + }); + test("builds packages without a preinstalled package manager and provides a production audit", async () => { const packageJson = JSON.parse( await readFile(new URL("../package.json", import.meta.url), "utf8"), From 9721adf844067f26c84a7e506a9c651bed8f53e8 Mon Sep 17 00:00:00 2001 From: Kyle Brown Date: Fri, 7 Aug 2026 11:02:34 -0700 Subject: [PATCH 3/3] [codex-security] Batch Windows credential ACL inspection (#300) --- .github/workflows/node-ci.yml | 2 +- sdk/typescript/src/runtime.ts | 212 +++++++++++++---------- sdk/typescript/tests-ts/runtime.test.ts | 127 ++++++++++++++ sdk/typescript/tests-ts/skeleton.test.ts | 7 +- 4 files changed, 256 insertions(+), 92 deletions(-) diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml index f1bd263f..c7a1e08e 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/node-ci.yml @@ -90,7 +90,7 @@ jobs: TMP: ${{ steps.windows-temp.outputs.path || runner.temp }} TMPDIR: ${{ steps.windows-temp.outputs.path || runner.temp }} CODEX_SECURITY_ALLOW_MACHINE_POLICY_TEST: ${{ runner.environment == 'github-hosted' && runner.os == 'Windows' && 'true' || 'false' }} - run: pnpm --dir sdk/typescript run test ${{ runner.os == 'Windows' && '--timeout 60000' || '' }} + run: pnpm --dir sdk/typescript run test - name: Check formatting run: pnpm --dir sdk/typescript run format diff --git a/sdk/typescript/src/runtime.ts b/sdk/typescript/src/runtime.ts index aad407da..476eaacf 100644 --- a/sdk/typescript/src/runtime.ts +++ b/sdk/typescript/src/runtime.ts @@ -371,6 +371,12 @@ class RepairableWindowsCredentialAclError extends Error { } } +class RepairableWindowsCredentialOwnerError extends Error { + public constructor(cause: UntrustedWindowsCredentialOwnerError) { + super(cause.message, { cause }); + } +} + /** Inspect a Windows DACL without translating locale-specific account names. */ export function inspectWindowsCredentialAcl( descriptor: string, @@ -573,6 +579,7 @@ function windowsAceAllowsAncestorReplacement( export async function verifyStableWindowsCredentialDescendants( path: string, inspectDescriptors: () => Promise, + options: { inspectEmpty?: boolean } = {}, ): Promise { for (let attempt = 0; attempt < 3; attempt += 1) { let descendants = 0; @@ -603,7 +610,7 @@ export async function verifyStableWindowsCredentialDescendants( } throw error; } - if (descendants === 0) return; + if (descendants === 0 && options.inspectEmpty !== true) return; if ((await inspectDescriptors()) === descendants) return; } @@ -670,6 +677,106 @@ export async function streamWindowsCredentialAclDescriptors( return descriptors; } +export async function inspectWindowsCredentialAclSnapshot( + path: string, + currentUserSid: string, + options: { + command: string; + args: readonly string[]; + environment?: NodeJS.ProcessEnv; + resolvedAliases?: Readonly>; + resolveDescriptorAliases?: (descriptor: string) => Promise; + }, +): Promise<{ + home: WindowsCredentialAcl; + descendantsArePrivate: boolean; +}> { + let ancestors = 0; + for (let ancestor = dirname(path); ; ancestor = dirname(ancestor)) { + ancestors += 1; + if (ancestor === dirname(ancestor)) break; + } + + let home: WindowsCredentialAcl | undefined; + let descendantsArePrivate = true; + await verifyStableWindowsCredentialDescendants( + path, + async () => { + home = undefined; + descendantsArePrivate = true; + let inspected = 0; + const descriptors = await streamWindowsCredentialAclDescriptors( + options.command, + options.args, + async (descriptor) => { + const index = inspected; + inspected += 1; + await options.resolveDescriptorAliases?.(descriptor); + + if (index < ancestors) { + const ancestor = inspectWindowsCredentialAcl( + descriptor, + currentUserSid, + { + resolvedAliases: options.resolvedAliases, + scope: "ancestor", + }, + ); + if (ancestor.untrustedPrincipals.length !== 0) { + throw new Error( + "Windows credential-home ancestor allows another identity to replace the directory", + ); + } + return; + } + + if (index === ancestors) { + try { + home = inspectWindowsCredentialAcl(descriptor, currentUserSid, { + resolvedAliases: options.resolvedAliases, + }); + } catch (error) { + if (error instanceof UntrustedWindowsCredentialOwnerError) { + throw new RepairableWindowsCredentialOwnerError(error); + } + throw new RepairableWindowsCredentialAclError(error); + } + return; + } + + const descendant = inspectWindowsCredentialAcl( + descriptor, + currentUserSid, + { + resolvedAliases: options.resolvedAliases, + scope: "file", + }, + ); + if ( + !descendant.grantsCurrentUserAccess || + descendant.untrustedPrincipals.length !== 0 + ) { + descendantsArePrivate = false; + } + }, + { environment: options.environment }, + ); + if (descriptors <= ancestors) { + throw new Error( + "Windows credential-home ancestry could not be verified", + ); + } + return descriptors - ancestors - 1; + }, + { inspectEmpty: true }, + ); + + if (home === undefined) { + throw new Error("Windows credential ACL could not be verified"); + } + return { home, descendantsArePrivate }; +} + async function secureWindowsCredentialHome(path: string): Promise { const systemRoot = process.env["SystemRoot"] ?? "C:\\Windows"; const systemDirectory = join(systemRoot, "System32"); @@ -715,7 +822,10 @@ async function secureWindowsCredentialHome(path: string): Promise { // arbitrary .NET constructors, static methods, and SID translation do not. const script = [ "$ErrorActionPreference = 'Stop'", + "$path = $env:CODEX_SECURITY_CREDENTIAL_ACL_PATH", + "while ($true) { $parent = Microsoft.PowerShell.Management\\Split-Path -Path $path -Parent; if (-not $parent -or $parent -eq $path) { break }; Microsoft.PowerShell.Security\\Get-Acl -LiteralPath $parent | Microsoft.PowerShell.Utility\\Select-Object -ExpandProperty Sddl; $path = $parent }", "Microsoft.PowerShell.Security\\Get-Acl -LiteralPath $env:CODEX_SECURITY_CREDENTIAL_ACL_PATH | Microsoft.PowerShell.Utility\\Select-Object -ExpandProperty Sddl", + "Microsoft.PowerShell.Management\\Get-ChildItem -LiteralPath $env:CODEX_SECURITY_CREDENTIAL_ACL_PATH -Recurse -Force | Microsoft.PowerShell.Security\\Get-Acl | Microsoft.PowerShell.Utility\\Select-Object -ExpandProperty Sddl", ].join("; "); const resolvePrincipalScript = [ "$ErrorActionPreference = 'Stop'", @@ -770,21 +880,17 @@ async function secureWindowsCredentialHome(path: string): Promise { remaining = rest; } }; + let descendantsArePrivate = true; const readAcl = async (): Promise => { - const descriptor = await execFile( - powershell, - ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script], - processOptions, - ); - try { - await resolveDescriptorAliases(descriptor.stdout); - return inspectWindowsCredentialAcl(descriptor.stdout, sid, { - resolvedAliases, - }); - } catch (error) { - if (error instanceof UntrustedWindowsCredentialOwnerError) throw error; - throw new RepairableWindowsCredentialAclError(error); - } + const snapshot = await inspectWindowsCredentialAclSnapshot(path, sid, { + command: powershell, + args: ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script], + environment: processOptions.env, + resolvedAliases, + resolveDescriptorAliases, + }); + descendantsArePrivate = snapshot.descendantsArePrivate; + return snapshot.home; }; const icacls = join(systemDirectory, "icacls.exe"); @@ -802,81 +908,12 @@ async function secureWindowsCredentialHome(path: string): Promise { processOptions, ); }; - const ancestorScript = [ - "$ErrorActionPreference = 'Stop'", - "$path = $env:CODEX_SECURITY_CREDENTIAL_ACL_PATH", - "while ($true) { $parent = Microsoft.PowerShell.Management\\Split-Path -Path $path -Parent; if (-not $parent -or $parent -eq $path) { break }; Microsoft.PowerShell.Security\\Get-Acl -LiteralPath $parent | Microsoft.PowerShell.Utility\\Select-Object -ExpandProperty Sddl; $path = $parent }", - ].join("; "); - const ancestorPaths: string[] = []; - for (let ancestor = dirname(path); ; ancestor = dirname(ancestor)) { - ancestorPaths.push(ancestor); - if (ancestor === dirname(ancestor)) break; - } - const ancestry = await execFile( - powershell, - ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", ancestorScript], - processOptions, - ); - const ancestorDescriptors = ancestry.stdout - .split(/\r?\n/u) - .filter((descriptor) => descriptor !== ""); - if (ancestorDescriptors.length !== ancestorPaths.length) { - throw new Error("Windows credential-home ancestry could not be verified"); - } - for (const descriptor of ancestorDescriptors) { - await resolveDescriptorAliases(descriptor); - const ancestor = inspectWindowsCredentialAcl(descriptor, sid, { - resolvedAliases, - scope: "ancestor", - }); - if (ancestor.untrustedPrincipals.length === 0) continue; - throw new Error( - "Windows credential-home ancestor allows another identity to replace the directory", - ); - } - - const descendantsArePrivate = async (): Promise => { - const descendantScript = [ - "$ErrorActionPreference = 'Stop'", - "Microsoft.PowerShell.Management\\Get-ChildItem -LiteralPath $env:CODEX_SECURITY_CREDENTIAL_ACL_PATH -Recurse -Force | Microsoft.PowerShell.Security\\Get-Acl | Microsoft.PowerShell.Utility\\Select-Object -ExpandProperty Sddl", - ].join("; "); - let privateDescendants = true; - await verifyStableWindowsCredentialDescendants(path, async () => { - privateDescendants = true; - return streamWindowsCredentialAclDescriptors( - powershell, - [ - "-NoLogo", - "-NoProfile", - "-NonInteractive", - "-Command", - descendantScript, - ], - async (descriptor) => { - await resolveDescriptorAliases(descriptor); - const descendant = inspectWindowsCredentialAcl(descriptor, sid, { - resolvedAliases, - scope: "file", - }); - if ( - !descendant.grantsCurrentUserAccess || - descendant.untrustedPrincipals.length !== 0 - ) { - privateDescendants = false; - } - }, - { environment: processOptions.env }, - ); - }); - return privateDescendants; - }; - let existing: WindowsCredentialAcl | undefined; for (let attempt = 0; existing === undefined && attempt < 3; attempt += 1) { try { existing = await readAcl(); } catch (error) { - if (error instanceof UntrustedWindowsCredentialOwnerError) { + if (error instanceof RepairableWindowsCredentialOwnerError) { await execFile(icacls, [path, "/setowner", `*${sid}`], processOptions); } else if (error instanceof RepairableWindowsCredentialAclError) { await installTrustedAcl(); @@ -947,13 +984,14 @@ async function secureWindowsCredentialHome(path: string): Promise { if (verified.untrustedPrincipals.length !== 0) { throw new Error("Windows credential ACL grants access to another identity"); } - if (!(await descendantsArePrivate())) { + if (!descendantsArePrivate) { await execFile( icacls, [join(path, "*"), "/reset", "/t", "/q"], processOptions, ); - if (!(await descendantsArePrivate())) { + await readAcl(); + if (!descendantsArePrivate) { throw new Error("Windows credential descendants remain accessible"); } } diff --git a/sdk/typescript/tests-ts/runtime.test.ts b/sdk/typescript/tests-ts/runtime.test.ts index 9310d68c..5bb7a4ca 100644 --- a/sdk/typescript/tests-ts/runtime.test.ts +++ b/sdk/typescript/tests-ts/runtime.test.ts @@ -58,6 +58,7 @@ import { codexSecurityStateDirectory, codexPlatformPackage, inspectWindowsCredentialAcl, + inspectWindowsCredentialAclSnapshot, isPythonPathCandidate, planOutputArchive, prepareCodexSecurityCredentialHome, @@ -2042,6 +2043,132 @@ describe("runtime directories and plugin Python boundary", () => { expect(attempts).toBe(3); }); + test("inspects Windows credential ancestry, home, and descendants in one subprocess", async () => { + const root = await temporaryDirectory(); + const home = join(root, "home"); + const inspectionCount = join(root, "inspection-count"); + await mkdir(home); + await writeFile(join(home, "auth.json"), "credential\n"); + const sid = "S-1-5-21-111-222-333-1001"; + const directory = `O:${sid}G:SYD:P(A;OICI;FA;;;${sid})`; + const file = `O:${sid}G:SYD:P(A;;FA;;;${sid})`; + const ancestors: string[] = []; + for (let ancestor = dirname(home); ; ancestor = dirname(ancestor)) { + ancestors.push(directory); + if (ancestor === dirname(ancestor)) break; + } + const descriptors = [...ancestors, directory, file]; + const script = [ + `require("node:fs").appendFileSync(${JSON.stringify(inspectionCount)}, "inspection\\n")`, + `process.stdout.write(${JSON.stringify(`${descriptors.join("\n")}\n`)})`, + ].join("; "); + + const snapshot = await inspectWindowsCredentialAclSnapshot(home, sid, { + command: process.execPath, + args: ["--eval", script], + }); + + expect(snapshot.home).toMatchObject({ + owner: sid, + protected: true, + grantsCurrentUserAccess: true, + untrustedPrincipals: [], + }); + expect(snapshot.descendantsArePrivate).toBe(true); + expect(await readFile(inspectionCount, "utf8")).toBe("inspection\n"); + }); + + test("inspects Windows credential ancestry and the home even without descendants", async () => { + const root = await temporaryDirectory(); + const home = join(root, "home"); + await mkdir(home); + const sid = "S-1-5-21-111-222-333-1001"; + const directory = `O:${sid}G:SYD:P(A;OICI;FA;;;${sid})`; + const ancestors: string[] = []; + for (let ancestor = dirname(home); ; ancestor = dirname(ancestor)) { + ancestors.push(directory); + if (ancestor === dirname(ancestor)) break; + } + const descriptors = [...ancestors, directory]; + + await expect( + inspectWindowsCredentialAclSnapshot(home, sid, { + command: process.execPath, + args: [ + "--eval", + `process.stdout.write(${JSON.stringify(`${descriptors.join("\n")}\n`)})`, + ], + }), + ).resolves.toMatchObject({ + home: { owner: sid, protected: true }, + descendantsArePrivate: true, + }); + }); + + test("rejects unsafe Windows credential ancestry during combined ACL inspection", async () => { + const root = await temporaryDirectory(); + const home = join(root, "home"); + await mkdir(home); + const sid = "S-1-5-21-111-222-333-1001"; + const unsafe = `O:${sid}G:SYD:P(A;OICI;FA;;;${sid})(A;OICI;FA;;;WD)`; + + await expect( + inspectWindowsCredentialAclSnapshot(home, sid, { + command: process.execPath, + args: [ + "--eval", + `process.stdout.write(${JSON.stringify(`${unsafe}\n`)})`, + ], + }), + ).rejects.toThrow( + "Windows credential-home ancestor allows another identity to replace the directory", + ); + }); + + test("rejects incomplete combined Windows credential ACL inspections", async () => { + const root = await temporaryDirectory(); + const home = join(root, "home"); + await mkdir(home); + const sid = "S-1-5-21-111-222-333-1001"; + const directory = `O:${sid}G:SYD:P(A;OICI;FA;;;${sid})`; + + await expect( + inspectWindowsCredentialAclSnapshot(home, sid, { + command: process.execPath, + args: [ + "--eval", + `process.stdout.write(${JSON.stringify(`${directory}\n`)})`, + ], + }), + ).rejects.toThrow("Windows credential-home ancestry could not be verified"); + }); + + test("detects unsafe descendants during combined Windows credential ACL inspections", async () => { + const root = await temporaryDirectory(); + const home = join(root, "home"); + await mkdir(home); + await writeFile(join(home, "auth.json"), "credential\n"); + const sid = "S-1-5-21-111-222-333-1001"; + const directory = `O:${sid}G:SYD:P(A;OICI;FA;;;${sid})`; + const unsafeFile = `O:${sid}G:SYD:P(A;;FA;;;${sid})(A;;FR;;;WD)`; + const ancestors: string[] = []; + for (let ancestor = dirname(home); ; ancestor = dirname(ancestor)) { + ancestors.push(directory); + if (ancestor === dirname(ancestor)) break; + } + const descriptors = [...ancestors, directory, unsafeFile]; + + await expect( + inspectWindowsCredentialAclSnapshot(home, sid, { + command: process.execPath, + args: [ + "--eval", + `process.stdout.write(${JSON.stringify(`${descriptors.join("\n")}\n`)})`, + ], + }), + ).resolves.toMatchObject({ descendantsArePrivate: false }); + }); + test("streams Windows credential ACL output larger than the subprocess buffer", async () => { const descriptor = "O:S-1-5-21-111-222-333-1001G:SYD:P(A;;FA;;;S-1-5-21-111-222-333-1001)"; diff --git a/sdk/typescript/tests-ts/skeleton.test.ts b/sdk/typescript/tests-ts/skeleton.test.ts index 7eed1c1c..128ec67b 100644 --- a/sdk/typescript/tests-ts/skeleton.test.ts +++ b/sdk/typescript/tests-ts/skeleton.test.ts @@ -70,7 +70,7 @@ describe("TypeScript package skeleton", () => { } }); - test("gives Windows credential integration tests a larger CI timeout", async () => { + test("uses the default test timeout consistently across CI platforms", async () => { const packageJson = JSON.parse( await readFile(new URL("../package.json", import.meta.url), "utf8"), ); @@ -82,9 +82,8 @@ describe("TypeScript package skeleton", () => { expect(packageJson.scripts.test).toBe( "bun test --timeout 30000 ./tests-ts", ); - expect(ciWorkflow).toContain( - "run: pnpm --dir sdk/typescript run test ${{ runner.os == 'Windows' && '--timeout 60000' || '' }}", - ); + expect(ciWorkflow).toContain("run: pnpm --dir sdk/typescript run test\n"); + expect(ciWorkflow).not.toContain("--timeout 60000"); }); test("builds packages without a preinstalled package manager and provides a production audit", async () => {