engineering-memory 1.11.1 → 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.1",
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",
@@ -4,7 +4,9 @@ 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 emailPattern = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i;
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/;
9
11
  const repositoryPathFields = new Set([
10
12
  'path',
@@ -13,21 +15,128 @@ const repositoryPathFields = new Set([
13
15
  'changedPaths',
14
16
  'pathPatterns',
15
17
  'evidencePaths',
18
+ 'screenPathPatterns',
19
+ 'componentPathPatterns',
20
+ 'pathChanges',
21
+ 'templatePath',
16
22
  ]);
17
- const iosAssetScalePathPattern = /^(?:[\p{L}\p{N}_. -]+\/)*[\p{L}\p{N}_. -]+\.xcassets\/[\p{L}\p{N}_. -]+\.(?:appiconset|imageset)\/[\p{L}\p{N}_. -]+@[123]x\.(?:png|jpe?g)$/iu;
18
- function emailScanValue(value, key) {
19
- if (!repositoryPathFields.has(key) ||
20
- !iosAssetScalePathPattern.test(value) ||
21
- value.split('/').some((part) => part === '.' || part === '..'))
22
- return value;
23
- return value.replace(/@[123]x(?=\.(?:png|jpe?g)$)/i, '');
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;
24
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;
25
134
  const bearerPattern = /\bbearer\s+[a-z0-9._~-]+/i;
26
135
  const jwtPattern = /\beyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\b/;
27
136
  const privateKeyPattern = /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/;
28
137
  const credentialUrlPattern = /\b[a-z][a-z0-9+.-]*:\/\/[^\s/@:]+:[^\s/@]+@/i;
29
138
  const ipv4Pattern = /\b(?:\d{1,3}\.){3}\d{1,3}\b/;
30
- const personalHomePathPattern = /(?:^|[\s"'(])(?:[A-Z]:[\\/]+Users[\\/]+[^\\/\s"']+(?:[\\/]|(?=$|\s))|\/(?:Users|home)\/[^/\s"']+(?:\/|(?=$|\s)))/i;
139
+ const personalHomePathPattern = /(?:[A-Z]:[\\/](?:Users|Documents and Settings)[\\/][^\\/\s"'<>:|?*]+|(?:^|[\s"'(<=])\/(?:Users|home)\/[^/\s"'<>*]+)(?:[\\/]|$)/i;
31
140
  export class OfflineOutbox {
32
141
  root;
33
142
  queues = new Map();
@@ -171,15 +280,17 @@ export function assertSafeToPersist(value, key = '') {
171
280
  throw new Error(`Sensitive field cannot be persisted: ${key}`);
172
281
  }
173
282
  if (typeof value === 'string') {
174
- if (emailPattern.test(emailScanValue(value, key)) ||
283
+ const repositoryPath = repositoryPathFields.has(key);
284
+ if (hasEmailAddress(value, repositoryPath) ||
175
285
  phonePattern.test(value) ||
176
286
  bearerPattern.test(value) ||
177
287
  jwtPattern.test(value) ||
178
288
  privateKeyPattern.test(value) ||
289
+ knownCredentialPattern.test(value) ||
179
290
  credentialUrlPattern.test(value) ||
180
- ipv4Pattern.test(value) ||
181
- personalHomePathPattern.test(value)) {
182
- throw new Error('PII or credentials cannot be persisted');
291
+ personalHomePathPattern.test(value) ||
292
+ (!repositoryPath && ipv4Pattern.test(value))) {
293
+ throw new Error(`PII or credentials cannot be persisted: ${key} ${value}`);
183
294
  }
184
295
  return;
185
296
  }