om-memory-system 3.3.0-next.18 → 3.4.0-next.20
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.
|
@@ -52,6 +52,14 @@ export declare function getTraceDirectory(): string;
|
|
|
52
52
|
* that do not match the trace name pattern are never touched.
|
|
53
53
|
*/
|
|
54
54
|
export declare function pruneTraces(config: CaptureDiagnosticsConfig, now?: Date): number;
|
|
55
|
+
type WindowsAclRunner = (command: string, args: string[], options: {
|
|
56
|
+
env: NodeJS.ProcessEnv;
|
|
57
|
+
timeout: number;
|
|
58
|
+
windowsHide: boolean;
|
|
59
|
+
stdio: "ignore";
|
|
60
|
+
}) => void;
|
|
61
|
+
/** Restrict a trace path to the current user on both Windows and POSIX. */
|
|
62
|
+
export declare function protectTracePath(path: string, mode: number, platform?: NodeJS.Platform, run?: WindowsAclRunner): void;
|
|
55
63
|
/**
|
|
56
64
|
* Append one trace entry to the day's JSON Lines file. A single append per
|
|
57
65
|
* entry keeps lines whole when Pi and OpenCode write at the same time.
|
|
@@ -64,3 +72,4 @@ export declare function writeTraceEntry(record: CaptureAttemptRecord, diagnostic
|
|
|
64
72
|
export declare function emitCaptureAttempt(record: CaptureAttemptRecord, diagnostics: CaptureAttemptDiagnostics, config: CaptureDiagnosticsConfig, now?: Date): void;
|
|
65
73
|
/** Test hook: forget which day the last trace was written. */
|
|
66
74
|
export declare function resetTraceStateForTests(): void;
|
|
75
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { appendFileSync, chmodSync, existsSync, lstatSync, mkdirSync, readdirSync, unlinkSync, } from "fs";
|
|
2
3
|
import { join } from "path";
|
|
3
4
|
import { getLogDirPath } from "./log-path.js";
|
|
4
5
|
import { log } from "./logger.js";
|
|
@@ -112,6 +113,54 @@ export function pruneTraces(config, now = new Date()) {
|
|
|
112
113
|
return removed;
|
|
113
114
|
}
|
|
114
115
|
let lastTraceDate = null;
|
|
116
|
+
const WINDOWS_TRACE_ACL = `$ErrorActionPreference = 'Stop'
|
|
117
|
+
$path = $env:OMMS_TRACE_ACL_PATH
|
|
118
|
+
$isDirectory = [System.IO.Directory]::Exists($path)
|
|
119
|
+
$acl = if ($isDirectory) {
|
|
120
|
+
[System.IO.Directory]::GetAccessControl($path)
|
|
121
|
+
} else {
|
|
122
|
+
[System.IO.File]::GetAccessControl($path)
|
|
123
|
+
}
|
|
124
|
+
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
125
|
+
$owner = $acl.GetOwner([System.Security.Principal.SecurityIdentifier])
|
|
126
|
+
if ($owner.Value -ne $currentUser.Value) {
|
|
127
|
+
throw 'Trace path has a different owner'
|
|
128
|
+
}
|
|
129
|
+
$acl.SetAccessRuleProtection($true, $false)
|
|
130
|
+
foreach ($rule in @($acl.Access)) {
|
|
131
|
+
[void]$acl.RemoveAccessRuleSpecific($rule)
|
|
132
|
+
}
|
|
133
|
+
$inheritance = if ($isDirectory) {
|
|
134
|
+
[System.Security.AccessControl.InheritanceFlags]'ContainerInherit, ObjectInherit'
|
|
135
|
+
} else {
|
|
136
|
+
[System.Security.AccessControl.InheritanceFlags]::None
|
|
137
|
+
}
|
|
138
|
+
$rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
|
|
139
|
+
$currentUser,
|
|
140
|
+
[System.Security.AccessControl.FileSystemRights]::FullControl,
|
|
141
|
+
$inheritance,
|
|
142
|
+
[System.Security.AccessControl.PropagationFlags]::None,
|
|
143
|
+
[System.Security.AccessControl.AccessControlType]::Allow
|
|
144
|
+
)
|
|
145
|
+
$acl.AddAccessRule($rule)
|
|
146
|
+
if ($isDirectory) {
|
|
147
|
+
[System.IO.Directory]::SetAccessControl($path, $acl)
|
|
148
|
+
} else {
|
|
149
|
+
[System.IO.File]::SetAccessControl($path, $acl)
|
|
150
|
+
}`;
|
|
151
|
+
/** Restrict a trace path to the current user on both Windows and POSIX. */
|
|
152
|
+
export function protectTracePath(path, mode, platform = process.platform, run = execFileSync) {
|
|
153
|
+
if (platform !== "win32") {
|
|
154
|
+
chmodSync(path, mode);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
run("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", WINDOWS_TRACE_ACL], {
|
|
158
|
+
env: { ...process.env, OMMS_TRACE_ACL_PATH: path },
|
|
159
|
+
timeout: 10_000,
|
|
160
|
+
windowsHide: true,
|
|
161
|
+
stdio: "ignore",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
115
164
|
/**
|
|
116
165
|
* Append one trace entry to the day's JSON Lines file. A single append per
|
|
117
166
|
* entry keeps lines whole when Pi and OpenCode write at the same time.
|
|
@@ -125,7 +174,18 @@ export function writeTraceEntry(record, diagnostics, config, now = new Date()) {
|
|
|
125
174
|
}
|
|
126
175
|
const dir = getTraceDirectory();
|
|
127
176
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
128
|
-
|
|
177
|
+
if (!lstatSync(dir).isDirectory())
|
|
178
|
+
throw new Error("Trace directory is not a real directory");
|
|
179
|
+
protectTracePath(dir, 0o700);
|
|
180
|
+
const dailyFile = `capture-${today}.jsonl`;
|
|
181
|
+
for (const name of readdirSync(dir)) {
|
|
182
|
+
if (!TRACE_FILE.test(name) || name === dailyFile)
|
|
183
|
+
continue;
|
|
184
|
+
const retained = join(dir, name);
|
|
185
|
+
if (!lstatSync(retained).isFile())
|
|
186
|
+
throw new Error("Trace path is not a regular file");
|
|
187
|
+
protectTracePath(retained, 0o600);
|
|
188
|
+
}
|
|
129
189
|
const redact = (value) => value === undefined ? null : redactTraceText(value, config);
|
|
130
190
|
const entry = {
|
|
131
191
|
schemaVersion: TRACE_SCHEMA_VERSION,
|
|
@@ -135,11 +195,18 @@ export function writeTraceEntry(record, diagnostics, config, now = new Date()) {
|
|
|
135
195
|
userPrompt: redact(diagnostics.userPrompt),
|
|
136
196
|
reply: redact(diagnostics.rawReply),
|
|
137
197
|
};
|
|
138
|
-
const file = join(dir,
|
|
198
|
+
const file = join(dir, dailyFile);
|
|
199
|
+
const existingFile = lstatSync(file, { throwIfNoEntry: false });
|
|
200
|
+
if (existingFile && !existingFile.isFile())
|
|
201
|
+
throw new Error("Trace path is not a regular file");
|
|
202
|
+
// Narrow an existing file before appending sensitive content.
|
|
203
|
+
const existingWindowsFile = process.platform === "win32" && Boolean(existingFile);
|
|
204
|
+
if (existingWindowsFile)
|
|
205
|
+
protectTracePath(file, 0o600);
|
|
139
206
|
appendFileSync(file, `${JSON.stringify(entry)}\n`, { mode: 0o600 });
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
207
|
+
// A new Windows file inherits the private directory ACL until this call.
|
|
208
|
+
if (!existingWindowsFile)
|
|
209
|
+
protectTracePath(file, 0o600);
|
|
143
210
|
}
|
|
144
211
|
catch (error) {
|
|
145
212
|
log("Capture trace write failed", { code: errorCode(error) });
|