@openclaw/fs-safe 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +28 -6
- package/dist/advanced.d.ts +1 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -0
- package/dist/archive-kind.d.ts +0 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +5 -17
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +6 -7
- package/dist/archive-tar-stream.d.ts +3 -0
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +56 -37
- package/dist/archive-tar-wasm.d.ts +16 -4
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +134 -34
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +5 -6
- package/dist/clone-metadata.d.ts +1 -0
- package/dist/clone-metadata.d.ts.map +1 -1
- package/dist/clone-metadata.js +6 -2
- package/dist/create-directory.d.ts +20 -0
- package/dist/create-directory.d.ts.map +1 -0
- package/dist/create-directory.js +130 -0
- package/dist/create-file-async.d.ts +7 -0
- package/dist/create-file-async.d.ts.map +1 -0
- package/dist/create-file-async.js +121 -0
- package/dist/create-file.d.ts +8 -0
- package/dist/create-file.d.ts.map +1 -0
- package/dist/create-file.js +190 -0
- package/dist/create-owned-file.d.ts +8 -0
- package/dist/create-owned-file.d.ts.map +1 -0
- package/dist/create-owned-file.js +16 -0
- package/dist/create.d.ts +4 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +2 -0
- package/dist/creation-darwin.d.ts +7 -0
- package/dist/creation-darwin.d.ts.map +1 -0
- package/dist/creation-darwin.js +79 -0
- package/dist/creation-file-state.d.ts +19 -0
- package/dist/creation-file-state.d.ts.map +1 -0
- package/dist/creation-file-state.js +118 -0
- package/dist/creation-path.d.ts +21 -0
- package/dist/creation-path.d.ts.map +1 -0
- package/dist/creation-path.js +71 -0
- package/dist/creation-permissions.d.ts +19 -0
- package/dist/creation-permissions.d.ts.map +1 -0
- package/dist/creation-permissions.js +125 -0
- package/dist/directory-durability.d.ts +1 -1
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +22 -80
- package/dist/directory-guard.d.ts +3 -0
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-mode-node.d.ts +2 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +8 -0
- package/dist/directory-receipt.d.ts +24 -0
- package/dist/directory-receipt.d.ts.map +1 -0
- package/dist/directory-receipt.js +127 -0
- package/dist/file-cleanup.d.ts +19 -0
- package/dist/file-cleanup.d.ts.map +1 -0
- package/dist/file-cleanup.js +78 -0
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +2 -6
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +3 -9
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +2 -5
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +36 -7
- package/dist/move-path.js +1 -1
- package/dist/native-binding.d.ts +11 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-fallback-warning.d.ts +4 -0
- package/dist/native-fallback-warning.d.ts.map +1 -0
- package/dist/native-fallback-warning.js +11 -0
- package/dist/native-operations.d.ts +0 -2
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -24
- package/dist/native-parent-admission.d.ts +2 -0
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +3 -2
- package/dist/native-pinned-write-windows.d.ts +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +173 -28
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +19 -3
- package/dist/native-policy-parent-windows.d.ts.map +1 -1
- package/dist/native-policy-parent-windows.js +15 -6
- package/dist/native-staged-file.d.ts +3 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +86 -39
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +10 -4
- package/dist/pinned-write-input.d.ts +4 -0
- package/dist/pinned-write-input.d.ts.map +1 -0
- package/dist/pinned-write-input.js +25 -0
- package/dist/pinned-write-mode.d.ts +5 -0
- package/dist/pinned-write-mode.d.ts.map +1 -0
- package/dist/pinned-write-mode.js +24 -0
- package/dist/pinned-write-staged.d.ts +6 -0
- package/dist/pinned-write-staged.d.ts.map +1 -0
- package/dist/pinned-write-staged.js +187 -0
- package/dist/pinned-write-types.d.ts +3 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +35 -145
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +18 -4
- package/dist/private-producer-handoff-sync.d.ts +14 -0
- package/dist/private-producer-handoff-sync.d.ts.map +1 -0
- package/dist/private-producer-handoff-sync.js +114 -0
- package/dist/private-producer-handoff.d.ts +22 -4
- package/dist/private-producer-handoff.d.ts.map +1 -1
- package/dist/private-producer-handoff.js +140 -77
- package/dist/publish-copy-stage.d.ts +2 -1
- package/dist/publish-copy-stage.d.ts.map +1 -1
- package/dist/publish-copy-stage.js +16 -7
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -2
- package/dist/replace-file-temp-owner.d.ts +0 -7
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +3 -54
- package/dist/root-create-input.d.ts +2 -1
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +13 -4
- package/dist/root-directory-creation.d.ts +3 -3
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +15 -3
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +28 -7
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +22 -13
- package/dist/root-options.d.ts +12 -4
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-stat.d.ts.map +1 -1
- package/dist/root-path-stat.js +59 -7
- package/dist/root-write-publication.js +1 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +1 -0
- package/dist/secure-file-windows.d.ts +6 -0
- package/dist/secure-file-windows.d.ts.map +1 -1
- package/dist/secure-file-windows.js +34 -117
- package/dist/secure-file.js +2 -2
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +2 -1
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +6 -6
- package/dist/staged-file-settlement.d.ts +17 -0
- package/dist/staged-file-settlement.d.ts.map +1 -0
- package/dist/staged-file-settlement.js +57 -0
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +2 -1
- package/dist/windows-security-bridge.cs +336 -0
- package/dist/windows-security-bridge.ps1 +15 -0
- package/dist/windows-security-command.d.ts +26 -0
- package/dist/windows-security-command.d.ts.map +1 -0
- package/dist/windows-security-command.js +363 -0
- package/dist/windows-security-facts.d.ts +6 -0
- package/dist/windows-security-facts.d.ts.map +1 -0
- package/dist/windows-security-facts.js +108 -0
- package/docs/advanced.md +3 -1
- package/docs/archive.md +61 -37
- package/docs/config.md +6 -2
- package/docs/contributing.md +44 -4
- package/docs/copy.md +2 -0
- package/docs/creation.md +128 -0
- package/docs/durability.md +24 -0
- package/docs/install.md +31 -7
- package/docs/migrating-to-0.5.md +15 -6
- package/docs/migrating-to-0.6.md +9 -4
- package/docs/native-helper.md +22 -9
- package/docs/native.md +38 -7
- package/docs/permissions.md +37 -14
- package/docs/root.md +34 -0
- package/docs/secret-file.md +11 -2
- package/docs/secure-file.md +9 -4
- package/docs/sidecar-lock.md +5 -4
- package/docs/staged-file.md +5 -0
- package/docs/writing.md +71 -5
- package/package.json +18 -15
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// Executed only by the built-in Windows PowerShell/.NET host. The raw security
|
|
2
|
+
// descriptor never passes through CommonSecurityDescriptor's ACE normalization.
|
|
3
|
+
using System;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.ComponentModel;
|
|
6
|
+
using System.IO;
|
|
7
|
+
using System.Runtime.InteropServices;
|
|
8
|
+
using System.Security.AccessControl;
|
|
9
|
+
using System.Security.Principal;
|
|
10
|
+
using System.Text;
|
|
11
|
+
using Microsoft.Win32.SafeHandles;
|
|
12
|
+
|
|
13
|
+
public static partial class FsSafeWindowsBridge {
|
|
14
|
+
[StructLayout(LayoutKind.Sequential)] struct IoStatus { public IntPtr Status; public UIntPtr Information; }
|
|
15
|
+
[StructLayout(LayoutKind.Sequential)] struct UnicodeString { public ushort Length, MaximumLength; public IntPtr Buffer; }
|
|
16
|
+
[StructLayout(LayoutKind.Sequential)] struct ObjectAttributes {
|
|
17
|
+
public uint Length; public IntPtr RootDirectory, ObjectName; public uint Attributes;
|
|
18
|
+
public IntPtr SecurityDescriptor, SecurityQualityOfService;
|
|
19
|
+
}
|
|
20
|
+
[StructLayout(LayoutKind.Sequential)] struct FileInfo {
|
|
21
|
+
public uint Attributes, CreationLow, CreationHigh, AccessLow, AccessHigh, WriteLow, WriteHigh;
|
|
22
|
+
public uint Volume, SizeHigh, SizeLow, Links, IndexHigh, IndexLow;
|
|
23
|
+
}
|
|
24
|
+
[StructLayout(LayoutKind.Sequential)] struct FileId {
|
|
25
|
+
public ulong Volume; [MarshalAs(UnmanagedType.ByValArray, SizeConst=16)] public byte[] Id;
|
|
26
|
+
}
|
|
27
|
+
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true)] static extern SafeFileHandle CreateFileW(string path, uint access, uint share, IntPtr security, uint disposition, uint flags, IntPtr template);
|
|
28
|
+
[DllImport("kernel32.dll", SetLastError=true)] static extern IntPtr GetStdHandle(int standard);
|
|
29
|
+
[DllImport("kernel32.dll", SetLastError=true)] static extern uint GetFileType(SafeFileHandle handle);
|
|
30
|
+
[DllImport("kernel32.dll", SetLastError=true)] static extern bool GetFileInformationByHandle(SafeFileHandle handle, out FileInfo information);
|
|
31
|
+
[DllImport("kernel32.dll", SetLastError=true)] static extern bool GetFileInformationByHandleEx(SafeFileHandle handle, int kind, out FileId information, uint size);
|
|
32
|
+
[DllImport("kernel32.dll", SetLastError=true)] static extern bool SetFileInformationByHandle(SafeFileHandle handle, int kind, ref uint information, uint size);
|
|
33
|
+
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true)] static extern uint GetFinalPathNameByHandleW(SafeFileHandle handle, StringBuilder path, uint capacity, uint flags);
|
|
34
|
+
[DllImport("kernel32.dll")] static extern IntPtr LocalFree(IntPtr memory);
|
|
35
|
+
[DllImport("advapi32.dll")] static extern uint GetSecurityInfo(SafeFileHandle handle, int kind, uint sections, out IntPtr owner, out IntPtr group, out IntPtr dacl, out IntPtr sacl, out IntPtr descriptor);
|
|
36
|
+
[DllImport("advapi32.dll")] static extern uint GetSecurityDescriptorLength(IntPtr descriptor);
|
|
37
|
+
[DllImport("advapi32.dll", SetLastError=true)] static extern bool GetSecurityDescriptorDacl(IntPtr descriptor, out bool present, out IntPtr dacl, out bool defaulted);
|
|
38
|
+
[DllImport("advapi32.dll", SetLastError=true)] static extern bool GetSecurityDescriptorOwner(IntPtr descriptor, out IntPtr owner, out bool defaulted);
|
|
39
|
+
[DllImport("advapi32.dll")] static extern uint SetSecurityInfo(SafeFileHandle handle, int kind, uint sections, IntPtr owner, IntPtr group, IntPtr dacl, IntPtr sacl);
|
|
40
|
+
[DllImport("ntdll.dll")] static extern int NtQueryInformationFile(SafeFileHandle handle, out IoStatus io, [Out] byte[] information, uint length, int kind);
|
|
41
|
+
[DllImport("ntdll.dll")] static extern int NtCreateFile(out SafeFileHandle handle, uint access, ref ObjectAttributes attributes, out IoStatus io, IntPtr allocation, uint flags, uint share, uint disposition, uint options, IntPtr ea, uint eaLength);
|
|
42
|
+
[DllImport("ntdll.dll")] static extern uint RtlNtStatusToDosError(int status);
|
|
43
|
+
|
|
44
|
+
sealed class Failure : Exception {
|
|
45
|
+
public readonly string Code;
|
|
46
|
+
public Failure(string code, string message) : base(message) { Code=code; }
|
|
47
|
+
}
|
|
48
|
+
static Failure OsFailure(uint error, string operation, bool identity=false, bool fullIdentity=false) {
|
|
49
|
+
// Native security calls retain EACCES; shared handle identity calls use EPERM.
|
|
50
|
+
string code=error==5 ? (identity ? "EPERM" : "EACCES") : error==80 || error==183 ? "EEXIST" : error==2 || error==3 ? "ENOENT" :
|
|
51
|
+
identity && (error==112 || error==39) ? "ENOSPC" : identity && (error==32 || error==33) ? "EBUSY" :
|
|
52
|
+
fullIdentity && (error==1 || error==50 || error==87) ? "ENOTSUP" : "EIO";
|
|
53
|
+
return new Failure(code, operation+" failed with Windows error "+error);
|
|
54
|
+
}
|
|
55
|
+
static Failure CreateFailure(uint error) {
|
|
56
|
+
var failure=OsFailure(error,"create private directory",true);
|
|
57
|
+
return failure.Code=="EPERM" ? new Failure("EACCES",failure.Message) : failure;
|
|
58
|
+
}
|
|
59
|
+
static void Require(bool condition, string code, string message) {
|
|
60
|
+
if (!condition) throw new Failure(code,message);
|
|
61
|
+
}
|
|
62
|
+
static Dictionary<string,object> Row(params object[] fields) {
|
|
63
|
+
var result=new Dictionary<string,object>();
|
|
64
|
+
for(int i=0;i<fields.Length;i+=2) result.Add((string)fields[i],fields[i+1]);
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
static SafeFileHandle Open(string path, uint access, bool noFollow) {
|
|
68
|
+
var handle=CreateFileW(path,access,7,IntPtr.Zero,3,0x02000000u | (noFollow ? 0x00200000u : 0),IntPtr.Zero);
|
|
69
|
+
if(handle.IsInvalid) { uint error=(uint)Marshal.GetLastWin32Error(); handle.Dispose(); throw OsFailure(error,"open security handle"); }
|
|
70
|
+
return handle;
|
|
71
|
+
}
|
|
72
|
+
static FileInfo Information(SafeFileHandle handle) {
|
|
73
|
+
FileInfo info;
|
|
74
|
+
if(!GetFileInformationByHandle(handle,out info)) throw OsFailure((uint)Marshal.GetLastWin32Error(),"inspect handle identity",true);
|
|
75
|
+
return info;
|
|
76
|
+
}
|
|
77
|
+
static string Identity(SafeFileHandle handle) {
|
|
78
|
+
var info=Information(handle);
|
|
79
|
+
return info.Volume.ToString("x8")+":"+(((ulong)info.IndexHigh<<32)|info.IndexLow).ToString("x16");
|
|
80
|
+
}
|
|
81
|
+
static string DirectoryIdentity(SafeFileHandle handle, bool requireLocal=false) {
|
|
82
|
+
var info=Information(handle);
|
|
83
|
+
Require((info.Attributes&0x400)==0,"ELOOP","private directory must not be a reparse point");
|
|
84
|
+
Require((info.Attributes&0x10)!=0,"ENOTDIR","private directory handle must name a directory");
|
|
85
|
+
if(requireLocal) Require(IsLocal(handle),"ENOTSUP","private directories require a local filesystem");
|
|
86
|
+
return FullIdentity(handle);
|
|
87
|
+
}
|
|
88
|
+
static string FullIdentity(SafeFileHandle handle) {
|
|
89
|
+
FileId id;
|
|
90
|
+
if(!GetFileInformationByHandleEx(handle,18,out id,24)) throw OsFailure((uint)Marshal.GetLastWin32Error(),"inspect 128-bit file identity",true,true);
|
|
91
|
+
Require(id.Id!=null && id.Id.Length==16,"EIO","file identity is incomplete");
|
|
92
|
+
return id.Volume.ToString("x16")+":"+BitConverter.ToString(id.Id).Replace("-","").ToLowerInvariant();
|
|
93
|
+
}
|
|
94
|
+
static string FileIdentity(SafeFileHandle handle, uint expectedLinks) {
|
|
95
|
+
var info=Information(handle);
|
|
96
|
+
Require((info.Attributes&0x400)==0,"ELOOP","private file must not be a reparse point");
|
|
97
|
+
Require((info.Attributes&0x10)==0 && GetFileType(handle)==1,"EINVAL","private file must be a regular disk file");
|
|
98
|
+
Require(info.Links==expectedLinks,"EIO","private file link count changed");
|
|
99
|
+
return FullIdentity(handle);
|
|
100
|
+
}
|
|
101
|
+
static string FinalPath(SafeFileHandle handle, uint flags) {
|
|
102
|
+
int capacity=512;
|
|
103
|
+
for(int attempt=0;attempt<4;attempt++) {
|
|
104
|
+
var path=new StringBuilder(capacity);
|
|
105
|
+
uint length=GetFinalPathNameByHandleW(handle,path,(uint)capacity,flags);
|
|
106
|
+
if(length==0) throw OsFailure((uint)Marshal.GetLastWin32Error(),"inspect handle locality");
|
|
107
|
+
if(length<capacity) return path.ToString();
|
|
108
|
+
Require(length<32768,"EIO","final handle path exceeds the inspection budget");
|
|
109
|
+
capacity=(int)length+1;
|
|
110
|
+
}
|
|
111
|
+
throw new Failure("EIO","final handle path changed during inspection");
|
|
112
|
+
}
|
|
113
|
+
static bool IsVolumeGuidPath(string path) {
|
|
114
|
+
return path.Length>=49 && path.StartsWith(@"\\?\Volume{",StringComparison.OrdinalIgnoreCase) &&
|
|
115
|
+
path[47]=='}' && path[48]=='\\' &&
|
|
116
|
+
System.Text.RegularExpressions.Regex.IsMatch(path.Substring(11,36),@"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$");
|
|
117
|
+
}
|
|
118
|
+
static bool IsLocalFinalPath(string path) {
|
|
119
|
+
return !path.StartsWith(@"\\?\UNC\",StringComparison.Ordinal) &&
|
|
120
|
+
(!path.StartsWith(@"\\",StringComparison.Ordinal) || path.StartsWith(@"\\?\",StringComparison.Ordinal));
|
|
121
|
+
}
|
|
122
|
+
static bool IsLocal(SafeFileHandle handle) {
|
|
123
|
+
IoStatus io; var remote=new byte[]{255};
|
|
124
|
+
int status=NtQueryInformationFile(handle,out io,remote,1,51);
|
|
125
|
+
if(status==0 && io.Information.ToUInt64()==1 && remote[0]<=1) return remote[0]==0;
|
|
126
|
+
try {
|
|
127
|
+
if(IsVolumeGuidPath(FinalPath(handle,9))) return true;
|
|
128
|
+
} catch(Failure) { }
|
|
129
|
+
return IsLocalFinalPath(FinalPath(handle,0));
|
|
130
|
+
}
|
|
131
|
+
static string Sid(SecurityIdentifier sid) {
|
|
132
|
+
Require(sid!=null,"EIO","security descriptor SID is missing");
|
|
133
|
+
return sid.Value.ToLowerInvariant();
|
|
134
|
+
}
|
|
135
|
+
static Dictionary<string,object> Security(SafeFileHandle handle, bool requireLocality=true) {
|
|
136
|
+
IntPtr owner,group,dacl,sacl,descriptor;
|
|
137
|
+
uint error=GetSecurityInfo(handle,1,5,out owner,out group,out dacl,out sacl,out descriptor);
|
|
138
|
+
if(error!=0) throw OsFailure(error,"read owner and DACL");
|
|
139
|
+
try {
|
|
140
|
+
Require(descriptor!=IntPtr.Zero && owner!=IntPtr.Zero,"EIO","security descriptor is incomplete");
|
|
141
|
+
uint length=GetSecurityDescriptorLength(descriptor);
|
|
142
|
+
Require(length>=20 && length<=1024*1024,"EIO","security descriptor exceeds the inspection budget");
|
|
143
|
+
var bytes=new byte[length]; Marshal.Copy(descriptor,bytes,0,(int)length);
|
|
144
|
+
var raw=new RawSecurityDescriptor(bytes,0);
|
|
145
|
+
string ownerSid=Sid(raw.Owner), currentSid;
|
|
146
|
+
using(var current=WindowsIdentity.GetCurrent()) currentSid=Sid(current.User);
|
|
147
|
+
var aces=new List<object>(); var unsupported=new List<int>();
|
|
148
|
+
if(raw.DiscretionaryAcl!=null) foreach(GenericAce ace in raw.DiscretionaryAcl) {
|
|
149
|
+
var basic=ace as CommonAce;
|
|
150
|
+
if(basic==null || basic.IsCallback || (ace.AceType!=AceType.AccessAllowed && ace.AceType!=AceType.AccessDenied)) {
|
|
151
|
+
unsupported.Add((int)ace.AceType); continue;
|
|
152
|
+
}
|
|
153
|
+
byte flags=(byte)ace.AceFlags;
|
|
154
|
+
aces.Add(Row("sid",Sid(basic.SecurityIdentifier),"mask",unchecked((uint)basic.AccessMask),
|
|
155
|
+
"aceType",ace.AceType==AceType.AccessAllowed ? "allow" : "deny",
|
|
156
|
+
"flags",Row("raw",flags,"objectInherit",(flags&1)!=0,"containerInherit",(flags&2)!=0,
|
|
157
|
+
"noPropagateInherit",(flags&4)!=0,"inheritOnly",(flags&8)!=0,"inherited",(flags&16)!=0,
|
|
158
|
+
"successfulAccess",(flags&64)!=0,"failedAccess",(flags&128)!=0)));
|
|
159
|
+
}
|
|
160
|
+
bool isLocal;
|
|
161
|
+
try { isLocal=IsLocal(handle); }
|
|
162
|
+
catch(Failure) { if(requireLocality) throw; isLocal=false; }
|
|
163
|
+
return Row("ownerSid",ownerSid,"currentUserSid",currentSid,"daclPresent",raw.DiscretionaryAcl!=null,
|
|
164
|
+
"daclProtected",(raw.ControlFlags&ControlFlags.DiscretionaryAclProtected)!=0,
|
|
165
|
+
"isLocal",isLocal,"aceListComplete",unsupported.Count==0,"unsupportedAceTypes",unsupported.ToArray(),"aces",aces.ToArray());
|
|
166
|
+
} finally { if(descriptor!=IntPtr.Zero) LocalFree(descriptor); }
|
|
167
|
+
}
|
|
168
|
+
static byte[] PrivateSecurity(bool inherit=true) {
|
|
169
|
+
SecurityIdentifier current;
|
|
170
|
+
using(var identity=WindowsIdentity.GetCurrent()) current=identity.User;
|
|
171
|
+
var acl=new RawAcl(2,3);
|
|
172
|
+
var principals=new[]{current,new SecurityIdentifier("S-1-5-18"),new SecurityIdentifier("S-1-5-32-544")};
|
|
173
|
+
var flags=inherit ? AceFlags.ObjectInherit|AceFlags.ContainerInherit : AceFlags.None;
|
|
174
|
+
foreach(var principal in principals) acl.InsertAce(acl.Count,new CommonAce(flags,AceQualifier.AccessAllowed,0x1f01ff,principal,false,null));
|
|
175
|
+
var descriptor=new RawSecurityDescriptor(ControlFlags.DiscretionaryAclPresent|ControlFlags.DiscretionaryAclProtected|ControlFlags.SelfRelative,current,null,null,acl);
|
|
176
|
+
var bytes=new byte[descriptor.BinaryLength]; descriptor.GetBinaryForm(bytes,0); return bytes;
|
|
177
|
+
}
|
|
178
|
+
static SafeFileHandle CreateRelative(SafeFileHandle parent,string name) {
|
|
179
|
+
byte[] security=PrivateSecurity();
|
|
180
|
+
var pin=GCHandle.Alloc(security,GCHandleType.Pinned);
|
|
181
|
+
IntPtr nameBuffer=Marshal.StringToHGlobalUni(name), unicodeBuffer=IntPtr.Zero;
|
|
182
|
+
try {
|
|
183
|
+
int byteLength=checked(name.Length*2);
|
|
184
|
+
Require(byteLength>0 && byteLength<=65532,"EINVAL","private directory name is invalid");
|
|
185
|
+
var unicode=new UnicodeString { Length=(ushort)byteLength, MaximumLength=(ushort)(byteLength+2), Buffer=nameBuffer };
|
|
186
|
+
unicodeBuffer=Marshal.AllocHGlobal(Marshal.SizeOf(typeof(UnicodeString))); Marshal.StructureToPtr(unicode,unicodeBuffer,false);
|
|
187
|
+
var attributes=new ObjectAttributes { Length=(uint)Marshal.SizeOf(typeof(ObjectAttributes)),RootDirectory=parent.DangerousGetHandle(),
|
|
188
|
+
ObjectName=unicodeBuffer,Attributes=0x40|0x1000,SecurityDescriptor=pin.AddrOfPinnedObject() };
|
|
189
|
+
IoStatus io; SafeFileHandle created;
|
|
190
|
+
int status=NtCreateFile(out created,0x00130180,ref attributes,out io,IntPtr.Zero,0,7,2,0x00200021,IntPtr.Zero,0);
|
|
191
|
+
if(status<0) { if(created!=null) created.Dispose(); throw CreateFailure(RtlNtStatusToDosError(status)); }
|
|
192
|
+
return created;
|
|
193
|
+
} finally { if(unicodeBuffer!=IntPtr.Zero) Marshal.FreeHGlobal(unicodeBuffer); Marshal.FreeHGlobal(nameBuffer); pin.Free(); }
|
|
194
|
+
}
|
|
195
|
+
static void RequirePrivate(Dictionary<string,object> facts, bool requireProtected=true, bool requirePrivateChildren=false, bool allowAdministratorsOwner=false) {
|
|
196
|
+
string current=(string)facts["currentUserSid"], owner=(string)facts["ownerSid"];
|
|
197
|
+
Require((bool)facts["isLocal"] && (bool)facts["daclPresent"] && (!requireProtected || (bool)facts["daclProtected"]) &&
|
|
198
|
+
(bool)facts["aceListComplete"] && (owner==current || (allowAdministratorsOwner && owner=="s-1-5-32-544")),
|
|
199
|
+
"EACCES","private object security was not enforced");
|
|
200
|
+
foreach(Dictionary<string,object> ace in (object[])facts["aces"]) {
|
|
201
|
+
var flags=(Dictionary<string,object>)ace["flags"];
|
|
202
|
+
string sid=(string)ace["sid"];
|
|
203
|
+
if((string)ace["aceType"]=="deny" || sid==current || sid=="s-1-5-18" || sid=="s-1-5-32-544") continue;
|
|
204
|
+
if((bool)flags["inheritOnly"] && (!requirePrivateChildren || (!(bool)flags["objectInherit"] && !(bool)flags["containerInherit"]))) continue;
|
|
205
|
+
uint mask=(uint)ace["mask"];
|
|
206
|
+
Require((mask&0xd00d01dfu)==0,"EACCES","private object permits untrusted access");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
static void SplitParent(string path, out string parentPath, out string name) {
|
|
210
|
+
// JavaScript has already admitted and resolved this spelling. Keep extended
|
|
211
|
+
// Win32 paths out of .NET Framework's separate pathname normalization.
|
|
212
|
+
int separator=path.LastIndexOf('\\');
|
|
213
|
+
Require(separator>0 && separator<path.Length-1,"EINVAL","private object must name a child");
|
|
214
|
+
parentPath=path.Substring(0,separator); name=path.Substring(separator+1);
|
|
215
|
+
if(parentPath.EndsWith(":")) parentPath+="\\";
|
|
216
|
+
}
|
|
217
|
+
static string ExpectedIdentity(string variable, bool required=true) {
|
|
218
|
+
string identity=Environment.GetEnvironmentVariable(variable) ?? "";
|
|
219
|
+
Require((!required && identity.Length==0) || System.Text.RegularExpressions.Regex.IsMatch(identity,@"\A[0-9a-f]{16}:[0-9a-f]{32}\z"),
|
|
220
|
+
"EINVAL","complete expected Windows identity is required");
|
|
221
|
+
return identity;
|
|
222
|
+
}
|
|
223
|
+
static void CheckParent(SafeFileHandle parent, string path, string identity) {
|
|
224
|
+
Require(DirectoryIdentity(parent,true)==identity,"EIO","retained private parent changed");
|
|
225
|
+
using(var named=Open(path,0x80,true)) Require(DirectoryIdentity(named,true)==identity,"EIO","private parent pathname changed");
|
|
226
|
+
}
|
|
227
|
+
static object InspectDirectory(string path) {
|
|
228
|
+
bool requirePrivate=Environment.GetEnvironmentVariable("FS_SAFE_WINDOWS_SECURITY_REQUIRE_PRIVATE")=="1";
|
|
229
|
+
using(var handle=Open(path,requirePrivate ? 0x00020080u : 0x80u,true)) {
|
|
230
|
+
string identity=DirectoryIdentity(handle,true);
|
|
231
|
+
if(requirePrivate) RequirePrivate(Security(handle),true,true);
|
|
232
|
+
return Row("identity",identity);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
static void ProtectFileSecurity(SafeFileHandle handle) {
|
|
236
|
+
byte[] security=PrivateSecurity(false);
|
|
237
|
+
var pin=GCHandle.Alloc(security,GCHandleType.Pinned);
|
|
238
|
+
try {
|
|
239
|
+
bool present,defaulted; IntPtr dacl,owner;
|
|
240
|
+
if(!GetSecurityDescriptorDacl(pin.AddrOfPinnedObject(),out present,out dacl,out defaulted)) {
|
|
241
|
+
throw OsFailure((uint)Marshal.GetLastWin32Error(),"inspect private file DACL");
|
|
242
|
+
}
|
|
243
|
+
Require(present && dacl!=IntPtr.Zero,"EIO","private file DACL is incomplete");
|
|
244
|
+
if(!GetSecurityDescriptorOwner(pin.AddrOfPinnedObject(),out owner,out defaulted)) {
|
|
245
|
+
throw OsFailure((uint)Marshal.GetLastWin32Error(),"inspect private file owner");
|
|
246
|
+
}
|
|
247
|
+
Require(owner!=IntPtr.Zero,"EIO","private file owner is incomplete");
|
|
248
|
+
uint error=SetSecurityInfo(handle,1,0x80000005u,owner,IntPtr.Zero,dacl,IntPtr.Zero);
|
|
249
|
+
if(error!=0) throw OsFailure(error,"protect private file owner and DACL");
|
|
250
|
+
} finally { pin.Free(); }
|
|
251
|
+
}
|
|
252
|
+
static void CheckFileAssociation(SafeFileHandle held, string path, string identity, uint links, SafeFileHandle parent, string parentPath, string parentIdentity) {
|
|
253
|
+
Require(FileIdentity(held,links)==identity,"EIO","retained private file changed");
|
|
254
|
+
using(var named=Open(path,0x80,true)) Require(FileIdentity(named,links)==identity,"EIO","private file pathname changed");
|
|
255
|
+
CheckParent(parent,parentPath,parentIdentity);
|
|
256
|
+
}
|
|
257
|
+
static object PrivateFile(string path, bool protect) {
|
|
258
|
+
string expectedParent=ExpectedIdentity("FS_SAFE_WINDOWS_SECURITY_PARENT_IDENTITY");
|
|
259
|
+
string expectedFile=protect ? "" : ExpectedIdentity("FS_SAFE_WINDOWS_SECURITY_FILE_IDENTITY");
|
|
260
|
+
uint links=1;
|
|
261
|
+
if(!protect) Require(uint.TryParse(Environment.GetEnvironmentVariable("FS_SAFE_WINDOWS_SECURITY_EXPECTED_LINKS"),
|
|
262
|
+
System.Globalization.NumberStyles.None,System.Globalization.CultureInfo.InvariantCulture,out links) && links>0,
|
|
263
|
+
"EINVAL","private file expected link count is invalid");
|
|
264
|
+
string parentPath,name; SplitParent(path,out parentPath,out name);
|
|
265
|
+
using(var held=new SafeFileHandle(GetStdHandle(-10),false)) {
|
|
266
|
+
Require(!held.IsInvalid,"EBADF","inherited file handle is unavailable");
|
|
267
|
+
string identity=FileIdentity(held,links);
|
|
268
|
+
if(!protect) Require(identity==expectedFile,"EIO","private file identity changed");
|
|
269
|
+
// Windows can use Administrators as the creating token's default owner.
|
|
270
|
+
// The inherited ACL must already be private: later changes cannot revoke
|
|
271
|
+
// an earlier reader handle.
|
|
272
|
+
RequirePrivate(Security(held),requireProtected:!protect,allowAdministratorsOwner:protect);
|
|
273
|
+
using(var parent=Open(parentPath,0x80,true)) {
|
|
274
|
+
CheckFileAssociation(held,path,identity,links,parent,parentPath,expectedParent);
|
|
275
|
+
if(protect) using(var writable=Open(path,0x000e0080,true)) {
|
|
276
|
+
Require(FileIdentity(writable,links)==identity,"EIO","private file changed before owner and DACL protection");
|
|
277
|
+
RequirePrivate(Security(writable),requireProtected:false,allowAdministratorsOwner:true);
|
|
278
|
+
CheckParent(parent,parentPath,expectedParent);
|
|
279
|
+
ProtectFileSecurity(writable);
|
|
280
|
+
RequirePrivate(Security(writable));
|
|
281
|
+
}
|
|
282
|
+
RequirePrivate(Security(held));
|
|
283
|
+
CheckFileAssociation(held,path,identity,links,parent,parentPath,expectedParent);
|
|
284
|
+
return Row("identity",identity);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
static object CreatePrivate(string path) {
|
|
289
|
+
string parentPath,name; SplitParent(path,out parentPath,out name);
|
|
290
|
+
string expectedParent=ExpectedIdentity("FS_SAFE_WINDOWS_SECURITY_PARENT_IDENTITY",false);
|
|
291
|
+
using(var parent=Open(parentPath,0xa4,true)) {
|
|
292
|
+
string parentId=DirectoryIdentity(parent,true);
|
|
293
|
+
if(expectedParent.Length!=0) Require(parentId==expectedParent,"EIO","private parent changed before creation");
|
|
294
|
+
using(var created=CreateRelative(parent,name)) {
|
|
295
|
+
try {
|
|
296
|
+
string createdId=DirectoryIdentity(created);
|
|
297
|
+
RequirePrivate(Security(created));
|
|
298
|
+
Require(DirectoryIdentity(parent)==parentId,"EIO","retained private parent changed");
|
|
299
|
+
using(var namedParent=Open(parentPath,0x80,true)) Require(DirectoryIdentity(namedParent,true)==parentId,"EIO","private parent pathname changed");
|
|
300
|
+
using(var named=Open(path,0x80,true)) {
|
|
301
|
+
Require(DirectoryIdentity(named,true)==createdId,"EIO","private directory pathname changed");
|
|
302
|
+
}
|
|
303
|
+
return Row("created",true,"identity",createdId);
|
|
304
|
+
} catch(Exception primary) {
|
|
305
|
+
uint flags=0x13;
|
|
306
|
+
if(!SetFileInformationByHandle(created,21,ref flags,4)) {
|
|
307
|
+
var failure=primary as Failure;
|
|
308
|
+
throw new Failure(failure==null ? "EIO" : failure.Code,
|
|
309
|
+
(failure==null ? "private directory validation failed" : failure.Message)+
|
|
310
|
+
"; owned-handle cleanup failed with Windows error "+Marshal.GetLastWin32Error());
|
|
311
|
+
}
|
|
312
|
+
throw;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
public static object Execute(string operation,string path) {
|
|
318
|
+
try {
|
|
319
|
+
object result;
|
|
320
|
+
if(operation=="create") result=CreatePrivate(path);
|
|
321
|
+
else if(operation=="directory") result=InspectDirectory(path);
|
|
322
|
+
else if(operation=="protect-file" || operation=="verify-file") result=PrivateFile(path,operation=="protect-file");
|
|
323
|
+
else if(operation=="descriptor") {
|
|
324
|
+
using(var handle=new SafeFileHandle(GetStdHandle(-10),false)) {
|
|
325
|
+
Require(!handle.IsInvalid,"EBADF","inherited file handle is unavailable");
|
|
326
|
+
result=Row("identity",Identity(handle),"security",Security(handle));
|
|
327
|
+
}
|
|
328
|
+
} else if(operation=="path") {
|
|
329
|
+
// Raw reporting retains ACL facts when locality is unknown; admission remains strict.
|
|
330
|
+
using(var handle=Open(path,0x00020080,false)) result=Security(handle,false);
|
|
331
|
+
} else throw new Failure("EINVAL","unknown Windows security operation");
|
|
332
|
+
return Row("ok",true,"result",result);
|
|
333
|
+
} catch(Failure error) { return Row("ok",false,"code",error.Code,"message",error.Message); }
|
|
334
|
+
catch(Exception) { return Row("ok",false,"code","EIO","message","Windows security descriptor processing failed"); }
|
|
335
|
+
}
|
|
336
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[Parameter(Mandatory = $true)]
|
|
3
|
+
[ValidateSet('path', 'descriptor', 'create', 'directory', 'protect-file', 'verify-file')]
|
|
4
|
+
[string] $Operation
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
$ErrorActionPreference = 'Stop'
|
|
8
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
9
|
+
# Avoid scanning unrelated installed modules before loading system cmdlets.
|
|
10
|
+
$env:PSModulePath = [IO.Path]::Combine($PSHOME, 'Modules')
|
|
11
|
+
[Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
|
|
12
|
+
|
|
13
|
+
Microsoft.PowerShell.Utility\Add-Type -LiteralPath ([IO.Path]::Combine($PSScriptRoot, 'windows-security-bridge.cs'))
|
|
14
|
+
$targetPath = [Environment]::GetEnvironmentVariable('FS_SAFE_WINDOWS_SECURITY_PATH')
|
|
15
|
+
[FsSafeWindowsBridge]::Execute($Operation, $targetPath) | Microsoft.PowerShell.Utility\ConvertTo-Json -Depth 8 -Compress
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { NativeWindowsDescriptorSecurityFacts, NativeWindowsSecurityFacts } from "./native-binding.js";
|
|
2
|
+
/** Cleanup must preserve stages still reachable by an unsettled command. */
|
|
3
|
+
export declare function hasUnsettledWindowsSecurityCommand(error: unknown): boolean;
|
|
4
|
+
export declare function readWindowsSecurityFactsCommand(targetPath: string): NativeWindowsSecurityFacts;
|
|
5
|
+
export declare function inspectWindowsDescriptorCommand(fd: number): Promise<NativeWindowsDescriptorSecurityFacts>;
|
|
6
|
+
export declare function createPrivateWindowsDirectoryCommand(targetPath: string, expectedParentIdentity?: string): Promise<{
|
|
7
|
+
identity: string;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function createPrivateWindowsDirectoryCommandSync(targetPath: string, expectedParentIdentity?: string): {
|
|
10
|
+
identity: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function inspectWindowsDirectoryCommandSync(targetPath: string, requirePrivate: boolean): {
|
|
13
|
+
identity: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function inspectWindowsDirectoryCommand(targetPath: string, requirePrivate: boolean): Promise<{
|
|
16
|
+
identity: string;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function protectPrivateWindowsFileCommandSync(fd: number, targetPath: string, expectedParentIdentity: string): {
|
|
19
|
+
identity: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function protectPrivateWindowsFileCommand(fd: number, targetPath: string, expectedParentIdentity: string): Promise<{
|
|
22
|
+
identity: string;
|
|
23
|
+
}>;
|
|
24
|
+
export declare function verifyPrivateWindowsFileCommandSync(fd: number, targetPath: string, expectedFileIdentity: string, expectedParentIdentity: string, expectedLinks?: number): void;
|
|
25
|
+
export declare function verifyPrivateWindowsFileCommand(fd: number, targetPath: string, expectedFileIdentity: string, expectedParentIdentity: string, expectedLinks?: number): Promise<void>;
|
|
26
|
+
//# sourceMappingURL=windows-security-command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows-security-command.d.ts","sourceRoot":"","sources":["../src/windows-security-command.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oCAAoC,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AA2J5G,4EAA4E;AAC5E,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAqB1E;AAwHD,wBAAgB,+BAA+B,CAAC,UAAU,EAAE,MAAM,GAAG,0BAA0B,CAE9F;AAED,wBAAsB,+BAA+B,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,oCAAoC,CAAC,CAM/G;AAED,wBAAsB,oCAAoC,CAAC,UAAU,EAAE,MAAM,EAAE,sBAAsB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAE7I;AAED,wBAAgB,wCAAwC,CAAC,UAAU,EAAE,MAAM,EAAE,sBAAsB,CAAC,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAElI;AAYD,wBAAgB,kCAAkC,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAEpH;AAED,wBAAsB,8BAA8B,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAE/H;AAED,wBAAgB,oCAAoC,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAGzI;AAED,wBAAsB,gCAAgC,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAGpJ;AAED,wBAAgB,mCAAmC,CACjD,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,aAAa,SAAI,GAC9G,IAAI,CAMN;AAED,wBAAsB,+BAA+B,CACnD,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,EAAE,aAAa,SAAI,GAC9G,OAAO,CAAC,IAAI,CAAC,CAMf"}
|