mecord-connect 1.0.0

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.
Files changed (82) hide show
  1. package/DISCLOSURE +47 -0
  2. package/LICENSE +64 -0
  3. package/README.md +44 -0
  4. package/bin/operator.mjs +10 -0
  5. package/package.json +58 -0
  6. package/runtime/app/apps/local-agent/src/approval-store.js +342 -0
  7. package/runtime/app/apps/local-agent/src/bootstrap-config.js +255 -0
  8. package/runtime/app/apps/local-agent/src/cli.js +237 -0
  9. package/runtime/app/apps/local-agent/src/device-reset.js +260 -0
  10. package/runtime/app/apps/local-agent/src/emergency-stop.js +141 -0
  11. package/runtime/app/apps/local-agent/src/main.js +311 -0
  12. package/runtime/app/apps/local-agent/src/privacy-data.js +129 -0
  13. package/runtime/app/apps/local-agent/src/relay-agent.js +333 -0
  14. package/runtime/app/apps/local-agent/src/relay-enrollment.js +156 -0
  15. package/runtime/app/apps/local-agent/src/relay-session-credentials.js +436 -0
  16. package/runtime/app/apps/local-agent/src/remote.js +130 -0
  17. package/runtime/app/apps/local-agent/src/runtime-factory.js +66 -0
  18. package/runtime/app/apps/local-agent/src/server.js +578 -0
  19. package/runtime/app/package.json +4 -0
  20. package/runtime/app/src/capabilities/browser-cdp-connection.js +199 -0
  21. package/runtime/app/src/capabilities/browser-cdp-frames.js +288 -0
  22. package/runtime/app/src/capabilities/browser-cdp-page.js +430 -0
  23. package/runtime/app/src/capabilities/browser-cdp.js +384 -0
  24. package/runtime/app/src/capabilities/browser-managed.js +345 -0
  25. package/runtime/app/src/capabilities/docker.js +448 -0
  26. package/runtime/app/src/capabilities/filesystem.js +264 -0
  27. package/runtime/app/src/capabilities/git-checkpoint.js +415 -0
  28. package/runtime/app/src/capabilities/git-write.js +321 -0
  29. package/runtime/app/src/capabilities/git.js +252 -0
  30. package/runtime/app/src/capabilities/index.js +16 -0
  31. package/runtime/app/src/capabilities/path-scope.js +86 -0
  32. package/runtime/app/src/capabilities/postgres.js +566 -0
  33. package/runtime/app/src/capabilities/process.js +258 -0
  34. package/runtime/app/src/capabilities/project-command.js +599 -0
  35. package/runtime/app/src/capabilities/project-transaction.js +215 -0
  36. package/runtime/app/src/capabilities/project.js +67 -0
  37. package/runtime/app/src/capabilities/system.js +40 -0
  38. package/runtime/app/src/capabilities/vscode.js +312 -0
  39. package/runtime/app/src/capabilities/windows-uia.js +331 -0
  40. package/runtime/app/src/core/account-device-registry.js +604 -0
  41. package/runtime/app/src/core/action-identity.js +44 -0
  42. package/runtime/app/src/core/audit.js +295 -0
  43. package/runtime/app/src/core/capability-policy.js +58 -0
  44. package/runtime/app/src/core/child-environment.js +29 -0
  45. package/runtime/app/src/core/device-enrollment.js +417 -0
  46. package/runtime/app/src/core/device-identity.js +436 -0
  47. package/runtime/app/src/core/device-registry.js +477 -0
  48. package/runtime/app/src/core/device-reset.js +178 -0
  49. package/runtime/app/src/core/device-routing.js +305 -0
  50. package/runtime/app/src/core/durable-state.js +306 -0
  51. package/runtime/app/src/core/errors.js +20 -0
  52. package/runtime/app/src/core/evidence.js +9 -0
  53. package/runtime/app/src/core/index.js +17 -0
  54. package/runtime/app/src/core/machine-state.js +160 -0
  55. package/runtime/app/src/core/network-authority.js +20 -0
  56. package/runtime/app/src/core/policy.js +67 -0
  57. package/runtime/app/src/core/product-identity.js +7 -0
  58. package/runtime/app/src/core/provenance.js +18 -0
  59. package/runtime/app/src/core/provider-learning.js +274 -0
  60. package/runtime/app/src/core/public-plugin-surface.js +25 -0
  61. package/runtime/app/src/core/public-restricted-data.js +105 -0
  62. package/runtime/app/src/core/rate-limit.js +118 -0
  63. package/runtime/app/src/core/relay-client.js +568 -0
  64. package/runtime/app/src/core/relay-delivery-store.js +512 -0
  65. package/runtime/app/src/core/relay-result-store.js +321 -0
  66. package/runtime/app/src/core/release-update.js +253 -0
  67. package/runtime/app/src/core/router.js +96 -0
  68. package/runtime/app/src/core/runtime.js +136 -0
  69. package/runtime/app/src/core/scoped-path-syntax.js +16 -0
  70. package/runtime/app/src/core/session-token.js +498 -0
  71. package/runtime/app/src/core/task-orchestrator.js +1148 -0
  72. package/runtime/app/src/core/task-store.js +517 -0
  73. package/runtime/app/src/core/task.js +142 -0
  74. package/runtime/app/src/core/trusted-executable.js +124 -0
  75. package/runtime/app/src/core/types.js +83 -0
  76. package/runtime/app/src/core/windows-path-lease.js +138 -0
  77. package/runtime/native/operator-windows-dpapi.exe +0 -0
  78. package/runtime/native/operator-windows-path-lease.exe +0 -0
  79. package/runtime/native/operator-windows-uia.exe +0 -0
  80. package/runtime/runtime-manifest.json +380 -0
  81. package/src/cli.mjs +345 -0
  82. package/src/publish-check.mjs +22 -0
@@ -0,0 +1,105 @@
1
+ import path from 'node:path';
2
+ import { OperatorError } from './errors.js';
3
+
4
+ const SENSITIVE_BASENAMES = [
5
+ /^\.env(?:\..+)?$/i,
6
+ /^\.npmrc$/i,
7
+ /^\.netrc$/i,
8
+ /^credentials(?:\..+)?$/i,
9
+ /^service[-_.]?account.*\.json$/i,
10
+ /^id_(?:rsa|dsa|ecdsa|ed25519)(?:\.pub)?$/i,
11
+ /^authorized_keys$/i,
12
+ /^kubeconfig$/i,
13
+ /^wallet(?:\..+)?$/i,
14
+ /^.*\.(?:pem|key|p12|pfx|kdbx)$/i
15
+ ];
16
+
17
+ const SENSITIVE_SEGMENTS = new Set([
18
+ '.ssh', '.aws', '.azure', '.gnupg', '.docker',
19
+ 'credentials', 'secrets', 'passwords'
20
+ ]);
21
+
22
+ const SECRET_PATTERNS = [
23
+ /-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/i,
24
+ /\bAKIA[0-9A-Z]{16}\b/,
25
+ /\b(?:ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]{20,}\b/,
26
+ /\bglpat-[A-Za-z0-9_-]{20,}\b/,
27
+ /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/,
28
+ /\bAIza[0-9A-Za-z_-]{30,}\b/,
29
+ /\bsk_(?:live|test)_[0-9A-Za-z]{16,}\b/,
30
+ /\bsk-[A-Za-z0-9_-]{20,}\b/,
31
+ /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/,
32
+ /\b(?:authorization|proxy-authorization)\s*:\s*(?:bearer|basic)\s+[A-Za-z0-9._~+/=-]{8,}/i,
33
+ /\b(?:otp|one[- ]time(?: password| code)?|verification code|recovery code)\s*[:=]\s*[A-Za-z0-9-]{4,20}\b/i,
34
+ /\b(?:ssn|social security(?: number)?|aadhaar|aadhar|passport(?: number)?|driver'?s? license(?: number)?)\s*[:=]\s*[A-Za-z0-9 -]{5,32}\b/i,
35
+ /\b(?:medical record(?: number)?|mrn|diagnosis|patient(?: name| id)|health insurance(?: number)?)\s*[:=]\s*[^\r\n]{2,96}/i,
36
+ /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|mssql|redis):\/\/[^\s:@/]+:[^\s@/]+@[^\s"']+/i,
37
+ /\b(?:DATABASE_URL|REDIS_URL|MONGODB_URI)\s*[:=]\s*["']?[^\s"']+:[^\s"']+@[^\s"']+/i,
38
+ /\b(?:API[_-]?KEY|APIKEY|ACCESS[_-]?KEY(?:_ID)?|SECRET[_-]?KEY|CLIENT[_-]?SECRET|ACCESS[_-]?TOKEN|REFRESH[_-]?TOKEN|AUTH[_-]?TOKEN|_AUTH(?:TOKEN)?|NPM_TOKEN)\s*[:=]\s*["']?[^\s"']{8,}["']?/i,
39
+ /\bAWS_SECRET_ACCESS_KEY\s*[:=]\s*["']?[A-Za-z0-9/+=]{20,}["']?/i,
40
+ /\b(?:PASSWORD|PASSWD|PWD)\s*[:=]\s*["']?[^\s"';,}{]{4,}["']?/i,
41
+ /\b(?:AccountKey|SharedAccessKey)\s*=\s*[A-Za-z0-9+/=]{16,}/i,
42
+ /\b(?:session|sessionid|session_id|auth_cookie|cookie)\s*[:=]\s*["']?[A-Za-z0-9._~+%/=-]{12,}["']?/i
43
+ ];
44
+ const SENSITIVE_KEYS = /(?:password|passwd|pwd|secret|token|api.?key|access.?key|client.?secret|authorization|private.?key|otp|cvv|card.?number|ssn|social.?security|aadhaar|aadhar|passport|driver.?license|medical.?record|mrn|diagnosis|patient.?name|patient.?id|health.?insurance|account.?key|cookie|session)/i;
45
+
46
+ export function assertPublicSafePath(input ) {
47
+ const normalized = String(input ?? '').replace(/\\/g, '/');
48
+ const segments = normalized.split('/').filter(Boolean);
49
+ const basename = segments.at(-1) ?? '';
50
+ if (SENSITIVE_BASENAMES.some((pattern) => pattern.test(basename))) {
51
+ throw new OperatorError('RESTRICTED_DATA_PATH_DENIED', 'The public plugin cannot access credential or secret-bearing files.');
52
+ }
53
+ if (segments.some((segment) => SENSITIVE_SEGMENTS.has(segment.toLowerCase()))) {
54
+ throw new OperatorError('RESTRICTED_DATA_PATH_DENIED', 'The public plugin cannot access credential or secret-bearing directories.');
55
+ }
56
+ }
57
+
58
+ export function containsRestrictedData(value , keyHint = '') {
59
+ if (value === null || value === undefined) return false;
60
+ if (typeof value === 'string') {
61
+ if (keyHint && SENSITIVE_KEYS.test(keyHint) && value.length > 0) return true;
62
+ if (SECRET_PATTERNS.some((pattern) => pattern.test(value))) return true;
63
+ return containsPaymentCard(value);
64
+ }
65
+ if (Array.isArray(value)) return value.some((item) => containsRestrictedData(item, keyHint));
66
+ if (typeof value !== 'object') return false;
67
+ return Object.entries(value )
68
+ .some(([key, item]) => containsRestrictedData(item, key));
69
+ }
70
+ export function assertNoRestrictedData(value ) {
71
+ if (!containsRestrictedData(value)) return;
72
+ throw new OperatorError(
73
+ 'RESTRICTED_DATA_BLOCKED',
74
+ 'The public plugin refused content that may contain credentials, payment data, authentication codes, or other restricted data.'
75
+ );
76
+ }
77
+
78
+ export function isSensitiveEntryName(name ) {
79
+ const base = path.basename(String(name ?? ''));
80
+ return SENSITIVE_BASENAMES.some((pattern) => pattern.test(base))
81
+ || SENSITIVE_SEGMENTS.has(base.toLowerCase());
82
+ }
83
+
84
+ function containsPaymentCard(text ) {
85
+ const candidates = text.match(/(?:\d[ -]?){13,19}/g) ?? [];
86
+ return candidates.some((candidate) => {
87
+ const digits = candidate.replace(/\D/g, '');
88
+ return digits.length >= 13 && digits.length <= 19 && luhn(digits);
89
+ });
90
+ }
91
+
92
+ function luhn(digits ) {
93
+ let sum = 0;
94
+ let double = false;
95
+ for (let index = digits.length - 1; index >= 0; index -= 1) {
96
+ let value = Number(digits[index]);
97
+ if (double) {
98
+ value *= 2;
99
+ if (value > 9) value -= 9;
100
+ }
101
+ sum += value;
102
+ double = !double;
103
+ }
104
+ return sum % 10 === 0;
105
+ }
@@ -0,0 +1,118 @@
1
+ import crypto from 'node:crypto';
2
+ import { isIP } from 'node:net';
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+ export class FixedWindowRateLimiter {
14
+ #limit ;
15
+ #windowMs ;
16
+ #maxKeys ;
17
+ #clock ;
18
+ #entries = new Map ();
19
+
20
+ constructor(options ) {
21
+ if (!Number.isInteger(options.limit) || options.limit < 1) throw new Error('Rate limit must be a positive integer.');
22
+ if (!Number.isInteger(options.windowMs) || options.windowMs < 1000) throw new Error('Rate-limit window must be at least 1000ms.');
23
+ this.#limit = options.limit;
24
+ this.#windowMs = options.windowMs;
25
+ this.#maxKeys = options.maxKeys ?? 20_000;
26
+ if (!Number.isInteger(this.#maxKeys) || this.#maxKeys < 1 || this.#maxKeys > 1_000_000) throw new Error('Rate-limit maxKeys is invalid.');
27
+ this.#clock = options.clock ?? Date.now;
28
+ }
29
+
30
+ hit(keyInput ) {
31
+ const key = boundedKey(keyInput);
32
+ const now = this.#clock();
33
+ let entry = this.#entries.get(key);
34
+ if (entry && now - entry.windowStartedAt >= this.#windowMs) {
35
+ this.#entries.delete(key);
36
+ entry = undefined;
37
+ }
38
+ if (!entry) {
39
+ this.#evictForInsert();
40
+ entry = { count: 0, windowStartedAt: now };
41
+ }
42
+ entry.count += 1;
43
+ this.#touch(key, entry);
44
+ const remaining = Math.max(0, this.#limit - entry.count);
45
+ const retryAfterSeconds = Math.max(1, Math.ceil((entry.windowStartedAt + this.#windowMs - now) / 1000));
46
+ return { allowed: entry.count <= this.#limit, remaining, retryAfterSeconds };
47
+ }
48
+
49
+ isLimited(keyInput ) {
50
+ const key = boundedKey(keyInput);
51
+ const now = this.#clock();
52
+ const entry = this.#entries.get(key);
53
+ if (!entry || now - entry.windowStartedAt >= this.#windowMs) {
54
+ if (entry) this.#entries.delete(key);
55
+ return { allowed: true, remaining: this.#limit, retryAfterSeconds: 1 };
56
+ }
57
+ this.#touch(key, entry);
58
+ return {
59
+ allowed: entry.count < this.#limit,
60
+ remaining: Math.max(0, this.#limit - entry.count),
61
+ retryAfterSeconds: Math.max(1, Math.ceil((entry.windowStartedAt + this.#windowMs - now) / 1000))
62
+ };
63
+ }
64
+
65
+ clear(keyInput ) {
66
+ this.#entries.delete(boundedKey(keyInput));
67
+ }
68
+
69
+ get size() { return this.#entries.size; }
70
+
71
+ #touch(key , entry ) {
72
+ this.#entries.delete(key);
73
+ this.#entries.set(key, entry);
74
+ }
75
+
76
+ #evictForInsert() {
77
+ if (this.#entries.size < this.#maxKeys) return;
78
+ const oldest = this.#entries.keys().next().value ;
79
+ if (oldest !== undefined) this.#entries.delete(oldest);
80
+ }
81
+ }
82
+
83
+ export function requestClientKey(request ) {
84
+ const peer = normalizeIp(request.socket.remoteAddress ?? '');
85
+ const forwarded = request.headers['x-forwarded-for'];
86
+ if (isLoopback(peer) && typeof forwarded === 'string') {
87
+ const first = normalizeIp(forwarded.split(',')[0]?.trim() ?? '');
88
+ if (isIP(first)) return `ip:${first}`;
89
+ }
90
+ return `ip:${peer || 'unknown'}`;
91
+ }
92
+
93
+ export function principalRateKey(issuer , subject ) {
94
+ return `principal:${crypto.createHash('sha256').update(JSON.stringify({ issuer, subject }), 'utf8').digest('base64url')}`;
95
+ }
96
+
97
+ function boundedKey(value ) {
98
+ const text = String(value ?? '');
99
+ if (!text || text.length > 512 || /[\0\r\n]/.test(text)) throw new Error('Rate-limit key is invalid.');
100
+ return text;
101
+ }
102
+
103
+ function normalizeIp(input ) {
104
+ const value = String(input ?? '').trim().toLowerCase();
105
+ if (value.startsWith('::ffff:')) return value.slice(7);
106
+ return value.replace(/^\[|\]$/g, '');
107
+ }
108
+
109
+ function isLoopback(input ) {
110
+ return input === '127.0.0.1' || input === '::1' || input === 'localhost';
111
+ }
112
+
113
+ export function envRateLimit(value , fallback , label ) {
114
+ if (value === undefined || value.trim() === '') return fallback;
115
+ const parsed = Number(value);
116
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 100_000) throw new Error(`${label} must be an integer between 1 and 100000.`);
117
+ return parsed;
118
+ }