Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ members = [
# xmas-js
"package-manager",
"modules",
"vsys",
"repl",
]

Expand All @@ -30,6 +31,7 @@ rsquickjs = { version = "0.10.0", path = "rsquickjs" }

xmas-js-modules = { path = "modules" }
xmas-package-manager = { path = "package-manager" }
xmas-vsys = { path = "vsys" }

[dependencies]
xmas-js-modules = { workspace = true }
Expand Down
75 changes: 75 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,81 @@ Traditional System Scripts Modern System Scripts with Xmas.JS

---

## 🏗️ Virtual System Layer

Xmas.JS uses a **pluggable virtual system layer** called `vsys` to abstract all system-level operations. This enables:

- 🔒 **Sandboxed execution** for serverless/edge computing
- 💾 **Custom filesystem** implementations (in-memory, virtual, restricted)
- 🌐 **Custom network** implementations (proxied, restricted, mocked)
- � **Custom module loading** (load from DB, bundle, remote URL)
- 🔐 **Fine-grained permissions** control

```
┌─────────────────────────────────────────────────────────────────┐
│ modules (JS Binding Layer) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────────────┐ │
│ │ fs/mod │ │http/mod │ │module/ │ │Other JS Modules │ │
│ │(ModuleDef│ │(ModuleDef│ │loader │ │(Only registration, │ │
│ │ only) │ │ only) │ │resolver │ │ calls vsys) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └─────────┬──────────┘ │
└───────┼────────────┼────────────┼─────────────────┼─────────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ vsys (Virtual System Layer) │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ pub struct VsysVTable { ││
│ │ // Filesystem ││
│ │ pub fs_read, fs_write, fs_stat, fs_readdir, ... ││
│ │ // Network ││
│ │ pub http_request, dns_lookup, ... ││
│ │ // Module Loading (key for serverless!) ││
│ │ pub module_resolve, module_load, module_exists, ... ││
│ │ // Permissions ││
│ │ pub check_fs_permission, check_net_permission, ... ││
│ │ } ││
│ └─────────────────────────────────────────────────────────────┘│
│ ┌───────────────┴───────────────┐ │
│ ▼ ▼ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Default Impl │ │ User Custom Impl │ │
│ │ (std::fs, tokio, │ OR │ (VFS, sandboxed, │ │
│ │ hyper, etc.) │ │ in-memory, etc.) │ │
│ └─────────────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```

### What Problems Does vsys Solve?

| Scenario | Problem Without vsys | With vsys |
| ---------------------- | --------------------------------------------------- | ------------------------------------------------- |
| **Serverless/Edge** | Runtime has full system access, security risk | Sandboxed execution, only expose what you allow |
| **Multi-tenant SaaS** | Tenant A can access Tenant B's files | Each tenant gets isolated virtual filesystem |
| **Database Scripting** | Scripts need real filesystem, deployment complexity | Virtual FS backed by database, zero external deps |
| **Bundled Deploy** | Need node_modules on disk, slow cold start | Load modules from single bundle or remote URL |
| **Testing** | Need real network/files, slow and flaky tests | Mock everything, fast and deterministic |
| **Embedded/IoT** | Heavy system dependencies | Minimal footprint, platform-agnostic |
| **Game Scripting** | Lua-style sandboxing is complex | Built-in isolation, expose only game APIs |

### Example: Secure Serverless Function

```rust
// User's untrusted code can only:
// - Read from /app/data (virtual, mapped to S3)
// - Make HTTP requests to allowlisted domains
// - Load modules from pre-bundled package (no filesystem access)
// - No filesystem writes, no arbitrary network access
let runtime = XmasRuntime::new()
.with_vsys(VsysVTable::new()
.fs_read_only(s3_virtual_fs("/app/data"))
.module_loader(bundled_modules("app.bundle"))
.net_allowlist(&["api.example.com", "cdn.example.com"])
.deny_all_else()
);
```
---

## 📦 Installation

### 🚧 From Binary (Coming soon ❄️)
Expand Down
24 changes: 23 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

# WASM

i decided to use WAMR,
Expand Down Expand Up @@ -144,4 +145,25 @@ Global methods / properties:
- [x] Repl
- [ ] complete method/property name
- [ ] complete globalThis property name
- [x] package manager commands
- [x] package manager commands

---

# Vsys (Virtual System Layer)


- [x] vsys crate
- [x] FsVTable
- [x] Permissions
- [x] ModuleLoaderVTable
- [x] modules/src/fs


- [ ] modules/src/module/package/resolver.rs
- `fs::read`, `Path::is_file()`, `Path::is_dir()`, `Path::exists()`, `read_link()`
- [ ] modules/src/module/package/loader.rs - `std::fs::read`, `File::open`
- [ ] modules/src/module/module/require.rs - `fs::read_to_string`
- [ ] NetVTable
- [ ] EnvVTable

---
1 change: 1 addition & 0 deletions modules/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ keywords = ["quickjs", "ecmascript", "javascript", "es6", "es2020"]

[dependencies]
rsquickjs = { workspace = true, features = ["macro", "either", "phf"] }
xmas-vsys = { path = "../vsys" }
tracing = "0.1.44"
itoa = "1.0.15"
ryu = "1.0.20"
Expand Down
35 changes: 3 additions & 32 deletions modules/src/fetch/security.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,9 @@ use rsquickjs::{Ctx, Error, Exception, Result};
use crate::permissions;

pub fn ensure_url_access(ctx: &Ctx<'_>, uri: &Uri) -> Result<()> {
let permissions = ctx.userdata::<permissions::Permissions>().unwrap();
match &permissions.net {
permissions::BlackOrWhiteList::BlackList(items) => {
if url_match(
&items
.iter()
.map(|e| Uri::try_from(e))
.filter_map(|ruri| match ruri {
Ok(uri) => Some(uri),
Err(_) => None,
})
.collect::<Vec<Uri>>(),
uri,
) {
return Err(url_restricted_error(ctx, "URL denied", uri));
}
}
permissions::BlackOrWhiteList::WhiteList(items) => {
if !url_match(
&items
.iter()
.map(|e| Uri::try_from(e))
.filter_map(|ruri| match ruri {
Ok(uri) => Some(uri),
Err(_) => None,
})
.collect::<Vec<Uri>>(),
uri,
) {
return Err(url_restricted_error(ctx, "URL not allowed", uri));
}
}
let host = uri.host().unwrap_or_default();
if !permissions::check_net_permission(ctx, host) {
return Err(url_restricted_error(ctx, "URL not allowed", uri));
}
Comment on lines 6 to 10
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

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

The fetch security check now uses a simplified permission model that may not match URLs correctly. The old implementation attempted to parse URIs from the whitelist/blacklist items and match them properly. The new implementation only checks the host string, which may not handle URL patterns with ports, paths, or query parameters correctly.

Copilot uses AI. Check for mistakes.
// if let Some(allow_list) = HTTP_ALLOW_LIST.get() {
// if !url_match(allow_list, uri) {
Expand Down
Loading
Loading