kiro-mobile-bridge 1.0.7 → 1.0.10

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.
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Hash utilities for content change detection
3
+ *
4
+ * NOTE: MD5 is used here for change detection only, NOT for security purposes.
5
+ * MD5 is fast and sufficient for detecting content changes in snapshots.
6
+ * Do NOT use these functions for password hashing, authentication, or any security-sensitive operations.
7
+ */
8
+ import crypto from 'crypto';
9
+
10
+ /**
11
+ * Generate a unique ID from a string (e.g., WebSocket URL)
12
+ * Used for cascade identification, not security
13
+ * @param {string} input - String to hash
14
+ * @returns {string} - 8-character hash ID
15
+ */
16
+ export function generateId(input) {
17
+ if (typeof input !== 'string' || !input) {
18
+ return crypto.randomBytes(4).toString('hex');
19
+ }
20
+ return crypto.createHash('md5').update(input).digest('hex').substring(0, 8);
21
+ }
22
+
23
+ /**
24
+ * Compute MD5 hash for change detection
25
+ * Used to detect content changes in snapshots, not for security
26
+ * @param {string} content - Content to hash
27
+ * @returns {string} - Full MD5 hash
28
+ */
29
+ export function computeHash(content) {
30
+ if (typeof content !== 'string') {
31
+ return '';
32
+ }
33
+ return crypto.createHash('md5').update(content).digest('hex');
34
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Network utilities
3
+ */
4
+ import { networkInterfaces } from 'os';
5
+
6
+ /**
7
+ * Get local IP address for LAN access
8
+ * Returns the first non-internal IPv4 address found.
9
+ *
10
+ * NOTE: On systems with multiple network interfaces, this returns the first one found.
11
+ * For more control, consider using environment variables or configuration.
12
+ *
13
+ * @returns {string} - Local IP or 'localhost' if no suitable interface found
14
+ */
15
+ export function getLocalIP() {
16
+ const interfaces = networkInterfaces();
17
+
18
+ // Prioritize common interface names
19
+ const priorityInterfaces = ['eth0', 'en0', 'wlan0', 'Wi-Fi', 'Ethernet'];
20
+
21
+ // First, try priority interfaces
22
+ for (const name of priorityInterfaces) {
23
+ const ifaces = interfaces[name];
24
+ if (ifaces) {
25
+ for (const iface of ifaces) {
26
+ if (iface.family === 'IPv4' && !iface.internal) {
27
+ return iface.address;
28
+ }
29
+ }
30
+ }
31
+ }
32
+
33
+ // Fallback: return first non-internal IPv4
34
+ for (const name of Object.keys(interfaces)) {
35
+ for (const iface of interfaces[name]) {
36
+ if (iface.family === 'IPv4' && !iface.internal) {
37
+ return iface.address;
38
+ }
39
+ }
40
+ }
41
+
42
+ return 'localhost';
43
+ }
44
+
45
+ /**
46
+ * Get all available local IP addresses
47
+ * Useful for debugging or when user needs to choose interface
48
+ *
49
+ * @returns {Array<{name: string, address: string}>} - Array of interface names and addresses
50
+ */
51
+ export function getAllLocalIPs() {
52
+ const interfaces = networkInterfaces();
53
+ const results = [];
54
+
55
+ for (const name of Object.keys(interfaces)) {
56
+ for (const iface of interfaces[name]) {
57
+ if (iface.family === 'IPv4' && !iface.internal) {
58
+ results.push({ name, address: iface.address });
59
+ }
60
+ }
61
+ }
62
+
63
+ return results;
64
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Security utilities for input validation and sanitization
3
+ * Prevents path traversal, XSS, and other security vulnerabilities
4
+ */
5
+ import path from 'path';
6
+
7
+ /**
8
+ * Validate that a file path resolves within an allowed root directory
9
+ * Prevents path traversal attacks (e.g., ../../etc/passwd)
10
+ *
11
+ * @param {string} filePath - The file path to validate (can be relative or absolute)
12
+ * @param {string} rootDir - The allowed root directory
13
+ * @returns {{valid: boolean, resolvedPath: string|null, error: string|null}}
14
+ */
15
+ export function validatePathWithinRoot(filePath, rootDir) {
16
+ if (!filePath || typeof filePath !== 'string') {
17
+ return { valid: false, resolvedPath: null, error: 'Invalid file path' };
18
+ }
19
+
20
+ if (!rootDir || typeof rootDir !== 'string') {
21
+ return { valid: false, resolvedPath: null, error: 'Invalid root directory' };
22
+ }
23
+
24
+ try {
25
+ // Normalize and resolve both paths to absolute paths
26
+ const normalizedRoot = path.resolve(rootDir);
27
+ const resolvedPath = path.resolve(rootDir, filePath);
28
+
29
+ // Ensure the resolved path starts with the root directory
30
+ // Add path.sep to prevent matching partial directory names
31
+ // e.g., /home/user vs /home/username
32
+ const rootWithSep = normalizedRoot.endsWith(path.sep)
33
+ ? normalizedRoot
34
+ : normalizedRoot + path.sep;
35
+
36
+ if (!resolvedPath.startsWith(rootWithSep) && resolvedPath !== normalizedRoot) {
37
+ return {
38
+ valid: false,
39
+ resolvedPath: null,
40
+ error: 'Path traversal detected: path resolves outside allowed directory'
41
+ };
42
+ }
43
+
44
+ return { valid: true, resolvedPath, error: null };
45
+ } catch (err) {
46
+ return { valid: false, resolvedPath: null, error: `Path validation error: ${err.message}` };
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Escape a string for safe inclusion in JavaScript code
52
+ * Handles all special characters that could break string literals or enable injection
53
+ *
54
+ * @param {string} str - The string to escape
55
+ * @returns {string} - Escaped string safe for JS inclusion
56
+ */
57
+ export function escapeForJavaScript(str) {
58
+ if (typeof str !== 'string') {
59
+ return '';
60
+ }
61
+
62
+ return str
63
+ .replace(/\\/g, '\\\\') // Backslashes first (must be first!)
64
+ .replace(/'/g, "\\'") // Single quotes
65
+ .replace(/"/g, '\\"') // Double quotes
66
+ .replace(/`/g, '\\`') // Backticks (template literals)
67
+ .replace(/\$/g, '\\$') // Dollar signs (template literal interpolation)
68
+ .replace(/\n/g, '\\n') // Newlines
69
+ .replace(/\r/g, '\\r') // Carriage returns
70
+ .replace(/\t/g, '\\t') // Tabs
71
+ .replace(/\0/g, '\\0') // Null bytes
72
+ .replace(/\u2028/g, '\\u2028') // Line separator
73
+ .replace(/\u2029/g, '\\u2029'); // Paragraph separator
74
+ }
75
+
76
+ /**
77
+ * Validate and sanitize click info object
78
+ * Ensures all properties are of expected types and within reasonable limits
79
+ *
80
+ * @param {object} clickInfo - The click info object to validate
81
+ * @returns {{valid: boolean, sanitized: object|null, error: string|null}}
82
+ */
83
+ export function sanitizeClickInfo(clickInfo) {
84
+ if (!clickInfo || typeof clickInfo !== 'object') {
85
+ return { valid: false, sanitized: null, error: 'Click info must be an object' };
86
+ }
87
+
88
+ const sanitized = {};
89
+
90
+ // String properties with max length
91
+ const stringProps = [
92
+ { name: 'tag', maxLength: 50 },
93
+ { name: 'text', maxLength: 200 },
94
+ { name: 'ariaLabel', maxLength: 200 },
95
+ { name: 'role', maxLength: 50 },
96
+ { name: 'className', maxLength: 500 },
97
+ { name: 'tabLabel', maxLength: 100 },
98
+ { name: 'parentTabLabel', maxLength: 100 },
99
+ { name: 'filePath', maxLength: 500 },
100
+ { name: 'toggleId', maxLength: 100 }
101
+ ];
102
+
103
+ for (const { name, maxLength } of stringProps) {
104
+ if (clickInfo[name] !== undefined) {
105
+ if (typeof clickInfo[name] !== 'string') {
106
+ sanitized[name] = String(clickInfo[name]).substring(0, maxLength);
107
+ } else {
108
+ sanitized[name] = clickInfo[name].substring(0, maxLength);
109
+ }
110
+ }
111
+ }
112
+
113
+ // Boolean properties
114
+ const boolProps = [
115
+ 'isTab', 'isCloseButton', 'isToggle', 'isModelSelector', 'isModelOption',
116
+ 'isSendButton', 'isFileLink', 'isNotificationButton', 'isIconButton', 'isHistoryItem'
117
+ ];
118
+
119
+ for (const name of boolProps) {
120
+ if (clickInfo[name] !== undefined) {
121
+ sanitized[name] = Boolean(clickInfo[name]);
122
+ }
123
+ }
124
+
125
+ return { valid: true, sanitized, error: null };
126
+ }
127
+
128
+ /**
129
+ * Validate message text for injection
130
+ *
131
+ * @param {string} message - The message to validate
132
+ * @returns {{valid: boolean, error: string|null}}
133
+ */
134
+ export function validateMessage(message) {
135
+ if (!message || typeof message !== 'string') {
136
+ return { valid: false, error: 'Message must be a non-empty string' };
137
+ }
138
+
139
+ if (message.length > 50000) {
140
+ return { valid: false, error: 'Message exceeds maximum length (50000 characters)' };
141
+ }
142
+
143
+ return { valid: true, error: null };
144
+ }
145
+
146
+ /**
147
+ * Sanitize a file path by removing null bytes and normalizing
148
+ * Does NOT validate path traversal - use validatePathWithinRoot for that
149
+ *
150
+ * @param {string} filePath - The file path to sanitize
151
+ * @returns {string} - Sanitized file path
152
+ */
153
+ export function sanitizeFilePath(filePath) {
154
+ if (typeof filePath !== 'string') {
155
+ return '';
156
+ }
157
+
158
+ // Remove null bytes which can be used to bypass security checks
159
+ return filePath.replace(/\0/g, '');
160
+ }