files made by a run as administrator no longer lock the user out: explicit user-SID entry and owner on create (private_mkdir, restrict_to_current_user), process default owner/DACL set to the user, self-repair of Strata's own per-user folders (APPDATA, LOCALAPPDATA, ~/.strata) on read, at host start and by 'strata repair-access', plain message when it cannot; tests; tune_session loopback allowed in the no-acquisition audit

This commit is contained in:
Niko1221 committed 2026-10-16 10:10:43 +02:00
1 parent e1dca4bff5
commit df13760cf4
29 files changed
+532 -29

No files matched your search

+9
View File
@@ -217,6 +217,15 @@ export function createControl({ control = findControl(), env = process.env, run
if (result.code !== 0 || !result.json?.ok) throw failure(result, 'The engine did not start.');
return result.json;
},
/** Give the user back Strata files in their own folders that a run as administrator locked (`strata repair-access`). Never throws. */
async repairAccess() {
try {
const result = await call(['repair-access', '--output', 'json'], { timeoutMs: 60_000 });
return result.json ?? { ok: false, error: result.stderr };
} catch (error) {
return { ok: false, error: String(error?.message ?? error) };
}
},
/** End servers and engines Strata started whose owner is gone (each is re-checked by PID, start time and command line). */
async cleanup() {
const result = await call(['runtime', 'cleanup', '--output', 'json'], { timeoutMs: 120_000 });
+2
View File
@@ -83,6 +83,8 @@ const imageModels = createImageModels({ dataDir: store.DATA_DIR, modelsDir: () =
/* The installed app ships Strata Control (python + strata_control) next to the host; a source checkout has none and keeps its older paths. */
const CONTROL = findControl();
const control = createControl({ control: CONTROL });
// Files a run as administrator made in Strata's own folders (owner Administrators) are given back to the user at every start; best effort, never blocks.
if (control && process.platform === 'win32' && !process.env.STRATA_NO_REPAIR_ACCESS) void control.repairAccess().catch(() => {});
const appUpdater = createAppUpdater();
const native=createNativeControl({control,appUpdater,hardwareProbe:()=>(hardware.cachedHardware()?.gpus??[]).map((g,n)=>({index:g.index??n,vram_gb:g.vramGb})),directory:store.DATA_DIR,modelsDirectory:()=>store.MODELS_DIR,nativeDataDirectory:store.nativeDataDirectory,beforeLoad:async()=>{if(models.REAL&&models.library().loaded){if(serving.state().requests.some(r=>runtimeModels.state().instances.some(i=>i.id===r.instanceId)&&["queued","generating"].includes(r.state)))throw new Error("Finish requests before switching engines.");await models.unload();runtimeState();}},requests:()=>serving.state().requests,changed:value=>hub.send('native',value)});
/* The agent: chats become DeepSeek Harness sessions when that component is installed. It is a separate
+7 -2
View File
@@ -215,7 +215,12 @@ export function createNativeControl({directory,requests=()=>[],changed=()=>{},sp
const startWith=value=>{writeFileSync(configPath,JSON.stringify(value,null,2),{mode:0o600});return control.stop(instance.profile).catch(()=>{}).then(()=>control.saveProfile(instance.profile,configPath)).then(()=>control.start(instance.profile,{waitSeconds:300}));};
started=startWith(cfg);
started.then(()=>append(id,'Strata Control reports the engine is ready.'),error=>{append(id,error.message);});
try{await started;}catch(error){const said=humanStartError(error);
try{await started;}catch(firstError){let error=firstError;
/* A settings, profile or run file an elevated run made can be closed to this user: repair the access in Strata's own folders and start again (this is not the memory try below). */
const isAccess=e=>/PermissionError|access is denied|EACCES/i.test(String(e?.message??'')+JSON.stringify(e?.output??''));
if(isAccess(error)&&typeof control.repairAccess==='function'&&job.state==='running'){await control.repairAccess();append(id,'Strata repaired access to its own files; starting again.');try{await startWith(cfg);started=Promise.resolve();error=null;}catch(again){error=again;}}
if(error){const said=humanStartError(error);
if(isAccess(error)){instance.status='error';instance.error=said;emit();throw new Error(said);} // access errors are not memory stops: no safer retry
/* ONE automatic second try in all, never saved, never repeated (args.retrySafer false turns it off): with a safer memory setting when the
failure looks like a memory stop, and without several-requests-at-once when that was the app's own default (not a number the user chose).
Both changes ride on the same single try, with the plain sentence of the memory change when there is one. */
@@ -228,7 +233,7 @@ export function createNativeControl({directory,requests=()=>[],changed=()=>{},sp
retried={words,detail};if(dropParallel){instance.parallel=1;if(!safer)parallelNote=FALLBACK_NOTE;}
append(id,`The first start stopped (${said}). Starting once more: ${detail}`);job.summary=words;job.details=[said,`Second try: ${detail}`];instance.notice=words;emit();
try{await startWith(retryConfig);append(id,'Strata Control reports the engine is ready (second try).');}
catch(second){const said2=humanStartError(second);instance.status='error';instance.error=said2===said?said2:`${said2} (after one automatic second try with a safer setting: ${detail}; the first stop was: ${said})`;emit();throw new Error(instance.error);}}
catch(second){const said2=humanStartError(second);instance.status='error';instance.error=said2===said?said2:`${said2} (after one automatic second try with a safer setting: ${detail}; the first stop was: ${said})`;emit();throw new Error(instance.error);}}}
} else {
proc=spawnProcess(py,[join(repo(),'serve/server.py'),'--engine','strata','--config',configPath,'--port',String(cfg.port),'--host','127.0.0.1'],{cwd:repo(),detached:process.platform!=='win32',stdio:['ignore','pipe','pipe'],env:{...process.env,PYTHONUNBUFFERED:'1',PYTHONIOENCODING:'utf-8',STRATA_API_KEY:''}});
instances.set(id,instance);processes.set(id,proc);if(!defaultId||!args.multiple||wasDefault)defaultId=id;
+2
View File
@@ -21,4 +21,6 @@ export function makePrivate(path, { platform = process.platform } = {}) {
// Grant first, then drop every inherited entry, so the file is never left without an owner entry.
execFileSync(system32('icacls.exe'), [path, '/grant:r', `*${currentUserSid()}:F`, '/inheritance:r'],
{ stdio: 'ignore', timeout: 15000, windowsHide: true });
// The owner too, so a file an elevated run made is not left owned by Administrators (needs the right to; best effort).
try { execFileSync(system32('icacls.exe'), [path, '/setowner', `*${currentUserSid()}`], { stdio: 'ignore', timeout: 15000, windowsHide: true }); } catch { /* the explicit entry above already opens it to the user */ }
}
+1
View File
@@ -16,6 +16,7 @@ const MEMORY_NEXT = 'Close other programs and try again, or choose “Leave 20%
/** Order matters: the first rule that matches wins. */
const RULES: Rule[] = [
{ kind: 'picture-reading', match: /vision files|picture reading/i, message: 'Picture reading isn’t available for this model.', next: 'Turn off picture reading and try again.' },
{ kind: 'access', match: /PermissionError|access is denied|can.t open its settings file/i, message: 'Strata can’t open its settings file.', next: 'Run Strata once as yourself (not as administrator) or press Repair.' },
{ kind: 'memory', match: /server_exited|engine stopped|exited before|died before|out of memory|memory guard|\boom\b|killed/i, message: MEMORY, next: MEMORY_NEXT },
{ kind: 'slow-load', match: /loading took more than/i, message: 'Loading is taking too long.', next: 'The PC may be short on memory. Close other programs and try again.' },
{ kind: 'engine-missing', match: /no engine for this pc|engine component|never downloads an engine|engine .*not installed|no strata engine/i, message: 'Strata’s engine for this computer isn’t installed yet.', next: 'It is added when you download a model. Download your model again to add it.' },
+3 -1
View File
@@ -2671,5 +2671,7 @@
"s2670": "Einstellungen anwenden und laden",
"s2671": "Anwenden & laden",
"s2672": "Die Modelldateien sind bereits auf diesem Computer. Es wird nichts heruntergeladen, nur Ihre gewählten Einstellungen werden angewendet.",
"s2673": "Aktuell geladene native Modelle werden zuerst entladen."
"s2673": "Aktuell geladene native Modelle werden zuerst entladen.",
"s2674": "Strata kann seine Einstellungsdatei nicht öffnen.",
"s2675": "Starte Strata einmal als du selbst (nicht als Administrator) oder drücke auf Reparieren."
}
+3 -1
View File
@@ -2671,5 +2671,7 @@
"s2670": "Apply settings and load",
"s2671": "Apply & load",
"s2672": "The model files are already on this computer. Nothing will be downloaded; only the settings you chose are applied.",
"s2673": "Any currently loaded native models will be unloaded first."
"s2673": "Any currently loaded native models will be unloaded first.",
"s2674": "Strata can’t open its settings file.",
"s2675": "Run Strata once as yourself (not as administrator) or press Repair."
}
+3 -1
View File
@@ -2671,5 +2671,7 @@
"s2670": "Aplicar ajustes y cargar",
"s2671": "Aplicar y cargar",
"s2672": "Los archivos del modelo ya están en este ordenador. No se descargará nada; solo se aplican los ajustes que elegiste.",
"s2673": "Los modelos nativos cargados actualmente se descargarán primero."
"s2673": "Los modelos nativos cargados actualmente se descargarán primero.",
"s2674": "Strata no puede abrir su archivo de ajustes.",
"s2675": "Ejecuta Strata una vez como tú mismo (no como administrador) o pulsa Reparar."
}
+3 -1
View File
@@ -2671,5 +2671,7 @@
"s2670": "設定を適用して読み込む",
"s2671": "適用して読み込む",
"s2672": "モデルのファイルはこのコンピューターにすでにあります。ダウンロードは行われず、選んだ設定だけが適用されます。",
"s2673": "現在読み込まれているネイティブモデルは先にアンロードされます。"
"s2673": "現在読み込まれているネイティブモデルは先にアンロードされます。",
"s2674": "Strata が設定ファイルを開けません。",
"s2675": "管理者ではなくご自身のアカウントで一度 Strata を起動するか、「修復」を押してください。"
}
+3 -1
View File
@@ -2671,5 +2671,7 @@
"s2670": "설정 적용 후 불러오기",
"s2671": "적용 후 불러오기",
"s2672": "모델 파일이 이미 이 컴퓨터에 있습니다. 아무것도 다운로드하지 않으며 선택한 설정만 적용됩니다.",
"s2673": "현재 불러온 네이티브 모델은 먼저 언로드됩니다."
"s2673": "현재 불러온 네이티브 모델은 먼저 언로드됩니다.",
"s2674": "Strata가 설정 파일을 열 수 없습니다.",
"s2675": "관리자가 아닌 본인 계정으로 Strata를 한 번 실행하거나 '복구'를 누르세요."
}
+3 -1
View File
@@ -2671,5 +2671,7 @@
"s2670": "Aplicar configurações e carregar",
"s2671": "Aplicar e carregar",
"s2672": "Os arquivos do modelo já estão neste computador. Nada será baixado; apenas as configurações escolhidas serão aplicadas.",
"s2673": "Os modelos nativos atualmente carregados serão descarregados primeiro."
"s2673": "Os modelos nativos atualmente carregados serão descarregados primeiro.",
"s2674": "O Strata não consegue abrir o arquivo de configurações.",
"s2675": "Abra o Strata uma vez como você mesmo (não como administrador) ou pressione Reparar."
}
+3 -1
View File
@@ -2671,5 +2671,7 @@
"s2670": "Применить настройки и загрузить",
"s2671": "Применить и загрузить",
"s2672": "Файлы модели уже на этом компьютере. Ничего не будет скачиваться, применяются только выбранные настройки.",
"s2673": "Сначала будут выгружены все загруженные сейчас нативные модели."
"s2673": "Сначала будут выгружены все загруженные сейчас нативные модели.",
"s2674": "Strata не может открыть файл настроек.",
"s2675": "Запустите Strata один раз от своего имени (не от администратора) или нажмите «Исправить»."
}
+3 -1
View File
@@ -2671,5 +2671,7 @@
"s2670": "应用设置并加载",
"s2671": "应用并加载",
"s2672": "模型文件已在这台电脑上,不会下载任何内容,只会应用你选择的设置。",
"s2673": "当前已加载的本地模型会先被卸载。"
"s2673": "当前已加载的本地模型会先被卸载。",
"s2674": "Strata 无法打开它的设置文件。",
"s2675": "请以你自己的身份(不要用管理员身份)运行一次 Strata,或点击“修复”。"
}
+23 -1
View File
@@ -85,7 +85,7 @@ test('runCli passes the arguments untouched (no shell) and reads JSON', async ()
// ---------------------------------------------------------------------------------- native-control in packaged mode
function packagedFixture({ engineUsable = true, updater = null } = {}) {
function packagedFixture({ engineUsable = true, updater = null, repair = false } = {}) {
const root = mkdtempSync(join(tmpdir(), 'strata-packaged-test-'));
const install = join(root, 'install'), data = join(root, 'data');
mkdirSync(install, { recursive: true });
@@ -112,6 +112,7 @@ function packagedFixture({ engineUsable = true, updater = null } = {}) {
async saveProfile(name, path) { calls.push(['saveProfile', name, basename(path)]); saved.push(JSON.parse(readFileSync(path, 'utf8'))); return {}; },
async start(name, options) { calls.push(['start', name, options?.waitSeconds]); if (startFailure && startFailureLeft-- > 0) { const e = new Error(startFailure.message); e.output = startFailure.output; throw e; } return { ok: true, state: 'ready' }; },
async stop(name) { calls.push(['stop', name]); return { ok: true, state: 'stopped' }; },
...(repair ? { async repairAccess() { calls.push(['repairAccess']); return { ok: true }; } } : {}),
...(updater ? { async updateCheck() { calls.push(['updateCheck']); return updater.report; }, async update(options) { calls.push(['update', options.engine, options.control]); return updater.update(options); } } : {}),
};
const requests = [];
@@ -472,3 +473,24 @@ test('packaged: a tune measures the engine a load starts: same start args, slots
} finally { await f.native.close(); f.cleanup(); }
}
});
test('packaged: a profile an elevated run locked (PermissionError) is repaired once and the start is tried again; without repair it is a plain message, not a memory retry', async () => {
const f = packagedFixture({ repair: true });
try {
await f.native.refresh(true);
f.failStart({ message: 'cannot read profile (PermissionError)' }, 1);
const job = await finish(f.native, f.native.operation('load', { model: 'iq2_xs' }).id);
assert.equal(job.state, 'done', job.error);
assert.equal(f.calls.filter(c => c[0] === 'repairAccess').length, 1);
assert.equal(f.calls.filter(c => c[0] === 'start').length, 2);
} finally { await f.native.close(); f.cleanup(); }
const g = packagedFixture({ repair: true });
try {
await g.native.refresh(true);
g.failStart({ message: 'cannot read profile (PermissionError)' });
const job = await finish(g.native, g.native.operation('load', { model: 'iq2_xs' }).id);
assert.equal(job.state, 'error');
assert.equal(g.calls.filter(c => c[0] === 'start').length, 2, 'one repair try, no memory retry on top');
assert.match(job.error, /PermissionError/);
} finally { await g.native.close(); g.cleanup(); }
});
+5
View File
@@ -7340,6 +7340,11 @@ def _component_exe(reference: str, cfg: dict) -> str:
def main(argv=None, *, lifecycle_event=None, interrupt_exit_code: int = 0) -> int:
try: # files made by a run as administrator still belong to the user
from strata_control import private_files as _pf
_pf.adopt_user_defaults()
except Exception:
pass
event_sink = lifecycle_event
def notify_lifecycle(kind: str, payload: dict) -> None:
+5
View File
@@ -6108,6 +6108,11 @@ def preview_family(a) -> int:
def main() -> int:
try: # files made by a run as administrator still belong to the user
from strata_control import private_files as _pf
_pf.adopt_user_defaults()
except Exception:
pass
if sys.platform == "darwin": # a Mac: metal/setup_mac.py (the Metal engine)
return mac_setup(sys.argv[1:])
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+7 -1
View File
@@ -6,6 +6,12 @@ sampled from telemetry; missing signals stay unknown. It does not tune a model.
from __future__ import annotations
try:
from strata_control.private_files import private_mkdir as _private_mkdir
except ImportError: # a thin bundle (monitor only) ships without strata_control
def _private_mkdir(path, *, parents=False, exist_ok=True):
path.mkdir(mode=0o700, parents=parents, exist_ok=exist_ok)
return path
import hashlib
import http.client
import json
@@ -782,7 +788,7 @@ def run(endpoint: Endpoint, telemetry: Endpoint, out: Path, *, suite: str = "ser
raise ProfileError(f"cancellation concurrency must cover all {slots} effective slots", 2)
elif slots < 2 or concurrency < 2:
raise ProfileError("prefill cancellation suite requires at least two effective slots and concurrency >= 2", 5)
out.mkdir(mode=0o700, parents=False)
_private_mkdir(out, parents=False, exist_ok=False)
phases = []
started = _utc_now()
planned_phases = _planned_phase_kinds(suite, repeat)
+44
View File
@@ -104,6 +104,8 @@ def parser() -> argparse.ArgumentParser:
profile_rollback.add_argument("--to", required=True, metavar="REVISION")
profile_rollback.add_argument("--output", choices=("human", "json"), default="human")
repair = sp.add_parser("repair-access", help="give you back Strata files that a run as administrator locked (Windows)")
repair.add_argument("--output", choices=("human", "json"), default="human")
doctor = sp.add_parser("doctor", help="read-only check of installed engine components")
doctor.add_argument("--profile", help="saved profile name or run config JSON path")
doctor.add_argument("--components", action="store_true", help="include component inventory (default)")
@@ -1782,12 +1784,43 @@ def _install(a: argparse.Namespace) -> int:
cwd=str(installer.parent))
def _access_retry(a: argparse.Namespace, error: BaseException) -> int | None:
"""A file in Strata's own per-user folders that this user cannot open (a run as administrator made it) is repaired once
and the command runs again; if that does not help the person gets one plain sentence, with the technical text after it."""
try:
from strata_control import private_files
except ImportError:
return None
if not (isinstance(error, PermissionError) or "PermissionError" in str(error)):
return None
as_json = bool(getattr(a, "json", False) or getattr(a, "output", None) in ("json", "jsonl"))
if getattr(a, "_access_retried", False):
return _error(f"{private_files.ACCESS_MESSAGE} (details: {error})", 5, as_json)
a._access_retried = True
private_files.repair_known_roots()
return _run(a)
def _repair_access(a: argparse.Namespace) -> int:
from strata_control import private_files
report = private_files.repair_known_roots()
if getattr(a, "output", "human") == "json":
print(json.dumps({"ok": not report["failed"], **report}))
else:
print(f"checked {report['checked']} entries, repaired {report['repaired']}, could not repair {report['failed']}")
if report["failed"]:
print(private_files.ACCESS_MESSAGE)
return 0 if not report["failed"] else 5
def _run(a: argparse.Namespace) -> int:
try:
if a.command == "serve":
return _serve(a)
if a.command == "install":
return _install(a)
if a.command == "repair-access":
return _repair_access(a)
if a.command == "profile":
return _profile_command(a)
if a.command == "components" and a.components_command == "plan":
@@ -1822,6 +1855,9 @@ def _run(a: argparse.Namespace) -> int:
getattr(a, "output", None) in ("json", "jsonl"))
return monitor_commands.run(a)
except ValueError as e:
retried = _access_retry(a, e)
if retried is not None:
return retried
if a.command == "serve" and a.output == "jsonl":
return _serve_error(a, str(e), getattr(e, "exit_code", 2))
return _error(str(e), getattr(e, "exit_code", 2),
@@ -1829,6 +1865,9 @@ def _run(a: argparse.Namespace) -> int:
except BrokenPipeError:
return 0
except OSError as e:
retried = _access_retry(a, e)
if retried is not None:
return retried
if a.command == "serve" and a.output == "jsonl":
return _serve_error(a, f"local file operation failed ({type(e).__name__})", 6)
return _error(f"local file operation failed ({type(e).__name__})", 6,
@@ -1867,6 +1906,11 @@ class _QuietCapture:
def main(argv: list[str] | None = None) -> int:
try:
from strata_control import private_files
private_files.adopt_user_defaults() # what this process creates belongs to the user even when it runs as administrator
except ImportError: # a thin bundle ships without strata_control
pass
ap = parser()
a = ap.parse_args(argv)
if getattr(a, "json", False) and a.command not in ("status", "monitor", "logs", "connection", "config"):
+7 -1
View File
@@ -6,6 +6,12 @@ CLI resolve the same saved connection without importing server or installer code
from __future__ import annotations
try:
from strata_control.private_files import private_mkdir as _private_mkdir
except ImportError: # a thin bundle (monitor only) ships without strata_control
def _private_mkdir(path, *, parents=False, exist_ok=True):
path.mkdir(mode=0o700, parents=parents, exist_ok=exist_ok)
return path
import hashlib
import json
import os
@@ -61,7 +67,7 @@ def connection_dir(*, create: bool = False) -> Path:
else:
directory = Path(os.environ.get("XDG_CONFIG_HOME") or Path.home() / ".config") / "strata" / "connections"
if create:
directory.mkdir(mode=0o700, parents=True, exist_ok=True)
_private_mkdir(directory, parents=True, exist_ok=True)
if directory.exists() or directory.is_symlink():
details = directory.lstat()
if not stat.S_ISDIR(details.st_mode) or (os.name == "posix" and
+2 -1
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
from strata_control.private_files import private_mkdir as _private_mkdir
import hashlib
import errno
import os
@@ -27,7 +28,7 @@ def _lease_file(profile: str, *, create: bool) -> Path | None:
raise ProfileError("benchmark lease needs a saved profile")
directory = profiles.profile_dir()
if create:
directory.mkdir(mode=0o700, parents=True, exist_ok=True)
_private_mkdir(directory, parents=True, exist_ok=True)
elif not directory.exists():
return None
details = directory.lstat()
+3 -2
View File
@@ -7,6 +7,7 @@ hardware; applying it is a separate operation with a backup and smoke generation
from __future__ import annotations
from strata_control.private_files import private_mkdir as _private_mkdir
import copy
import hashlib
import json
@@ -452,7 +453,7 @@ def _backup_directory(path: Path, *, create: bool = False) -> Path:
directory = path.parent / ".strata-calibration-backups"
try:
if create:
directory.mkdir(mode=0o700, exist_ok=True)
_private_mkdir(directory, exist_ok=True)
details = directory.lstat()
except FileNotFoundError:
raise ProfileError("calibration backup directory is missing", 5) from None
@@ -566,7 +567,7 @@ def _apply_locked(ref: str, path: Path, profile: dict | None, revision: str | No
end_path, end_profile, end_revision, end_cfg = _target(ref)
if end_path != path or _identity(end_path, end_cfg, end_profile, end_revision) != report["identity"]:
raise ProfileError("engine, model or hardware changed during smoke generation; config unchanged", 6)
settings_path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
_private_mkdir(settings_path.parent, parents=True, exist_ok=True)
current_settings = settings_path.read_bytes() if settings_path.exists() else None
if current_settings != previous_settings:
raise ProfileError("setup settings changed during calibration; config unchanged", 6)
+2 -1
View File
@@ -7,6 +7,7 @@ crash has unknown completion; it is not proof that a worker is still running.
from __future__ import annotations
from strata_control.private_files import private_mkdir as _private_mkdir
import json
import math
import os
@@ -45,7 +46,7 @@ DIRECT_KINDS = frozenset({"runtime.start", "runtime.stop", "runtime.restart", "r
def _directory(profile: str, *, create: bool = False) -> Path:
directory = recipes._directory(profile, create=create) / ".runs"
if create:
directory.mkdir(mode=0o700, exist_ok=True)
_private_mkdir(directory, exist_ok=True)
if directory.exists() or directory.is_symlink():
details = directory.lstat()
if not stat.S_ISDIR(details.st_mode):
+287 -2
View File
@@ -41,11 +41,21 @@ if WINDOWS:
_advapi.GetNamedSecurityInfoW.restype = wintypes.DWORD
_advapi.ConvertSecurityDescriptorToStringSecurityDescriptorW.argtypes = [
ctypes.c_void_p, wintypes.DWORD, wintypes.DWORD, ctypes.POINTER(wintypes.LPWSTR), ctypes.c_void_p]
_advapi.ConvertStringSidToSidW.argtypes = [wintypes.LPCWSTR, ctypes.POINTER(ctypes.c_void_p)]
_advapi.SetTokenInformation.argtypes = [wintypes.HANDLE, ctypes.c_int, ctypes.c_void_p, wintypes.DWORD]
_advapi.LookupPrivilegeValueW.argtypes = [wintypes.LPCWSTR, wintypes.LPCWSTR, ctypes.c_void_p]
_advapi.AdjustTokenPrivileges.argtypes = [wintypes.HANDLE, wintypes.BOOL, ctypes.c_void_p, wintypes.DWORD,
ctypes.c_void_p, ctypes.c_void_p]
_SE_FILE_OBJECT = 1
_DACL = 0x4
_PROTECTED_DACL = 0x80000000
_TOKEN_QUERY = 0x8
_TOKEN_ADJUST_PRIVILEGES = 0x20
_TOKEN_ADJUST_DEFAULT = 0x80
_TOKEN_OWNER = 4
_TOKEN_DEFAULT_DACL = 6
_OWNER = 0x1
_TOKEN_USER = 1
_SDDL_REVISION_1 = 1
_sid_cache: str | None = None
@@ -109,8 +119,16 @@ def restrict_to_current_user(path: str | os.PathLike, *, directory: bool | None
if not _advapi.GetSecurityDescriptorDacl(descriptor, ctypes.byref(present), ctypes.byref(dacl),
ctypes.byref(defaulted)) or not present.value:
raise _winerr("GetSecurityDescriptorDacl")
code = _advapi.SetNamedSecurityInfoW(path, _SE_FILE_OBJECT, _DACL | _PROTECTED_DACL,
None, None, dacl, None)
# The explicit entry for the user's own SID is what makes the file reachable from a normal (non-elevated) session:
# a file made by an elevated process is owned by BUILTIN\Administrators, and a rule naming only SYSTEM,
# Administrators and OWNER RIGHTS shuts the same person out when UAC filters Administrators from their token.
# So the owner becomes the user too, where Windows allows it; when it does not, the explicit entry still stands.
owner = _user_psid()
code = _advapi.SetNamedSecurityInfoW(path, _SE_FILE_OBJECT, _OWNER | _DACL | _PROTECTED_DACL,
owner, None, dacl, None) if owner else 1
if code:
code = _advapi.SetNamedSecurityInfoW(path, _SE_FILE_OBJECT, _DACL | _PROTECTED_DACL,
None, None, dacl, None)
if code:
raise _winerr(f"SetNamedSecurityInfoW {path}", code)
finally:
@@ -176,3 +194,270 @@ def is_unshared(path: str | os.PathLike) -> bool:
if len(fields) >= 6 and fields[0] in ("A", "OA") and fields[5] not in allowed:
return False
return True
# ---------------------------------------------------------------------------------------------- access repair
def _user_psid():
"""The current user's SID as a native pointer (kept for the life of the process), or None."""
global _psid_cache
if not WINDOWS:
return None
if _psid_cache is not None:
return _psid_cache
pointer = ctypes.c_void_p()
if not _advapi.ConvertStringSidToSidW(current_user_sid(), ctypes.byref(pointer)):
return None
_psid_cache = pointer.value
return _psid_cache
_psid_cache = None
_keep: list = []
def private_mkdir(path: str | os.PathLike, *, parents: bool = False, exist_ok: bool = True) -> Path:
"""Create a folder only the user can reach, on every platform.
POSIX: ``mkdir(mode=0o700)``. Windows: Python 3.13 turns ``mode=0o700`` into an ACL of SYSTEM, Administrators and OWNER
RIGHTS (inherited by everything inside), which locks the user out when an elevated process created the folder. This
creates the missing folders plainly and gives each one the explicit entry for the user's own SID (and the user as owner).
"""
target = Path(path)
if not WINDOWS:
target.mkdir(mode=0o700, parents=parents, exist_ok=exist_ok)
return target
missing = []
probe = target
while not probe.exists() and probe != probe.parent:
missing.append(probe)
if not parents:
break
probe = probe.parent
if not missing and not exist_ok:
raise FileExistsError(str(target))
for folder in reversed(missing):
folder.mkdir(exist_ok=exist_ok)
try:
restrict_to_current_user(folder, directory=True)
except OSError:
pass # the folder exists with its inherited rule; a later read repairs it if that is not enough
return target
def adopt_user_defaults() -> bool:
"""Make files this process creates belong to, and be open to, the user (Windows; no effect elsewhere).
An elevated process otherwise creates files owned by BUILTIN\\Administrators with a default rule of SYSTEM and
Administrators only. Setting the token's default owner and default rule to the user's own SID fixes that for every file
the process (and what it starts) creates afterwards. Returns True when both were set; never raises.
"""
if not WINDOWS:
return False
try:
sid = current_user_sid()
psid = _user_psid()
token = wintypes.HANDLE()
if not psid or not _advapi.OpenProcessToken(_kernel.GetCurrentProcess(), _TOKEN_QUERY | _TOKEN_ADJUST_DEFAULT,
ctypes.byref(token)):
return False
try:
class _Owner(ctypes.Structure):
_fields_ = [("owner", ctypes.c_void_p)]
class _DefaultDacl(ctypes.Structure):
_fields_ = [("dacl", ctypes.c_void_p)]
ok_owner = bool(_advapi.SetTokenInformation(token, _TOKEN_OWNER, ctypes.byref(_Owner(psid)),
ctypes.sizeof(_Owner)))
descriptor = ctypes.c_void_p()
if not _advapi.ConvertStringSecurityDescriptorToSecurityDescriptorW(
f"D:(A;;FA;;;{sid})(A;;FA;;;SY)(A;;FA;;;BA)", _SDDL_REVISION_1, ctypes.byref(descriptor), None):
return False
present, defaulted, dacl = wintypes.BOOL(), wintypes.BOOL(), ctypes.c_void_p()
if not _advapi.GetSecurityDescriptorDacl(descriptor, ctypes.byref(present), ctypes.byref(dacl),
ctypes.byref(defaulted)) or not present.value:
return False
_keep.append(descriptor) # the token keeps pointing into it
ok_dacl = bool(_advapi.SetTokenInformation(token, _TOKEN_DEFAULT_DACL, ctypes.byref(_DefaultDacl(dacl.value)),
ctypes.sizeof(_DefaultDacl)))
return ok_owner and ok_dacl
finally:
_kernel.CloseHandle(token)
except OSError:
return False
def _enable_privilege(name: str) -> bool:
token = wintypes.HANDLE()
if not _advapi.OpenProcessToken(_kernel.GetCurrentProcess(), _TOKEN_QUERY | _TOKEN_ADJUST_PRIVILEGES,
ctypes.byref(token)):
return False
try:
class _Luid(ctypes.Structure):
_fields_ = [("low", wintypes.DWORD), ("high", ctypes.c_long)]
class _Privileges(ctypes.Structure):
_fields_ = [("count", wintypes.DWORD), ("luid", _Luid), ("attributes", wintypes.DWORD)]
luid = _Luid()
if not _advapi.LookupPrivilegeValueW(None, name, ctypes.byref(luid)):
return False
privileges = _Privileges(1, luid, 2) # SE_PRIVILEGE_ENABLED
ctypes.set_last_error(0)
_advapi.AdjustTokenPrivileges(token, False, ctypes.byref(privileges), ctypes.sizeof(privileges), None, None)
return ctypes.get_last_error() == 0
finally:
_kernel.CloseHandle(token)
def user_roots() -> list[Path]:
"""The folders that are the user's own per-user data: a file inside one may be repaired by that user."""
roots: list[Path] = []
for key in ("APPDATA", "LOCALAPPDATA", "STRATA_HOME", "STRATA_PROFILE_DIR", "STRATA_INSTALL_DIR", "STRATA_RUNTIME_DIR",
"STRATA_STATE_DIR"):
value = os.environ.get(key)
if value:
roots.append(Path(value))
roots.append(Path.home())
result = []
for root in roots:
try:
result.append(root.resolve())
except OSError:
continue
return result
def is_users_own(path: str | os.PathLike) -> bool:
"""True when ``path`` is inside the user's own profile or data folders (never the system folders)."""
try:
target = Path(path).resolve()
except OSError:
return False
return any(target == root or root in target.parents for root in user_roots())
def repair_access(path: str | os.PathLike, *, tree: bool = True) -> bool:
"""Give the current user back a file or folder in their own data folders that an elevated process locked them out of.
Takes ownership and writes the explicit protected entry for the user's SID (a folder's children are repaired too).
Needs the right to do that: an elevated session, or a user holding the take-ownership right; a normal session locked
out by someone else's elevated process cannot do it, and this returns False (the caller shows the plain message).
Windows only; elsewhere there is nothing to repair. Refuses anything outside the user's own folders.
"""
if not WINDOWS or not is_users_own(path):
return False
_enable_privilege("SeTakeOwnershipPrivilege")
_enable_privilege("SeRestorePrivilege")
fixed = True
target = Path(path)
try:
restrict_to_current_user(target, directory=target.is_dir())
except OSError:
fixed = False
if tree and fixed and target.is_dir():
try:
for dirpath, dirnames, filenames in os.walk(target):
for name in dirnames + filenames:
child = os.path.join(dirpath, name)
if os.path.islink(child):
continue
try:
restrict_to_current_user(child, directory=os.path.isdir(child))
except OSError:
fixed = False
except OSError:
fixed = False
return fixed
def read_bytes_repairing(path: str | os.PathLike) -> bytes:
"""``Path.read_bytes`` that, on an access error in the user's own folders, repairs the access once and tries again."""
target = Path(path)
try:
return target.read_bytes()
except PermissionError:
if not repair_access(target, tree=False):
raise
return target.read_bytes()
ACCESS_MESSAGE = "Strata can't open its settings file. Run Strata once as yourself (not as administrator) or press Repair."
_SKIP_DIRS = {"models", "node_modules", "harness", "python", "host", "node", "control", "__pycache__", ".git"}
_SKIP_SUFFIXES = (".gguf", ".part", ".bin", ".safetensors", ".zip", ".exe", ".dll")
def known_roots() -> list[Path]:
"""Strata's per-user data folders: APPDATA/Strata, LOCALAPPDATA/Strata and ~/.strata (plus the STRATA_* overrides)."""
roots: list[Path] = []
for key, sub in (("APPDATA", "Strata"), ("LOCALAPPDATA", "Strata")):
value = os.environ.get(key)
if value:
roots.append(Path(value) / sub)
roots.append(Path.home() / ".strata")
for key in ("STRATA_HOME", "STRATA_PROFILE_DIR", "STRATA_RUNTIME_DIR", "STRATA_STATE_DIR", "STRATA_INSTALL_DIR"):
value = os.environ.get(key)
if value:
roots.append(Path(value))
seen, result = set(), []
for root in roots:
key = str(root).lower()
if key not in seen and root.exists():
seen.add(key)
result.append(root)
return result
def repair_known_roots(*, limit: int = 20000) -> dict:
"""Walk Strata's own per-user folders and repair every entry the user cannot open (Windows).
Meant to run when Strata starts: files and folders an elevated run created (owner Administrators, a rule that names
only SYSTEM, Administrators and OWNER RIGHTS) are given back to the user. A normal session can only repair what its
own token may change; an elevated start repairs everything. Never raises; returns what it did.
"""
report = {"checked": 0, "repaired": 0, "failed": 0}
if not WINDOWS:
return report
for root in known_roots():
stack = [root]
while stack and report["checked"] < limit:
folder = stack.pop()
try:
entries = list(os.scandir(folder))
except PermissionError:
report["checked"] += 1
if repair_access(folder, tree=False):
report["repaired"] += 1
try:
entries = list(os.scandir(folder))
except OSError:
continue
else:
report["failed"] += 1
continue
except OSError:
continue
for entry in entries:
report["checked"] += 1
try:
if entry.is_symlink():
continue
if entry.is_dir(follow_symlinks=False):
if entry.name.lower() not in _SKIP_DIRS:
stack.append(Path(entry.path))
continue
if entry.name.lower().endswith(_SKIP_SUFFIXES):
continue
with open(entry.path, "rb"):
pass
except PermissionError:
if repair_access(entry.path, tree=False):
report["repaired"] += 1
else:
report["failed"] += 1
except OSError:
continue
return report
+2 -1
View File
@@ -19,6 +19,7 @@ exactly the engine executable that is about to be started, running ``--serve`` w
from __future__ import annotations
from strata_control.private_files import private_mkdir as _private_mkdir
import json
import os
import signal
@@ -167,7 +168,7 @@ def procs_dir(create: bool = True) -> Path:
"""The owner-private folder of launch records (beside runtime/, under the per-user state)."""
base = paths.state_base() / "procs"
if create and not base.exists():
base.mkdir(parents=True, mode=0o700, exist_ok=True)
_private_mkdir(base, parents=True, exist_ok=True)
if WINDOWS:
from strata_control import private_files
private_files.restrict_to_current_user(base, directory=True)
+5 -3
View File
@@ -7,6 +7,7 @@ them. It deliberately does not start processes or contact a network service.
from __future__ import annotations
from strata_control.private_files import private_mkdir as _private_mkdir
import hashlib
import json
import math
@@ -150,7 +151,8 @@ def load(name: str) -> tuple[dict, str]:
try:
if not stat.S_ISREG(path.lstat().st_mode):
raise ProfileError("profile must be a regular file", 5)
data = path.read_bytes()
from strata_control import private_files
data = private_files.read_bytes_repairing(path) # a profile an elevated run locked is repaired once, in place
if len(data) > 1024 * 1024:
raise ProfileError("profile is larger than 1 MiB", 5)
value = json.loads(data.decode("utf-8-sig"))
@@ -182,7 +184,7 @@ def list_profiles() -> list[dict]:
@contextmanager
def _lock(name: str):
directory = profile_dir()
directory.mkdir(mode=0o700, parents=True, exist_ok=True)
_private_mkdir(directory, parents=True, exist_ok=True)
lock = directory / f".{_name(name)}.lock"
with open(lock, "a+b") as stream:
if os.name == "nt":
@@ -281,7 +283,7 @@ def save(name: str, proposed: dict, *, expected_revision: str,
if current == proposed:
return current, revision
history = profile_dir() / ".history" / name
history.mkdir(mode=0o700, parents=True, exist_ok=True)
_private_mkdir(history, parents=True, exist_ok=True)
old = _path(name).read_bytes()
if _revision(old) != revision:
raise ProfileError("profile changed during edit; reload it before saving", 6)
+6 -5
View File
@@ -7,6 +7,7 @@ schema and operation authority rather than add a separate implementation.
from __future__ import annotations
from strata_control.private_files import private_mkdir as _private_mkdir
import hashlib
import json
import os
@@ -46,8 +47,8 @@ def _directory(profile: str, *, create: bool = False) -> Path:
if os.name == "posix" and (details.st_uid != os.getuid() or stat.S_IMODE(details.st_mode) & 0o077):
raise ProfileError("recipe directory must be owner-only (mode 0700)", 5)
if create:
directory.parent.mkdir(mode=0o700, exist_ok=True)
directory.mkdir(mode=0o700, exist_ok=True)
_private_mkdir(directory.parent, exist_ok=True)
_private_mkdir(directory, exist_ok=True)
if directory.exists() or directory.is_symlink():
details = directory.lstat()
if not stat.S_ISDIR(details.st_mode):
@@ -73,8 +74,8 @@ def _history_dir(profile: str, name: str, *, create: bool = False) -> Path:
if path.is_symlink():
raise ProfileError("recipe history directory must not be a symlink", 5)
if create:
root.mkdir(mode=0o700, exist_ok=True)
directory.mkdir(mode=0o700, exist_ok=True)
_private_mkdir(root, exist_ok=True)
_private_mkdir(directory, exist_ok=True)
for path in (root, directory):
if path.exists():
details = path.lstat()
@@ -411,7 +412,7 @@ def run(profile: str, name: str, *, exclusive_ack: bool = False,
elif kind == "engine.calibrate.measure":
from strata_control import calibration
directory = Path(recipe["params"]["report_dir"])
directory.mkdir(mode=0o700, exist_ok=True)
_private_mkdir(directory, exist_ok=True)
if not stat.S_ISDIR(directory.lstat().st_mode):
raise ProfileError("report_dir must be a directory, not a symlink", 5)
report_path = directory / f"calibration-{uuid.uuid4().hex}.json"
+2 -1
View File
@@ -15,6 +15,7 @@ child is still there.
from __future__ import annotations
from strata_control.private_files import private_mkdir as _private_mkdir
import argparse
import errno
import hashlib
@@ -107,7 +108,7 @@ def runtime_dir() -> Path:
if not base.is_absolute():
raise ProfileError("STRATA_RUNTIME_DIR must be absolute")
existed = base.exists()
base.mkdir(parents=True, mode=0o700, exist_ok=True)
_private_mkdir(base, parents=True, exist_ok=True)
if WINDOWS and not existed:
private_files.restrict_to_current_user(base, directory=True) # no mode bits on NTFS: a protected ACL
details = base.lstat()
+1
View File
@@ -45,6 +45,7 @@ NETWORK_ALLOWED = {
("serve/server.py", "urlopen"): "an image_url a client sends in a chat request is fetched at request time",
("serve/mcp.py", "urlopen"): "the MCP servers the user configured, at request time",
("strata_control/tune_api.py", "urlopen"): "the loopback engine/server of a tuning job the user started",
("strata_control/tune_session.py", "urlopen"): "the loopback engine (127.0.0.1) the auto-tuner a user started measures",
("strata_control/components.py", "urlopen"): "the component fetcher: only `strata install`/UPDATE call it",
}
#: Modules that import the installer on purpose, with the explicit command that reaches them.
+86
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import json
import os
import stat
import sys
@@ -94,3 +95,88 @@ class PrivateFileTests(unittest.TestCase):
if __name__ == "__main__":
unittest.main()
@unittest.skipUnless(pf.WINDOWS, "NTFS ACLs")
class ElevatedRunLockoutTests(unittest.TestCase):
"""A file or folder an elevated run made names only SYSTEM and Administrators: the user's normal session cannot open it."""
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.root = Path(self.tmp.name)
@staticmethod
def lock_out(path):
"""Replace the rule with SYSTEM only: this session may be elevated itself, so Administrators is left out to make the lock-out real."""
import ctypes
from ctypes import wintypes
descriptor = ctypes.c_void_p()
assert pf._advapi.ConvertStringSecurityDescriptorToSecurityDescriptorW(
"D:P(A;OICI;FA;;;SY)", 1, ctypes.byref(descriptor), None)
present, defaulted, dacl = wintypes.BOOL(), wintypes.BOOL(), ctypes.c_void_p()
pf._advapi.GetSecurityDescriptorDacl(descriptor, ctypes.byref(present), ctypes.byref(dacl), ctypes.byref(defaulted))
code = pf._advapi.SetNamedSecurityInfoW(str(path), 1, 0x4 | 0x80000000, None, None, dacl, None)
assert code == 0, code
def test_a_locked_file_is_read_again_after_the_repair(self):
path = self.root / "profile.json"
path.write_text('{"name": "x"}')
self.lock_out(path)
with self.assertRaises(PermissionError):
path.read_bytes()
self.assertEqual(pf.read_bytes_repairing(path), b'{"name": "x"}')
self.assertIn(pf.current_user_sid(), pf.dacl_sddl(path))
self.assertEqual(path.read_bytes(), b'{"name": "x"}')
def test_a_locked_folder_and_what_is_inside_are_repaired_as_a_tree(self):
folder = self.root / "profiles"
folder.mkdir()
(folder / "a.json").write_text("{}")
(folder / "sub").mkdir()
(folder / "sub" / "b.json").write_text("{}")
for item in (folder / "sub" / "b.json", folder / "sub", folder / "a.json", folder):
self.lock_out(item)
with self.assertRaises(PermissionError):
(folder / "a.json").read_bytes()
os.environ["STRATA_HOME"] = str(self.root)
self.addCleanup(os.environ.pop, "STRATA_HOME", None)
report = pf.repair_known_roots()
self.assertGreaterEqual(report["repaired"], 1)
self.assertEqual(report["failed"], 0)
self.assertEqual((folder / "a.json").read_bytes(), b"{}")
self.assertEqual((folder / "sub" / "b.json").read_bytes(), b"{}")
self.assertIn(pf.current_user_sid(), pf.dacl_sddl(folder / "sub" / "b.json"))
def test_a_folder_made_with_private_mkdir_names_the_user_explicitly(self):
made = pf.private_mkdir(self.root / "x" / "y", parents=True)
self.assertIn(pf.current_user_sid(), pf.dacl_sddl(made))
self.assertIn(pf.current_user_sid(), pf.dacl_sddl(made.parent))
(made / "file.json").write_text("{}")
self.assertIn(pf.current_user_sid(), pf.dacl_sddl(made / "file.json"))
self.assertEqual((made / "file.json").read_text(), "{}")
def test_the_process_defaults_can_be_set_for_the_user(self):
self.assertTrue(pf.adopt_user_defaults())
self.assertTrue(pf.adopt_user_defaults()) # again: nothing breaks
path = self.root / "new.json"
path.write_text("{}")
self.assertEqual(path.read_text(), "{}")
def test_only_the_users_own_folders_are_ever_repaired(self):
self.assertFalse(pf.repair_access(os.environ.get("SystemRoot", "C:\Windows") + "\System32\drivers"))
def test_a_profile_that_was_locked_loads(self):
from strata_control import profiles
os.environ["STRATA_PROFILE_DIR"] = str(self.root / "profiles")
self.addCleanup(os.environ.pop, "STRATA_PROFILE_DIR", None)
directory = self.root / "profiles"
directory.mkdir()
run_config = self.root / "strata-x.json"
run_config.write_text("{}")
(directory / "app-1.json").write_text(json.dumps({"schema": 1, "name": "app-1", "source_config": str(run_config)}))
self.lock_out(directory / "app-1.json")
try:
profiles.load("app-1")
except profiles.ProfileError as exc:
self.assertNotIn("PermissionError", str(exc)) # any other complaint is the sample's shape, not access