The future of installation scripts
An introduction to ZSM, a modern, security-first replacement for bash-based installation scripts.
Running curl -fsSL <https://example.com/install.sh> | bash is a common but dangerous pattern. It grants arbitrary, unvetted execution privileges to a remote script, often requiring sudo.
You cannot trust a foreign script running on your local machine without reading it first, but for most people that is not the case they just trust whatever the script contains and run it.
The ZSM Solution
ZSM introduces a Capability-based Security model for installation scripts. Instead of running imperative shell code, you run a .zsm script that declares the permissions it needs. ZSM acts as a complete lifecycle manager.
Please note that this is mainly a proof-of-concept and may not be ideal for most applications, want to improve it? Contribute Here.
Key Features
-
Declarative Permissions: Scripts must explicitly request access to the network, filesystem, or system configuration.
-
Deep Supervision: Lock external binaries to specific argument patterns (e.g.
require bin: curl --silent). -
User Approval & Persistence: Users review and approve specific capabilities before any code runs. Approvals are securely cached based on the script's URL.
-
Full Lifecycle Management: ZSM tracks created files and supports
zsm uninstallfor clean removal, including custom uninstall hooks. -
Small & Portable: Built with Zig 0.16.0 for zero-dependency, high-performance execution.
Getting Started
You can install ZSM via Zoi:
zoi install @zillowe/zsmUsage
ZSM can fetch and run scripts natively or via pipes:
# Native execution (Recommended)
zsm install https://example.com/install.zsm
# Piped execution
curl -fsSL https://example.com/install.zsm | zsmScript Example
ZSM scripts use a declarative format to specify metadata and requirements, followed by safe built-in logic:
#!/usr/bin/env zsm
# :: name: my-app
# :: version: 1.0.0
# :: require bin: gpg, tar
# :: perm net: example.com
# :: perm fs: ~/.local/bin
LATEST_TAG = web.get_json_field("https://example.com/api/latest", "tag_name")
web.download("https://example.com/${LATEST_TAG}.tar.gz", "/tmp/app.tar.gz")
fs.move("/tmp/app", "~/.local/bin/app")Please check out the docs for more information.
