@optimuslabs/harness-map-staging 1.5.30 → 1.5.32
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.
|
@@ -13,6 +13,9 @@ function buildGetOptions(url, timeoutMs) {
|
|
|
13
13
|
path: url.pathname + url.search,
|
|
14
14
|
method: 'GET',
|
|
15
15
|
timeout: timeoutMs,
|
|
16
|
+
// A fresh socket per GET: Node 19+ keep-alive would reuse a socket left idle
|
|
17
|
+
// during the inventory disk scan, and a server FIN then looks like a failed fetch.
|
|
18
|
+
agent: false,
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
21
|
function buildBodyOptions(url, payload, method, timeoutMs) {
|
|
@@ -31,23 +34,36 @@ function buildBodyOptions(url, payload, method, timeoutMs) {
|
|
|
31
34
|
timeout: timeoutMs,
|
|
32
35
|
};
|
|
33
36
|
}
|
|
34
|
-
|
|
35
|
-
export function executeGet(urlStr, timeoutMs) {
|
|
37
|
+
function executeGetOnce(urlStr, timeoutMs) {
|
|
36
38
|
const url = new URL(urlStr);
|
|
37
39
|
const options = buildGetOptions(url, timeoutMs);
|
|
38
40
|
const transport = url.protocol === 'https:' ? https.request : http.request;
|
|
39
41
|
return new Promise((resolve) => {
|
|
42
|
+
let done = false;
|
|
43
|
+
const finish = (result) => {
|
|
44
|
+
if (done)
|
|
45
|
+
return;
|
|
46
|
+
done = true;
|
|
47
|
+
resolve(result);
|
|
48
|
+
};
|
|
40
49
|
const req = transport(options, (res) => {
|
|
41
50
|
let body = '';
|
|
42
51
|
res.setEncoding('utf8');
|
|
43
52
|
res.on('data', (chunk) => { body += chunk; });
|
|
44
|
-
res.on('end', () =>
|
|
53
|
+
res.on('end', () => finish({ statusCode: res.statusCode ?? 0, body }));
|
|
45
54
|
});
|
|
46
|
-
req.on('error', () =>
|
|
47
|
-
req.on('timeout', () => { req.destroy();
|
|
55
|
+
req.on('error', () => finish({ statusCode: 0, body: '' }));
|
|
56
|
+
req.on('timeout', () => { req.destroy(); finish({ statusCode: 0, body: '' }); });
|
|
48
57
|
req.end();
|
|
49
58
|
});
|
|
50
59
|
}
|
|
60
|
+
/** Execute a GET. Retries once on a dropped connection (status 0). */
|
|
61
|
+
export async function executeGet(urlStr, timeoutMs) {
|
|
62
|
+
const first = await executeGetOnce(urlStr, timeoutMs);
|
|
63
|
+
if (first.statusCode !== 0)
|
|
64
|
+
return first;
|
|
65
|
+
return executeGetOnce(urlStr, timeoutMs);
|
|
66
|
+
}
|
|
51
67
|
/** Execute a POST/PATCH request. A Buffer payload is sent as `Content-Encoding: gzip`. Rejects on network error or timeout. */
|
|
52
68
|
export function executeBody(urlStr, method, payload, timeoutMs) {
|
|
53
69
|
const url = new URL(urlStr);
|
|
@@ -2,9 +2,9 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
const HOME_DIR = homedir();
|
|
5
|
-
import { getFileCollectionPatterns, FILE_PATH_REGISTRY_FILE_PATTERNS_PATH } from '../../endpoint_client/index.js';
|
|
5
|
+
import { getFileCollectionPatterns, getSensitivePathsAuditCandidates, FILE_PATH_REGISTRY_FILE_PATTERNS_PATH, } from '../../endpoint_client/index.js';
|
|
6
6
|
import { OPTIMUSLABS_LOGS_REL, LOG_GROUP_AUDIT } from '../../bootstrap_constants.js';
|
|
7
|
-
import { runSensitivePathsAudit } from '../../log_sensitive_paths_audit.js';
|
|
7
|
+
import { CandidatesFetchError, runSensitivePathsAudit } from '../../log_sensitive_paths_audit.js';
|
|
8
8
|
import { buildPathPermissionsAuditEntry, grantPathsFromConfig } from '../../log_path_permissions_audit.js';
|
|
9
9
|
import { loadEndpointBase, getEndpointSource } from '../sender/endpoint_config.js';
|
|
10
10
|
import { hookLogReplace, hookRunLog, hookLogError, readHookLog } from './hook_logger.js';
|
|
@@ -115,6 +115,8 @@ function filterToActiveWorktrees(configFiles) {
|
|
|
115
115
|
return { configFiles: filtered, worktreeReport };
|
|
116
116
|
}
|
|
117
117
|
async function collectAllConfigFiles(endpointBase) {
|
|
118
|
+
// Same moment as file-patterns, not after the disk scan: that gap is what dropped this GET.
|
|
119
|
+
const candidatesPromise = getSensitivePathsAuditCandidates(endpointBase);
|
|
118
120
|
const patternsResponse = await fetchPatterns(endpointBase);
|
|
119
121
|
const configFiles = scanPatterns(patternsResponse);
|
|
120
122
|
const seen = new Set(configFiles.map(configFileKey));
|
|
@@ -134,26 +136,45 @@ async function collectAllConfigFiles(endpointBase) {
|
|
|
134
136
|
const skillsCliEntry = collectSkillsCliInstalled(PROJECT_ROOT, hookRunLog);
|
|
135
137
|
if (skillsCliEntry)
|
|
136
138
|
addUnseen(configFiles, seen, [skillsCliEntry]);
|
|
137
|
-
return filterToActiveWorktrees(configFiles);
|
|
139
|
+
return { ...filterToActiveWorktrees(configFiles), candidatesPromise };
|
|
138
140
|
}
|
|
139
141
|
/** Adds the sensitive paths audit to the batch; false when it could not be run (nothing added). */
|
|
140
|
-
async function addSensitivePathsAudit(endpointBase, configFiles) {
|
|
142
|
+
async function addSensitivePathsAudit(endpointBase, configFiles, candidatesPromise) {
|
|
141
143
|
hookRunLog(`running sensitive paths audit`);
|
|
142
144
|
const auditOutputDir = join(homedir(), OPTIMUSLABS_LOGS_REL, LOG_GROUP_AUDIT);
|
|
145
|
+
const prefetched = await candidatesPromise;
|
|
143
146
|
try {
|
|
144
|
-
const auditResult = await runSensitivePathsAudit(endpointBase, auditOutputDir, PROJECT_ROOT);
|
|
147
|
+
const auditResult = await runSensitivePathsAudit(endpointBase, auditOutputDir, PROJECT_ROOT, prefetched);
|
|
145
148
|
hookRunLog(`sensitive_paths_audit: ${auditResult.paths.length} path(s)`);
|
|
146
149
|
configFiles.push({ file_type: 'sensitive_paths_audit', file_path: 'sensitive_paths_audit.txt', raw_content: { paths: auditResult.paths } });
|
|
147
150
|
return true;
|
|
148
151
|
}
|
|
149
152
|
catch (err) {
|
|
150
|
-
//
|
|
151
|
-
|
|
153
|
+
// Keep the last good audit either way. Only a missed candidates GET is transient;
|
|
154
|
+
// a local write failure (EACCES, ENOSPC) is an endpoint fault and must still
|
|
155
|
+
// report a failed FunctionalRun so Sensor failing stays visible.
|
|
156
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
157
|
+
hookRunLog(`sensitive_paths_audit_error: ${message} (audit not sent)`);
|
|
152
158
|
hookLogError('sensitive_paths_audit', err, { endpoint: endpointBase, output_dir: auditOutputDir });
|
|
153
|
-
|
|
159
|
+
console.error(`sensitive_paths_audit: ${message}`);
|
|
160
|
+
if (!(err instanceof CandidatesFetchError)) {
|
|
161
|
+
await reportFunctionalFailureAwait('inventory', 'sensitive_paths_audit', err);
|
|
162
|
+
}
|
|
154
163
|
return false;
|
|
155
164
|
}
|
|
156
165
|
}
|
|
166
|
+
async function collectInventory(endpointBase) {
|
|
167
|
+
const collected = await collectAllConfigFiles(endpointBase);
|
|
168
|
+
const sensitiveAudited = await addSensitivePathsAudit(endpointBase, collected.configFiles, collected.candidatesPromise);
|
|
169
|
+
ensureCursorUserSettingsSnapshotInBatch(collected.configFiles);
|
|
170
|
+
if (sensitiveAudited) {
|
|
171
|
+
addPathPermissionsAudit(collected.configFiles);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
hookRunLog('path_permissions_audit: skipped (no sensitive paths audit this run)');
|
|
175
|
+
}
|
|
176
|
+
return collected;
|
|
177
|
+
}
|
|
157
178
|
/** POSIX mode of every collected file, plus granted folders (and their ancestors) and sensitive audit hits within a budget. stat() only. */
|
|
158
179
|
function addPathPermissionsAudit(configFiles) {
|
|
159
180
|
const visited = [];
|
|
@@ -251,7 +272,7 @@ async function main() {
|
|
|
251
272
|
let configFiles;
|
|
252
273
|
let worktreeReport = [];
|
|
253
274
|
try {
|
|
254
|
-
const collected = await
|
|
275
|
+
const collected = await collectInventory(endpointBase);
|
|
255
276
|
configFiles = collected.configFiles;
|
|
256
277
|
worktreeReport = collected.worktreeReport;
|
|
257
278
|
}
|
|
@@ -259,16 +280,6 @@ async function main() {
|
|
|
259
280
|
hookRunLog(`patterns_error: ${err instanceof Error ? err.message : String(err)}`);
|
|
260
281
|
throw err;
|
|
261
282
|
}
|
|
262
|
-
const sensitiveAudited = await addSensitivePathsAudit(endpointBase, configFiles);
|
|
263
|
-
ensureCursorUserSettingsSnapshotInBatch(configFiles);
|
|
264
|
-
if (sensitiveAudited) {
|
|
265
|
-
addPathPermissionsAudit(configFiles);
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
// Built without the sensitive paths, it would replace the backend's last good permissions
|
|
269
|
-
// audit and drop those paths' modes; skip it so both audits stay as last sent.
|
|
270
|
-
hookRunLog('path_permissions_audit: skipped (no sensitive paths audit this run)');
|
|
271
|
-
}
|
|
272
283
|
const fileTypes = [...new Set(configFiles.map((c) => c.file_type))].join(', ');
|
|
273
284
|
hookRunLog(`collected ${configFiles.length} config file(s) file_types=${fileTypes}`);
|
|
274
285
|
for (const c of configFiles) {
|
|
@@ -333,4 +344,4 @@ async function logSingleFile(filePath) {
|
|
|
333
344
|
}
|
|
334
345
|
return sendConfigFile({ file_type: fileType, file_path: filePath, raw_content: rawContent }, hardwareUuid, authKey);
|
|
335
346
|
}
|
|
336
|
-
export { main, logSingleFile, createSignature, canonicalizePayload, sendConfigFile, sendConfigFilesBatch, BATCH_CHUNK_SIZE };
|
|
347
|
+
export { main, logSingleFile, createSignature, canonicalizePayload, sendConfigFile, sendConfigFilesBatch, BATCH_CHUNK_SIZE, addSensitivePathsAudit, };
|
|
@@ -9,10 +9,17 @@
|
|
|
9
9
|
import { existsSync, writeFileSync, mkdirSync, statSync } from 'node:fs';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { homedir } from 'node:os';
|
|
12
|
-
import { getSensitivePathsAuditCandidates } from './endpoint_client/index.js';
|
|
12
|
+
import { getSensitivePathsAuditCandidates, } from './endpoint_client/index.js';
|
|
13
13
|
import { OPTIMUSLABS_LOGS_REL, LOG_GROUP_AUDIT } from './bootstrap_constants.js';
|
|
14
14
|
import { loadEndpointBase } from './log_config_files/sender/endpoint_config.js';
|
|
15
15
|
const AUDIT_FILENAME = 'sensitive_paths_audit.txt';
|
|
16
|
+
/** Thrown when the candidates GET returned nothing usable. Transient: do not mark the sensor as failing. */
|
|
17
|
+
export class CandidatesFetchError extends Error {
|
|
18
|
+
constructor(endpointBase) {
|
|
19
|
+
super(`could not fetch sensitive path candidates from ${endpointBase}`);
|
|
20
|
+
this.name = 'CandidatesFetchError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
16
23
|
function expandPath(template, cwd) {
|
|
17
24
|
if (template.startsWith('~/')) {
|
|
18
25
|
return join(homedir(), template.slice(2));
|
|
@@ -66,10 +73,13 @@ export function writeSensitivePathsAudit(outputDir, pathTemplates, cwd = process
|
|
|
66
73
|
* Throws when the candidates can't be fetched: an audit run without them would report no
|
|
67
74
|
* sensitive paths, and sending it would replace the machine's last good audit on the backend.
|
|
68
75
|
*/
|
|
69
|
-
export async function runSensitivePathsAudit(endpointBase, outputDir = join(homedir(), OPTIMUSLABS_LOGS_REL, LOG_GROUP_AUDIT), cwd = process.cwd()) {
|
|
70
|
-
|
|
76
|
+
export async function runSensitivePathsAudit(endpointBase, outputDir = join(homedir(), OPTIMUSLABS_LOGS_REL, LOG_GROUP_AUDIT), cwd = process.cwd(), prefetched) {
|
|
77
|
+
let response = prefetched;
|
|
78
|
+
if (response == null) {
|
|
79
|
+
response = await getSensitivePathsAuditCandidates(endpointBase);
|
|
80
|
+
}
|
|
71
81
|
if (!response) {
|
|
72
|
-
throw new
|
|
82
|
+
throw new CandidatesFetchError(endpointBase);
|
|
73
83
|
}
|
|
74
84
|
const paths = writeSensitivePathsAudit(outputDir, response.paths, cwd);
|
|
75
85
|
const outPath = join(outputDir, AUDIT_FILENAME);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pii-scrub shim: the one place that imports @optimuslabs/pii-scrub-staging.
|
|
3
|
+
*
|
|
4
|
+
* Loaded the way tofu.ts loads tofu: the sibling local dist when environment=development and it
|
|
5
|
+
* is built, the published npm package otherwise.
|
|
6
|
+
*/
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import { shouldUseLocalTofuDist } from "./tofu_environment.js";
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const localPiiScrubPath = path.join(__dirname, "../../pii-scrub/dist/index.js");
|
|
12
|
+
const piiScrub = (shouldUseLocalTofuDist(localPiiScrubPath)
|
|
13
|
+
? await import(localPiiScrubPath)
|
|
14
|
+
: await import("@optimuslabs/pii-scrub-staging"));
|
|
15
|
+
export const { SCRUBBER_VERSION, containsPlaceholder, scrubFields, scrubText, scrubValue } = piiScrub;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optimuslabs/harness-map-staging",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.32",
|
|
4
4
|
"description": "CLI helpers for logging hardware UUIDs and posting startup payloads to Optimus Security.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -49,17 +49,17 @@
|
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "^24.10.1",
|
|
52
|
-
"@vitest/coverage-v8": "^4.1.
|
|
53
|
-
"@vitest/ui": "^4.1.
|
|
52
|
+
"@vitest/coverage-v8": "^4.1.12",
|
|
53
|
+
"@vitest/ui": "^4.1.12",
|
|
54
54
|
"ts-node": "^10.9.2",
|
|
55
55
|
"typescript": "^5.4.5",
|
|
56
|
-
"vitest": "^4.1.
|
|
56
|
+
"vitest": "^4.1.12"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@optimuslabs/tofu-staging": "^0.1.18",
|
|
60
60
|
"axios": "^1.20.0",
|
|
61
61
|
"canonicalize": "^2.1.0",
|
|
62
|
-
"smol-toml": "^1.
|
|
62
|
+
"smol-toml": "^1.9.1"
|
|
63
63
|
},
|
|
64
64
|
"publishConfig": {
|
|
65
65
|
"access": "public"
|