Skip to content

luci-app-veracrypt: add optional LuCI UI - #9069

Open
flatstik wants to merge 1 commit into
openwrt:masterfrom
flatstik:luci-app-veracrypt
Open

flatstik wants to merge 1 commit into
openwrt:masterfrom
flatstik:luci-app-veracrypt

Conversation

@flatstik

@flatstik flatstik commented Sep 25, 2026 •

Copy link
Copy Markdown

Pull request details

Description

Optional LuCI UI for console VeraCrypt. Not bundled into the veracrypt package.

  • apk add luci-app-veracrypt pulls +veracrypt
  • Calls veracrypt --text only (no VeraCrypt GUI toolkit)
  • Current password on stdin (--stdin), never --password
  • Interactive operations (change password, header backup/restore) answer veracrypt's prompts one at a time through a private FIFO; an unexpected or repeated prompt aborts. New password, hidden-volume password and token PIN never go on the command line
  • All paths (volumes, mountpoints, keyfiles, header backups, directory listing) are confined to /mnt/<name>/..., symlinks resolved; volumes may also be whole disks/partitions. Token libraries must be under /usr/lib
  • Each job has a random id and its own 0700 state directory
  • Installing missing filesystem tools needs the separate luci-app-veracrypt-pkg ACL grant
  • Edits /etc/config/veracrypt, which the veracrypt package ships; favorites are its volume sections. Favorites are mounted on request with the password entered then, never at boot, and passwords are not stored
  • tests/run.sh <openwrt-rootfs.tar.gz> runs the rpcd backend against a fake veracrypt inside an OpenWrt rootfs (unprivileged namespace chroot): 121 checks
  • Mounts always use --mount-options=nokernelcrypto (FUSE3 only; dmsetup/lvm2 is not a dependency)

This replaces closed PR #9031 with a clean single-commit history.

Maintainer (preferred)

@flatstik


Tested on

OpenWrt version: OpenWrt 25.12.5 (r33051-f5dae5ece4), ASUS RT-AX53U, ramips/mt7621
LuCI version: LuCI openwrt-25.12 branch (26.253.65060~7f05feb)
Web browser(s): Firefox 150.0.1 (64-bit)

The reworked backend was verified with tests/run.sh in the OpenWrt 25.12 x86_64 rootfs, and on the device above over ubus (tools, status, listdir/listdev confinement, a mount job on a USB disk; the password never appears in ps).


Checklist

@openwrt openwrt Bot added the add package Introduces a new package Makefile build script label Sep 25, 2026
Comment thread applications/luci-app-veracrypt/root/usr/libexec/rpcd/luci.veracrypt Outdated
Comment thread applications/luci-app-veracrypt/Makefile Outdated
@flatstik
flatstik requested a review from openwrt-ai September 25, 2026 16:47
@flatstik
flatstik force-pushed the luci-app-veracrypt branch 2 times, most recently from e7a24ae to 13588e9 Compare September 25, 2026 16:51
Comment thread applications/luci-app-veracrypt/root/usr/libexec/rpcd/luci.veracrypt Outdated
Comment thread applications/luci-app-veracrypt/root/usr/libexec/rpcd/luci.veracrypt Outdated
@flatstik
flatstik force-pushed the luci-app-veracrypt branch 2 times, most recently from 0902961 to 8442ad7 Compare September 26, 2026 12:33
Comment thread applications/luci-app-veracrypt/root/usr/libexec/rpcd/luci.veracrypt Outdated
Comment thread applications/luci-app-veracrypt/root/usr/libexec/rpcd/luci.veracrypt Outdated
@openwrt openwrt Bot added the not following guidelines Pull request does not follow formatting guidelines label Sep 27, 2026
@openwrt openwrt Bot removed the not following guidelines Pull request does not follow formatting guidelines label Sep 27, 2026
@flatstik
flatstik requested a review from openwrt-ai September 27, 2026 17:17

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit checks

  • 5a42342 "fix: preserve backslashes in path escaping" — fix: is not the luci-app-veracrypt: scope of the file it touches, and it only fixes code added in 29e1a8d. Squash it into 29e1a8d to keep the single-commit history the PR body describes.

Generated by Claude Code

@flatstik
flatstik requested a review from openwrt-ai September 27, 2026 17:47
@flatstik
flatstik force-pushed the luci-app-veracrypt branch 6 times, most recently from d0c4171 to 2c1f1e3 Compare September 27, 2026 20:14

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 new commits; no new issues found.


Generated by Claude Code

@flatstik
flatstik requested a review from openwrt-ai September 28, 2026 08:30

@BKPepe BKPepe left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes

I did a full pass over the current PR, including the rpcd backend, ACL, UCI handling, job lifecycle and the LuCI view.

There are still several blocking issues that need to be addressed before this is ready:

  1. The root run RPC accepts arbitrary absolute filesystem paths. valid_path() only rejects malformed paths; it does not constrain volume, mountpoint, backup_file or keyfile creation to the application's namespace. This means an account with the luci-app-veracrypt ACL can create/overwrite files outside /mnt and mount volumes on arbitrary system paths such as /etc, /overlay or /root. The /mnt/<name>/... allowlist used by mkdir/rm should be applied consistently to all filesystem operations.

  2. listdir exposes arbitrary root filesystem enumeration. It is in the read ACL, but the backend allows paths such as /etc, /root, /overlay and /www. The browser should be restricted to the same explicitly allowed namespace, or the ACL design needs to be reconsidered.

  3. The job implementation is not concurrency-safe. All requests share /tmp/luci-veracrypt-job*, including PID, log, volume, slot and the interactive FIFO. Concurrent requests can overwrite each other's state, causing job_abort, job_answer or job_dismount to operate on another request's job. Jobs need an explicit per-job ID and isolated state.

  4. vc_list_field() matches slot OR volume instead of matching both when both are supplied. A stale slot can therefore select a different mounted volume than the requested path. This is particularly dangerous for fsck/unmount operations.

  5. veracrypt --list parsing is whitespace-based in both shell and JavaScript. set -- $rest and split(/\s+/) break paths containing spaces, even though the path validation explicitly allows them. This affects volume/device/mountpoint detection and consequently fsck/unmount behavior.

  6. create can report success after the follow-up mount failed. The mount exit status is collected, but $JOB.rc is unconditionally set to 0 afterwards.

  7. The favorite keyfile handling is broken for multiple/token keyfiles. saveFavorite() passes the complete keyfile value through safeAbsPath(), which only accepts one absolute POSIX path. Such keyfiles are silently omitted from the saved favorite.

There are also several secondary concerns around simulating VeraCrypt's interactive prompt protocol for --change/--backup-headers/--restore-headers, the overly broad pkg_install privilege in the application ACL, and the robustness of the UCI section-name parsing.

Given that this code runs as the rpcd/root boundary and performs destructive storage operations, I would not merge the PR until the issues above are resolved and the corresponding paths/job lifecycle are covered by tests.

Comment thread applications/luci-app-veracrypt/Makefile Outdated
Comment thread applications/luci-app-veracrypt/Makefile Outdated
Comment thread applications/luci-app-veracrypt/Makefile Outdated
Comment thread applications/luci-app-veracrypt/README.md Outdated
Comment thread applications/luci-app-veracrypt/root/etc/config/veracrypt Outdated
@flatstik
flatstik force-pushed the luci-app-veracrypt branch 2 times, most recently from 6e02851 to 89bbe81 Compare September 30, 2026 07:29
@flatstik

flatstik commented Oct 2, 2026

Copy link
Copy Markdown
Author

Pushed 002dc9f. It addresses the Copilot and openwrt-ai comments above, plus a fifth strict review of the backend:

  • New folders: mkdir creates them as 755 directly (umask 022) instead of chmoding by name, which a swapped-in symlink could redirect to a root-owned file.
  • Unmount, Abort and Close: they act only on what VeraCrypt lists for the job or request. A favorite's configured slot holding another volume is never force-unmounted, and nothing is unmounted when the volume isn't listed.
  • Mounted containers: header restore and password change get the same in-use check as create and mount. That covers the router's own disk, its eMMC (including fitblk boot) and devices VeraCrypt has mounted. Mounted containers are neither restored over nor deleted.
  • Job limit: at most 4, now reserved atomically under flock.
  • Hidden create and header restore: the container is hard-linked into the private directory, so it stays at its name throughout. Filesystems without hard links fall back to the rename. The recovery record lives in the private directory, so a container interrupted by a reboot is put back by the next create in that folder.
  • Smaller fixes:
    • Partition names are matched exactly (sda1 vs sda10).
    • A normal create on a block device doesn't need --size.
    • The unmount lookup passes paths via ENVIRON.
    • Favorites need a mountpoint directly under /mnt.
    • A deferred Abort also works during package installs.
    • A failed run call no longer leaves the dialog stuck.
  • Translations: a regex literal I had added broke i18n-scan.pl ("unterminated string"). It's replaced by a helper, and all 240 strings are in the pot.

Tests: the backend suite passes 197/197, with new t-97. ESLint and the formality check are clean. A full run on an RT-AX53U (mipsel_24kc, OpenWrt 25.12.5) with real veracrypt passes 42/42 through ubus, including the new checks (single link after hidden create; rm/restore of a mounted container refused; wrong-slot unmount refused).

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by Claude Code


Generated by Claude Code

@flatstik
flatstik force-pushed the luci-app-veracrypt branch from 002dc9f to 4b6a06f Compare October 3, 2026 07:30
@flatstik

flatstik commented Oct 3, 2026

Copy link
Copy Markdown
Author

Pushed 4b6a06f:

  • The "Properties" button on a mounted volume only showed "Finished."
    • The dialog never displayed the output for this action; it now shows it, with copy/save.
    • VeraCrypt's --volume-properties ignores --slot and lists every mounted volume. The backend now finds the volume in veracrypt --list and passes its listed path, so a favorite whose configured slot holds another volume shows the right properties.
    • Not mounted → a clear error.
  • openwrt-ai nit: pin_recover in hard-link mode, see the reply above.

The backend suite passes 201/201. A run on an RT-AX53U (mipsel_24kc, 25.12.5) with real veracrypt passes 43/43, including properties of a mounted volume requested with a wrong slot.

Comment thread applications/luci-app-veracrypt/root/usr/libexec/rpcd/luci.veracrypt Outdated
@flatstik
flatstik force-pushed the luci-app-veracrypt branch from 4b6a06f to 1cfc984 Compare October 4, 2026 00:10
@flatstik

flatstik commented Oct 4, 2026

Copy link
Copy Markdown
Author

Pushed 1cfc984 with fixes from another strict review of the app:

  • No more silent overwrites:
    • A header backup is refused when the file exists (unless "overwrite" is ticked) or when it is the volume itself.
    • create-keyfile is refused over an existing file.
    • place_file never replaces an existing name unless forced.
  • fsck: Close, Abort and fsck's own error paths unmount only a mapping that the job itself decrypted. A volume that was already mapped stays mapped.
  • Unmount everything: it now needs an explicit all (and a confirmation in the UI). An empty request, or a favorite without paths, no longer unmounts everything.
  • Mount dialog: "Add as favorite" no longer races the page reload; the page reloads after the favorite's UCI apply.
  • Favorites: they pass their keyfiles, so a token PIN is asked for. Auto-mount gives favorites the same device and path checks as a manual mount. The restore guard and Properties also match the path a volume was mounted with.
  • fsck_auto: validated; repair runs only when explicitly requested.
  • Cleanup and consistency:
    • Removed dead code: the unreachable dmsetup branches and unused fields and variables.
    • The package-install allowlist is limited to what the UI offers.
    • Stale comments are corrected, error messages and UI strings made consistent, and the click handlers reindented.
  • Tests: t-50's rm test can now actually fail. New cases cover backup and keyfile no-clobber, unmount-all, and fsck on an existing mapping.

Results:

  • the backend suite passes 211/211;
  • ESLint, the i18n scan and the formality check are clean;
  • an on-device run with real veracrypt on an RT-AX53U (mipsel_24kc, OpenWrt 25.12.5) passes 46/46.

Web UI for the console veracrypt package (packages feed):
openwrt/packages#30597

Not bundled into veracrypt; apk add luci-app-veracrypt depends on
+veracrypt. Calls veracrypt --text only. The current password is
passed on stdin (--stdin), never --password. Other secrets (new
password, hidden volume password, token PIN) are answered to
veracrypt's prompts one at a time through a private FIFO; values with
line breaks are refused, and secrets are kept out of the environment.

All paths are confined to /mnt/<name>/... (or a whole disk device)
and used in resolved form. Each job has its own random id and state
directory under /var/run, and package installation needs the separate
luci-app-veracrypt-pkg ACL grant. File names, paths and veracrypt
output are always shown as text, never parsed as HTML. tests/run.sh
exercises the rpcd backend in an OpenWrt rootfs.

Signed-off-by: Ville Takio <ville+git@takio.fi>
@flatstik
flatstik force-pushed the luci-app-veracrypt branch from 1cfc984 to 004c93e Compare October 4, 2026 12:47
@flatstik

flatstik commented Oct 4, 2026

Copy link
Copy Markdown
Author

Pushed 004c93e with fixes from another full audit of the app, plus the VC_LIST nit above:

  • Filesystems:
    • Creating a FAT volume sends fat. VeraCrypt rejects vfat; older favorites with vfat still work.
    • The package suggestions use real OpenWrt packages: exfat-mkfs/exfat-fsck, ntfs-3g-utils, and dosfstools only for checking FAT, since VeraCrypt formats FAT itself.
  • fsck:
    • The result is judged per tool (e2fsck, fsck.exfat, fsck.fat and ntfsfix use their exit codes differently).
    • NTFS "check only" runs ntfsfix -n.
  • Security tokens: --token-lib is passed for every action that may open token:// keyfiles, right after --text.
  • Favorites and slots:
    • The no-op "No kernel crypto" favorite flag is removed; LuCI always mounts through FUSE.
    • next_slot is the first free slot.
    • A favorite stored with a symlinked path can be unmounted.
  • Backend robustness:
    • kill_tree stops the parent before listing its children.
    • Errors from rm/mkdir are reported correctly.
    • The job-limit refusal has its own message.
  • ACL: a read-only file "list" grant for /mnt and /usr/lib, for the file pickers in the favorites form.
  • UI and texts: UI texts are corrected, the dialog buttons reindented, and a save-favorite error stays visible.
  • Tests:
    • Failure checks first assert that a job started.
    • The stub checks --stdin passwords and slot collisions.
    • Test files are named by topic.

Results:

  • backend suite passes (214 checks, ESLint and i18n scan clean, formality check passes);
  • an RT-AX53U (mipsel_24kc, 25.12.5) run with real veracrypt passes 46/46.

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 new commits; no new issues found.


Generated by Claude Code

@BKPepe BKPepe left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the work on this. I can see you put a lot of effort into handling edge cases and security concerns here.

However, after going through the current implementation, I think the backend has become much more complicated than it needs to be. There is a lot of custom logic around path_has_metas(), new_job(), create_pinned() / pin_recover(), cached veracrypt --list state, prompt handling, etc. Some of these mechanisms are quite elaborate for what should essentially be a thin LuCI wrapper around veracrypt --text.

I am also concerned that this complexity is making the code harder to review and maintain. We've already found several correctness and security issues in these additional layers during review, which is a good indication that the implementation is becoming too complex.

I think it would be better to step back and simplify the design rather than continuing to add more handling for individual edge cases. In particular, I'd avoid building a custom security/filesystem/job framework in shell unless there is a clear need for it.

Could you please have another look at the overall approach and see which parts can be removed or significantly simplified? I think this would make the PR much easier to review and maintain in the long term.

@flatstik

flatstik commented Oct 8, 2026 •

Copy link
Copy Markdown
Author

Thanks, @BKPepe. Agreed: the backend grew a job, path and prompt framework in shell, and that is where the review findings came from. I'll rewrite the implementation to be much smaller and simpler:

  • ucode rpcd plugin (like luci, luci.wol or dockerman's) instead of the shell plugin: commands run with argv arrays (no shell, so no quoting or metacharacter handling such as path_has_metas()), JSON is native.
  • One small background helper for the long operations (mount, create and the like take 1-2 minutes on a router because of key derivation, longer than the rpc timeout). It starts veracrypt --text with secrets on stdin, never in argv or logs; a simple per-job directory and lock replace the current job framework.
  • One path check (under /mnt, no .. or control characters, realpath under /mnt, on a mounted disk, new files must not exist and the parent must not be writable by other users) replaces the validators and create_pinned() / pin_recover().
  • No cached state: veracrypt --list --verbose is parsed per request.
  • Prompts: instead of the interactive prompt emulator, the few commands that prompt (change password, header backup/restore, hidden-volume protection, token PIN) get a fixed answer sequence on stdin, with each answer checked to be a single line.

I'll make another PR when the rewrite is done and run-tested on the router.

@flatstik

flatstik commented Oct 8, 2026 •

Copy link
Copy Markdown
Author

I think this "lite" version is just watered-down mickey-mouse software. I would go with the full version. I'm quite sure it will never be upgraded and if it is, I'll probably be the one doing it anyway.

@flatstik flatstik mentioned this pull request Oct 8, 2026
2 tasks done
@flatstik

flatstik commented Oct 8, 2026

Copy link
Copy Markdown
Author

The reduced version is now a separate package, luci-app-veracrypt-lite: #9119. The two PRs are independent, so either or both can be merged; both depend on the veracrypt package in openwrt/packages#30597.

flatstik added a commit to flatstik/luci that referenced this pull request Oct 9, 2026
A small web UI for the console veracrypt package (packages feed):
openwrt/packages#30597

It is an alternative to luci-app-veracrypt
(openwrt#9069): mount, unmount and create
volumes (container files or whole free disks), create keyfiles, and
favorites in /etc/config/veracrypt, which both apps share. Change
password, header backup/restore, hidden volumes, security tokens,
fsck and package installation are only in luci-app-veracrypt. The two
can be installed side by side.

The backend is a ucode rpcd plugin. Mount and create take minutes on
a router, so they run in the background through a small helper that
starts veracrypt --text --non-interactive --stdin with the password on
stdin, never in argv or logs; one job at a time. Volumes, keyfiles and
mount directories must lie under /mnt on a mounted disk, also after
resolving symlinks. tests/run.sh exercises the plugin and the helper
in an OpenWrt rootfs.

Signed-off-by: Ville Takio <ville+git@takio.fi>
flatstik added a commit to flatstik/luci that referenced this pull request Oct 14, 2026
A small web UI for the console veracrypt package (packages feed):
openwrt/packages#30597

It is an alternative to luci-app-veracrypt
(openwrt#9069): mount, unmount and create
volumes (container files or whole free disks), create keyfiles, and
favorites in /etc/config/veracrypt, which both apps share and create
with a uci-defaults script when it is missing. Change password, header
backup/restore, hidden volumes, security tokens, fsck and package
installation are only in luci-app-veracrypt. The two can be installed
side by side.

The backend is a ucode rpcd plugin. Mount and create take minutes on
a router, so they run in the background through a small helper that
starts veracrypt --text --non-interactive --stdin with the password on
stdin, never in argv or logs; one job at a time. Volumes, keyfiles and
mount directories must lie under /mnt on a mounted disk, also after
resolving symlinks. tests/run.sh exercises the plugin and the helper
in an OpenWrt rootfs.

Signed-off-by: Ville Takio <ville+git@takio.fi>
Comment thread applications/luci-app-veracrypt/root/etc/uci-defaults/40_luci-veracrypt Outdated
flatstik added a commit to flatstik/luci that referenced this pull request Oct 14, 2026
A small web UI for the console veracrypt package (packages feed):
openwrt/packages#30597

It is an alternative to luci-app-veracrypt
(openwrt#9069): mount, unmount and create
volumes (container files or whole free disks), create keyfiles, and
favorites in /etc/config/luci-veracrypt, which both apps share and
create with a uci-defaults script when it is missing. Change password,
header backup/restore, hidden volumes, security tokens, fsck and package
installation are only in luci-app-veracrypt. The two can be installed
side by side.

The backend is a ucode rpcd plugin. Mount and create take minutes on
a router, so they run in the background through a small helper that
starts veracrypt --text --non-interactive --stdin with the password on
stdin, never in argv or logs; one job at a time. Volumes, keyfiles and
mount directories must lie under /mnt on a mounted disk, also after
resolving symlinks. tests/run.sh exercises the plugin and the helper
in an OpenWrt rootfs.

Signed-off-by: Ville Takio <ville+git@takio.fi>
@flatstik
flatstik force-pushed the luci-app-veracrypt branch 2 times, most recently from 004c93e to 5f84ab2 Compare October 15, 2026 15:08

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 new commits; no new issues found.


Generated by Claude Code

@BKPepe BKPepe left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend is doing a lot of privileged filesystem and process management in one large shell script. I'd like to see this simplified and split into smaller, testable pieces, preferably using ucode for the RPC backend.

Please reconsider the hardcoded nokernelcrypto option and make the backend choice explicit, with FUSE as a sensible default and kernel crypto as an opt-in when its dependencies are available. The path restrictions should remain security-conscious, but the UI should support explicitly configured keyfiles outside /mnt without granting unrestricted filesystem access. I'd also remove file deletion from the file picker and keep destructive operations separate.

@flatstik

Copy link
Copy Markdown
Author

@BKPepe Thanks. I'll come back to this once openwrt/packages#30597 is merged, since this app depends on its config. Then: an explicit FUSE/kernel-crypto choice (FUSE default), configured keyfiles outside /mnt (e.g. /etc/veracrypt/keyfiles), and deletion as a separate confirmed action instead of in the file picker.

@openwrt-ai openwrt-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 new commits; no new issues found.


Generated by Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

add package Introduces a new package Makefile build script

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants