engineering-memory 1.11.0 → 1.11.2
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "engineering-memory",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.2",
|
|
4
4
|
"description": "Installs the Engineering Memory skill and its local MCP bridge. Sign in after installing; your organization and project are resolved from your account.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -1019,16 +1019,26 @@ export class BridgeService {
|
|
|
1019
1019
|
if (!run || typeof run.id !== 'string')
|
|
1020
1020
|
throw refuse('The inspection response has no run identity.', 'memory.sync_status');
|
|
1021
1021
|
if (run.state === 'inventory' || run.state === 'planning') {
|
|
1022
|
-
for (let offset = 0; offset < snapshot.files.length;
|
|
1023
|
-
const page =
|
|
1022
|
+
for (let offset = 0; offset < snapshot.files.length;) {
|
|
1023
|
+
const page = {
|
|
1024
1024
|
projectId: input.projectId,
|
|
1025
1025
|
runId: run.id,
|
|
1026
|
-
files:
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1026
|
+
files: [],
|
|
1027
|
+
};
|
|
1028
|
+
let bytes = Buffer.byteLength(JSON.stringify(page));
|
|
1029
|
+
while (offset < snapshot.files.length && page.files.length < 1000) {
|
|
1030
|
+
const file = snapshot.files[offset];
|
|
1031
|
+
const addedBytes = Buffer.byteLength(JSON.stringify(file)) + (page.files.length ? 1 : 0);
|
|
1032
|
+
if (page.files.length && bytes + addedBytes > 64 * 1024)
|
|
1033
|
+
break;
|
|
1034
|
+
page.files.push(file);
|
|
1035
|
+
bytes += addedBytes;
|
|
1036
|
+
offset += 1;
|
|
1037
|
+
}
|
|
1038
|
+
assertSafeToPersist(cleanJson(page));
|
|
1029
1039
|
await this.dependencies.client.request('/memory/sync/inventory', {
|
|
1030
1040
|
method: 'POST',
|
|
1031
|
-
body: page,
|
|
1041
|
+
body: cleanJson(page),
|
|
1032
1042
|
});
|
|
1033
1043
|
}
|
|
1034
1044
|
await this.dependencies.client.request('/memory/sync/inventory', {
|
|
@@ -4,14 +4,139 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { assertManagedPath, ensureManagedDirectory, readJson, removeFile, writeJson, } from '../utilities/files.js';
|
|
5
5
|
import { sha256, stableStringify } from '../utilities/hash.js';
|
|
6
6
|
const sensitiveKeyPattern = /(authorization|password|passcode|secret|token|cookie|email|phone|mobile|customer|account|user.?id|user.?name)/i;
|
|
7
|
-
const
|
|
7
|
+
const emailSource = '(?:^|[^\\p{L}\\p{N}._%+-])[\\p{L}\\p{N}._%+-]{1,64}@([\\p{L}\\p{N}.-]+\\.[\\p{L}]{2,63})(?=$|[^\\p{L}\\p{N}.-])';
|
|
8
|
+
const emailPattern = new RegExp(emailSource, 'iu');
|
|
9
|
+
const emailMatchPattern = new RegExp(emailSource, 'giu');
|
|
8
10
|
const phonePattern = /\+\d[\d ()-]{7,}\d/;
|
|
11
|
+
const repositoryPathFields = new Set([
|
|
12
|
+
'path',
|
|
13
|
+
'paths',
|
|
14
|
+
'originalPath',
|
|
15
|
+
'changedPaths',
|
|
16
|
+
'pathPatterns',
|
|
17
|
+
'evidencePaths',
|
|
18
|
+
'screenPathPatterns',
|
|
19
|
+
'componentPathPatterns',
|
|
20
|
+
'pathChanges',
|
|
21
|
+
'templatePath',
|
|
22
|
+
]);
|
|
23
|
+
const topLevelDomains = new Set([
|
|
24
|
+
'com',
|
|
25
|
+
'net',
|
|
26
|
+
'org',
|
|
27
|
+
'edu',
|
|
28
|
+
'gov',
|
|
29
|
+
'mil',
|
|
30
|
+
'int',
|
|
31
|
+
'info',
|
|
32
|
+
'biz',
|
|
33
|
+
'name',
|
|
34
|
+
'pro',
|
|
35
|
+
'mobi',
|
|
36
|
+
'tel',
|
|
37
|
+
'asia',
|
|
38
|
+
'jobs',
|
|
39
|
+
'coop',
|
|
40
|
+
'aero',
|
|
41
|
+
'museum',
|
|
42
|
+
'travel',
|
|
43
|
+
'app',
|
|
44
|
+
'dev',
|
|
45
|
+
'cloud',
|
|
46
|
+
'online',
|
|
47
|
+
'site',
|
|
48
|
+
'store',
|
|
49
|
+
'tech',
|
|
50
|
+
'space',
|
|
51
|
+
'website',
|
|
52
|
+
'digital',
|
|
53
|
+
'agency',
|
|
54
|
+
'systems',
|
|
55
|
+
'solutions',
|
|
56
|
+
'email',
|
|
57
|
+
'xyz',
|
|
58
|
+
'top',
|
|
59
|
+
'club',
|
|
60
|
+
'live',
|
|
61
|
+
'life',
|
|
62
|
+
'world',
|
|
63
|
+
'today',
|
|
64
|
+
'network',
|
|
65
|
+
'group',
|
|
66
|
+
'company',
|
|
67
|
+
'center',
|
|
68
|
+
'media',
|
|
69
|
+
'studio',
|
|
70
|
+
'design',
|
|
71
|
+
'software',
|
|
72
|
+
'tools',
|
|
73
|
+
'works',
|
|
74
|
+
'team',
|
|
75
|
+
'zone',
|
|
76
|
+
'one',
|
|
77
|
+
'ltd',
|
|
78
|
+
'inc',
|
|
79
|
+
'llc',
|
|
80
|
+
'gmbh',
|
|
81
|
+
'shop',
|
|
82
|
+
'page',
|
|
83
|
+
'blog',
|
|
84
|
+
'wiki',
|
|
85
|
+
'news',
|
|
86
|
+
'art',
|
|
87
|
+
'link',
|
|
88
|
+
'host',
|
|
89
|
+
'press',
|
|
90
|
+
'fyi',
|
|
91
|
+
'test',
|
|
92
|
+
'example',
|
|
93
|
+
'invalid',
|
|
94
|
+
'local',
|
|
95
|
+
'localhost',
|
|
96
|
+
'internal',
|
|
97
|
+
]);
|
|
98
|
+
const countryCodeTopLevelDomains = new Set('ac ad ae af ag ai al am ao aq ar as at au aw ax az ba bb bd be bf bg bh bi bj bm bn bo br bs bt bw by bz ca cc cd cf cg ch ci ck cl cm cn co cr cu cv cw cx cy cz de dj dk dm do dz ec ee eg er es et eu fi fj fk fm fo fr ga gd ge gf gg gh gi gl gm gn gp gq gr gs gt gu gw gy hk hm hn hr ht hu id ie il im in io iq ir is it je jm jo jp ke kg kh ki km kn kp kr kw ky kz la lb lc li lk lr ls lt lu lv ly ma mc md me mg mh mk ml mm mn mo mp mq mr ms mt mu mv mw mx my mz na nc ne nf ng ni nl no np nr nu nz om pa pe pf pg ph pk pl pm pn pr ps pt pw py qa re ro rs ru rw sa sb sc sd se sg sh si sk sl sm sn so sr ss st su sv sx sy sz tc td tf tg th tj tk tl tm tn to tr tt tv tw tz ua ug uk us uy uz va vc ve vg vi vn vu wf ws ye yt za zm zw'.split(' '));
|
|
99
|
+
function isTopLevelDomain(label) {
|
|
100
|
+
const value = label.toLowerCase();
|
|
101
|
+
return countryCodeTopLevelDomains.has(value) || topLevelDomains.has(value);
|
|
102
|
+
}
|
|
103
|
+
const extensionTopLevelDomains = new Set([
|
|
104
|
+
'ai',
|
|
105
|
+
'app',
|
|
106
|
+
'as',
|
|
107
|
+
'cc',
|
|
108
|
+
'md',
|
|
109
|
+
'ml',
|
|
110
|
+
'mm',
|
|
111
|
+
'pl',
|
|
112
|
+
'py',
|
|
113
|
+
'rs',
|
|
114
|
+
'sh',
|
|
115
|
+
'so',
|
|
116
|
+
]);
|
|
117
|
+
function hasEmailAddress(value, repositoryPath) {
|
|
118
|
+
if (!repositoryPath)
|
|
119
|
+
return emailPattern.test(value);
|
|
120
|
+
for (const match of value.matchAll(emailMatchPattern)) {
|
|
121
|
+
const labels = (match[1] ?? '').split('.');
|
|
122
|
+
const last = (labels[labels.length - 1] ?? '').toLowerCase();
|
|
123
|
+
if (isTopLevelDomain(last) && !extensionTopLevelDomains.has(last))
|
|
124
|
+
return true;
|
|
125
|
+
const beforeExtension = (labels[labels.length - 2] ?? '').toLowerCase();
|
|
126
|
+
if (labels.length >= 3 &&
|
|
127
|
+
topLevelDomains.has(beforeExtension) &&
|
|
128
|
+
!extensionTopLevelDomains.has(beforeExtension))
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
const knownCredentialPattern = /\b(?:A(?:KI|SI)A[0-9A-Z]{16}|gh[pousr]_[A-Z0-9]{30,}|github_pat_[A-Z0-9_]{30,}|sk_live_[A-Z0-9]{16,}|rk_live_[A-Z0-9]{16,}|xox[baprs]-[A-Z0-9-]{12,}|AIza[A-Z0-9_-]{35}|sk-(?:proj-)?[A-Z0-9_-]{20,})\b/i;
|
|
9
134
|
const bearerPattern = /\bbearer\s+[a-z0-9._~-]+/i;
|
|
10
135
|
const jwtPattern = /\beyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\b/;
|
|
11
136
|
const privateKeyPattern = /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/;
|
|
12
137
|
const credentialUrlPattern = /\b[a-z][a-z0-9+.-]*:\/\/[^\s/@:]+:[^\s/@]+@/i;
|
|
13
138
|
const ipv4Pattern = /\b(?:\d{1,3}\.){3}\d{1,3}\b/;
|
|
14
|
-
const personalHomePathPattern = /(
|
|
139
|
+
const personalHomePathPattern = /(?:[A-Z]:[\\/](?:Users|Documents and Settings)[\\/][^\\/\s"'<>:|?*]+|(?:^|[\s"'(<=])\/(?:Users|home)\/[^/\s"'<>*]+)(?:[\\/]|$)/i;
|
|
15
140
|
export class OfflineOutbox {
|
|
16
141
|
root;
|
|
17
142
|
queues = new Map();
|
|
@@ -155,15 +280,17 @@ export function assertSafeToPersist(value, key = '') {
|
|
|
155
280
|
throw new Error(`Sensitive field cannot be persisted: ${key}`);
|
|
156
281
|
}
|
|
157
282
|
if (typeof value === 'string') {
|
|
158
|
-
|
|
283
|
+
const repositoryPath = repositoryPathFields.has(key);
|
|
284
|
+
if (hasEmailAddress(value, repositoryPath) ||
|
|
159
285
|
phonePattern.test(value) ||
|
|
160
286
|
bearerPattern.test(value) ||
|
|
161
287
|
jwtPattern.test(value) ||
|
|
162
288
|
privateKeyPattern.test(value) ||
|
|
289
|
+
knownCredentialPattern.test(value) ||
|
|
163
290
|
credentialUrlPattern.test(value) ||
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
throw new Error(
|
|
291
|
+
personalHomePathPattern.test(value) ||
|
|
292
|
+
(!repositoryPath && ipv4Pattern.test(value))) {
|
|
293
|
+
throw new Error(`PII or credentials cannot be persisted: ${key} ${value}`);
|
|
167
294
|
}
|
|
168
295
|
return;
|
|
169
296
|
}
|
|
@@ -199,13 +326,13 @@ export function redactForCache(value) {
|
|
|
199
326
|
return null;
|
|
200
327
|
}
|
|
201
328
|
}
|
|
202
|
-
function redactValue(value) {
|
|
329
|
+
function redactValue(value, key = '') {
|
|
203
330
|
if (typeof value === 'string') {
|
|
204
|
-
assertSafeToPersist(value);
|
|
331
|
+
assertSafeToPersist(value, key);
|
|
205
332
|
return value;
|
|
206
333
|
}
|
|
207
334
|
if (Array.isArray(value)) {
|
|
208
|
-
return value.map((entry) => redactValue(entry));
|
|
335
|
+
return value.map((entry) => redactValue(entry, key));
|
|
209
336
|
}
|
|
210
337
|
if (value !== null && typeof value === 'object') {
|
|
211
338
|
const result = {};
|
|
@@ -213,7 +340,7 @@ function redactValue(value) {
|
|
|
213
340
|
if (sensitiveKeyPattern.test(key)) {
|
|
214
341
|
continue;
|
|
215
342
|
}
|
|
216
|
-
result[key] = redactValue(entry);
|
|
343
|
+
result[key] = redactValue(entry, key);
|
|
217
344
|
}
|
|
218
345
|
return result;
|
|
219
346
|
}
|