@triflux/core 10.78.0 → 10.80.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/package.json
CHANGED
|
@@ -125,6 +125,7 @@ function trustedPackageTree(packageRoot, nodePath, tmuxPath) {
|
|
|
125
125
|
const meta = existing(current);
|
|
126
126
|
if (
|
|
127
127
|
!meta ||
|
|
128
|
+
path.basename(current).toLowerCase() === ".git" ||
|
|
128
129
|
meta.isSymbolicLink() ||
|
|
129
130
|
![0, process.getuid()].includes(meta.uid) ||
|
|
130
131
|
(meta.mode & 0o002) !== 0
|
|
@@ -232,8 +233,8 @@ export function inspectPeerPermission({
|
|
|
232
233
|
tmuxPath,
|
|
233
234
|
platform = process.platform,
|
|
234
235
|
} = {}) {
|
|
236
|
+
if (platform === "win32") return { status: "unsupported" };
|
|
235
237
|
const plan = peerPermissionPlan({ home, packageRoot, nodePath, tmuxPath });
|
|
236
|
-
if (platform === "win32") return { status: "unsupported", plan };
|
|
237
238
|
const effectiveNodePath = nodePath || resolveStableNodeBin();
|
|
238
239
|
if (
|
|
239
240
|
!trustedPackageTree(
|
|
@@ -334,12 +335,9 @@ export function installPeerPermission({
|
|
|
334
335
|
platform,
|
|
335
336
|
});
|
|
336
337
|
const { plan, status } = inspected;
|
|
338
|
+
if (status === "unsupported") return { status, changed: false };
|
|
337
339
|
if (status === "approved") return { status: "approved", changed: false };
|
|
338
|
-
if (
|
|
339
|
-
status === "conflict" ||
|
|
340
|
-
status === "unavailable" ||
|
|
341
|
-
status === "unsupported"
|
|
342
|
-
)
|
|
340
|
+
if (status === "conflict" || status === "unavailable")
|
|
343
341
|
return {
|
|
344
342
|
status,
|
|
345
343
|
changed: false,
|
|
@@ -502,8 +500,10 @@ export async function offerPeerPermission({
|
|
|
502
500
|
tmuxPath,
|
|
503
501
|
platform,
|
|
504
502
|
});
|
|
505
|
-
if (state.status === "unsupported")
|
|
503
|
+
if (state.status === "unsupported") {
|
|
504
|
+
notice("Windows 는 자동 승인 없이 tfx-peer 를 직접 실행한다");
|
|
506
505
|
return { status: "unsupported", changed: false };
|
|
506
|
+
}
|
|
507
507
|
if (state.status === "approved")
|
|
508
508
|
return { status: "approved", changed: false };
|
|
509
509
|
if (fromUpdate) {
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
const WINDOWS_POWERSHELL = String.raw`C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`;
|
|
4
|
+
|
|
5
|
+
function windowsPowerShellEnv() {
|
|
6
|
+
// PowerShell 7의 모듈 경로를 빼서 Windows PowerShell 5.1이 기본값을 복원하게 한다.
|
|
7
|
+
return Object.fromEntries(
|
|
8
|
+
Object.entries(process.env).filter(([key]) =>
|
|
9
|
+
["systemroot", "windir", "temp", "tmp", "userprofile"].includes(
|
|
10
|
+
key.toLowerCase(),
|
|
11
|
+
),
|
|
12
|
+
),
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function readWindowsPeerState(
|
|
17
|
+
files,
|
|
18
|
+
{ pids = [], execFile = execFileSync } = {},
|
|
19
|
+
) {
|
|
20
|
+
if (!files.length && !pids.length)
|
|
21
|
+
return { acls: new Map(), procStarts: new Map() };
|
|
22
|
+
const script = `
|
|
23
|
+
$ErrorActionPreference = 'Stop'
|
|
24
|
+
[Console]::InputEncoding = [Text.UTF8Encoding]::new($false)
|
|
25
|
+
[Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
|
|
26
|
+
$request = ConvertFrom-Json ([Console]::In.ReadToEnd())
|
|
27
|
+
$userSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
28
|
+
$acls = @(foreach ($p in $request.paths) {
|
|
29
|
+
try {
|
|
30
|
+
$item = if ([System.IO.Directory]::Exists($p)) { [System.IO.DirectoryInfo]::new($p) } else { [System.IO.FileInfo]::new($p) }
|
|
31
|
+
$acl = $item.GetAccessControl()
|
|
32
|
+
[pscustomobject]@{
|
|
33
|
+
path = $p
|
|
34
|
+
userSid = $userSid
|
|
35
|
+
ownerSid = $acl.GetOwner([System.Security.Principal.SecurityIdentifier]).Value
|
|
36
|
+
reparsePoint = [bool]($item.Attributes -band [IO.FileAttributes]::ReparsePoint)
|
|
37
|
+
access = @(foreach ($rule in $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])) {
|
|
38
|
+
[pscustomobject]@{
|
|
39
|
+
sid = $rule.IdentityReference.Value
|
|
40
|
+
rights = [long]$rule.FileSystemRights
|
|
41
|
+
type = $rule.AccessControlType.ToString()
|
|
42
|
+
inheritanceFlags = [int]$rule.InheritanceFlags
|
|
43
|
+
propagationFlags = [int]$rule.PropagationFlags
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
[pscustomobject]@{ path = $p; error = $_.Exception.Message }
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
$processes = @(foreach ($processId in $request.pids) {
|
|
52
|
+
try {
|
|
53
|
+
$start = [System.Diagnostics.Process]::GetProcessById($processId).StartTime.ToFileTimeUtc().ToString([Globalization.CultureInfo]::InvariantCulture)
|
|
54
|
+
[pscustomobject]@{ pid = $processId; procStart = $start }
|
|
55
|
+
} catch {
|
|
56
|
+
[pscustomobject]@{ pid = $processId; error = $_.Exception.Message }
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
ConvertTo-Json -InputObject @{ acls = $acls; processes = $processes } -Depth 5 -Compress
|
|
60
|
+
`;
|
|
61
|
+
const rows = JSON.parse(
|
|
62
|
+
execFile(
|
|
63
|
+
WINDOWS_POWERSHELL,
|
|
64
|
+
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
65
|
+
{
|
|
66
|
+
input: JSON.stringify({ paths: files, pids }),
|
|
67
|
+
encoding: "utf8",
|
|
68
|
+
timeout: 10000,
|
|
69
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
70
|
+
windowsHide: true,
|
|
71
|
+
env: windowsPowerShellEnv(),
|
|
72
|
+
},
|
|
73
|
+
),
|
|
74
|
+
);
|
|
75
|
+
if (!Array.isArray(rows.acls) || !Array.isArray(rows.processes))
|
|
76
|
+
throw new Error("Invalid Windows peer response");
|
|
77
|
+
return {
|
|
78
|
+
acls: new Map(
|
|
79
|
+
rows.acls
|
|
80
|
+
.filter((row) => !Object.hasOwn(row, "error"))
|
|
81
|
+
.map((row) => [row.path, row]),
|
|
82
|
+
),
|
|
83
|
+
procStarts: new Map(
|
|
84
|
+
rows.processes
|
|
85
|
+
.filter(
|
|
86
|
+
(row) =>
|
|
87
|
+
!Object.hasOwn(row, "error") &&
|
|
88
|
+
typeof row.procStart === "string" &&
|
|
89
|
+
/^\d+$/.test(row.procStart),
|
|
90
|
+
)
|
|
91
|
+
.map((row) => [row.pid, row.procStart]),
|
|
92
|
+
),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function readWindowsAcls(files, options) {
|
|
97
|
+
return readWindowsPeerState(files, options).acls;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function assertWindowsAcl(acl, kind, { privateMode = true } = {}) {
|
|
101
|
+
const trusted = new Set([acl?.userSid, "S-1-5-18", "S-1-5-32-544"]);
|
|
102
|
+
// 개별 쓰기·삭제·ACL 변경과 generic write/all을 함께 거른다.
|
|
103
|
+
const writes =
|
|
104
|
+
0x2 |
|
|
105
|
+
0x4 |
|
|
106
|
+
0x10 |
|
|
107
|
+
0x40 |
|
|
108
|
+
0x100 |
|
|
109
|
+
0x10000 |
|
|
110
|
+
0x40000 |
|
|
111
|
+
0x80000 |
|
|
112
|
+
0x40000000 |
|
|
113
|
+
0x10000000;
|
|
114
|
+
if (
|
|
115
|
+
!/^S-1-\d+(?:-\d+)+$/.test(acl?.userSid || "") ||
|
|
116
|
+
!trusted.has(acl.ownerSid) ||
|
|
117
|
+
acl.reparsePoint !== false ||
|
|
118
|
+
!Array.isArray(acl.access) ||
|
|
119
|
+
!acl.access.length ||
|
|
120
|
+
acl.access.some(
|
|
121
|
+
(ace) =>
|
|
122
|
+
!/^S-1-\d+(?:-\d+)+$/.test(ace.sid || "") ||
|
|
123
|
+
!["Allow", "Deny"].includes(ace.type) ||
|
|
124
|
+
!Number.isInteger(ace.rights) ||
|
|
125
|
+
ace.rights < -2147483648 ||
|
|
126
|
+
ace.rights > 0xffffffff ||
|
|
127
|
+
(ace.type === "Allow" &&
|
|
128
|
+
!(
|
|
129
|
+
Number.isInteger(ace.propagationFlags) &&
|
|
130
|
+
(ace.propagationFlags & 2) !== 0
|
|
131
|
+
) &&
|
|
132
|
+
!trusted.has(ace.sid) &&
|
|
133
|
+
(privateMode || (ace.rights & writes) !== 0)),
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
throw new Error(`Unsafe ${kind}`);
|
|
137
|
+
}
|