deepadb 1.1.1 → 1.1.3
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/LICENSE +202 -21
- package/NOTICE +16 -0
- package/README.md +55 -29
- package/build/bridge/adb-bridge.d.ts +14 -0
- package/build/bridge/adb-bridge.d.ts.map +1 -1
- package/build/bridge/adb-bridge.js +34 -5
- package/build/bridge/adb-bridge.js.map +1 -1
- package/build/bridge/device-manager.d.ts.map +1 -1
- package/build/bridge/device-manager.js +18 -9
- package/build/bridge/device-manager.js.map +1 -1
- package/build/bridge/local-bridge.d.ts +10 -2
- package/build/bridge/local-bridge.d.ts.map +1 -1
- package/build/bridge/local-bridge.js +51 -16
- package/build/bridge/local-bridge.js.map +1 -1
- package/build/config/config.d.ts +7 -0
- package/build/config/config.d.ts.map +1 -1
- package/build/config/config.js +26 -10
- package/build/config/config.js.map +1 -1
- package/build/graphql-api.d.ts.map +1 -1
- package/build/graphql-api.js +73 -3
- package/build/graphql-api.js.map +1 -1
- package/build/http-transport.d.ts.map +1 -1
- package/build/http-transport.js +44 -7
- package/build/http-transport.js.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +41 -16
- package/build/index.js.map +1 -1
- package/build/middleware/auth.d.ts.map +1 -1
- package/build/middleware/auth.js +43 -6
- package/build/middleware/auth.js.map +1 -1
- package/build/middleware/chipset.d.ts.map +1 -1
- package/build/middleware/chipset.js +16 -1
- package/build/middleware/chipset.js.map +1 -1
- package/build/middleware/cleanup.d.ts +39 -7
- package/build/middleware/cleanup.d.ts.map +1 -1
- package/build/middleware/cleanup.js +148 -16
- package/build/middleware/cleanup.js.map +1 -1
- package/build/middleware/fetch-utils.d.ts +50 -7
- package/build/middleware/fetch-utils.d.ts.map +1 -1
- package/build/middleware/fetch-utils.js +127 -14
- package/build/middleware/fetch-utils.js.map +1 -1
- package/build/middleware/fs-utils.d.ts +126 -0
- package/build/middleware/fs-utils.d.ts.map +1 -0
- package/build/middleware/fs-utils.js +229 -0
- package/build/middleware/fs-utils.js.map +1 -0
- package/build/middleware/logger.d.ts +16 -0
- package/build/middleware/logger.d.ts.map +1 -1
- package/build/middleware/logger.js +18 -0
- package/build/middleware/logger.js.map +1 -1
- package/build/middleware/output-processor.d.ts.map +1 -1
- package/build/middleware/output-processor.js +43 -8
- package/build/middleware/output-processor.js.map +1 -1
- package/build/middleware/parse-utils.d.ts +19 -0
- package/build/middleware/parse-utils.d.ts.map +1 -0
- package/build/middleware/parse-utils.js +30 -0
- package/build/middleware/parse-utils.js.map +1 -0
- package/build/middleware/png-utils.d.ts.map +1 -1
- package/build/middleware/png-utils.js +33 -11
- package/build/middleware/png-utils.js.map +1 -1
- package/build/middleware/result-handle.d.ts +76 -0
- package/build/middleware/result-handle.d.ts.map +1 -0
- package/build/middleware/result-handle.js +378 -0
- package/build/middleware/result-handle.js.map +1 -0
- package/build/middleware/sanitize.d.ts +26 -0
- package/build/middleware/sanitize.d.ts.map +1 -1
- package/build/middleware/sanitize.js +43 -2
- package/build/middleware/sanitize.js.map +1 -1
- package/build/middleware/security.d.ts.map +1 -1
- package/build/middleware/security.js +30 -5
- package/build/middleware/security.js.map +1 -1
- package/build/middleware/ui-dump.d.ts.map +1 -1
- package/build/middleware/ui-dump.js +30 -10
- package/build/middleware/ui-dump.js.map +1 -1
- package/build/server.d.ts.map +1 -1
- package/build/server.js +34 -5
- package/build/server.js.map +1 -1
- package/build/tool-context.d.ts.map +1 -1
- package/build/tool-context.js +2 -0
- package/build/tool-context.js.map +1 -1
- package/build/tools/accessibility.d.ts.map +1 -1
- package/build/tools/accessibility.js +39 -40
- package/build/tools/accessibility.js.map +1 -1
- package/build/tools/at-commands.d.ts.map +1 -1
- package/build/tools/at-commands.js +31 -6
- package/build/tools/at-commands.js.map +1 -1
- package/build/tools/baseband.d.ts.map +1 -1
- package/build/tools/baseband.js +18 -7
- package/build/tools/baseband.js.map +1 -1
- package/build/tools/build.d.ts.map +1 -1
- package/build/tools/build.js +30 -8
- package/build/tools/build.js.map +1 -1
- package/build/tools/ci.d.ts.map +1 -1
- package/build/tools/ci.js +16 -5
- package/build/tools/ci.js.map +1 -1
- package/build/tools/control.d.ts.map +1 -1
- package/build/tools/control.js +19 -11
- package/build/tools/control.js.map +1 -1
- package/build/tools/device-farm.d.ts.map +1 -1
- package/build/tools/device-farm.js +27 -3
- package/build/tools/device-farm.js.map +1 -1
- package/build/tools/device-profiles.d.ts.map +1 -1
- package/build/tools/device-profiles.js +70 -8
- package/build/tools/device-profiles.js.map +1 -1
- package/build/tools/device.d.ts.map +1 -1
- package/build/tools/device.js +4 -2
- package/build/tools/device.js.map +1 -1
- package/build/tools/diagnostics.d.ts.map +1 -1
- package/build/tools/diagnostics.js +17 -9
- package/build/tools/diagnostics.js.map +1 -1
- package/build/tools/emulator.d.ts.map +1 -1
- package/build/tools/emulator.js +12 -13
- package/build/tools/emulator.js.map +1 -1
- package/build/tools/files.d.ts +1 -1
- package/build/tools/files.d.ts.map +1 -1
- package/build/tools/files.js +98 -56
- package/build/tools/files.js.map +1 -1
- package/build/tools/firmware-analysis.d.ts.map +1 -1
- package/build/tools/firmware-analysis.js +35 -12
- package/build/tools/firmware-analysis.js.map +1 -1
- package/build/tools/forwarding.d.ts.map +1 -1
- package/build/tools/forwarding.js +248 -6
- package/build/tools/forwarding.js.map +1 -1
- package/build/tools/health.d.ts.map +1 -1
- package/build/tools/health.js +14 -3
- package/build/tools/health.js.map +1 -1
- package/build/tools/input-gestures.d.ts.map +1 -1
- package/build/tools/input-gestures.js +65 -13
- package/build/tools/input-gestures.js.map +1 -1
- package/build/tools/logcat-watch.d.ts.map +1 -1
- package/build/tools/logcat-watch.js +21 -14
- package/build/tools/logcat-watch.js.map +1 -1
- package/build/tools/logs.d.ts.map +1 -1
- package/build/tools/logs.js +8 -4
- package/build/tools/logs.js.map +1 -1
- package/build/tools/mirroring.d.ts.map +1 -1
- package/build/tools/mirroring.js +21 -19
- package/build/tools/mirroring.js.map +1 -1
- package/build/tools/multi-device.d.ts.map +1 -1
- package/build/tools/multi-device.js +10 -0
- package/build/tools/multi-device.js.map +1 -1
- package/build/tools/network-capture.d.ts.map +1 -1
- package/build/tools/network-capture.js +32 -8
- package/build/tools/network-capture.js.map +1 -1
- package/build/tools/network-discovery.d.ts.map +1 -1
- package/build/tools/network-discovery.js +11 -2
- package/build/tools/network-discovery.js.map +1 -1
- package/build/tools/ota-monitor.d.ts.map +1 -1
- package/build/tools/ota-monitor.js +26 -12
- package/build/tools/ota-monitor.js.map +1 -1
- package/build/tools/packages.d.ts.map +1 -1
- package/build/tools/packages.js +18 -11
- package/build/tools/packages.js.map +1 -1
- package/build/tools/plugins.d.ts +18 -0
- package/build/tools/plugins.d.ts.map +1 -1
- package/build/tools/plugins.js +47 -2
- package/build/tools/plugins.js.map +1 -1
- package/build/tools/prompts.d.ts.map +1 -1
- package/build/tools/prompts.js +2 -0
- package/build/tools/prompts.js.map +1 -1
- package/build/tools/qemu.d.ts +6 -3
- package/build/tools/qemu.d.ts.map +1 -1
- package/build/tools/qemu.js +28 -16
- package/build/tools/qemu.js.map +1 -1
- package/build/tools/registry.d.ts.map +1 -1
- package/build/tools/registry.js +76 -26
- package/build/tools/registry.js.map +1 -1
- package/build/tools/regression.d.ts +37 -0
- package/build/tools/regression.d.ts.map +1 -1
- package/build/tools/regression.js +63 -22
- package/build/tools/regression.js.map +1 -1
- package/build/tools/resources.d.ts.map +1 -1
- package/build/tools/resources.js +62 -15
- package/build/tools/resources.js.map +1 -1
- package/build/tools/result-handles.d.ts +37 -0
- package/build/tools/result-handles.d.ts.map +1 -0
- package/build/tools/result-handles.js +286 -0
- package/build/tools/result-handles.js.map +1 -0
- package/build/tools/ril-intercept.d.ts.map +1 -1
- package/build/tools/ril-intercept.js +15 -18
- package/build/tools/ril-intercept.js.map +1 -1
- package/build/tools/screen-record.d.ts.map +1 -1
- package/build/tools/screen-record.js +34 -5
- package/build/tools/screen-record.js.map +1 -1
- package/build/tools/screenshot-diff.d.ts.map +1 -1
- package/build/tools/screenshot-diff.js +29 -21
- package/build/tools/screenshot-diff.js.map +1 -1
- package/build/tools/selinux-audit.d.ts.map +1 -1
- package/build/tools/selinux-audit.js +7 -5
- package/build/tools/selinux-audit.js.map +1 -1
- package/build/tools/sensors.d.ts.map +1 -1
- package/build/tools/sensors.js +26 -4
- package/build/tools/sensors.js.map +1 -1
- package/build/tools/shell.d.ts.map +1 -1
- package/build/tools/shell.js +9 -0
- package/build/tools/shell.js.map +1 -1
- package/build/tools/snapshot.d.ts.map +1 -1
- package/build/tools/snapshot.js +38 -19
- package/build/tools/snapshot.js.map +1 -1
- package/build/tools/split-apk.d.ts.map +1 -1
- package/build/tools/split-apk.js +48 -24
- package/build/tools/split-apk.js.map +1 -1
- package/build/tools/test-gen.d.ts.map +1 -1
- package/build/tools/test-gen.js +29 -10
- package/build/tools/test-gen.js.map +1 -1
- package/build/tools/testing.d.ts.map +1 -1
- package/build/tools/testing.js +27 -9
- package/build/tools/testing.js.map +1 -1
- package/build/tools/thermal-power.d.ts +8 -1
- package/build/tools/thermal-power.d.ts.map +1 -1
- package/build/tools/thermal-power.js +35 -10
- package/build/tools/thermal-power.js.map +1 -1
- package/build/tools/ui.d.ts.map +1 -1
- package/build/tools/ui.js +20 -13
- package/build/tools/ui.js.map +1 -1
- package/build/tools/wireless-firmware.d.ts.map +1 -1
- package/build/tools/wireless-firmware.js +21 -7
- package/build/tools/wireless-firmware.js.map +1 -1
- package/build/tools/wireless.d.ts.map +1 -1
- package/build/tools/wireless.js +39 -3
- package/build/tools/wireless.js.map +1 -1
- package/build/tools/workflow-market.d.ts.map +1 -1
- package/build/tools/workflow-market.js +67 -13
- package/build/tools/workflow-market.js.map +1 -1
- package/build/tools/workflow.d.ts.map +1 -1
- package/build/tools/workflow.js +77 -8
- package/build/tools/workflow.js.map +1 -1
- package/build/ws-transport.d.ts.map +1 -1
- package/build/ws-transport.js +21 -3
- package/build/ws-transport.js.map +1 -1
- package/package.json +20 -8
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// Copyright 2026 Jason <fullread@github>
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Filesystem Utilities - Shared helpers for safe on-disk state.
|
|
5
|
+
*
|
|
6
|
+
* Four primitives used across DeepADB tempDir-backed storage:
|
|
7
|
+
*
|
|
8
|
+
* 1. ensurePrivateDir(path) - Create a directory with restrictive
|
|
9
|
+
* permissions (0o700 on Unix; mode bits ignored on Windows). Use
|
|
10
|
+
* everywhere we create a subdirectory under tempDir. Defends against
|
|
11
|
+
* other-user reads on shared Unix hosts.
|
|
12
|
+
*
|
|
13
|
+
* 2. writeAtomicSync(path, data) - Write via a unique-per-call .tmp
|
|
14
|
+
* path then atomic rename. Defends against (a) mid-write crashes
|
|
15
|
+
* leaving corrupt data on disk, (b) concurrent readers seeing a
|
|
16
|
+
* partially-written file, (c) concurrent writers to the same path
|
|
17
|
+
* racing on .tmp filenames.
|
|
18
|
+
*
|
|
19
|
+
* 3. sanitizeFilenameComponent(name, maxLen?) - Replace any character
|
|
20
|
+
* outside [a-zA-Z0-9_-] with underscore. Optional maxLen truncates
|
|
21
|
+
* the result. Empty results are coerced to "_" so the caller never
|
|
22
|
+
* receives a zero-length component. Centralizes the pattern used
|
|
23
|
+
* across several tool modules for user-supplied identifier strings.
|
|
24
|
+
*
|
|
25
|
+
* 4. sanitizeFilenameComponentDotted(name, maxLen?) - Like (3) but
|
|
26
|
+
* ALSO allows "." in the output (for package names like
|
|
27
|
+
* "com.example.app"). Defangs pure-dot strings (".", "..", "...")
|
|
28
|
+
* by coercing them to "_", so a caller using the result directly
|
|
29
|
+
* as a path component cannot be steered into parent-directory
|
|
30
|
+
* traversal by an adversarial input.
|
|
31
|
+
*
|
|
32
|
+
* Note on existing directories: ensurePrivateDir only sets mode on
|
|
33
|
+
* directories it CREATES. Directories that already exist (from older
|
|
34
|
+
* DeepADB versions) keep their existing permissions. To remediate, the
|
|
35
|
+
* operator can chmod 700 the affected directories manually, or just
|
|
36
|
+
* delete them so they are recreated with the new mode.
|
|
37
|
+
*/
|
|
38
|
+
import { mkdirSync, writeFileSync, renameSync, unlinkSync, readFileSync, openSync, fsyncSync, closeSync } from "fs";
|
|
39
|
+
import { resolve } from "path";
|
|
40
|
+
import { randomBytes } from "crypto";
|
|
41
|
+
/** Create directory with owner-only permissions. Idempotent. */
|
|
42
|
+
export function ensurePrivateDir(path) {
|
|
43
|
+
mkdirSync(path, { recursive: true, mode: 0o700 });
|
|
44
|
+
}
|
|
45
|
+
/** Atomic write: unique .tmp + rename. Throws on failure (caller handles). */
|
|
46
|
+
export function writeAtomicSync(path, data) {
|
|
47
|
+
// Unique per call: process + time + 4 random bytes. Defends against
|
|
48
|
+
// concurrent writers to the same target path (each picks its own .tmp).
|
|
49
|
+
const suffix = randomBytes(4).toString("hex");
|
|
50
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.${suffix}.tmp`;
|
|
51
|
+
try {
|
|
52
|
+
writeFileSync(tmp, data);
|
|
53
|
+
// A4 fix: fsync the .tmp file's contents to durable storage BEFORE the
|
|
54
|
+
// atomic rename. Without this, a system crash between write and rename
|
|
55
|
+
// could leave the target file pointing at an empty or partial inode
|
|
56
|
+
// (the rename is atomic at the directory-entry level, but the data
|
|
57
|
+
// blocks of the renamed inode may not have been flushed yet on some
|
|
58
|
+
// filesystems). Cost: one extra syscall per write; negligible compared
|
|
59
|
+
// to the protection against crash-induced corruption of profiles,
|
|
60
|
+
// baselines, snapshots, and other persistent state. Falls through on
|
|
61
|
+
// platforms where fsync raises (rare in practice) — the rename still
|
|
62
|
+
// happens, matching pre-fix behavior.
|
|
63
|
+
try {
|
|
64
|
+
const fd = openSync(tmp, "r+");
|
|
65
|
+
try {
|
|
66
|
+
fsyncSync(fd);
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
closeSync(fd);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch { /* fsync best-effort; do not block the rename */ }
|
|
73
|
+
renameSync(tmp, path);
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
// Cleanup any partial state. If writeFileSync failed early, tmp may
|
|
77
|
+
// not exist; that is fine - unlinkSync just throws which we swallow.
|
|
78
|
+
try {
|
|
79
|
+
unlinkSync(tmp);
|
|
80
|
+
}
|
|
81
|
+
catch { /* ignore */ }
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Sanitize a string for use as a filename component. Replaces every
|
|
87
|
+
* character outside [a-zA-Z0-9_-] with "_".
|
|
88
|
+
*
|
|
89
|
+
* Safe against path traversal because `.`, `/`, and `\\` are all outside
|
|
90
|
+
* the allowlist (they become `_`). For cases where rejection is preferable
|
|
91
|
+
* to silent replacement, validate the raw input against /^[a-zA-Z0-9_-]+$/
|
|
92
|
+
* before calling tools.
|
|
93
|
+
*
|
|
94
|
+
* @param name Raw user-supplied identifier.
|
|
95
|
+
* @param maxLen Optional length cap applied after sanitization. Callers
|
|
96
|
+
* that build long paths (Windows MAX_PATH-sensitive) should
|
|
97
|
+
* always pass this.
|
|
98
|
+
* @returns Sanitized string, guaranteed non-empty (empty input collapses
|
|
99
|
+
* to "_" so callers never receive a zero-length component).
|
|
100
|
+
*/
|
|
101
|
+
export function sanitizeFilenameComponent(name, maxLen) {
|
|
102
|
+
let s = name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
103
|
+
// A6 fix: truncation can also produce empty (maxLen=0 edge case). Apply
|
|
104
|
+
// the empty-check AFTER truncation, matching the order used in
|
|
105
|
+
// sanitizeFilenameComponentDotted post-A7. Defense in depth: empty input
|
|
106
|
+
// would produce an empty component, which then concatenated with an
|
|
107
|
+
// extension yields a hidden file like ".json".
|
|
108
|
+
if (maxLen !== undefined && s.length > maxLen)
|
|
109
|
+
s = s.substring(0, maxLen);
|
|
110
|
+
// Collapse empty to "_" so the caller always gets a real component.
|
|
111
|
+
if (s.length === 0)
|
|
112
|
+
s = "_";
|
|
113
|
+
return s;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Like sanitizeFilenameComponent but ALSO allows "." in the output.
|
|
117
|
+
* Intended for Android-style package identifiers ("com.example.app") and
|
|
118
|
+
* similar dotted names where stripping dots would damage the identifier.
|
|
119
|
+
*
|
|
120
|
+
* Pure-dot strings (".", "..", "...", etc.) are coerced to "_" so a
|
|
121
|
+
* caller that uses the result directly as a path component cannot be
|
|
122
|
+
* steered into parent-directory traversal by an adversarial input.
|
|
123
|
+
*
|
|
124
|
+
* @param name Raw user-supplied dotted identifier (e.g., package name).
|
|
125
|
+
* @param maxLen Optional length cap applied after sanitization.
|
|
126
|
+
* @returns Sanitized string, guaranteed non-empty AND not a pure-dot
|
|
127
|
+
* traversal sequence.
|
|
128
|
+
*/
|
|
129
|
+
/**
|
|
130
|
+
* A8 note: a LEADING dot is preserved (only character-class filtering
|
|
131
|
+
* applies — the dot is in the allowed set). On Unix, a result like
|
|
132
|
+
* `.hidden` becomes an actual hidden file in the result-handle store and
|
|
133
|
+
* other directory listings. This is intentional under the design (callers
|
|
134
|
+
* who want to allow package names like com.example.app need the dot), but
|
|
135
|
+
* means operators browsing the temp directory should pass `ls -A` not
|
|
136
|
+
* bare `ls` to see all entries. If a future caller wants the no-leading-
|
|
137
|
+
* dot behavior, it should sanitize the input before calling this helper.
|
|
138
|
+
*/
|
|
139
|
+
export function sanitizeFilenameComponentDotted(name, maxLen) {
|
|
140
|
+
let s = name.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
141
|
+
// A7 fix: defenses run AFTER truncation, not before. The previous order
|
|
142
|
+
// (defang → empty-check → truncate) had a gap: input "..a" with maxLen=2
|
|
143
|
+
// would pass the defang (not pure-dot), then truncate to "..", and a
|
|
144
|
+
// pure-dot traversal string would reach the caller. Same hazard if
|
|
145
|
+
// truncation produces an empty string. Apply both the pure-dot defang
|
|
146
|
+
// and the empty-check LAST so any reduction-by-truncation is caught.
|
|
147
|
+
if (maxLen !== undefined && s.length > maxLen)
|
|
148
|
+
s = s.substring(0, maxLen);
|
|
149
|
+
// Defang pure-dot strings: ".", "..", "..." would resolve to current-dir
|
|
150
|
+
// or parent-dir if passed as a path component without an extension.
|
|
151
|
+
// `join(dir, "..")` escapes dir entirely; this prevents that.
|
|
152
|
+
if (/^\.+$/.test(s))
|
|
153
|
+
s = "_";
|
|
154
|
+
// Empty-input defense (sanitizeFilenameComponent has the same).
|
|
155
|
+
if (s.length === 0)
|
|
156
|
+
s = "_";
|
|
157
|
+
return s;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Returns true iff `candidatePath`, after path resolution, is contained
|
|
161
|
+
* within `dir` — either equal to `dir` or strictly underneath it.
|
|
162
|
+
*
|
|
163
|
+
* Containment uses a path-separator boundary check, NOT a string prefix.
|
|
164
|
+
* Without the boundary, `candidatePath = /parent/tempDir_evil/x` would
|
|
165
|
+
* pass containment against `dir = /parent/tempDir` because the absolute
|
|
166
|
+
* path string-prefix-matches — even though the two are sibling directories.
|
|
167
|
+
*
|
|
168
|
+
* Both POSIX `/` and Windows `\\` separators are recognized so a candidate
|
|
169
|
+
* resolved on either platform is handled correctly.
|
|
170
|
+
*
|
|
171
|
+
* Usage:
|
|
172
|
+
* if (!isWithinDir(userOutputDir, ctx.config.tempDir)) {
|
|
173
|
+
* throw new Error("Output directory must be inside tempDir");
|
|
174
|
+
* }
|
|
175
|
+
*
|
|
176
|
+
* This is the centralized successor to the inline pattern that previously
|
|
177
|
+
* lived in tools/qemu.ts (verifyContainment, correctly using a separator
|
|
178
|
+
* boundary) and tools/split-apk.ts / tools/registry.ts (which used a bare
|
|
179
|
+
* string-prefix check and were vulnerable to the sibling-directory bypass).
|
|
180
|
+
* Lifting to shared middleware fixes the bypass at both call sites.
|
|
181
|
+
*/
|
|
182
|
+
export function isWithinDir(candidatePath, dir) {
|
|
183
|
+
const resolvedCandidate = resolve(candidatePath);
|
|
184
|
+
const resolvedDir = resolve(dir);
|
|
185
|
+
if (resolvedCandidate === resolvedDir)
|
|
186
|
+
return true;
|
|
187
|
+
// A9 note: the two startsWith checks are intentionally redundant. After
|
|
188
|
+
// path.resolve(), the separator is platform-native — `/` on POSIX, `\\`
|
|
189
|
+
// on Windows — so one of the two checks ALWAYS misses on a given run.
|
|
190
|
+
// Keeping both means the code reads correctly regardless of platform,
|
|
191
|
+
// and the dead branch costs one extra string comparison per call. If a
|
|
192
|
+
// future contributor "cleans up" by removing one of the checks, they
|
|
193
|
+
// would break the code on the other platform. The dual check is a
|
|
194
|
+
// platform-portability defense, not a bug.
|
|
195
|
+
return resolvedCandidate.startsWith(resolvedDir + "/")
|
|
196
|
+
|| resolvedCandidate.startsWith(resolvedDir + "\\");
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Read and JSON-parse a file. On any failure (missing, unreadable, malformed),
|
|
200
|
+
* log a warning via the supplied logger and return null. Used by tools that
|
|
201
|
+
* walk a directory of JSON files (fingerprints, baselines, marketplace
|
|
202
|
+
* workflows, plugin metadata) and want to skip-but-surface corruption rather
|
|
203
|
+
* than abort the whole walk OR silently drop the entry.
|
|
204
|
+
*
|
|
205
|
+
* Closes audit findings AE8 / AO6 / AU6 / BJ7 / BN7 — the recurring silent
|
|
206
|
+
* `} catch { /* skip corrupt * / }` pattern. Surfaces corruption to the
|
|
207
|
+
* operator so a quietly-shrinking handle count doesn't go unnoticed.
|
|
208
|
+
*
|
|
209
|
+
* The logger is optional so this is safe to call from contexts without a
|
|
210
|
+
* Logger in scope (some loadFingerprints / listing functions don't take a
|
|
211
|
+
* ToolContext). When absent, the warning goes to stderr — non-silent.
|
|
212
|
+
*
|
|
213
|
+
* Returns null on any failure; caller should skip the entry.
|
|
214
|
+
*/
|
|
215
|
+
export function tryReadJsonOrWarn(filePath, context, logger) {
|
|
216
|
+
try {
|
|
217
|
+
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
221
|
+
const msg = `tryReadJsonOrWarn[${context}]: skipped ${filePath} — ${detail}`;
|
|
222
|
+
if (logger)
|
|
223
|
+
logger.warn(msg);
|
|
224
|
+
else
|
|
225
|
+
process.stderr.write(msg + "\n");
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=fs-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-utils.js","sourceRoot":"","sources":["../../src/middleware/fs-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
*
|
|
4
4
|
* IMPORTANT: MCP stdio servers MUST NOT write to stdout.
|
|
5
5
|
* All logging goes to stderr to avoid corrupting JSON-RPC messages.
|
|
6
|
+
*
|
|
7
|
+
* M1 THREAT-MODEL NOTE: This Logger does NOT redact sensitive content.
|
|
8
|
+
* Direct calls like `logger.info(\`token=${token}\`)` log credentials
|
|
9
|
+
* verbatim. Redaction lives at the SecurityMiddleware layer for audit-log
|
|
10
|
+
* entries (see middleware/security.ts `redactForLog`) and does NOT cover
|
|
11
|
+
* arbitrary Logger calls. Adding redaction at the Logger level was
|
|
12
|
+
* considered and rejected — too aggressive, it would hide useful debug
|
|
13
|
+
* info (e.g., truncated stack traces with hex offsets that look like
|
|
14
|
+
* tokens to a redactor). The divergent behavior IS the design:
|
|
15
|
+
*
|
|
16
|
+
* • SecurityMiddleware audit log → redacted (untrusted command strings)
|
|
17
|
+
* • Logger.info/warn/error → verbatim (callers are trusted code)
|
|
18
|
+
*
|
|
19
|
+
* Callers writing secret values into log strings are responsible for
|
|
20
|
+
* pre-redaction. The Logger trusts its own callers; it does not trust the
|
|
21
|
+
* commands flowing through SecurityMiddleware.
|
|
6
22
|
*/
|
|
7
23
|
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
8
24
|
export declare class Logger {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/middleware/logger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/middleware/logger.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAS3D,qBAAa,MAAM;IACjB,OAAO,CAAC,KAAK,CAAS;gBAEV,KAAK,GAAE,QAAiB;IAIpC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAIhD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAI/C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAI/C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAIhD,OAAO,CAAC,GAAG;CAOZ"}
|
|
@@ -1,8 +1,26 @@
|
|
|
1
|
+
// Copyright 2026 Jason <fullread@github>
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
3
|
/**
|
|
2
4
|
* Logger — Simple stderr-based logger for MCP servers.
|
|
3
5
|
*
|
|
4
6
|
* IMPORTANT: MCP stdio servers MUST NOT write to stdout.
|
|
5
7
|
* All logging goes to stderr to avoid corrupting JSON-RPC messages.
|
|
8
|
+
*
|
|
9
|
+
* M1 THREAT-MODEL NOTE: This Logger does NOT redact sensitive content.
|
|
10
|
+
* Direct calls like `logger.info(\`token=${token}\`)` log credentials
|
|
11
|
+
* verbatim. Redaction lives at the SecurityMiddleware layer for audit-log
|
|
12
|
+
* entries (see middleware/security.ts `redactForLog`) and does NOT cover
|
|
13
|
+
* arbitrary Logger calls. Adding redaction at the Logger level was
|
|
14
|
+
* considered and rejected — too aggressive, it would hide useful debug
|
|
15
|
+
* info (e.g., truncated stack traces with hex offsets that look like
|
|
16
|
+
* tokens to a redactor). The divergent behavior IS the design:
|
|
17
|
+
*
|
|
18
|
+
* • SecurityMiddleware audit log → redacted (untrusted command strings)
|
|
19
|
+
* • Logger.info/warn/error → verbatim (callers are trusted code)
|
|
20
|
+
*
|
|
21
|
+
* Callers writing secret values into log strings are responsible for
|
|
22
|
+
* pre-redaction. The Logger trusts its own callers; it does not trust the
|
|
23
|
+
* commands flowing through SecurityMiddleware.
|
|
6
24
|
*/
|
|
7
25
|
const LOG_LEVELS = {
|
|
8
26
|
debug: 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/middleware/logger.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/middleware/logger.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,sCAAsC;AACtC;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,MAAM,UAAU,GAA6B;IAC3C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,MAAM,OAAO,MAAM;IACT,KAAK,CAAS;IAEtB,YAAY,QAAkB,MAAM;QAClC,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAG,IAAe;QACvC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAG,IAAe;QACtC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAG,IAAe;QACtC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAG,IAAe;QACvC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IACtC,CAAC;IAEO,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,GAAG,IAAe;QAC9D,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACpC,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC3C,MAAM,MAAM,GAAG,IAAI,SAAS,MAAM,KAAK,CAAC,WAAW,EAAE,GAAG,CAAC;YACzD,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-processor.d.ts","sourceRoot":"","sources":["../../src/middleware/output-processor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"output-processor.d.ts","sourceRoot":"","sources":["../../src/middleware/output-processor.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAIH,qBAAa,eAAe;IAC1B;;;;OAIG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM;IAmCvD;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAOlC;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM;IAItD;;OAEG;IACH,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM;IAuB1C;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAWzD;;;;;;;OAOG;IACH,MAAM,CAAC,YAAY,CACjB,MAAM,EAAE,oBAAoB,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,EAChD,OAAO,CAAC,EAAE,MAAM,EAChB,QAAQ,SAAgB,GACvB,MAAM;IAST;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAyCxC;;;OAGG;IACH,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAmBxC;;;OAGG;IACH,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CAUzD"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Copyright 2026 Jason <fullread@github>
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
3
|
/**
|
|
2
4
|
* Output Processor — Truncation, formatting, structured parsing, and binary handling.
|
|
3
5
|
*
|
|
@@ -15,10 +17,17 @@ export class OutputProcessor {
|
|
|
15
17
|
const limit = maxSize ?? config.maxOutputSize;
|
|
16
18
|
if (text.length <= limit)
|
|
17
19
|
return text;
|
|
18
|
-
//
|
|
20
|
+
// G1 fix: budget the truncation-suffix size into the limit BEFORE
|
|
21
|
+
// searching for a cut point. The suffix is appended after the cut,
|
|
22
|
+
// so without this reservation the returned string overshoots `limit`
|
|
23
|
+
// by ~75 chars (varies with the comma-formatted "remaining" count).
|
|
24
|
+
// Worst-case remaining count length: ~20 chars (for ~10^16 chars omitted).
|
|
25
|
+
const SUFFIX_RESERVE = 80;
|
|
26
|
+
const effectiveLimit = Math.max(0, limit - SUFFIX_RESERVE);
|
|
27
|
+
// Scan backward from the effective limit to find a clean break point
|
|
19
28
|
// Look within the last 500 chars of the allowed range
|
|
20
|
-
const searchStart = Math.max(0,
|
|
21
|
-
const searchRegion = text.substring(searchStart,
|
|
29
|
+
const searchStart = Math.max(0, effectiveLimit - 500);
|
|
30
|
+
const searchRegion = text.substring(searchStart, effectiveLimit);
|
|
22
31
|
// Prefer section separators, then newlines
|
|
23
32
|
let breakOffset = -1;
|
|
24
33
|
const sectionBreak = searchRegion.lastIndexOf("\n\n");
|
|
@@ -31,7 +40,7 @@ export class OutputProcessor {
|
|
|
31
40
|
breakOffset = searchStart + lineBreak;
|
|
32
41
|
}
|
|
33
42
|
}
|
|
34
|
-
const cutPoint = breakOffset > 0 ? breakOffset :
|
|
43
|
+
const cutPoint = breakOffset > 0 ? breakOffset : effectiveLimit;
|
|
35
44
|
const truncated = text.substring(0, cutPoint);
|
|
36
45
|
const remaining = text.length - cutPoint;
|
|
37
46
|
return `${truncated}\n\n--- OUTPUT TRUNCATED (${remaining.toLocaleString()} characters omitted) ---`;
|
|
@@ -56,9 +65,26 @@ export class OutputProcessor {
|
|
|
56
65
|
* Format a structured error response.
|
|
57
66
|
*/
|
|
58
67
|
static formatError(error) {
|
|
68
|
+
// G2 fix: handle structured-object exceptions (some third-party libs
|
|
69
|
+
// throw plain objects rather than Error instances). Probe for a
|
|
70
|
+
// .message property first, then fall back to JSON for structured
|
|
71
|
+
// values, and finally String() for primitives. Avoids the
|
|
72
|
+
// unhelpful "Error: [object Object]" output.
|
|
59
73
|
if (error instanceof Error) {
|
|
60
74
|
return `Error: ${error.message}`;
|
|
61
75
|
}
|
|
76
|
+
if (error && typeof error === "object") {
|
|
77
|
+
const msg = error.message;
|
|
78
|
+
if (typeof msg === "string" && msg.length > 0) {
|
|
79
|
+
return `Error: ${msg}`;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
return `Error: ${JSON.stringify(error)}`;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Circular references or BigInt — fall through.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
62
88
|
return `Error: ${String(error)}`;
|
|
63
89
|
}
|
|
64
90
|
/**
|
|
@@ -118,10 +144,19 @@ export class OutputProcessor {
|
|
|
118
144
|
const plugMap = { "0": "Unplugged", "1": "AC", "2": "USB", "4": "Wireless" };
|
|
119
145
|
pairs.push(`Plugged: ${plugMap[plugged] ?? plugged}`);
|
|
120
146
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
147
|
+
// G4 fix: skip non-numeric temperature/voltage values rather than emit
|
|
148
|
+
// "Temperature: NaN°C" or "Voltage: NaNV". Quirky ROMs or partial dumpsys
|
|
149
|
+
// output occasionally produce non-integer values here.
|
|
150
|
+
if (temperature) {
|
|
151
|
+
const tempNum = parseInt(temperature, 10);
|
|
152
|
+
if (Number.isFinite(tempNum))
|
|
153
|
+
pairs.push(`Temperature: ${(tempNum / 10).toFixed(1)}°C`);
|
|
154
|
+
}
|
|
155
|
+
if (voltage) {
|
|
156
|
+
const voltNum = parseInt(voltage, 10);
|
|
157
|
+
if (Number.isFinite(voltNum))
|
|
158
|
+
pairs.push(`Voltage: ${(voltNum / 1000).toFixed(3)}V`);
|
|
159
|
+
}
|
|
125
160
|
return pairs.length > 0 ? pairs.join(" | ") : raw.trim();
|
|
126
161
|
}
|
|
127
162
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-processor.js","sourceRoot":"","sources":["../../src/middleware/output-processor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,MAAM,OAAO,eAAe;IAC1B;;;;OAIG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAY,EAAE,OAAgB;QAC5C,MAAM,KAAK,GAAG,OAAO,IAAI,MAAM,CAAC,aAAa,CAAC;QAC9C,IAAI,IAAI,CAAC,MAAM,IAAI,KAAK;YAAE,OAAO,IAAI,CAAC;QAEtC,
|
|
1
|
+
{"version":3,"file":"output-processor.js","sourceRoot":"","sources":["../../src/middleware/output-processor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse Utilities — shared numeric/string parsing helpers.
|
|
3
|
+
*
|
|
4
|
+
* Q2 fix: previously \`parseIntSafe\` was duplicated in config.ts and
|
|
5
|
+
* result-handle.ts with slight signature differences. This module is the
|
|
6
|
+
* single source of truth. Both callers now import from here.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Parse a numeric environment variable with a defensive fallback.
|
|
10
|
+
*
|
|
11
|
+
* - Returns \`fallback\` when the value is undefined.
|
|
12
|
+
* - Returns \`fallback\` and logs a warning to stderr when the value is
|
|
13
|
+
* non-numeric or negative (operator typos).
|
|
14
|
+
* - The negative-rejection is intentional: every numeric config in DeepADB
|
|
15
|
+
* is a duration, count, or size — none of which has a legitimate
|
|
16
|
+
* negative interpretation.
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseIntSafe(value: string | undefined, fallback: number): number;
|
|
19
|
+
//# sourceMappingURL=parse-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-utils.d.ts","sourceRoot":"","sources":["../../src/middleware/parse-utils.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQhF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Copyright 2026 Jason <fullread@github>
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Parse Utilities — shared numeric/string parsing helpers.
|
|
5
|
+
*
|
|
6
|
+
* Q2 fix: previously \`parseIntSafe\` was duplicated in config.ts and
|
|
7
|
+
* result-handle.ts with slight signature differences. This module is the
|
|
8
|
+
* single source of truth. Both callers now import from here.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Parse a numeric environment variable with a defensive fallback.
|
|
12
|
+
*
|
|
13
|
+
* - Returns \`fallback\` when the value is undefined.
|
|
14
|
+
* - Returns \`fallback\` and logs a warning to stderr when the value is
|
|
15
|
+
* non-numeric or negative (operator typos).
|
|
16
|
+
* - The negative-rejection is intentional: every numeric config in DeepADB
|
|
17
|
+
* is a duration, count, or size — none of which has a legitimate
|
|
18
|
+
* negative interpretation.
|
|
19
|
+
*/
|
|
20
|
+
export function parseIntSafe(value, fallback) {
|
|
21
|
+
if (value === undefined)
|
|
22
|
+
return fallback;
|
|
23
|
+
const parsed = parseInt(value, 10);
|
|
24
|
+
if (isNaN(parsed) || parsed < 0) {
|
|
25
|
+
process.stderr.write(`[DeepADB] Invalid config value "${value}", using default: ${fallback}\n`);
|
|
26
|
+
return fallback;
|
|
27
|
+
}
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=parse-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-utils.js","sourceRoot":"","sources":["../../src/middleware/parse-utils.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,sCAAsC;AACtC;;;;;;GAMG;AAEH;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,KAAyB,EAAE,QAAgB;IACtE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IACzC,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACnC,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,KAAK,qBAAqB,QAAQ,IAAI,CAAC,CAAC;QAChG,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"png-utils.d.ts","sourceRoot":"","sources":["../../src/middleware/png-utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"png-utils.d.ts","sourceRoot":"","sources":["../../src/middleware/png-utils.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAyDH,iEAAiE;AACjE,eAAO,MAAM,cAAc,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CASlE,CAAC;AA4BX;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAChE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAC9C,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EACxC,SAAS,SAAI,GACZ,IAAI,CAmBN;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAChE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EACjC,OAAO,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GACzC,IAAI,CA4CN;AAID,qEAAqE;AACrE,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAU,MAAM,CAAC;IACtB,MAAM,EAAS,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAS,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAkFhE;AAID;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,MAAM,CAqC3F"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Copyright 2026 Jason <fullread@github>
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
3
|
/**
|
|
2
4
|
* PNG Utilities — Zero-dependency PNG decode and encode for screenshot analysis and annotation.
|
|
3
5
|
*
|
|
@@ -66,14 +68,26 @@ export const ELEMENT_COLORS = [
|
|
|
66
68
|
[160, 50, 220], // purple
|
|
67
69
|
];
|
|
68
70
|
// ── Pixel operations ───────────────────────────────────────────────────
|
|
69
|
-
/** Write one pixel into a packed row-major RGB/RGBA pixel buffer. No-op if
|
|
70
|
-
|
|
71
|
+
/** Write one pixel into a packed row-major RGB/RGBA pixel buffer. No-op if
|
|
72
|
+
* x or y is out of range. Defense in depth: callers (drawRect, drawLabel)
|
|
73
|
+
* already pre-check bounds, but having the guard here too means a future
|
|
74
|
+
* caller that forgets the check cannot silently corrupt a different row
|
|
75
|
+
* — without the y-bound check, a y >= imgHeight produces a positive offset
|
|
76
|
+
* that still falls inside the buffer (it just points to a wrong row, or
|
|
77
|
+
* past the end where Node's Buffer silently no-ops). */
|
|
78
|
+
function setPixel(pixels, imgWidth, imgHeight, bpp, x, y, r, g, b) {
|
|
71
79
|
if (x < 0 || x >= imgWidth)
|
|
72
80
|
return;
|
|
81
|
+
if (y < 0 || y >= imgHeight)
|
|
82
|
+
return;
|
|
83
|
+
// I1 fix: clamp R/G/B to [0, 255]. Previously out-of-range values were
|
|
84
|
+
// silently truncated by Buffer coercion — pixels[off]=300 wraps to 44
|
|
85
|
+
// (300 & 0xFF). Clamping surfaces the caller bug as a visible saturated
|
|
86
|
+
// pixel instead of producing arbitrary wrong colors.
|
|
73
87
|
const off = (y * imgWidth + x) * bpp;
|
|
74
|
-
pixels[off] = r;
|
|
75
|
-
pixels[off + 1] = g;
|
|
76
|
-
pixels[off + 2] = b;
|
|
88
|
+
pixels[off] = Math.max(0, Math.min(255, r | 0));
|
|
89
|
+
pixels[off + 1] = Math.max(0, Math.min(255, g | 0));
|
|
90
|
+
pixels[off + 2] = Math.max(0, Math.min(255, b | 0));
|
|
77
91
|
if (bpp === 4)
|
|
78
92
|
pixels[off + 3] = 255;
|
|
79
93
|
}
|
|
@@ -92,12 +106,12 @@ export function drawRect(pixels, imgWidth, imgHeight, bpp, x1, y1, x2, y2, color
|
|
|
92
106
|
if (top > bot || left > right)
|
|
93
107
|
break;
|
|
94
108
|
for (let x = left; x <= right; x++) {
|
|
95
|
-
setPixel(pixels, imgWidth, bpp, x, top, r, g, b);
|
|
96
|
-
setPixel(pixels, imgWidth, bpp, x, bot, r, g, b);
|
|
109
|
+
setPixel(pixels, imgWidth, imgHeight, bpp, x, top, r, g, b);
|
|
110
|
+
setPixel(pixels, imgWidth, imgHeight, bpp, x, bot, r, g, b);
|
|
97
111
|
}
|
|
98
112
|
for (let y = top + 1; y < bot; y++) {
|
|
99
|
-
setPixel(pixels, imgWidth, bpp, left, y, r, g, b);
|
|
100
|
-
setPixel(pixels, imgWidth, bpp, right, y, r, g, b);
|
|
113
|
+
setPixel(pixels, imgWidth, imgHeight, bpp, left, y, r, g, b);
|
|
114
|
+
setPixel(pixels, imgWidth, imgHeight, bpp, right, y, r, g, b);
|
|
101
115
|
}
|
|
102
116
|
}
|
|
103
117
|
}
|
|
@@ -127,7 +141,7 @@ export function drawLabel(pixels, imgWidth, imgHeight, bpp, x, y, num, bgColor)
|
|
|
127
141
|
// Fill background rectangle
|
|
128
142
|
for (let dy = 0; dy < labelH && ly + dy < imgHeight; dy++) {
|
|
129
143
|
for (let dx = 0; dx < labelW && lx + dx < imgWidth; dx++) {
|
|
130
|
-
setPixel(pixels, imgWidth, bpp, lx + dx, ly + dy, bgR, bgG, bgB);
|
|
144
|
+
setPixel(pixels, imgWidth, imgHeight, bpp, lx + dx, ly + dy, bgR, bgG, bgB);
|
|
131
145
|
}
|
|
132
146
|
}
|
|
133
147
|
// Render each digit glyph
|
|
@@ -141,7 +155,7 @@ export function drawLabel(pixels, imgWidth, imgHeight, bpp, x, y, num, bgColor)
|
|
|
141
155
|
const px = lx + PAD + ci * CHAR_W + col;
|
|
142
156
|
const py = ly + PAD + row;
|
|
143
157
|
if (px < imgWidth && py < imgHeight) {
|
|
144
|
-
setPixel(pixels, imgWidth, bpp, px, py, fr, fg, fb);
|
|
158
|
+
setPixel(pixels, imgWidth, imgHeight, bpp, px, py, fr, fg, fb);
|
|
145
159
|
}
|
|
146
160
|
}
|
|
147
161
|
}
|
|
@@ -248,6 +262,14 @@ export function decodePngPixels(pngPath) {
|
|
|
248
262
|
* bpp must be 3 (RGB) or 4 (RGBA) to match the input data.
|
|
249
263
|
*/
|
|
250
264
|
export function encodePng(width, height, pixels, bpp) {
|
|
265
|
+
// I3 fix: pixel buffer size precondition. A mismatched buffer (e.g.,
|
|
266
|
+
// wrong bpp passed) previously produced a corrupt PNG silently because
|
|
267
|
+
// pixels.copy(..., over-read) is a no-op. Surface the mismatch as an
|
|
268
|
+
// error so the caller bug is caught at the boundary.
|
|
269
|
+
const expectedSize = width * height * bpp;
|
|
270
|
+
if (pixels.length < expectedSize) {
|
|
271
|
+
throw new Error(`encodePng: pixel buffer too small. Got ${pixels.length} bytes, expected at least ${expectedSize} (${width}x${height} at ${bpp} bpp).`);
|
|
272
|
+
}
|
|
251
273
|
const SIG = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
|
|
252
274
|
const ihdr = Buffer.allocUnsafe(13);
|
|
253
275
|
ihdr.writeUInt32BE(width, 0);
|