@withtruing/companion 0.14.2 → 0.14.4
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/dist/whole-read/pages.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { canonicalJson } from './identity.js';
|
|
3
|
-
import { sanitizeBuildMetadata } from './sanitize.js';
|
|
3
|
+
import { inspectFragmentSlice, sanitizeBuildMetadata } from './sanitize.js';
|
|
4
4
|
import { WHOLE_READ_PAGE_PROTOCOL, } from './types.js';
|
|
5
5
|
export const MAX_METADATA_PAGE_BYTES = 512 * 1024;
|
|
6
6
|
export const MAX_TRANSPORT_RECORD_BYTES = 16 * 1024;
|
|
@@ -267,7 +267,7 @@ function assertTransportRecord(record) {
|
|
|
267
267
|
catch {
|
|
268
268
|
return fail('collection_invalid');
|
|
269
269
|
}
|
|
270
|
-
if (
|
|
270
|
+
if (inspectFragmentSlice(decoded) !== null)
|
|
271
271
|
fail('unsafe_metadata');
|
|
272
272
|
}
|
|
273
273
|
else
|
|
@@ -116,6 +116,73 @@ function inspectString(value) {
|
|
|
116
116
|
return 'secret_value_detected';
|
|
117
117
|
return containsCodeQuote(value) ? 'code_quote_detected' : null;
|
|
118
118
|
}
|
|
119
|
+
const SIMPLE_ESCAPES = {
|
|
120
|
+
'"': '"', '\\': '\\', '/': '/', n: '\n', t: '\t', r: '\r', b: '\b', f: '\f',
|
|
121
|
+
};
|
|
122
|
+
function unescapeJsonText(raw) {
|
|
123
|
+
let result = '';
|
|
124
|
+
for (let index = 0; index < raw.length; index += 1) {
|
|
125
|
+
const character = raw[index];
|
|
126
|
+
if (character !== '\\') {
|
|
127
|
+
result += character;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const next = raw[index + 1];
|
|
131
|
+
if (next === undefined)
|
|
132
|
+
break; // escape cut at the edge: drop it
|
|
133
|
+
if (next === 'u') {
|
|
134
|
+
const hex = raw.slice(index + 2, index + 6);
|
|
135
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex))
|
|
136
|
+
break; // incomplete \uXXXX at the edge: drop it
|
|
137
|
+
result += String.fromCharCode(Number.parseInt(hex, 16));
|
|
138
|
+
index += 5;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
result += SIMPLE_ESCAPES[next] ?? next;
|
|
142
|
+
index += 1;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Splits a slice of serialized JSON on its unescaped double quotes and returns the unescaped text of
|
|
149
|
+
* every segment: complete strings, a partial string at the start (the slice began inside a string)
|
|
150
|
+
* and a partial string at the end (the slice ended inside one). Segments between strings are JSON
|
|
151
|
+
* structure; they are short and are returned too, which is harmless for the code-quote heuristic.
|
|
152
|
+
*/
|
|
153
|
+
export function extractSliceStrings(text) {
|
|
154
|
+
const segments = [];
|
|
155
|
+
let current = '';
|
|
156
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
157
|
+
const character = text[index];
|
|
158
|
+
if (character === '\\') {
|
|
159
|
+
current += character + (text[index + 1] ?? '');
|
|
160
|
+
index += 1;
|
|
161
|
+
}
|
|
162
|
+
else if (character === '"') {
|
|
163
|
+
segments.push(current);
|
|
164
|
+
current = '';
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
current += character;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
segments.push(current);
|
|
171
|
+
return segments.map(unescapeJsonText);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Checks one raw slice of a record's serialized JSON (a transport fragment). Secrets and absolute
|
|
175
|
+
* host paths are checked on the whole slice. The code-quote heuristic runs on the string values in
|
|
176
|
+
* the slice, never on the JSON structure (raw JSON always looks like code). A fragment edge can only
|
|
177
|
+
* shorten a string, so the full check on the reassembled record remains the backstop.
|
|
178
|
+
*/
|
|
179
|
+
export function inspectFragmentSlice(text) {
|
|
180
|
+
if (containsHostPath(text))
|
|
181
|
+
return 'cleanup_path_forbidden';
|
|
182
|
+
if (validateNoSecretValues(text).ok === false)
|
|
183
|
+
return 'secret_value_detected';
|
|
184
|
+
return extractSliceStrings(text).some(containsCodeQuote) ? 'code_quote_detected' : null;
|
|
185
|
+
}
|
|
119
186
|
function cloneMetadata(value, allowCleanupPath, field, ancestors = new Set(), depth = 0) {
|
|
120
187
|
if (depth > 64)
|
|
121
188
|
return { ok: false, code: 'metadata_schema_invalid' };
|
package/dist/whole-read-temp.js
CHANGED
|
@@ -174,7 +174,10 @@ export function createCompanionAttemptSourceManager(dependencies = {}) {
|
|
|
174
174
|
const reportCleanup = dependencies.reportCleanup ?? (async () => false);
|
|
175
175
|
const scheduleInterval = dependencies.setInterval ?? setInterval;
|
|
176
176
|
const cancelInterval = dependencies.clearInterval ?? clearInterval;
|
|
177
|
-
|
|
177
|
+
// On by default: Truing's server decides whether build 3 runs (its own WHOLE_READ_BUILDS switch), and a
|
|
178
|
+
// released connector must carry it without the user setting anything (TRUING.md §9). WHOLE_READ_BUILDS=0
|
|
179
|
+
// on the user's computer is an emergency off switch.
|
|
180
|
+
const buildsEnabled = dependencies.buildsEnabled ?? (() => process.env.WHOLE_READ_BUILDS !== '0');
|
|
178
181
|
const readers = new Map();
|
|
179
182
|
const activeLocations = new Set();
|
|
180
183
|
const cleanupJobs = new Map();
|