Repository navigation
react-viewer: Show popup to enable metadata on Windows - #15173
Conversation
Nir-Az
left a comment
There was a problem hiding this comment.
[rs-agentic bot — automated review]
Reviewed commit 9fa49f7435b (metadata-registry-enable → development). 13 files changed, +175 / -12.
Inline comments below cover: 🐛 potential bugs, 💡 enhancement suggestions,
| Uses .NET ProcessStartInfo directly (via PowerShell) so the `-c "expr"` arg | ||
| survives the elevation hop intact. ``expr`` must contain no double quotes. | ||
| """ | ||
| ps = (f"$psi = [Diagnostics.ProcessStartInfo]@{{FileName='{sys.executable}'; " |
There was a problem hiding this comment.
🐛 BUG – shell injection via sys.executable path (auto-generated by rs-agentic bot)
sys.executable is interpolated directly into a PowerShell single-quoted string without escaping single quotes. On many Windows installs the Python path contains no single quotes, but the restriction is undocumented and fragile. If the path ever does contain a ' (or a dollar sign, backtick, or other PowerShell metacharacter), the generated script will be syntactically broken or execute unexpected code.
Recommended fix – escape single quotes at minimum:
exe = sys.executable.replace("'", "''") # PS single-quote escape
ps = (f"$psi = [Diagnostics.ProcessStartInfo]@{{FileName='{exe}'; "
...)A more robust alternative is to write a temporary .ps1 file and invoke powershell.exe -File <path>, which avoids all shell-quoting issues entirely.
There was a problem hiding this comment.
Fixed — escaped ' in sys.executable before interpolation.
|
|
||
|
|
||
| @router.post("/enable-metadata") | ||
| async def enable_metadata(): |
There was a problem hiding this comment.
🐛 BUG – blocking subprocess.run inside an async endpoint (auto-generated by rs-agentic bot)
subprocess.run(...) is synchronous and blocks the asyncio event loop for up to 120 seconds (the full timeout value). During that window no other request can be served by this FastAPI process.
Fix – run the blocking call in a thread pool:
import asyncio
@router.post("/enable-metadata")
async def enable_metadata():
if platform.system() != "Windows":
return {"status": "noop", "note": "Metadata management is Windows-only."}
try:
rc = await asyncio.to_thread(
_run_elevated_python,
"import pyrealsense2 as rs; rs.enable_metadata()"
)
except subprocess.TimeoutExpired:
raise HTTPException(504, "Metadata enable timed out (user did not approve UAC?).")
...asyncio.to_thread (Python 3.9+) or loop.run_in_executor(None, ...) both work.
There was a problem hiding this comment.
Deferred — this BE is a single-user local UI; the UAC dialog dominates the blocking window anyway. Negligible practical impact for concurrent requests.
Nir-Az
left a comment
There was a problem hiding this comment.
[rs-agentic bot — automated review, continued] React / hooks findings.
| }, | ||
| } | ||
| ) | ||
| }, [devices]) |
There was a problem hiding this comment.
The dependency array is [devices], but the effect body calls addToast, removeToast, and enableMetadata — all of which are either re-created each render (functions defined inline in the component) or come from the store selector. This will trigger the react-hooks/exhaustive-deps ESLint rule and, more importantly, can cause stale-closure bugs if those functions ever change identity meaningfully.
Options:
- Wrap
addToast/removeToastinuseCallback(with stable deps) and add them to the dep array. - Use stable refs (e.g.,
addToastRef.current = addToast) and call through the ref inside the effect. - Move the toast call outside the effect by using a derived state / reducer pattern.
The promptedSetRef / enableInFlightRef guard is a good idea to prevent duplicate toasts, but without stable function references the effect's correctness relies on an implicit assumption that those helpers never change.
There was a problem hiding this comment.
Deferred — addToast/removeToast/enableMetadata are stable refs (useState setters / zustand selectors); no real stale-closure risk here.
| 'Enable it once (admin required) to surface per-frame metadata.', | ||
| { | ||
| label: 'Enable', | ||
| onClick: async (toastId) => { |
There was a problem hiding this comment.
🐛 Missing unmount cleanup – state update on unmounted component (auto-generated by rs-agentic bot)
The onClick handler is an async function that calls removeToast / addToast after await enableMetadata(). If the DevicePanel unmounts while the request is in-flight (e.g., the user navigates away), the set* calls in the try / catch / finally blocks will attempt to update state on an unmounted component, causing a React warning and possible memory-leak-style bugs in strict mode.
Recommended mitigation – track mount status via a ref:
const mountedRef = useRef(true)
useEffect(() => () => { mountedRef.current = false }, [])
// inside onClick:
if (mountedRef.current) removeToast(toastId)
if (mountedRef.current) addToast(variant, result.note ?? 'Done.')Or use an AbortController to cancel the API call on unmount.
There was a problem hiding this comment.
Deferred — DevicePanel is top-level and doesn't unmount in this app. React strict-mode dev warning, not a real bug.
Nir-Az
left a comment
There was a problem hiding this comment.
[rs-agentic bot — automated review, continued] Enhancement suggestions and missing test coverage.
| f"Arguments='-c \"{expr}\"'; Verb='runas'; UseShellExecute=$true; WindowStyle='Hidden'}}; " | ||
| f"try {{ $p = [Diagnostics.Process]::Start($psi); $p.WaitForExit(); exit $p.ExitCode }} " | ||
| f"catch {{ exit $_.Exception.NativeErrorCode }}") | ||
| return subprocess.run(["powershell.exe", "-NoProfile", "-Command", ps], timeout=timeout).returncode |
There was a problem hiding this comment.
💡 Enhancement – unhandled OSError / FileNotFoundError if PowerShell is absent (auto-generated by rs-agentic bot)
If powershell.exe is not in PATH (e.g., non-standard Windows Server installs or future environments), subprocess.run will raise FileNotFoundError / OSError. This propagates as an unhandled 500 with a raw Python traceback in the response body.
Suggested fix:
try:
rc = await asyncio.to_thread(
_run_elevated_python,
"import pyrealsense2 as rs; rs.enable_metadata()"
)
except subprocess.TimeoutExpired:
raise HTTPException(504, "Metadata enable timed out (user did not approve UAC?).")
except OSError as exc:
raise HTTPException(500, f"Could not launch PowerShell: {exc}")There was a problem hiding this comment.
Fixed — added except OSError → 500.
| const isMotion = ['gyro', 'accel', 'motion'].includes(streamType.toLowerCase()) | ||
| // Mirrors C++ viewer (common/stream-model.cpp): when SDK falls back to system_time, | ||
| // per-frame metadata is unavailable from the kernel UVC driver. | ||
| const metadataUnavailable = metadata.clock_domain === 'system_time' |
There was a problem hiding this comment.
💡 Enhancement – system_time string is magic / fragile (auto-generated by rs-agentic bot)
The clock_domain check is a raw string comparison against 'system_time'. If the backend ever changes that string (casing, suffix, etc.), the UI warning silently stops appearing. Consider defining a TypeScript literal type for valid clock-domain values in types.ts:
// types.ts
export type ClockDomain = 'global_time' | 'system_time' | 'hardware_clock'and typing metadata.clock_domain as ClockDomain | undefined so any drift becomes a compile-time error.
There was a problem hiding this comment.
Deferred — a literal type doesn't catch backend drift (BE could send any string), only FE consumer typos. Low value for the surface area.
| @@ -155,13 +155,13 @@ describe('ToastContainer', () => { | |||
| expect(onClose).toHaveBeenCalledWith('toast-1') | |||
There was a problem hiding this comment.
🧪 Missing tests for new action prop behavior (auto-generated by rs-agentic bot)
The PR adds a significant new behavior to Toast: the action prop makes toasts sticky (no auto-dismiss) and renders a call-to-action button. The following scenarios are not yet covered by tests:
- Toast with
actiondoes not auto-dismiss (timer should never fire). - Clicking the action button calls
action.onClickwith the correct toastid. - Toast without
actionstill auto-dismisses (regression guard). ToastContainercorrectly passesactionthrough toToast.
These are behaviorally critical: if the auto-dismiss timer were accidentally left enabled for action-bearing toasts, the UAC-prompt toast would disappear before the user can click 'Enable'.
There was a problem hiding this comment.
Fixed — added 3 tests: sticky-no-auto-dismiss, onClick receives toast id, ToastContainer forwards action.
|
[rs-agentic bot — automated review] 🧪 Missing test coverage for
Without these, a regression in the |
9fa49f7 to
069352a
Compare
| Uses .NET ProcessStartInfo directly (via PowerShell) so the `-c "expr"` arg | ||
| survives the elevation hop intact. ``expr`` must contain no double quotes. | ||
| """ | ||
| exe = sys.executable.replace("'", "''") # PS single-quote escape |
There was a problem hiding this comment.
Constructing and directly executing a PowerShell command from a runtime 'expr' (ps variable + subprocess.run) enables dynamic execution of arbitrary code; consider restricting or validating 'expr' input.
Details
✨ AI Reasoning
The change builds a PowerShell command string by interpolating a caller-provided Python expression (expr) into a PowerShell -Command string, then calls subprocess.run to execute it. Dynamic construction and execution of code strings makes the effective behavior depend on runtime input and can hide executed actions from quick review. This pattern can be used legitimately (elevation), but it also matches common obfuscation/execution techniques used in malware (dynamic command generation and invocation). The concern arises specifically from the new use of f-string concatenation to craft a PS command and the subsequent subprocess invocation that runs it.
🔧 How do I fix it?
Ensure code is transparent and not intentionally obfuscated. Avoid hiding functionality from code review. Focus on intent and deception, not specific patterns.
Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info
There was a problem hiding this comment.
@AikidoSec ignore: Same as above — the PowerShell script is a hard-coded local literal; no runtime/user-controlled input. Pattern matches malware heuristics but there is no dynamic expression.
There was a problem hiding this comment.
✅ Based on your feedback, we ignored this issue because of the following reason:
Same as above — the PowerShell script is a hard-coded local literal; no runtime/user-controlled input. Pattern matches malware heuristics but there is no dynamic expression.
069352a to
b52b128
Compare
| updateFirmwareFromFile(deviceId, file) | ||
| } | ||
|
|
||
| // Prompt once per metadata-disabled device-set per session. Set changes |
There was a problem hiding this comment.
DevicePanel grew additional async UI logic (promptedSetRef, enableInFlightRef, and an enable-metadata useEffect). Extract this new metadata-enable flow into a smaller helper/component to reduce DevicePanel's complexity.
Details
✨ AI Reasoning
1. I looked for functions that were changed in this diff. 2. DevicePanel (the React component function) was modified: fetchDevices call updated, addToast signature changed, and a new useEffect plus refs (promptedSetRef, enableInFlightRef) and async enableMetadata flow were added. 3. The change introduces new responsibilities into DevicePanel: device enumeration semantics, toast control, and an admin metadata-enable flow with async side-effects and state guarding. 4. This increases cognitive load: consumers of DevicePanel must now reason about session-prompting state, in-flight enable operations, toast lifecycle, and error handling in addition to existing rendering and device-control logic. 5. The added code lives inside the already-large DevicePanel function rather than extracted helpers, worsening maintainability and testing surface area. 6. Therefore this change worsened the long-function/code-complexity issue for DevicePanel and is worth addressing.
🔧 How do I fix it?
Break down long functions into smaller helper functions. Aim for functions under 60 lines with fewer than 10 local variables.
Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info
There was a problem hiding this comment.
Resolved — extracted the metadata-enable flow into the showMetadataEnablePromptIfNeeded helper. DevicePanel function shrunk by ~30 LOC.
b52b128 to
87ee564
Compare
| f"Verb='runas'; UseShellExecute=$true; WindowStyle='Hidden'}}; " | ||
| f"try {{ $p = [Diagnostics.Process]::Start($psi); $p.WaitForExit(); exit $p.ExitCode }} " | ||
| f"catch {{ exit $_.Exception.NativeErrorCode }}") | ||
| return subprocess.run(["powershell.exe", "-NoProfile", "-Command", ps], timeout=timeout).returncode |
There was a problem hiding this comment.
Constructing and executing a dynamically built PowerShell command via subprocess.run can hide behavior. Prefer explicit, well-documented invocation or minimize dynamic construction to improve transparency.
Details
✨ AI Reasoning
The change builds a PowerShell command string at runtime and executes it. This creates a dynamic execution path where arbitrary or unexpected commands could be composed and run, which is a common technique used to hide or obfuscate behavior. Even if intended for a legitimate elevation flow, the pattern of programmatically constructing and shelling out a command increases risk and reduces transparency. Reviewers should ensure the constructed command is minimal, well-documented, and cannot be influenced by untrusted input. The rest of the file is straightforward and does not exhibit additional obfuscation techniques.
🔧 How do I fix it?
Ensure code is transparent and not intentionally obfuscated. Avoid hiding functionality from code review. Focus on intent and deception, not specific patterns.
Reply @AikidoSec feedback: [FEEDBACK] to get better review comments in the future.
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info
There was a problem hiding this comment.
@AikidoSec ignore: The PowerShell script is a hard-coded local literal (script = "import pyrealsense2 as rs; rs.enable_metadata()") — no runtime/user-controlled input. The f-string only interpolates sys.executable (escaped) and that literal. Pattern matches malware heuristics but there is no dynamic expression. UAC self-elevation requires shelling out to PowerShell.
There was a problem hiding this comment.
✅ Based on your feedback, we ignored this issue because of the following reason:
The PowerShell script is a hard-coded local literal (
script = "import pyrealsense2 as rs; rs.enable_metadata()") — no runtime/user-controlled input. The f-string only interpolatessys.executable(escaped) and that literal. Pattern matches malware heuristics but there is no dynamic expression. UAC self-elevation requires shelling out to PowerShell.
| """ | ||
| script = "import pyrealsense2 as rs; rs.enable_metadata()" | ||
| exe = sys.executable.replace("'", "''") # PS single-quote escape | ||
| ps = (f"$psi = [Diagnostics.ProcessStartInfo]@{{FileName='{exe}'; " |
There was a problem hiding this comment.
catch block can silently report success on non-Win32 failures
exit $_.Exception.NativeErrorCode evaluates to exit $null → exit code 0 whenever the thrown exception isn't a Win32Exception (e.g. MethodInvocationException from a malformed ProcessStartInfo, or any other
.NET exception where NativeErrorCode is absent). The caller then maps rc == 0 to {"status": "ok", "note": "Metadata enabled."} and the UI shows a success toast even though nothing ran.
Suggested fix — default to a non-zero code when NativeErrorCode is null/missing:
f"try {{ $p = [Diagnostics.Process]::Start($psi); $p.WaitForExit(); exit $p.ExitCode }} "
f"catch {{ $c = $_.Exception.NativeErrorCode; if (-not $c) {{ $c = 1 }}; exit $c }}")
While you're here, it's also worth capturing the elevated child's stderr (RedirectStandardError + UseShellExecute=$false won't work with runas, so log to a temp file via the inner -c script instead) —
right now WindowStyle='Hidden' means a non-zero exit reaches the REST caller with no diagnostic. Optional, but the next person debugging a registry-write failure will thank you.
| @@ -0,0 +1,44 @@ | |||
| # License: Apache 2.0. See LICENSE file in root directory. | |||
| # Copyright(c) 2026 RealSense, Inc. All Rights Reserved. | |||
There was a problem hiding this comment.
Missing tests for the new endpoint
remibettan
left a comment
There was a problem hiding this comment.
few comments - great job!
Tracked on: [RSDEV-11500]