The previous version told users to wrap every decrypt run with a
'security find-generic-password' command — but the macOS and Linux
decrypt scripts already do that lookup internally. So the wrapper
was redundant: just storing the key in Keychain (macOS) or via
secret-tool (Linux) is enough; the script finds it on its own.
• macOS: GUI walkthrough for Keychain Access (New Password Item,
Name=ComfyUI_Encryption_Key, Account=ComfyUI), plus the
'security add-generic-password' CLI equivalent. No wrapper.
• Linux: 'secret-tool store' one-liner. No wrapper.
• Windows: User Environment Variable via sysdm.cpl, since the
Windows decrypt script does NOT auto-lookup Credential Manager
and only reads the env var. (Removed the PowerShell
Get-StoredCredential complexity that was solving a problem the
Windows script doesn't have.)
The key insight, now stated explicitly: each decrypt script does its
own platform-appropriate lookup. Users don't need to bridge the
keystore-to-env-var gap themselves.
No code changes.
The previous "Store your encryption key locally" section told macOS
users to put the key in Keychain Access — but stopped there, without
explaining that Keychain alone does not export the value to the env
var the decrypt script reads. Anyone following the old instructions
would have a "stored" key that ComfyUI couldn't find.
Replaced with two complete approaches per platform:
• macOS — Keychain via `security` CLI (recommended, encrypted at
rest, locked behind login) plus shell-rc fallback.
• Windows — Credential Manager via the CredentialManager PowerShell
module (recommended) plus User Environment Variable fallback.
• Linux — gnome-keyring via `secret-tool` (recommended on GNOME
desktops) plus shell-rc fallback.
Each "recommended" path includes a working one-line wrapper that
fetches the key from the system keystore and injects it as
COMFYUI_ENCRYPTION_KEY for the duration of one decrypt run, so the
key never lives in a plaintext dotfile.
No code changes.
Two completely separate top-level sections, each fully self-contained
so a user picking one never has to jump back into the other. Steps
now match the natural order a user follows:
1. Install plugin
2. Set the allow-setup gate (cloud: pod template; local: shell export)
3. Open the Setup Node in ComfyUI
4. Create the Dropbox app (links + permissions list)
5. Configure the Setup Node (display_only vs env_file)
6. Click "Set credentials" and paste App Key + Secret (auth_code blank)
7. Run, authorize at Dropbox (Continue, Allow), copy code, paste back
into the auto-popup modal along with App Key + Secret again, Save,
Run again
8. (cloud) Save credentials to RunPod Secrets
(local) Optional ComfyUI restart
9. (cloud) Add secrets to pod template
10. (cloud) Restart pod
(local) Restart ComfyUI
Both: delete the Setup Node, add the AutoUploader Node
Adds a "Best practices" section covering:
• never share App Secret / refresh token / encryption key
• close the Dropbox authorization window after copying the code
• the credentials panel is one-shot
• back up the encryption key before rotating
• when to keep COMFYUI_DROPSEND_ALLOW_SETUP off
• verify env vars on each new pod
• decrypt only locally
Removes the previous "Pick your path" + shared "Installation" + shared
"Create a Dropbox App" sections in favor of duplicating those steps
inside each path. The duplication is intentional — a user following a
single path reads start-to-finish without forking.
No code changes. Comfy Registry stays at v1.1.1; this lands on main
only.
Rotation section now leads with a "back up the old key first" callout
covering the most common foot-gun: rotating the key while .enc files
still exist anywhere encrypted under the old key. Adds the explicit
backup-then-test-recovery flow with platform-specific paths to look
up the current key (env var / .env for local, Secrets panel for
cloud).
Cloud and local setup paths are now fully separate top-level sections
with the gate-setup as the first action in each. Every .env reference
is tagged "(local only)" to prevent the recurring confusion where
cloud users tried to write secrets to disk on a hosted pod.
Env-var entry tables now use explicit Field/Value column pairs
(Name = COMFYUI_DROPSEND_ALLOW_SETUP, Value = 1) to match the layout
of the actual RunPod Environment Variables UI.
No code changes. Comfy Registry stays at v1.1.1; this commit lands on
main only and is visible on the GitHub README. A v1.1.2 bump can
publish the doc delta to the registry whenever convenient.
Documentation-only release. No code or behavior changes.
• README ## Encryption key rotation gains a "Recovering files
encrypted with an old key" subsection — step-by-step procedure
for decrypting .enc files using a stored older key, including
the v1.1.0 advisory recovery case.
• Header image refreshed to show the new node UI (modal-based
credential entry, no more text fields).
Why a separate version: Comfy Registry treats published versions as
immutable. v1.1.0's docs were captured at the moment of the first
tag push. Subsequent force-pushes of the v1.1.0 tag (to add the
recovery subsection and swap the header image) updated GitHub but
were rejected by the registry-publish workflow with "node version
already exists". v1.1.1 ships the doc delta to the registry.
Setup Node refactor (the secret-bearing fields no longer exist on the
node at all):
• Removed app_key / app_secret / auth_code from INPUT_TYPES. The
Setup Node now exposes only storage_method, encryption_key_method,
and reconnect — none of which are credentials.
• Added a "Set credentials..." button on the node (LiteGraph button
widget installed via beforeRegisterNodeDef + onNodeCreated
prototype hook so it survives reload, drag-and-drop workflow
JSON, and PNG metadata restore).
• Click opens a browser-only modal with three <input type=password>
fields. Browser-native dot masking the whole time the user types
or pastes — no canvas-rendered plaintext, no shoulder-surf.
• Modal Save POSTs values to a new same-origin route
/dropsend/setup/stash and closes. setup() consumes the stash
entry on the next Queue (one-shot, keyed by client_id, 60-second
TTL, 32-entry cap with oldest-eviction).
• Architectural consequence: secrets never become widget values, so
they cannot enter saved workflow JSON, ComfyUI's localStorage
auto-save, PNG metadata, copy-pasted nodes, or PromptServer.history
(and therefore the unauthenticated /history HTTP endpoint).
Auto-curl auth flow (one-click second paste):
• After Run 1 prints the OAuth URL banner, setup() sends a
sid-targeted dropsend_credentials_needed WebSocket event.
• JS handler auto-opens the entry modal in the originating browser
so the user can paste auth_code as soon as Dropbox shows it,
without clicking the button between runs.
• Defense in depth: client_id is echoed in the WS payload and the
JS handler verifies it matches api.clientId before opening.
Security hardening on /dropsend/setup/stash:
• Same-origin enforcement: Origin header must match request.host.
Closes cross-origin CSRF from another browser tab even when an
attacker has sniffed a victim's client_id.
• Content-Type lockdown: rejects anything other than
application/json with HTTP 415. Closes the confused-deputy class
where form-encoded bodies could be parsed as JSON.
• Live-session check: client_id must correspond to an active
PromptServer.instance.sockets entry. Returns 403 otherwise.
Closes the stash-poisoning vector where an attacker who guesses
a victim's client_id could pre-populate credentials.
• Per-IP rate limit: 30 POSTs / 10-second sliding window, returns
429 over budget. Defense against stash-spam DoS attempting to
evict a legitimate user's entry.
• Body cap (32 KB), client_id regex validation, JSON parse errors
return 400, empty payloads dropped.
• Both rate-limit and live-session checks empirically verified via
curl smoke tests.
Reconnect now revokes the Dropbox refresh token at the source:
• reconnect=True populates DropboxAuthManager from os.environ and
calls reset(revoke_token=True).
• POST /2/auth/token/revoke with an 8-second timeout so a Dropbox
outage cannot hang the operator mid-reconnect. Local cleanup
proceeds regardless of revocation outcome.
• Revocation outcome is now logged at logger.error level (not just
print) and the failure message tells the operator to manually
disconnect at https://www.dropbox.com/account/connected_apps so
a silent revocation failure cannot leave a still-valid token at
Dropbox.
Logging:
• Single FileHandler attached to the root logger from __init__.py,
pointed at a plugin-relative dropsend.log. Replaces the four
duplicated logging.basicConfig() blocks in monitor_output.py /
encrypt_file.py / dropbox_upload.py / dropsend_uploader_node.py
that were dead code (basicConfig is a no-op once ComfyUI has
already configured root). Log records from every DropSend module
now actually persist to disk.
• Race-free creation: pre-create with os.open(O_CREAT | O_WRONLY |
O_APPEND, 0o600) before FileHandler attaches, follow-up chmod as
belt-and-suspenders. Closes the brief default-umask window
between FileHandler creating the file and post-hoc chmod.
• Rotation via RotatingFileHandler (5 MB per file, 3 backups, ~20
MB max). Bounds disk use on long-lived hosts.
• dropbox_auth_manager.py now has its own logger so revocation
successes/failures land in dropsend.log alongside stdout.
Tests (tests/ directory, unittest, no extra deps):
• test_safe_paths.py — 23 tests covering symlink rejection, path-
traversal rejection, allowlist behaviour, dropbox_dest validation
(NUL bytes, missing leading slash, parent-traversal segments),
and resolve_safe_watch_folder edge cases. safe_paths is the only
barrier between remote workflow inputs and arbitrary-file-read
on the host; this is regression coverage for that barrier.
• test_setup_node_security.py — static AST analysis of
dropsend_setup_node.py asserting that every send_sync call for a
credential event passes a non-None sid AND sits inside an
"if sid:" guard. Catches future regressions of the original
WebSocket-broadcast vulnerability deterministically. Also asserts
INPUT_TYPES never re-introduces app_key/app_secret/auth_code.
WebSocket-broadcast guard maintained on all three event sites
(dropsend_credentials_ready, dropbox_reconnect_complete,
dropsend_credentials_needed) — refuse to send when client_id is None
to prevent send_sync(..., None) from broadcasting credentials to
every connected browser tab.
README:
• New Changelog section documenting the v1.1.0 security advisory
(workflow-persistence + WS-broadcast leaks in earlier versions)
and the fixes shipped in this release.
• New "Bind ComfyUI to localhost or front it with auth" Best
Practice item (item 4) explicitly calling out that ComfyUI's
/prompt endpoint is unauthenticated.
• New "Encryption key rotation" subsection covering symmetric-key
implications, leaked-key recovery, and how to generate a new key.
• New "Uninstalling / disconnecting" subsection with step-by-step
instructions for revoking Dropbox app, resetting app secret,
removing platform secrets, and handling existing .enc files.
• Threat model reflects same-origin/Content-Type checks, log
rotation, plugin-directory metadata-leak note for multi-tenant
hosts, reconnect-revocation silent-failure note, and Dropbox
SDK error-message log review.
.gitignore:
• Expanded to cover *.bak, *.enc, *.key/.pem/.p12/.pfx, .idea/,
.pytest_cache/, build artifacts, Thumbs.db/Desktop.ini.
Bundled in this minor release:
• Single-Observer fix for the macOS _fsevents conflict that
previously prevented .enc files from being picked up by the
upload watcher.
• folder_format dropdown for AutoUploader (project_name default
plus five worldwide date formats), with two-base persistence so
switching between project-name and dated modes preserves both
patterns.
• RUNNING banner with per-setting plain-English explanations.
• Branded watching/encrypted/sent log emojis.
• Per-shell copy-paste commands and echo verification for the
COMFYUI_DROPSEND_ALLOW_SETUP gate.
Updated encryption key references from 'DropSend' to 'ComfyUI' throughout the README, added important notes on universal .sh using both DropSend and DriveSend, and improved clarity on environment variable setup.