Duck ToolBox is a root module (KernelSU, APatch or Magisk) that puts several Android
attestation tools behind one WebUI. The backend is a Cargo workspace of single-purpose crates
compiled into one JSON CLI (duckd); the WebUI is a set of feature folders that the app
discovers at build time. Features only meet through versioned contracts, so each one can be
changed, removed or added on its own.
-
Tricky Store manager: a complete port of KOWX712's Tricky Addon: Update Target List, for whichever keystore module is active:
- Tricky Store:
config.iniwith per-app modes (pkg,pkg!,pkg?) and per-app policy, and the legacytarget.txt+security_patch.txtlayout. - TEESimulator:
config.jsonprofiles (mode, patch levels, identity, auto-include). - OhMyKeymint:
injector.tomlscoop and everyconfig.toml[trust]key.
It has the same features as upstream:
- Target list with search, select all, deselect all and refresh.
- Mode sheet on long press, and per-app policy.
- Actions: select from DenyList (Magisk), deselect unnecessary apps (online list plus Xposed modules), add system apps, auto-add new apps.
- Keyboxes from AOSP, generated "unknown", a local file, the keybox repository (the same iframe protocol) or custom providers (add, edit, remove, reset, import, export).
- Prop handler and Verified Boot hash, schema-driven default policy with Today, help.
- Keyboard shortcuts (Ctrl+A/D/F/S, Esc), the back gesture closing dialogs, and upstream's 23 translations, including right-to-left languages.
- Optionally, a WebUI entry on the keystore module itself, and the TSupport-Advance auto-target stop flag.
- Tricky Store:
-
RKP Workbench: Remote Key Provisioning.
- Builds
AuthenticatedRequestCSRs (DICE + COSE, per the AOSPIRemotelyProvisionedComponentHAL) and talks to the RKP server. - Verifies CSRs offline and exports
keybox.xml, which the Tricky Store manager can install directly. - Device values are read from AOSP build properties.
- Builds
-
Device ID Provisioner: writes attestation device IDs into the Qualcomm Keymaster trusted app through
libQSEEComAPI.so, with a dry-run mode. -
Module & updates: version and device summary, stable and canary updates with changelog, output files, command history, uninstall and reboot.
Every tool is translated into 14 languages: English, Simplified and Traditional Chinese, Japanese, Korean, Spanish, French, German, Italian, Brazilian Portuguese, Russian, Arabic, Vietnamese and Indonesian. Tricky Addon's other translations cover the strings the Tricky Store manager shares with it.
crates/
duck-core/ Shared runtime: paths, JSON envelope, Feature trait, command log,
and a Sysroot so every feature is testable off-device.
duck-platform/ Device adapters: props/resetprop, pm, modules, root managers, HTTP.
duck-rkp/ RKP workbench. duck-device-ids/ Device ID provisioning.
duck-tricky-store/ Keystore module manager. duck-system/ Module lifecycle, updates.
duckd/ The binary: builds its CLI from the compiled-in features.
xtask/ Repository checks (`cargo xtask line-limit`).
ui/src/
app/ Shell: router, app bar, home and settings; discovers features.
core/ Bridge to the root manager, validated duckd client, i18n, theme,
extension points and shared components.
features/<id>/ One folder per tool: index.ts, api.ts (zod), locales/, pages, mock.ts.
components/ui/ shadcn-vue components, restyled for Material 3 Expressive on phones.
module/ Module payload: scripts, then the built WebUI and backend binary.What keeps it modular and resistant to breaking changes:
- Features never import each other. Backend feature crates depend only on
duck-coreandduck-platform. WebUI features depend only oncore/; ESLint (eslint-plugin-boundaries) rejects anything else, and onlycore/bridgemay touch the KernelSU JavaScript API. - Extension points instead of imports. A feature contributes to others through
contributes:keyboxTargets(RKP installs its keybox through the Tricky Store manager),settingsSectionsandhomeWidgets. - Versioned contracts. Every command prints
{ ok, api, command, data, error, ts }.duckd featureslists the compiled-in features and their contract versions; the WebUI disables a tool whose backend is missing or on another contract. It validates every response with zod, so a changed shape fails loudly ascontract_mismatch. - Keystore modules are adapters. Each backend implements
ConfigAdapterand describes its editable policy as a schema; the WebUI renders whatever the schema declares. - Mature tools, no home-grown frameworks.
- Backend: clap, serde, tokio, reqwest, the RustCrypto crates, rcgen and
toml_edit(which keeps comments). - WebUI: Vue 3, vue-router, Pinia, TanStack Query, vue-i18n, zod, Tailwind CSS 4 and shadcn-vue on reka-ui, with VueUse.
- Backend: clap, serde, tokio, reqwest, the RustCrypto crates, rcgen and
- No source file over 600 lines.
cargo xtask line-limitfails CI for any source file in any language, including scripts without an extension; ESLint'smax-linesflags it while editing. - Dependency updates are reviewable. Dependabot groups minor and patch updates; every major update gets its own pull request and a full CI run.
- Backend: create
crates/duck-<name>with aclapsubcommand enum and apub static FEATURE: ClapFeature<Command>(seeduck-system). Add it toduckdas an optional dependency and cargo feature, and one entry in theFEATURESarray incrates/duckd/src/main.rs. - WebUI: create
ui/src/features/<id>/with anindex.tsexportingdefineFeature({ id, namespace, icon, order, routes, messages }), anapi.tsthat callsduckd()with zod schemas, alocales/<language>.jsonfor each of the 14 languages (src/locales.test.tsfails on a missing key), and optionally amock.ts. The home page, router, i18n, availability check and dev mock pick it up automatically.
| Stage | What runs |
|---|---|
customize.sh |
Checks the root manager, ABI and API level; prepares /data/adb/duck-toolbox; normalizes the boot hash; drops action.sh where the manager has a WebUI button; requests hot install from metamodules that support it. |
post-fs-data.sh / late-load.sh |
Early pass of the sensitive-prop handler (resetprop -n, never setprop). KernelSU's late-load mode runs late-load.sh instead of post-fs-data.sh. |
service.sh |
Repairs the runtime directory. On Magisk, which has no boot-completed stage, it waits for boot and runs boot-completed.sh. |
boot-completed.sh |
Late prop pass, keystore-module entry links, auto-add of new apps, the module description (KernelSU temporary override.description), and the TSupport-Advance flag. |
action.sh (Magisk) |
Opens the WebUI in KSUWebUIStandalone or WebUI X, installing KSUWebUIStandalone if neither is present. |
uninstall.sh |
Removes entry links, flags, the boot hash and /data/adb/duck-toolbox. |
Saved data lives in /data/adb/duck-toolbox/var, outside the module directory, so updates
keep it.
Updates. The stable channel follows updateJson in module.prop. The canary channel
reads the newest CI artifact through nightly.link: CI uploads the module directory as
duck-toolbox-<version>-<commit count>-canary, and the commit count is its versionCode.
duckd features | describe
duckd rkp profile show|save|clear|detect
duckd rkp info | provision | keybox | verify <file>
duckd device-ids defaults | provision
duckd tricky-store status | save | props | auto-apply
duckd tricky-store keybox install <file> | import | set-aosp | generate | fetch --url <u> [--decode <steps>]
duckd tricky-store keybox providers list | save | reset | import <file> | import-content | export [--path <file>]
duckd tricky-store apps xposed | denylist | unnecessary [--refresh]
duckd tricky-store entry status | enable | disable | apply | remove
duckd system info | update check|install [--channel stable|canary] | artifacts | files --path <dir> --extension <ext>
duckd system log [--limit <n>] | open-url <url> | uninstall | rebootCommands that take input read JSON from stdin with --stdin-json.
Requirements:
- Rust stable with the
aarch64-linux-androidtarget (rust-toolchain.tomlinstalls it). - Android NDK r30.
- Node.js 24 and pnpm 12 (
corepack enablepicks the version fromui/package.json). Type checking runs the TypeScript 7 engine through typescript-native-bridge, because stock TypeScript 7 no longer has the API vue-tsc needs.
cargo xtask line-limit
cargo fmt --all --check && cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
# Device binary, e.g. with the NDK's clang as linker
cargo build --release --package duckd --target aarch64-linux-android
cd ui
pnpm install --frozen-lockfile
pnpm lint && pnpm format:check && pnpm test
pnpm build # type-check, then bundle into module/webroot
pnpm test:contract # every API call through duckctl.sh and a host duckd build
pnpm test:e2e # Playwright against a mock KernelSU host, with screenshots
pnpm dev # the same mock host in a desktop browser
pnpm locales:import <Tricky-Addon>/webui/public/locales/strings # refresh translationsOn Windows, pwsh ./scripts/build.ps1 -PackageModule builds everything with cargo-ndk
and writes the module zip to dist/.
On a real KernelSU. scripts/device-test.sh <module zip> prepares a disposable
userdebug emulator (or any device with root adbd) and runs ui/e2e-device there:
- KernelSU is late-loaded with
ksud late-load, so no boot image is patched. - The module and the Tricky Store, TEESimulator and OhMyKeymint releases are installed
with
ksud module install. - For each keystore module, only that one is enabled and the device reboots.
- The tests drive the WebUI inside the KernelSU manager through Playwright's Android support. They read the files back over adb, and check each daemon's own log to confirm it accepted what the WebUI wrote.
- The Tricky Store manager, its translations and the keybox repository protocol come from KOWX712's Tricky Addon: Update Target List (Apache-2.0).
- The bundled software keybox is AOSP's reference attestation key
(
system/keymaster/contexts/soft_attestation_cert.cpp).