use mlua::Lua;
use serde_json::{Value, json};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::fs;
use std::io::Read;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
#[cfg(unix)]
use std::os::unix::fs::OpenOptionsExt;
#[cfg(windows)]
use std::os::windows::ffi::OsStrExt;
#[cfg(windows)]
use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
use std::path::{Component, Path, PathBuf};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{
Arc, Mutex, MutexGuard, OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak,
};
#[cfg(windows)]
use windows_sys::Win32::Foundation::{GENERIC_READ, INVALID_HANDLE_VALUE};
#[cfg(windows)]
use windows_sys::Win32::Storage::FileSystem::{
BY_HANDLE_FILE_INFORMATION, CreateFileW, FILE_ATTRIBUTE_REPARSE_POINT,
FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT, FILE_READ_ATTRIBUTES,
FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, GetFileInformationByHandle,
OPEN_EXISTING,
};
static NEXT_MANAGED_RUNTIME_OWNER_TOKEN: AtomicU64 = AtomicU64::new(0);
static MANAGED_RUNTIME_OWNER_STATES: OnceLock<Mutex<HashMap<u64, Weak<ManagedRuntimeOwnerState>>>> =
OnceLock::new();
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ManagedFilesystemObjectIdentity {
#[cfg(unix)]
pub(crate) device: u64,
#[cfg(unix)]
pub(crate) inode: u64,
#[cfg(windows)]
pub(crate) volume_serial_number: u32,
#[cfg(windows)]
pub(crate) file_index: u64,
}
struct ManagedDependencyManifestContext {
path: PathBuf,
filesystem_identity: ManagedFilesystemObjectIdentity,
manifest: PackageDependencyManifest,
}
use crate::runtime::managed_runtime::ManagedRuntimeRoots;
use crate::runtime::path::render_host_visible_path;
use crate::skill::dependencies::PackageDependencyManifest;
use crate::skill::manifest::validate_luaskills_identifier;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) enum ManagedRuntimePackageKind {
Skill,
SystemPlugin,
}
impl ManagedRuntimePackageKind {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Skill => "skill",
Self::SystemPlugin => "system_plugin",
}
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub(crate) struct ManagedRuntimePackageIdentity {
kind: ManagedRuntimePackageKind,
package_id: String,
canonical_package_root: PathBuf,
}
impl ManagedRuntimePackageIdentity {
pub(crate) fn kind(&self) -> ManagedRuntimePackageKind {
self.kind
}
pub(crate) fn package_id(&self) -> &str {
&self.package_id
}
pub(crate) fn package_root(&self) -> &Path {
&self.canonical_package_root
}
pub(crate) fn stable_hash(&self) -> String {
let mut hasher = Sha256::new();
hasher.update(self.kind.as_str().as_bytes());
hasher.update([0]);
hasher.update(self.package_id.as_bytes());
hasher.update([0]);
hash_path_identity(&mut hasher, &self.canonical_package_root);
format!("{:x}", hasher.finalize())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ManagedRuntimeLeaseIdentity {
lease_id: String,
generation: u64,
}
impl ManagedRuntimeLeaseIdentity {
pub(crate) fn lease_id(&self) -> &str {
&self.lease_id
}
pub(crate) fn generation(&self) -> u64 {
self.generation
}
}
#[derive(Debug)]
pub(crate) struct ManagedRuntimeLeaseBinding {
sid: String,
workspace_root: Option<PathBuf>,
mounts: Value,
identity: RwLock<Option<ManagedRuntimeLeaseIdentity>>,
}
impl ManagedRuntimeLeaseBinding {
pub(crate) fn new(sid: String, workspace_root: Option<PathBuf>, mounts: Value) -> Self {
Self {
sid,
workspace_root,
mounts,
identity: RwLock::new(None),
}
}
pub(crate) fn bind(&self, lease_id: String, generation: u64) -> Result<(), String> {
let mut identity = write_lease_identity(&self.identity);
if identity.is_some() {
return Err(format!(
"managed runtime lease binding for SID `{}` is already initialized",
self.sid
));
}
*identity = Some(ManagedRuntimeLeaseIdentity {
lease_id,
generation,
});
Ok(())
}
pub(crate) fn sid(&self) -> &str {
&self.sid
}
pub(crate) fn workspace_root(&self) -> Option<&Path> {
self.workspace_root.as_deref()
}
pub(crate) fn mounts(&self) -> &Value {
&self.mounts
}
pub(crate) fn identity(&self) -> Option<ManagedRuntimeLeaseIdentity> {
read_lease_identity(&self.identity).clone()
}
}
#[derive(Debug)]
pub(crate) struct ManagedRuntimePackageContext {
identity: ManagedRuntimePackageIdentity,
package_root_filesystem_identity: ManagedFilesystemObjectIdentity,
managed_runtime_roots: Arc<ManagedRuntimeRoots>,
dependency_manifest_path: Option<PathBuf>,
dependency_manifest_filesystem_identity: Option<ManagedFilesystemObjectIdentity>,
dependency_manifest: Option<Arc<PackageDependencyManifest>>,
lease_binding: Option<Arc<ManagedRuntimeLeaseBinding>>,
owner_token: u64,
owner_state: Arc<ManagedRuntimeOwnerState>,
}
#[derive(Debug)]
pub(crate) struct ManagedRuntimeOwnerState {
retired: AtomicBool,
}
impl ManagedRuntimeOwnerState {
pub(crate) fn is_retired(&self) -> bool {
self.retired.load(Ordering::Acquire)
}
fn retire(&self) {
self.retired.store(true, Ordering::Release);
}
}
impl ManagedRuntimePackageContext {
#[cfg(test)]
pub(crate) fn for_skill(
package_id: &str,
package_root: &Path,
runtime_root: &Path,
dependency_manifest: Option<PackageDependencyManifest>,
) -> Result<Arc<Self>, String> {
let compatible_roots = Arc::new(ManagedRuntimeRoots::new(runtime_root, None, None)?);
Self::for_skill_with_roots(
package_id,
package_root,
compatible_roots,
dependency_manifest,
)
}
pub(crate) fn for_skill_with_roots(
package_id: &str,
package_root: &Path,
managed_runtime_roots: Arc<ManagedRuntimeRoots>,
dependency_manifest: Option<PackageDependencyManifest>,
) -> Result<Arc<Self>, String> {
let canonical_package_root = canonicalize_existing_directory(package_root, "package root")?;
managed_runtime_roots.validate_live_filesystem_identity()?;
let dependency_manifest_path = dependency_manifest
.as_ref()
.map(|_| {
resolve_existing_package_file(
&canonical_package_root,
"dependencies.yaml",
"dependency manifest",
)
})
.transpose()?;
let dependency_manifest_context =
match (dependency_manifest, dependency_manifest_path.as_deref()) {
(Some(prepared_manifest), Some(path)) => {
let (current_manifest, identity) =
load_managed_dependency_manifest_from_fixed_object(path)?;
if current_manifest != prepared_manifest {
return Err(
"dependency manifest changed after dependency preparation".to_string()
);
}
Some(ManagedDependencyManifestContext {
path: path.to_path_buf(),
filesystem_identity: identity,
manifest: current_manifest,
})
}
(None, None) => None,
_ => {
return Err(
"dependency manifest path and parsed value must be present together"
.to_string(),
);
}
};
Self::build(
ManagedRuntimePackageKind::Skill,
package_id,
canonical_package_root,
managed_runtime_roots,
dependency_manifest_context,
None,
)
}
#[cfg(test)]
pub(crate) fn for_system_plugin(
package_id: &str,
package_root: &Path,
runtime_root: &Path,
system_lua_root: &Path,
dependency_file: &str,
lease_binding: Arc<ManagedRuntimeLeaseBinding>,
) -> Result<Arc<Self>, String> {
let compatible_roots = Arc::new(ManagedRuntimeRoots::new(runtime_root, None, None)?);
Self::for_system_plugin_with_roots(
package_id,
package_root,
compatible_roots,
system_lua_root,
dependency_file,
lease_binding,
)
}
pub(crate) fn for_system_plugin_with_roots(
package_id: &str,
package_root: &Path,
managed_runtime_roots: Arc<ManagedRuntimeRoots>,
system_lua_root: &Path,
dependency_file: &str,
lease_binding: Arc<ManagedRuntimeLeaseBinding>,
) -> Result<Arc<Self>, String> {
if !package_root.is_absolute() {
return Err("system_package.root must be an absolute path".to_string());
}
let canonical_system_lua_root =
canonicalize_existing_directory(system_lua_root, "system_lua_lib root")?;
let canonical_package_root =
canonicalize_existing_directory(package_root, "system_package.root")?;
if canonical_package_root == canonical_system_lua_root
|| !canonical_package_root.starts_with(&canonical_system_lua_root)
{
return Err(format!(
"system_package.root {} must be a strict descendant of system_lua_lib root {}",
render_host_visible_path(&canonical_package_root),
render_host_visible_path(&canonical_system_lua_root)
));
}
validate_lua_search_root_path(&canonical_package_root, "system_package.root")?;
managed_runtime_roots.validate_live_filesystem_identity()?;
let dependency_manifest_path = resolve_existing_package_file(
&canonical_package_root,
dependency_file,
"system_package.dependencies_file",
)?;
let (dependency_manifest, dependency_manifest_filesystem_identity) =
load_managed_dependency_manifest_from_fixed_object(&dependency_manifest_path)?;
Self::build(
ManagedRuntimePackageKind::SystemPlugin,
package_id,
canonical_package_root,
managed_runtime_roots,
Some(ManagedDependencyManifestContext {
path: dependency_manifest_path,
filesystem_identity: dependency_manifest_filesystem_identity,
manifest: dependency_manifest,
}),
Some(lease_binding),
)
}
fn build(
kind: ManagedRuntimePackageKind,
package_id: &str,
canonical_package_root: PathBuf,
managed_runtime_roots: Arc<ManagedRuntimeRoots>,
dependency_manifest_context: Option<ManagedDependencyManifestContext>,
lease_binding: Option<Arc<ManagedRuntimeLeaseBinding>>,
) -> Result<Arc<Self>, String> {
let normalized_package_id = package_id.trim();
validate_luaskills_identifier(normalized_package_id, "package_id")?;
let package_root_filesystem_identity = managed_filesystem_object_identity(
&canonical_package_root,
"package root",
ManagedFilesystemObjectKind::Directory,
)?;
let (
dependency_manifest_path,
dependency_manifest_filesystem_identity,
dependency_manifest,
) = match dependency_manifest_context {
Some(context) => (
Some(context.path),
Some(context.filesystem_identity),
Some(context.manifest),
),
None => (None, None, None),
};
let owner_token = allocate_managed_runtime_owner_token()?;
let owner_state = Arc::new(ManagedRuntimeOwnerState {
retired: AtomicBool::new(false),
});
register_managed_runtime_owner_state(owner_token, &owner_state);
Ok(Arc::new(Self {
identity: ManagedRuntimePackageIdentity {
kind,
package_id: normalized_package_id.to_string(),
canonical_package_root,
},
package_root_filesystem_identity,
managed_runtime_roots,
dependency_manifest_path,
dependency_manifest_filesystem_identity,
dependency_manifest: dependency_manifest.map(Arc::new),
lease_binding,
owner_token,
owner_state,
}))
}
pub(crate) fn identity(&self) -> &ManagedRuntimePackageIdentity {
&self.identity
}
pub(crate) fn package_root(&self) -> &Path {
self.identity.package_root()
}
pub(crate) fn package_root_filesystem_identity(&self) -> &ManagedFilesystemObjectIdentity {
&self.package_root_filesystem_identity
}
#[cfg(test)]
pub(crate) fn runtime_root(&self) -> &Path {
self.managed_runtime_roots.runtime_root()
}
pub(crate) fn managed_runtime_roots(&self) -> Arc<ManagedRuntimeRoots> {
Arc::clone(&self.managed_runtime_roots)
}
pub(crate) fn dependency_manifest_path(&self) -> Option<&Path> {
self.dependency_manifest_path.as_deref()
}
pub(crate) fn dependency_manifest(&self) -> Option<&PackageDependencyManifest> {
self.dependency_manifest.as_deref()
}
pub(crate) fn lease_binding(&self) -> Option<&Arc<ManagedRuntimeLeaseBinding>> {
self.lease_binding.as_ref()
}
pub(crate) fn owner_token(&self) -> u64 {
self.owner_token
}
pub(crate) fn owner_state(&self) -> Weak<ManagedRuntimeOwnerState> {
Arc::downgrade(&self.owner_state)
}
pub(crate) fn validate_live_filesystem_identity(&self) -> Result<(), String> {
validate_unchanged_filesystem_identity(
self.package_root(),
&self.package_root_filesystem_identity,
"package root",
ManagedFilesystemObjectKind::Directory,
)?;
self.managed_runtime_roots
.validate_live_filesystem_identity()?;
if let Some(manifest_path) = self.dependency_manifest_path() {
let expected_identity = self
.dependency_manifest_filesystem_identity
.as_ref()
.ok_or_else(|| "dependency manifest native identity is unavailable".to_string())?;
validate_unchanged_filesystem_identity(
manifest_path,
expected_identity,
"dependency manifest",
ManagedFilesystemObjectKind::File,
)?;
}
Ok(())
}
pub(crate) fn resolve_existing_file(
&self,
relative_path: &str,
field_name: &str,
) -> Result<PathBuf, String> {
resolve_existing_package_file(self.package_root(), relative_path, field_name)
}
pub(crate) fn worker_context_json(&self) -> Value {
let mut context = json!({
"package": {
"kind": self.identity.kind().as_str(),
"id": self.identity.package_id(),
"root": render_host_visible_path(self.package_root()),
}
});
if let Some(binding) = self.lease_binding() {
let identity = binding.identity();
context["system_plugin"] = json!({
"lease_id": identity.as_ref().map(ManagedRuntimeLeaseIdentity::lease_id),
"sid": binding.sid(),
"generation": identity.as_ref().map(ManagedRuntimeLeaseIdentity::generation),
});
context["workspace_root"] = binding
.workspace_root()
.map(render_host_visible_path)
.map(Value::String)
.unwrap_or(Value::Null);
context["mounts"] = binding.mounts().clone();
}
context
}
}
fn allocate_managed_runtime_owner_token() -> Result<u64, String> {
NEXT_MANAGED_RUNTIME_OWNER_TOKEN
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
current.checked_add(1)
})
.map(|previous| previous + 1)
.map_err(|_| "managed runtime owner token space is exhausted".to_string())
}
pub(crate) fn validate_lua_search_root_path(path: &Path, field_name: &str) -> Result<(), String> {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
let bytes = path.as_os_str().as_bytes();
if bytes.iter().any(|byte| matches!(*byte, b';' | b'?' | 0)) {
return Err(format!(
"{field_name} contains a character reserved by Lua package search paths: {}",
render_host_visible_path(path)
));
}
}
#[cfg(windows)]
{
use std::os::windows::ffi::OsStrExt;
let units = path.as_os_str().encode_wide().collect::<Vec<_>>();
let path_body = if units.starts_with(&[0x005C, 0x005C, 0x003F, 0x005C]) {
&units[4..]
} else {
units.as_slice()
};
if path_body
.iter()
.any(|unit| matches!(*unit, 0x003B | 0x003F | 0))
{
return Err(format!(
"{field_name} contains a character reserved by Lua package search paths: {}",
render_host_visible_path(path)
));
}
}
#[cfg(not(any(unix, windows)))]
{
let text = path.as_os_str().to_string_lossy();
if text.contains([';', '?', '\0']) {
return Err(format!(
"{field_name} contains a character reserved by Lua package search paths: {}",
render_host_visible_path(path)
));
}
}
Ok(())
}
#[derive(Clone, Copy)]
enum ManagedFilesystemObjectKind {
Directory,
File,
}
fn validate_unchanged_filesystem_identity(
expected_path: &Path,
expected_identity: &ManagedFilesystemObjectIdentity,
label: &str,
kind: ManagedFilesystemObjectKind,
) -> Result<(), String> {
let current = fs::canonicalize(expected_path).map_err(|error| {
format!(
"failed to revalidate {label} {}: {error}",
render_host_visible_path(expected_path)
)
})?;
if current != expected_path {
return Err(format!(
"{label} identity changed after package activation: expected {}, got {}",
render_host_visible_path(expected_path),
render_host_visible_path(¤t)
));
}
let actual_identity = managed_filesystem_object_identity(¤t, label, kind)?;
if &actual_identity != expected_identity {
return Err(format!(
"{label} filesystem object changed after package activation: {}",
render_host_visible_path(¤t)
));
}
Ok(())
}
pub(crate) fn capture_managed_directory_identity(
path: &Path,
label: &str,
) -> Result<ManagedFilesystemObjectIdentity, String> {
managed_filesystem_object_identity(path, label, ManagedFilesystemObjectKind::Directory)
}
pub(crate) fn validate_managed_directory_identity(
path: &Path,
identity: &ManagedFilesystemObjectIdentity,
label: &str,
) -> Result<(), String> {
validate_unchanged_filesystem_identity(
path,
identity,
label,
ManagedFilesystemObjectKind::Directory,
)
}
fn load_managed_dependency_manifest_from_fixed_object(
path: &Path,
) -> Result<(PackageDependencyManifest, ManagedFilesystemObjectIdentity), String> {
#[cfg(unix)]
let (mut file, identity) = {
let file = fs::OpenOptions::new()
.read(true)
.custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC)
.open(path)
.map_err(|error| {
format!(
"failed to open dependency manifest {}: {error}",
render_host_visible_path(path)
)
})?;
let metadata = file.metadata().map_err(|error| {
format!(
"failed to inspect dependency manifest handle {}: {error}",
render_host_visible_path(path)
)
})?;
if !metadata.is_file() {
return Err(format!(
"dependency manifest is not a regular file: {}",
render_host_visible_path(path)
));
}
let identity = ManagedFilesystemObjectIdentity {
device: metadata.dev(),
inode: metadata.ino(),
};
(file, identity)
};
#[cfg(windows)]
let (mut file, identity) = {
let mut wide_path = path.as_os_str().encode_wide().collect::<Vec<_>>();
wide_path.push(0);
let raw_handle = unsafe {
CreateFileW(
wide_path.as_ptr(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
std::ptr::null(),
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT,
std::ptr::null_mut(),
)
};
if raw_handle == INVALID_HANDLE_VALUE {
return Err(format!(
"failed to open dependency manifest {}: {}",
render_host_visible_path(path),
std::io::Error::last_os_error()
));
}
let file = unsafe { fs::File::from_raw_handle(raw_handle as _) };
let mut information: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
if unsafe { GetFileInformationByHandle(file.as_raw_handle() as _, &mut information) } == 0 {
return Err(format!(
"failed to read dependency manifest identity {}: {}",
render_host_visible_path(path),
std::io::Error::last_os_error()
));
}
if information.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 {
return Err(format!(
"dependency manifest must not be a reparse point: {}",
render_host_visible_path(path)
));
}
let identity = ManagedFilesystemObjectIdentity {
volume_serial_number: information.dwVolumeSerialNumber,
file_index: (u64::from(information.nFileIndexHigh) << 32)
| u64::from(information.nFileIndexLow),
};
(file, identity)
};
#[cfg(not(any(unix, windows)))]
let (mut file, identity) = {
let _ = path;
return Err("fixed-object dependency manifest reads are unsupported".to_string());
};
let mut yaml_text = String::new();
file.read_to_string(&mut yaml_text).map_err(|error| {
format!(
"failed to read dependency manifest {}: {error}",
render_host_visible_path(path)
)
})?;
let manifest = serde_yaml::from_str(&yaml_text).map_err(|error| {
format!(
"failed to parse dependency manifest {}: {error}",
render_host_visible_path(path)
)
})?;
Ok((manifest, identity))
}
fn managed_filesystem_object_identity(
path: &Path,
label: &str,
kind: ManagedFilesystemObjectKind,
) -> Result<ManagedFilesystemObjectIdentity, String> {
let metadata = fs::symlink_metadata(path).map_err(|error| {
format!(
"failed to inspect {label} {}: {error}",
render_host_visible_path(path)
)
})?;
if metadata.file_type().is_symlink() {
return Err(format!(
"{label} must not be a symbolic link: {}",
render_host_visible_path(path)
));
}
let kind_matches = match kind {
ManagedFilesystemObjectKind::Directory => metadata.is_dir(),
ManagedFilesystemObjectKind::File => metadata.is_file(),
};
if !kind_matches {
return Err(format!(
"{label} has an unexpected filesystem object type: {}",
render_host_visible_path(path)
));
}
#[cfg(unix)]
{
Ok(ManagedFilesystemObjectIdentity {
device: metadata.dev(),
inode: metadata.ino(),
})
}
#[cfg(windows)]
{
let mut wide_path = path.as_os_str().encode_wide().collect::<Vec<_>>();
wide_path.push(0);
let raw_handle = unsafe {
CreateFileW(
wide_path.as_ptr(),
FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
std::ptr::null(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
std::ptr::null_mut(),
)
};
if raw_handle == INVALID_HANDLE_VALUE {
return Err(format!(
"failed to open {label} identity handle {}: {}",
render_host_visible_path(path),
std::io::Error::last_os_error()
));
}
let handle = unsafe { OwnedHandle::from_raw_handle(raw_handle as _) };
let mut information: BY_HANDLE_FILE_INFORMATION = unsafe { std::mem::zeroed() };
if unsafe { GetFileInformationByHandle(raw_handle, &mut information) } == 0 {
return Err(format!(
"failed to read {label} filesystem identity {}: {}",
render_host_visible_path(path),
std::io::Error::last_os_error()
));
}
let _handle_owner = handle;
Ok(ManagedFilesystemObjectIdentity {
volume_serial_number: information.dwVolumeSerialNumber,
file_index: (u64::from(information.nFileIndexHigh) << 32)
| u64::from(information.nFileIndexLow),
})
}
#[cfg(not(any(unix, windows)))]
{
let _ = metadata;
Err(format!(
"platform-native {label} filesystem identity is unsupported: {}",
render_host_visible_path(path)
))
}
}
fn register_managed_runtime_owner_state(
owner_token: u64,
owner_state: &Arc<ManagedRuntimeOwnerState>,
) {
let mut states = lock_managed_runtime_owner_states();
states.retain(|_, state| state.strong_count() > 0);
states.insert(owner_token, Arc::downgrade(owner_state));
}
pub(crate) fn retire_managed_runtime_owner_state(owner_token: u64) -> bool {
let owner_state = {
let mut states = lock_managed_runtime_owner_states();
states.retain(|_, state| state.strong_count() > 0);
states.get(&owner_token).and_then(Weak::upgrade)
};
if let Some(owner_state) = owner_state {
owner_state.retire();
true
} else {
false
}
}
fn lock_managed_runtime_owner_states()
-> MutexGuard<'static, HashMap<u64, Weak<ManagedRuntimeOwnerState>>> {
MANAGED_RUNTIME_OWNER_STATES
.get_or_init(|| Mutex::new(HashMap::new()))
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn hash_path_identity(hasher: &mut Sha256, path: &Path) {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
hasher.update(path.as_os_str().as_bytes());
}
#[cfg(windows)]
{
use std::os::windows::ffi::OsStrExt;
for unit in path.as_os_str().encode_wide() {
hasher.update(unit.to_le_bytes());
}
}
#[cfg(not(any(unix, windows)))]
{
hasher.update(path.as_os_str().to_string_lossy().as_bytes());
}
}
#[derive(Clone, Default)]
pub(crate) struct ManagedRuntimePackageContextSlot {
context: Option<Arc<ManagedRuntimePackageContext>>,
}
pub(crate) fn replace_lua_managed_package_context(
lua: &Lua,
context: Option<Arc<ManagedRuntimePackageContext>>,
) -> Option<Arc<ManagedRuntimePackageContext>> {
lua.set_app_data(ManagedRuntimePackageContextSlot { context })
.and_then(|slot| slot.context)
}
pub(crate) fn current_lua_managed_package_context(
lua: &Lua,
api_name: &str,
) -> Result<Arc<ManagedRuntimePackageContext>, mlua::Error> {
let context = lua
.app_data_ref::<ManagedRuntimePackageContextSlot>()
.and_then(|slot| slot.context.clone());
context.ok_or_else(|| mlua::Error::runtime(format!("{api_name}: no active package context")))
}
pub(crate) fn optional_lua_managed_package_context(
lua: &Lua,
) -> Option<Arc<ManagedRuntimePackageContext>> {
lua.app_data_ref::<ManagedRuntimePackageContextSlot>()
.and_then(|slot| slot.context.clone())
}
fn resolve_existing_package_file(
canonical_package_root: &Path,
relative_path: &str,
field_name: &str,
) -> Result<PathBuf, String> {
let path = Path::new(relative_path);
if relative_path.trim().is_empty()
|| path.is_absolute()
|| !path
.components()
.all(|component| matches!(component, Component::Normal(_)))
{
return Err(format!(
"{field_name} must be a non-empty safe path under the package root"
));
}
let candidate = canonical_package_root.join(path);
let canonical_candidate = fs::canonicalize(&candidate).map_err(|error| {
format!(
"failed to canonicalize {field_name} {}: {error}",
render_host_visible_path(&candidate)
)
})?;
if !canonical_candidate.starts_with(canonical_package_root) {
return Err(format!(
"{field_name} {} escapes package root {}",
render_host_visible_path(&canonical_candidate),
render_host_visible_path(canonical_package_root)
));
}
let metadata = fs::metadata(&canonical_candidate).map_err(|error| {
format!(
"failed to inspect {field_name} {}: {error}",
render_host_visible_path(&canonical_candidate)
)
})?;
if !metadata.is_file() {
return Err(format!(
"{field_name} is not a file: {}",
render_host_visible_path(&canonical_candidate)
));
}
Ok(canonical_candidate)
}
fn canonicalize_existing_directory(path: &Path, field_name: &str) -> Result<PathBuf, String> {
let canonical = fs::canonicalize(path).map_err(|error| {
format!(
"failed to canonicalize {field_name} {}: {error}",
render_host_visible_path(path)
)
})?;
let metadata = fs::metadata(&canonical).map_err(|error| {
format!(
"failed to inspect {field_name} {}: {error}",
render_host_visible_path(&canonical)
)
})?;
if !metadata.is_dir() {
return Err(format!(
"{field_name} is not a directory: {}",
render_host_visible_path(&canonical)
));
}
Ok(canonical)
}
fn read_lease_identity(
identity: &RwLock<Option<ManagedRuntimeLeaseIdentity>>,
) -> RwLockReadGuard<'_, Option<ManagedRuntimeLeaseIdentity>> {
identity
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
fn write_lease_identity(
identity: &RwLock<Option<ManagedRuntimeLeaseIdentity>>,
) -> RwLockWriteGuard<'_, Option<ManagedRuntimeLeaseIdentity>> {
identity
.write()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::{SystemTime, UNIX_EPOCH};
fn test_root(label: &str) -> PathBuf {
let suffix = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("test clock should be after epoch")
.as_nanos();
std::env::temp_dir().join(format!("luaskills-managed-package-{label}-{suffix}"))
}
#[test]
fn system_plugin_context_uses_canonical_trust_root() {
let runtime_root = test_root("canonical");
let system_root = runtime_root.join("system_lua_lib");
let package_root = system_root.join("vulcan-debug");
fs::create_dir_all(&package_root).expect("create package root");
fs::write(package_root.join("dependencies.yaml"), "{}").expect("write manifest");
let binding = Arc::new(ManagedRuntimeLeaseBinding::new(
"system.debug.workspace".to_string(),
None,
json!({}),
));
let context = ManagedRuntimePackageContext::for_system_plugin(
"vulcan-debug",
&package_root,
&runtime_root,
&system_root,
"dependencies.yaml",
binding,
)
.expect("build System Plugin context");
assert_eq!(
context.package_root(),
fs::canonicalize(&package_root).expect("canonical package root")
);
assert!(context.dependency_manifest().is_some());
let _ = fs::remove_dir_all(&runtime_root);
}
#[test]
fn system_plugin_context_rejects_root_outside_trust_anchor() {
let runtime_root = test_root("outside");
let system_root = runtime_root.join("system_lua_lib");
let package_root = runtime_root.join("outside-plugin");
fs::create_dir_all(&system_root).expect("create system root");
fs::create_dir_all(&package_root).expect("create outside package root");
fs::write(package_root.join("dependencies.yaml"), "{}").expect("write manifest");
let binding = Arc::new(ManagedRuntimeLeaseBinding::new(
"system.debug.workspace".to_string(),
None,
json!({}),
));
let error = ManagedRuntimePackageContext::for_system_plugin(
"vulcan-debug",
&package_root,
&runtime_root,
&system_root,
"dependencies.yaml",
binding,
)
.expect_err("outside package root should fail");
assert!(error.contains("must be a strict descendant of system_lua_lib root"));
let _ = fs::remove_dir_all(&runtime_root);
}
#[test]
fn package_file_resolver_rejects_parent_traversal() {
let package_root = test_root("parent-traversal");
fs::create_dir_all(&package_root).expect("create package root");
let canonical_root = fs::canonicalize(&package_root).expect("canonical package root");
let error = resolve_existing_package_file(&canonical_root, "../outside.py", "session.file")
.expect_err("parent traversal should fail");
assert!(error.contains("must be a non-empty safe path"));
let _ = fs::remove_dir_all(&package_root);
}
#[test]
fn system_plugin_context_rejects_lua_search_path_metacharacters() {
let runtime_root = test_root("search-metacharacters");
let system_root = runtime_root.join("system_lua_lib");
let package_root = system_root.join("plugin;injected-template");
fs::create_dir_all(&package_root).expect("create metacharacter package root");
fs::write(package_root.join("dependencies.yaml"), "{}")
.expect("write metacharacter package manifest");
let binding = Arc::new(ManagedRuntimeLeaseBinding::new(
"system.search.metacharacters".to_string(),
None,
json!({}),
));
let error = ManagedRuntimePackageContext::for_system_plugin(
"search-metacharacters",
&package_root,
&runtime_root,
&system_root,
"dependencies.yaml",
binding,
)
.expect_err("Lua package search metacharacters must be rejected");
assert!(error.contains("reserved by Lua package search paths"));
let _ = fs::remove_dir_all(&runtime_root);
}
#[test]
fn package_context_revalidation_rejects_removed_trust_root() {
let runtime_root = test_root("live-root-revalidation");
let package_root = runtime_root.join("package");
fs::create_dir_all(&package_root).expect("create revalidation package root");
let context = ManagedRuntimePackageContext::for_skill(
"live-root-revalidation",
&package_root,
&runtime_root,
None,
)
.expect("create revalidation package context");
fs::remove_dir_all(&package_root).expect("remove activated package root");
let error = context
.validate_live_filesystem_identity()
.expect_err("removed activated root must fail revalidation");
assert!(error.contains("failed to revalidate package root"));
let _ = fs::remove_dir_all(&runtime_root);
}
#[test]
fn package_context_revalidation_rejects_same_path_directory_replacement() {
let runtime_root = test_root("same-path-package-replacement");
let package_root = runtime_root.join("package");
let original_root = runtime_root.join("original-package");
fs::create_dir_all(&package_root).expect("create original package root");
let context = ManagedRuntimePackageContext::for_skill(
"same-path-package-replacement",
&package_root,
&runtime_root,
None,
)
.expect("create package context");
fs::rename(&package_root, &original_root).expect("move original package root");
fs::create_dir(&package_root).expect("create replacement package root");
let error = context
.validate_live_filesystem_identity()
.expect_err("same-path replacement must fail native identity validation");
assert!(error.contains("package root filesystem object changed"));
let _ = fs::remove_dir_all(&runtime_root);
}
#[test]
fn package_context_revalidation_rejects_same_path_manifest_replacement() {
let runtime_root = test_root("same-path-manifest-replacement");
let system_root = runtime_root.join("system_lua_lib");
let package_root = system_root.join("identity-plugin");
let manifest_path = package_root.join("dependencies.yaml");
let original_manifest = package_root.join("original-dependencies.yaml");
fs::create_dir_all(&package_root).expect("create manifest package root");
fs::write(&manifest_path, "{}").expect("write original manifest");
let binding = Arc::new(ManagedRuntimeLeaseBinding::new(
"system.identity.replacement".to_string(),
None,
json!({}),
));
let context = ManagedRuntimePackageContext::for_system_plugin(
"identity-plugin",
&package_root,
&runtime_root,
&system_root,
"dependencies.yaml",
binding,
)
.expect("create System package context");
fs::rename(&manifest_path, &original_manifest).expect("move original manifest");
fs::write(&manifest_path, "{}").expect("write replacement manifest");
let error = context
.validate_live_filesystem_identity()
.expect_err("same-path manifest replacement must fail native identity validation");
assert!(error.contains("dependency manifest filesystem object changed"));
let _ = fs::remove_dir_all(&runtime_root);
}
#[test]
fn skill_context_rejects_manifest_changed_after_preparation() {
let runtime_root = test_root("prepared-manifest-replacement");
let package_root = runtime_root.join("package");
let manifest_path = package_root.join("dependencies.yaml");
fs::create_dir_all(&package_root).expect("create prepared-manifest package root");
fs::write(
&manifest_path,
"python_runtime:\n version: '3.12.8'\n package_manager: uv\n package_manager_version: '0.6.0'\n lockfile: requirements.lock\n",
)
.expect("write prepared manifest");
let prepared_manifest = PackageDependencyManifest::load_from_path(&manifest_path)
.expect("parse prepared manifest");
fs::write(
&manifest_path,
"node_runtime:\n version: '24.18.0'\n package_manager: pnpm\n package_manager_version: '11.11.0'\n package_json: package.json\n lockfile: pnpm-lock.yaml\n",
)
.expect("replace prepared manifest contents");
let error = ManagedRuntimePackageContext::for_skill(
"prepared-manifest-replacement",
&package_root,
&runtime_root,
Some(prepared_manifest),
)
.expect_err("changed prepared manifest must fail activation");
assert!(error.contains("changed after dependency preparation"));
let _ = fs::remove_dir_all(&runtime_root);
}
#[test]
fn managed_runtime_owner_state_registry_prunes_expired_entries() {
let runtime_root = test_root("owner-state-pruning");
let package_root = runtime_root.join("package");
fs::create_dir_all(&package_root).expect("create owner-state package root");
let mut retired_tokens = Vec::new();
for _iteration in 0..128 {
let context = ManagedRuntimePackageContext::for_skill(
"owner-state-pruning",
&package_root,
&runtime_root,
None,
)
.expect("create owner-state package context");
assert!(retire_managed_runtime_owner_state(context.owner_token()));
retired_tokens.push(context.owner_token());
drop(context);
}
let final_context = ManagedRuntimePackageContext::for_skill(
"owner-state-pruning",
&package_root,
&runtime_root,
None,
)
.expect("create final owner-state package context");
let states = lock_managed_runtime_owner_states();
assert!(
retired_tokens
.iter()
.all(|owner_token| !states.contains_key(owner_token))
);
assert!(states.contains_key(&final_context.owner_token()));
drop(states);
drop(final_context);
let _ = fs::remove_dir_all(&runtime_root);
}
#[cfg(unix)]
#[test]
fn package_file_resolver_rejects_symlink_escape() {
use std::os::unix::fs::symlink;
let root = test_root("symlink");
let package_root = root.join("package");
let outside_file = root.join("outside.py");
fs::create_dir_all(&package_root).expect("create package root");
fs::write(&outside_file, "print('outside')").expect("write outside file");
symlink(&outside_file, package_root.join("escape.py")).expect("create escape symlink");
let canonical_root = fs::canonicalize(&package_root).expect("canonical package root");
let error = resolve_existing_package_file(&canonical_root, "escape.py", "session.file")
.expect_err("symlink escape should fail");
assert!(error.contains("escapes package root"));
let _ = fs::remove_dir_all(&root);
}
}