You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: stop camera credentials leaking through logs and the monitor settings UI (#308)
* fix(logs): find credentials under any URL scheme, keep ERROR details structured
A camera's password lives in the monitor's source path as URL userinfo
(rtsp://admin:secret@cam/live). Redaction never found it: sanitizeUrl fires
only on strings starting http:// or https://, and sanitizeLogMessage matches
only /https?:\/\//, so rtsp, rtmp, and anything mid-string passed through
verbatim. The HTTP client logs response bodies, and its failure branch logs
at ERROR, which clears the default level with no opt-in, so a failed
monitors.json request wrote every camera credential to the log file.
lib/security/url-credentials.ts owns the one regex that knows where a
password sits in a URL, whatever the scheme, wherever in the string. The two
copies of the string rules in log-sanitizer collapse into one sanitizeString
that runs it first. URL handling now precedes form-data handling, so a query
string with a password in it stays a readable URL instead of being
percent-encoded into one blob.
The ERROR path serialized its details with JSON.stringify before sanitizing.
The sanitizer redacts by key, and a flattened object has no keys, so every
secret in an ERROR detail survived across ~130 call sites. Details are
passed as objects at every level now; both sinks already pretty-print them.
Also: cookie and credential join the sensitive keys (ZMSESSID rode out in
logged headers), a sensitive key holding an object is recursed into rather
than stringified to '[obje...', a single-field form body no longer needs an
'&' to be recognized, and console.error(error.stack) is sanitized, since the
stack embeds the message where the URL usually is.
Refs #307
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(logs): redact ZoneMinder's own log lines before display and export
The Server tab maps zmLog.Message straight into the display entry, and the
Save and Share buttons export whatever is on screen. zmc and zma log the
ffmpeg command line they run, which carries the camera credential, so the
log file users attach to a bug report handed out every camera password on
the server.
Same sanitizeLogMessage the app's own entries go through, applied where the
server's entries enter the view.
Refs #307
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(monitors): mask camera credentials in the settings dialog
ZoneMinder returns Path, User, and Pass to any account that can view a
monitor, and pre-1.38 servers have nowhere but the source path to keep a
camera password. The dialog rendered both verbatim, with a reveal toggle on
the password field, so the credentials for every camera were one tap and one
screenshot away.
While log redaction is on, the source path shows with its password segment
masked and the Pass field loses its reveal button. Host, port, and stream
path stay readable: that is what makes the field worth showing.
Both fields stay editable. maskUrlCredentials produces what is on screen and
restoreUrlCredentials puts the real password back on save, so changing a
camera's hostname does not silently wipe a password the user cannot see.
Typing over the mask means the new value is the new password.
The same helpers back the log sanitizer, so what the UI hides and what the
logs hide cannot drift.
Refs #307
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(android): keep the access token out of logcat in the PiP activity
PipActivity logged the URL it was handed and the raw media3 playback error.
The URL is an event video URL, which carries the ZoneMinder access token as
a query parameter, and a media3 HTTP failure quotes the URL it failed on
with the cause chain repeating it. Both went to logcat in cleartext, where
any adb session or bug report picks them up.
The onCreate line logs whether a URL arrived rather than the URL. The error
line logs the error code name plus a message with query strings stripped,
and drops the throwable, whose cause chain reprinted the URL.
Not covered by the JS sanitizer: this is native logging, on the other side
of the bridge from lib/logger.ts. No gate reaches it, so it is prose in
agents/project/native.md rather than a lint rule.
Refs #307
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: agents/project/native.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,6 +6,7 @@ Read before Capacitor, TLS, Electron, downloads, or native-path work.
6
6
- Prefer least-breaking hardening. If a fix necessarily breaks an existing native behavior, document accepted risk.
7
7
- Plugin import style, TLS trust, and download paths: Native contract in `AGENTS.project.md`. Additionally, plugins match the `@capacitor/core` major version and get mocks in `tests/setup.ts`.
8
8
- Use `hooks/useCapacitorListener` for plugin listeners.
9
+
- Native logging (`Log.*`, `NSLog`, `CAPLog.print`) never passes through the Logging contract's sanitizer, which lives on the JS side. Nothing gates it, so it is on you: never log a URL handed across the bridge, and never log a media or HTTP error object raw. Stream and event URLs carry the access token in the query, media3 quotes the failing URL in its message and again in the cause chain, and logcat is readable by anyone with adb (refs #307). Log an error code and a scrubbed message instead.
9
10
-`npm run android:sync`, `npm run ios:sync`, and commands that invoke them bump native versions. Revert incidental bumps before commit. Intended bumps are standalone `chore:` commits.
10
11
- Never resolve a promise with a `registerPlugin` proxy: the proxy intercepts every property access as a native method call, so `.then` on it is probed as an unimplemented method and the awaiter hangs forever. An async helper resolves with the plugin module's namespace and callers destructure the plugin from it after the await.
0 commit comments