@milpa/comal 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/private-fs.mjs +11 -5
- package/src/source-ownership.mjs +4 -1
- package/src/windows-private.ps1 +2 -0
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ comal --help
|
|
|
28
28
|
|
|
29
29
|
Keep the PATH line in your shell startup file if you use that prefix. If your Node manager already provides a user prefix, you can omit `--prefix "$HOME/.local"`. The package registers the `comal` executable; you do not need to create a shell alias.
|
|
30
30
|
|
|
31
|
-
You can pin a version with `npx @milpa/comal@0.2.
|
|
31
|
+
You can pin a version with `npx @milpa/comal@0.2.1` or `npm install --global @milpa/comal@0.2.1`. The local release manifest preserves SHA-256 and SHA-512 integrity is checked against npm at publish time. No independent publisher signature is claimed.
|
|
32
32
|
|
|
33
33
|
## Test without account or model
|
|
34
34
|
|
package/package.json
CHANGED
package/src/private-fs.mjs
CHANGED
|
@@ -5,21 +5,27 @@ import path from 'node:path';
|
|
|
5
5
|
|
|
6
6
|
const fail=message=>Object.assign(new Error(message),{code:'PRIVATE_STATE_UNSAFE'});
|
|
7
7
|
let broker,starting,sequence=0;
|
|
8
|
+
// PowerShell puede tardar en arrancar (antivirus, runners en frío): su arranque tiene su propio plazo.
|
|
9
|
+
const STARTUP_MS=60000,REQUEST_MS=15000;
|
|
8
10
|
async function windowsBroker(){
|
|
9
11
|
if(broker)return broker;if(starting)return starting;
|
|
10
12
|
starting=(async()=>{
|
|
11
13
|
const script=await readFile(new URL('./windows-private.ps1',import.meta.url),'utf8'),program=path.join(process.env.SystemRoot||'C:\\Windows','System32','WindowsPowerShell','v1.0','powershell.exe');
|
|
12
14
|
const child=spawn(program,['-NoLogo','-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass','-EncodedCommand',Buffer.from(script,'utf16le').toString('base64')],{stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
13
|
-
const pending=new Map(),lines=createInterface({input:child.stdout});let diagnostic='';
|
|
15
|
+
const pending=new Map(),lines=createInterface({input:child.stdout});let diagnostic='',ready;
|
|
16
|
+
const readiness=new Promise((resolve,reject)=>{ready={resolve,reject};});
|
|
17
|
+
const startup=setTimeout(()=>{ready.reject(fail('Windows ACL helper did not start within its deadline. '+diagnostic.slice(0,300)));child.kill();},STARTUP_MS);
|
|
14
18
|
const references=active=>{const method=active?'ref':'unref';child[method]();for(const stream of[child.stdin,child.stdout,child.stderr])stream[method]?.();};
|
|
15
19
|
child.stderr.on('data',data=>{if(diagnostic.length<2000)diagnostic+=data;});child.stdin.on('error',()=>{});
|
|
16
|
-
const stopped=()=>{for(const job of pending.values()){clearTimeout(job.timer);job.reject(fail('Windows private-state inspection stopped. '+diagnostic.slice(0,300)));}pending.clear();if(broker?.child===child)broker=undefined;};
|
|
20
|
+
const stopped=()=>{clearTimeout(startup);ready.reject(fail('Windows private-state inspection stopped. '+diagnostic.slice(0,300)));for(const job of pending.values()){clearTimeout(job.timer);job.reject(fail('Windows private-state inspection stopped. '+diagnostic.slice(0,300)));}pending.clear();if(broker?.child===child)broker=undefined;};
|
|
17
21
|
child.on('error',stopped);child.on('close',stopped);
|
|
18
|
-
lines.on('line',line=>{let result;try{result=JSON.parse(line);}catch{stopped();child.kill();return;}const job=pending.get(result.id);if(!job)return;clearTimeout(job.timer);pending.delete(result.id);if(!pending.size)references(false);result.ok?job.resolve():job.reject(fail(result.error||'Windows private-state ACL does not verify.'));});
|
|
22
|
+
lines.on('line',line=>{let result;try{result=JSON.parse(line);}catch{stopped();child.kill();return;}if(result?.ready===true){clearTimeout(startup);ready.resolve();return;}const job=pending.get(result.id);if(!job)return;clearTimeout(job.timer);pending.delete(result.id);if(!pending.size)references(false);result.ok?job.resolve():job.reject(fail(result.error||'Windows private-state ACL does not verify.'));});
|
|
19
23
|
const result={child,request:(action,file)=>new Promise((resolve,reject)=>{
|
|
20
|
-
const id=++sequence;references(true);const timer=setTimeout(()=>{pending.delete(id);reject(fail('Windows ACL inspection exceeded its deadline.'));if(!pending.size)references(false);},
|
|
24
|
+
const id=++sequence;references(true);const timer=setTimeout(()=>{pending.delete(id);reject(fail('Windows ACL inspection exceeded its deadline.'));if(!pending.size)references(false);},REQUEST_MS);pending.set(id,{resolve,reject,timer});
|
|
21
25
|
child.stdin.write(JSON.stringify({id,action,path:path.resolve(file)})+'\n',error=>{if(error){clearTimeout(timer);pending.delete(id);reject(fail('Windows ACL inspection could not be sent.'));if(!pending.size)references(false);}});
|
|
22
|
-
})};
|
|
26
|
+
})};
|
|
27
|
+
try{await readiness;}catch(error){if(broker?.child===child)broker=undefined;throw error;}
|
|
28
|
+
references(false);broker=result;return result;
|
|
23
29
|
})();try{return await starting;}finally{starting=undefined;}
|
|
24
30
|
}
|
|
25
31
|
export async function ensurePrivateDirectory(directory){
|
package/src/source-ownership.mjs
CHANGED
|
@@ -22,8 +22,11 @@ export class SourceOwnership {
|
|
|
22
22
|
}
|
|
23
23
|
contains(file){const r=path.relative(this.directory,file);return r===''||r!=='..'&&!r.startsWith('..'+path.sep)&&!path.isAbsolute(r);}
|
|
24
24
|
async prepareDirectory(){
|
|
25
|
+
await mkdir(path.dirname(this.directory),{recursive:true,mode:0o700});
|
|
26
|
+
// El HOME puede llegar por un enlace del sistema (macOS: /tmp, /var → /private/…).
|
|
27
|
+
// Se resuelve el antecesor del registro; el padre (~/.comal) sigue sin poder ser enlace.
|
|
28
|
+
if(!this.canonical){const grand=await realpath(path.dirname(path.dirname(this.directory)));this.directory=path.join(grand,path.basename(path.dirname(this.directory)),path.basename(this.directory));this.canonical=true;}
|
|
25
29
|
const parent=path.dirname(this.directory);
|
|
26
|
-
await mkdir(parent,{recursive:true,mode:0o700});
|
|
27
30
|
if(await realpath(parent)!==parent)throw fail('Native source lock registry has a noncanonical parent.');
|
|
28
31
|
try{await ensurePrivateDirectory(this.directory);}catch(error){if(error.code==='PRIVATE_STATE_UNSAFE')throw fail('Native source lock registry is not private.');throw error;}
|
|
29
32
|
const info=await lstat(this.directory);
|
package/src/windows-private.ps1
CHANGED
|
@@ -3,6 +3,8 @@ $ErrorActionPreference = 'Stop'
|
|
|
3
3
|
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
|
|
4
4
|
$currentSid = [Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
5
5
|
$trusted = @($currentSid.Value, 'S-1-5-18', 'S-1-5-32-544')
|
|
6
|
+
[Console]::Out.WriteLine('{"ready":true}')
|
|
7
|
+
[Console]::Out.Flush()
|
|
6
8
|
|
|
7
9
|
function Assert-Private([string] $path, [bool] $directory) {
|
|
8
10
|
$attributes = [IO.File]::GetAttributes($path)
|