You are not logged in.
Hi,
I built Textile, an open source desktop intelligence engine and voice companion for Linux. It hooks the Gemini Live API up to Model Context Protocol tool execution, with a small Quickshell face UI so you get some visual feedback while it's working.
Most of the desktop AI assistants are either web wrappers or are just extremely slow. I wanted a solution that was fast, native to Linux, and capable of real-time interaction. I chose to follow open closed principle.
That's the whole goal behind the plugin system. You write a Yarn which is a class that groups related capabilities and then mark methods as Strands with a @strand decorator. Textile handles the schema validation using Pydantic v2, wires it into both the MCP server and the voice agent, and isolates anything risky in its own subprocess so a bad tool call don't crash the rest of the assistant. Then Weave and any MCP client and execute these strands seamlessly without any further tweaking.
The naming "Yarn, Strand, Loom, Warp, Weft, Tapestry..." is a whole textile metaphor, I just wanted a good naming theme so I chose this, I like it so much though.
Here a some components of the Textile, with their use:
Twill — the MCP server (system stats, process management)
Weave — the voice agent, LiveKit + Gemini Live(you can choose any Gemini model that LiveKit supports, default is Gemini 3.8 Live).
Loom — dispatches tool calls and enforces the tier/permission model
Canvas — the Quickshell UI that shows what the agent's doing, it also has fluid animations too.(my favourite)
Repo: https://github.com/themazyworlds/textile
I Would genuinely like feedback on the Yarn/Strand API, still not sure I've got the design right for people writing their own plugins although that's the part I've spent the most time working on.
Online
This project appears to have been vibe coded but there is not LLM attributions for the commits? The MCP server combined with the policy kit rules allow arbitrary commands to be executed as root without user intervention?
Offline
For the polkit thing, it's just a yarn(plugin) designed to provide one functionality, and can be disabled in one second. You can design your own yarns easily(check it out on the repo). If you want to disable the polkit yarn easily.
And about vive coding, I will be honest, I generated some parts of the codebase with the help of ai in which I was stuck but every single line was reviewed and tested, strict Pyright and Ruff rules are followed.
Online
polkit thing, it's just a yarn(plugin) designed to provide one functionality, and can be disabled in one second
"This is basically a remote root shell, but that's useless and you can easily disable it without any documentation - so it's gonna be fine"…
Did you ask the clanker who wrote this what one functionality that is and why it allows executing random commands if it's only relevant for *one* action?
help of ai in which I was stuck … every single line was reviewed and tested
Maybe ask the clanker to write your posts for you as well - it probably speaks English.
"reviewed and tested" by who and how exactly? How do you even review and test isolated LOC?
generated some parts of the codebase with the help of ai
The "codebase" is basically a massive initial code dump and then very rapid refactoring commits on top of that.
How much of that would you say did you author yourself, manually, w/o *any* AI intervention/consultation and why do the commits not say "co-authored by clanker foo"?
Offline
Textile is not a llm wrapper for bash commands. It calls strands(functions) within the yarns(plugins). It does have a yarn "dev_shell" which gives it bash access but it is completely unprivileged and has zero connection to polkit and is only used when strand equivalents are not available. Though you are right about Polkit, I will add clean Polkit popup for those operations instead of passwordless execution. Again, the yarns are not wrappers for raw bash commands.
The source code follows strict Pyright and Ruff rules(except S606 and S603 because of subprocess).
Online
For the polkit thing, it's just a yarn(plugin) designed to provide one functionality, and can be disabled in one second. You can design your own yarns easily(check it out on the repo). If you want to disable the polkit yarn easily.
What is that one thing? In the main README there is no mention of what appears to be the fundamental assumption of the project that the agent == user == root.
I believe it would take more second to resolve the security issues; LLM superficial scan:
RCE / injection:
- system_core/dev_shell.py:24,36,44: run_command shell=True arbitrary bash.
- system_core/process.py:38: launch_app(app) shell=True; :81-90 os.kill(pid,sig) any PID incl 1/-1, no ownership check; :148 setpriority any PID.
- compositor/hyprland.py:873-882: launch_app -> Hyprland exec (shell); :83-88,312-430 raw Lua string interpolation e.g. workspace="{ws}" — "};os.execute()-- escape; :868 exit kills session, :702 close_window destroys work.
- protocols/ydotool.py:90-147,197-243: type/paste/press_key arbitrary text+Enter into focused terminal/sudo prompt.
- protocols/atspi.py:650-677,728-814: set_text/insert_text/generate_key/click into password/polkit dialogs.
Priv-esc / persistence:
- protocols/dbus_system.py:238-280,105-123: arbitrary system bus destination/path/interface/method/args with ALLOW_INTERACTIVE_AUTHORIZATION trigger systemd1/login1/UDisks2 auth dialogs for user-tricking.
- protocols/polkit.py:235-274,195-233: generate_rule/policy(output_path) arbitrary write e.g. /etc/polkit-1/rules.d/*.rules, default Result.
YES, action_pattern="*" (:419), JS injection via action.id.match(/{pattern}/) + subject.user=="{u}". Exposed as MUTATE not PRIVILEGED.
- system_core/packagekit.py:469-488: arbitrary install/remove incl. removal with allow_deps=True.
- session/uwsm.py:87-101: stop/finalize session logout/teardown, no confirm.
FS / supply-chain:
- system_core/filesystem.py:315-318,426-457,357-367: _resolve() no jail; read/write/replace/chmod/stat/list/find any path (/etc/shadow,~/.ssh, ~/.bashrc), symlink-following, mkdir -p; find unbounded os.walk(/) DoS; chdir global state; inotify recursive watch fd-exhaustion.
- core/skein.py:23-27,79-99,57-69: ~/.config/textile/yarns/*.py exec_module overrides builtins + entry_points(group="textile.yarns") auto- load — file_write -> persistent code exec.
- core/base.py:755-799, core/isolated_runner.py:31-44: isolated = same-uid uv run --with <attacker-dep> + PYTHONPATH=cwd, full FS/net — not a sandbox.
Exfil / SSRF:
- protocols/atspi.py:199-208,353-453,484-491: full get_tree/find/focused incl. password Text, no redaction.
- protocols/clipboard.py:29-40: clipboard_get verbatim secrets.
- system_core/screen_vision.py:37-68: fullscreen grim/mss capture.
- protocols/journal.py:128-194: query_logs 200 lines/call repeatable, verbatim MESSAGE.
- system_core/web_research.py:122-155: fetch_webpage(url) no block for localhost/127.0.0.1/169.254.169.254, no redirect/port check.
MCP exposure: core/twill.py:50-69 + core/loom.py:106-135 publish+execute all strands to any stdio client, no auth; caller from spoofable
TEXTILE_CALLER; yarns/weave.py:75-89,127-137 hands full toolset to cloud voice LLM with parent env creds. Offline