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 { appendFileSync, chmodSync, existsSync, mkdirSync, readdirSync, unlinkSync } from "fs";
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
- chmodSync(dir, 0o700);
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, `capture-${today}.jsonl`);
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
- // The mode above applies only when the append creates the file; a file
141
- // that already existed with wider permissions is narrowed here.
142
- chmodSync(file, 0o600);
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) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "om-memory-system",
3
- "version": "3.3.0-next.18",
3
+ "version": "3.4.0-next.20",
4
4
  "description": "Opinionated modular memory system for coding agents (OpenCode + Pi)",
5
5
  "type": "module",
6
6
  "main": "dist/plugin.js",