ugly-app 0.1.651 → 0.1.652
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/dist/cli/version.d.ts +1 -1
- package/dist/cli/version.js +1 -1
- package/dist/native/server/binaries.d.ts +23 -0
- package/dist/native/server/binaries.d.ts.map +1 -0
- package/dist/native/server/binaries.js +125 -0
- package/dist/native/server/binaries.js.map +1 -0
- package/dist/native/server/index.d.ts +16 -0
- package/dist/native/server/index.d.ts.map +1 -0
- package/dist/native/server/index.js +16 -0
- package/dist/native/server/index.js.map +1 -0
- package/dist/native/server/permissions/audit.d.ts +29 -0
- package/dist/native/server/permissions/audit.d.ts.map +1 -0
- package/dist/native/server/permissions/audit.js +28 -0
- package/dist/native/server/permissions/audit.js.map +1 -0
- package/dist/native/server/permissions/gate.d.ts +63 -0
- package/dist/native/server/permissions/gate.d.ts.map +1 -0
- package/dist/native/server/permissions/gate.js +150 -0
- package/dist/native/server/permissions/gate.js.map +1 -0
- package/dist/native/server/permissions/grants.d.ts +36 -0
- package/dist/native/server/permissions/grants.d.ts.map +1 -0
- package/dist/native/server/permissions/grants.js +116 -0
- package/dist/native/server/permissions/grants.js.map +1 -0
- package/dist/native/server/permissions/scope.d.ts +32 -0
- package/dist/native/server/permissions/scope.d.ts.map +1 -0
- package/dist/native/server/permissions/scope.js +73 -0
- package/dist/native/server/permissions/scope.js.map +1 -0
- package/dist/native/server/permissions/types.d.ts +64 -0
- package/dist/native/server/permissions/types.d.ts.map +1 -0
- package/dist/native/server/permissions/types.js +25 -0
- package/dist/native/server/permissions/types.js.map +1 -0
- package/package.json +3 -2
- package/src/cli/version.ts +1 -1
- package/src/native/server/binaries.ts +136 -0
- package/src/native/server/index.ts +27 -0
- package/src/native/server/permissions/audit.ts +44 -0
- package/src/native/server/permissions/gate.test.ts +30 -0
- package/src/native/server/permissions/gate.ts +176 -0
- package/src/native/server/permissions/grants.test.ts +22 -0
- package/src/native/server/permissions/grants.ts +135 -0
- package/src/native/server/permissions/scope.ts +80 -0
- package/src/native/server/permissions/types.ts +75 -0
package/dist/cli/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.1.
|
|
1
|
+
export declare const CLI_VERSION = "0.1.652";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/cli/version.js
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface BinaryProvisionConfig {
|
|
2
|
+
/** Where bundled tools are installed, e.g. `~/.ugly-studio/binaries`. */
|
|
3
|
+
root: string;
|
|
4
|
+
/** Base URL hosting `binaries-manifest.json` (the R2 mirror). */
|
|
5
|
+
manifestBase: string;
|
|
6
|
+
}
|
|
7
|
+
/** The installed executable for `tool` under `root`, version-agnostic, or null. */
|
|
8
|
+
export declare function findBundledTool(root: string, tool: string): string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a tool name to a path: a downloaded binary under `cfg.root` when
|
|
11
|
+
* present, else the bare name so the daemon's spawn resolves it on the system
|
|
12
|
+
* PATH. `downloadable` lists tools the host actually mirrors (others always
|
|
13
|
+
* fall through to PATH without a directory probe).
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveBundledBinary(name: string, cfg: Pick<BinaryProvisionConfig, 'root'>, downloadable?: readonly string[]): string;
|
|
16
|
+
/**
|
|
17
|
+
* Best-effort: download `tool` from the mirror into `cfg.root` on first use.
|
|
18
|
+
* Never fetches from the upstream project at runtime (the binary is mirrored to
|
|
19
|
+
* R2 by the host's publish step). Never throws (falls back to a system tool);
|
|
20
|
+
* idempotent + single-flight per tool.
|
|
21
|
+
*/
|
|
22
|
+
export declare function ensureBundledTool(tool: string, cfg: BinaryProvisionConfig): Promise<void>;
|
|
23
|
+
//# sourceMappingURL=binaries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binaries.d.ts","sourceRoot":"","sources":["../../../src/native/server/binaries.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,qBAAqB;IACpC,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAC;CACtB;AAgBD,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWzE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,EACxC,YAAY,GAAE,SAAS,MAAM,EAAW,GACvC,MAAM,CAMR;AAsBD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCzF"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundled-binary provisioning for the native daemon (lifted from Ugly Studio so
|
|
3
|
+
* the daemon AND the test harness share one implementation). A host (Ugly
|
|
4
|
+
* Studio) bundles a small set of tools by downloading them from its own R2
|
|
5
|
+
* mirror on first launch; everything else resolves on the system PATH.
|
|
6
|
+
*
|
|
7
|
+
* Pure fs/child_process/fetch (no Electron) and fully parameterized — the
|
|
8
|
+
* install root + manifest mirror are injected via `BinaryProvisionConfig`, so
|
|
9
|
+
* nothing here hardcodes a particular app's paths or hostnames.
|
|
10
|
+
*
|
|
11
|
+
* The mirror exposes `<manifestBase>/binaries-manifest.json`:
|
|
12
|
+
* { "<tool>": { "version": "x", "archives": { "<platform>-<arch>": { "url" } } } }
|
|
13
|
+
* One `tar -xf` covers every platform's asset (bsdtar on mac/win unpacks zip).
|
|
14
|
+
*/
|
|
15
|
+
import { basename, join } from 'node:path';
|
|
16
|
+
import { chmodSync, existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { execFile } from 'node:child_process';
|
|
18
|
+
import { promisify } from 'node:util';
|
|
19
|
+
const execFileP = promisify(execFile);
|
|
20
|
+
const exeName = (tool) => (process.platform === 'win32' ? `${tool}.exe` : tool);
|
|
21
|
+
function safeReaddir(dir) {
|
|
22
|
+
try {
|
|
23
|
+
return readdirSync(dir);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** The installed executable for `tool` under `root`, version-agnostic, or null. */
|
|
30
|
+
export function findBundledTool(root, tool) {
|
|
31
|
+
const toolRoot = join(root, tool);
|
|
32
|
+
if (!existsSync(toolRoot))
|
|
33
|
+
return null;
|
|
34
|
+
for (const ver of safeReaddir(toolRoot)) {
|
|
35
|
+
const verDir = join(toolRoot, ver);
|
|
36
|
+
for (const sub of safeReaddir(verDir)) {
|
|
37
|
+
const p = join(verDir, sub, 'bin', exeName(tool));
|
|
38
|
+
if (existsSync(p))
|
|
39
|
+
return p;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a tool name to a path: a downloaded binary under `cfg.root` when
|
|
46
|
+
* present, else the bare name so the daemon's spawn resolves it on the system
|
|
47
|
+
* PATH. `downloadable` lists tools the host actually mirrors (others always
|
|
48
|
+
* fall through to PATH without a directory probe).
|
|
49
|
+
*/
|
|
50
|
+
export function resolveBundledBinary(name, cfg, downloadable = ['gh']) {
|
|
51
|
+
if (downloadable.includes(name)) {
|
|
52
|
+
const found = findBundledTool(cfg.root, name);
|
|
53
|
+
if (found)
|
|
54
|
+
return found;
|
|
55
|
+
}
|
|
56
|
+
return name;
|
|
57
|
+
}
|
|
58
|
+
/** Look up a tool's archive for this platform in the mirror's manifest. */
|
|
59
|
+
async function toolFromManifest(cfg, tool) {
|
|
60
|
+
try {
|
|
61
|
+
const res = await fetch(`${cfg.manifestBase}/binaries-manifest.json`, { cache: 'no-store' });
|
|
62
|
+
if (!res.ok)
|
|
63
|
+
return null;
|
|
64
|
+
const m = (await res.json());
|
|
65
|
+
const entry = m[tool];
|
|
66
|
+
const archive = entry?.archives[`${process.platform}-${process.arch}`];
|
|
67
|
+
if (!entry || !archive?.url)
|
|
68
|
+
return null;
|
|
69
|
+
return { version: entry.version, url: archive.url };
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const ensuring = new Map();
|
|
76
|
+
/**
|
|
77
|
+
* Best-effort: download `tool` from the mirror into `cfg.root` on first use.
|
|
78
|
+
* Never fetches from the upstream project at runtime (the binary is mirrored to
|
|
79
|
+
* R2 by the host's publish step). Never throws (falls back to a system tool);
|
|
80
|
+
* idempotent + single-flight per tool.
|
|
81
|
+
*/
|
|
82
|
+
export function ensureBundledTool(tool, cfg) {
|
|
83
|
+
const key = `${cfg.root}:${tool}`;
|
|
84
|
+
const inflight = ensuring.get(key);
|
|
85
|
+
if (inflight)
|
|
86
|
+
return inflight;
|
|
87
|
+
const run = (async () => {
|
|
88
|
+
try {
|
|
89
|
+
if (findBundledTool(cfg.root, tool))
|
|
90
|
+
return;
|
|
91
|
+
const man = await toolFromManifest(cfg, tool);
|
|
92
|
+
if (!man)
|
|
93
|
+
return; // not mirrored for this platform yet → system fallback
|
|
94
|
+
const dir = join(cfg.root, tool, man.version);
|
|
95
|
+
mkdirSync(dir, { recursive: true });
|
|
96
|
+
const archive = join(dir, basename(new URL(man.url).pathname));
|
|
97
|
+
const res = await fetch(man.url);
|
|
98
|
+
if (!res.ok)
|
|
99
|
+
return;
|
|
100
|
+
writeFileSync(archive, Buffer.from(await res.arrayBuffer()));
|
|
101
|
+
await execFileP('tar', ['-xf', archive, '-C', dir]);
|
|
102
|
+
try {
|
|
103
|
+
rmSync(archive);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
/* leave it */
|
|
107
|
+
}
|
|
108
|
+
const bin = findBundledTool(cfg.root, tool);
|
|
109
|
+
if (bin && process.platform !== 'win32') {
|
|
110
|
+
try {
|
|
111
|
+
chmodSync(bin, 0o755);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
/* already executable */
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
/* best-effort — fall back to a system tool */
|
|
120
|
+
}
|
|
121
|
+
})();
|
|
122
|
+
ensuring.set(key, run);
|
|
123
|
+
return run;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=binaries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binaries.js","sourceRoot":"","sources":["../../../src/native/server/binaries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/F,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAatC,MAAM,OAAO,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAEhG,SAAS,WAAW,CAAC,GAAW;IAC9B,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,IAAY;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,KAAK,MAAM,GAAG,IAAI,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACnC,KAAK,MAAM,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;YACtC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YAClD,IAAI,UAAU,CAAC,CAAC,CAAC;gBAAE,OAAO,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAY,EACZ,GAAwC,EACxC,eAAkC,CAAC,IAAI,CAAC;IAExC,IAAI,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC9C,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2EAA2E;AAC3E,KAAK,UAAU,gBAAgB,CAC7B,GAA0B,EAC1B,IAAY;IAEZ,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,GAAG,CAAC,YAAY,yBAAyB,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;QAC7F,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QACzB,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAqB,CAAC;QACjD,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,OAAO,GAAG,KAAK,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QACvE,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO,EAAE,GAAG;YAAE,OAAO,IAAI,CAAC;QACzC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC;AAElD;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,GAA0B;IACxE,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE;QACtB,IAAI,CAAC;YACH,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC;gBAAE,OAAO;YAC5C,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAC9C,IAAI,CAAC,GAAG;gBAAE,OAAO,CAAC,uDAAuD;YACzE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;YAC9C,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACpC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC/D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACjC,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,OAAO;YACpB,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;YAC7D,MAAM,SAAS,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;YACpD,IAAI,CAAC;gBACH,MAAM,CAAC,OAAO,CAAC,CAAC;YAClB,CAAC;YAAC,MAAM,CAAC;gBACP,cAAc;YAChB,CAAC;YACD,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC5C,IAAI,GAAG,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;gBACxC,IAAI,CAAC;oBACH,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;gBACxB,CAAC;gBAAC,MAAM,CAAC;oBACP,wBAAwB;gBAC1B,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;QAChD,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;IACL,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACvB,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native daemon-side engine (Node) — the capability/permission core + bundled-
|
|
3
|
+
* binary provisioning that gate every privileged `window.UglyNative` operation.
|
|
4
|
+
*
|
|
5
|
+
* Pure decision logic (no Electron): a host wires the prompt UI + on-disk grant
|
|
6
|
+
* persistence around it. Lifted out of Ugly Studio so the daemon AND the
|
|
7
|
+
* Playwright test harness enforce the SAME model from one source of truth —
|
|
8
|
+
* a test can no longer pass against an unrestricted host.
|
|
9
|
+
*/
|
|
10
|
+
export { AuditLog, type AuditEntry } from './permissions/audit.js';
|
|
11
|
+
export { CapabilityGate, type GateConfig, PermissionDenied } from './permissions/gate.js';
|
|
12
|
+
export { GrantStore, type GrantSnapshot } from './permissions/grants.js';
|
|
13
|
+
export { isWithinScope, originScopeDir, resolveScopedPath, ScopeViolation } from './permissions/scope.js';
|
|
14
|
+
export { type CapabilityRequest, EMPTY_GRANT, type OriginGrant, type PermissionDecision, } from './permissions/types.js';
|
|
15
|
+
export { type BinaryProvisionConfig, ensureBundledTool, findBundledTool, resolveBundledBinary, } from './binaries.js';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/native/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,KAAK,UAAU,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC1G,OAAO,EACL,KAAK,iBAAiB,EACtB,WAAW,EACX,KAAK,WAAW,EAChB,KAAK,kBAAkB,GACxB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,KAAK,qBAAqB,EAC1B,iBAAiB,EACjB,eAAe,EACf,oBAAoB,GACrB,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native daemon-side engine (Node) — the capability/permission core + bundled-
|
|
3
|
+
* binary provisioning that gate every privileged `window.UglyNative` operation.
|
|
4
|
+
*
|
|
5
|
+
* Pure decision logic (no Electron): a host wires the prompt UI + on-disk grant
|
|
6
|
+
* persistence around it. Lifted out of Ugly Studio so the daemon AND the
|
|
7
|
+
* Playwright test harness enforce the SAME model from one source of truth —
|
|
8
|
+
* a test can no longer pass against an unrestricted host.
|
|
9
|
+
*/
|
|
10
|
+
export { AuditLog } from './permissions/audit.js';
|
|
11
|
+
export { CapabilityGate, PermissionDenied } from './permissions/gate.js';
|
|
12
|
+
export { GrantStore } from './permissions/grants.js';
|
|
13
|
+
export { isWithinScope, originScopeDir, resolveScopedPath, ScopeViolation } from './permissions/scope.js';
|
|
14
|
+
export { EMPTY_GRANT, } from './permissions/types.js';
|
|
15
|
+
export { ensureBundledTool, findBundledTool, resolveBundledBinary, } from './binaries.js';
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/native/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAmB,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAmB,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAE,UAAU,EAAsB,MAAM,yBAAyB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC1G,OAAO,EAEL,WAAW,GAGZ,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAEL,iBAAiB,EACjB,eAAe,EACf,oBAAoB,GACrB,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-origin audit log. Every gated `window.ugly` operation (process
|
|
3
|
+
* spawn, fs write, serve, …) is appended here so the user can review
|
|
4
|
+
* what an app actually did. In-memory ring buffer with an optional sink
|
|
5
|
+
* the Electron layer wires to append to `~/.ugly-browser/audit/<...>`.
|
|
6
|
+
*/
|
|
7
|
+
export interface AuditEntry {
|
|
8
|
+
ts: number;
|
|
9
|
+
origin: string;
|
|
10
|
+
capability: string;
|
|
11
|
+
/** The specific resource — a path, a binary name, a port, etc. */
|
|
12
|
+
resource: string;
|
|
13
|
+
allowed: boolean;
|
|
14
|
+
/** Why it was denied (empty when allowed). */
|
|
15
|
+
reason?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare class AuditLog {
|
|
18
|
+
private readonly opts;
|
|
19
|
+
private readonly entries;
|
|
20
|
+
constructor(opts?: {
|
|
21
|
+
max?: number;
|
|
22
|
+
now?: () => number;
|
|
23
|
+
sink?: (e: AuditEntry) => void;
|
|
24
|
+
});
|
|
25
|
+
record(e: Omit<AuditEntry, 'ts'>): AuditEntry;
|
|
26
|
+
/** All entries, optionally filtered by origin. */
|
|
27
|
+
list(origin?: string): readonly AuditEntry[];
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../../src/native/server/permissions/audit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,QAAQ;IAIjB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;gBAGzB,IAAI,GAAE;QACrB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;QACnB,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAC;KAC3B;IAGR,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,UAAU;IAU7C,kDAAkD;IAClD,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,UAAU,EAAE;CAG7C"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-origin audit log. Every gated `window.ugly` operation (process
|
|
3
|
+
* spawn, fs write, serve, …) is appended here so the user can review
|
|
4
|
+
* what an app actually did. In-memory ring buffer with an optional sink
|
|
5
|
+
* the Electron layer wires to append to `~/.ugly-browser/audit/<...>`.
|
|
6
|
+
*/
|
|
7
|
+
export class AuditLog {
|
|
8
|
+
opts;
|
|
9
|
+
entries = [];
|
|
10
|
+
constructor(opts = {}) {
|
|
11
|
+
this.opts = opts;
|
|
12
|
+
}
|
|
13
|
+
record(e) {
|
|
14
|
+
const now = this.opts.now ?? (() => Date.now());
|
|
15
|
+
const entry = { ts: now(), ...e };
|
|
16
|
+
this.entries.push(entry);
|
|
17
|
+
const max = this.opts.max ?? 5000;
|
|
18
|
+
if (this.entries.length > max)
|
|
19
|
+
this.entries.splice(0, this.entries.length - max);
|
|
20
|
+
this.opts.sink?.(entry);
|
|
21
|
+
return entry;
|
|
22
|
+
}
|
|
23
|
+
/** All entries, optionally filtered by origin. */
|
|
24
|
+
list(origin) {
|
|
25
|
+
return origin ? this.entries.filter((e) => e.origin === origin) : this.entries;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../../src/native/server/permissions/audit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH,MAAM,OAAO,QAAQ;IAIA;IAHF,OAAO,GAAiB,EAAE,CAAC;IAE5C,YACmB,OAIb,EAAE;QAJW,SAAI,GAAJ,IAAI,CAIf;IACL,CAAC;IAEJ,MAAM,CAAC,CAAyB;QAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAChD,MAAM,KAAK,GAAe,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC;QAC9C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC;QAClC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,GAAG;YAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;QACjF,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC;QACxB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,kDAAkD;IAClD,IAAI,CAAC,MAAe;QAClB,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;IACjF,CAAC;CACF"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CapabilityGate — the single enforcement chokepoint the privileged
|
|
3
|
+
* daemon calls before performing any `window.ugly` operation. It
|
|
4
|
+
* composes the per-origin grants, the per-origin fs scope, the bundled-
|
|
5
|
+
* binary allowlist, and the audit log. Every call either returns the
|
|
6
|
+
* resolved/authorized value or throws `PermissionDenied` — and is
|
|
7
|
+
* recorded either way.
|
|
8
|
+
*
|
|
9
|
+
* Pure logic (no Electron / no fs): the Electron main wires the real
|
|
10
|
+
* `baseDir`, the live `GrantStore`, and an audit sink around it.
|
|
11
|
+
*/
|
|
12
|
+
import { AuditLog } from './audit.js';
|
|
13
|
+
import { GrantStore } from './grants.js';
|
|
14
|
+
export declare class PermissionDenied extends Error {
|
|
15
|
+
readonly origin: string;
|
|
16
|
+
readonly capability: string;
|
|
17
|
+
readonly resource: string;
|
|
18
|
+
constructor(origin: string, capability: string, resource: string, reason: string);
|
|
19
|
+
}
|
|
20
|
+
export interface GateConfig {
|
|
21
|
+
store: GrantStore;
|
|
22
|
+
/** Root for per-origin scoped folders, e.g. `~/.ugly-browser`. */
|
|
23
|
+
baseDir: string;
|
|
24
|
+
/** Binaries that CAN be granted at all (the bundled-tool allowlist). */
|
|
25
|
+
bundledBinaries: readonly string[];
|
|
26
|
+
audit?: AuditLog;
|
|
27
|
+
}
|
|
28
|
+
export declare class CapabilityGate {
|
|
29
|
+
private readonly store;
|
|
30
|
+
private readonly baseDir;
|
|
31
|
+
private readonly bundled;
|
|
32
|
+
readonly audit: AuditLog;
|
|
33
|
+
constructor(cfg: GateConfig);
|
|
34
|
+
/** The on-disk scope root for an origin (its sandboxed app folder). */
|
|
35
|
+
scopeRoot(origin: string): string;
|
|
36
|
+
private deny;
|
|
37
|
+
private allow;
|
|
38
|
+
/**
|
|
39
|
+
* Authorize + resolve an `ugly.fs.*` path. 'scoped' confines to the
|
|
40
|
+
* origin's folder; 'full' (sudo) allows any absolute path; 'none'
|
|
41
|
+
* denies. Returns the absolute path to operate on.
|
|
42
|
+
*/
|
|
43
|
+
resolveFsPath(origin: string, requestedPath: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve + confine a process's working directory. A `process` grant
|
|
46
|
+
* alone is enough to run inside the origin's scope folder — this does
|
|
47
|
+
* NOT require a separate `fs` grant (that gates `ugly.fs.*`). A 'full'
|
|
48
|
+
* (sudo) origin may cwd anywhere; everyone else is confined to scope.
|
|
49
|
+
*/
|
|
50
|
+
resolveProcessCwd(origin: string, cwd?: string): string;
|
|
51
|
+
/** Does this origin hold the github capability (authorizes gh + git)? */
|
|
52
|
+
holdsGithub(origin: string): boolean;
|
|
53
|
+
/** Authorize an `ugly.process` spawn of `binary`. Throws if not allowed. */
|
|
54
|
+
checkProcess(origin: string, binary: string): void;
|
|
55
|
+
checkServe(origin: string, port?: number): void;
|
|
56
|
+
checkNet(origin: string, target?: string): void;
|
|
57
|
+
checkWorkers(origin: string, opts?: {
|
|
58
|
+
boot?: boolean;
|
|
59
|
+
}): void;
|
|
60
|
+
checkSecrets(origin: string, key?: string): void;
|
|
61
|
+
checkBrowse(origin: string, target?: string): void;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../../../src/native/server/permissions/gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,qBAAa,gBAAiB,SAAQ,KAAK;IAEvC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM;gBAFhB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EACzB,MAAM,EAAE,MAAM;CAKjB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,UAAU,CAAC;IAClB,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;gBAEb,GAAG,EAAE,UAAU;IAO3B,uEAAuE;IACvE,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAIjC,OAAO,CAAC,IAAI;IAUZ,OAAO,CAAC,KAAK;IAIb;;;;OAIG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM;IAsB5D;;;;;OAKG;IACH,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM;IAiBvD,yEAAyE;IACzE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIpC,4EAA4E;IAC5E,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAalD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,SAAI,GAAG,IAAI;IAO1C,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,IAAI;IAO3C,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,IAAI;IASjE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,IAAI;IAO5C,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,IAAI;CAM/C"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CapabilityGate — the single enforcement chokepoint the privileged
|
|
3
|
+
* daemon calls before performing any `window.ugly` operation. It
|
|
4
|
+
* composes the per-origin grants, the per-origin fs scope, the bundled-
|
|
5
|
+
* binary allowlist, and the audit log. Every call either returns the
|
|
6
|
+
* resolved/authorized value or throws `PermissionDenied` — and is
|
|
7
|
+
* recorded either way.
|
|
8
|
+
*
|
|
9
|
+
* Pure logic (no Electron / no fs): the Electron main wires the real
|
|
10
|
+
* `baseDir`, the live `GrantStore`, and an audit sink around it.
|
|
11
|
+
*/
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { AuditLog } from './audit.js';
|
|
14
|
+
import { originScopeDir, resolveScopedPath, ScopeViolation } from './scope.js';
|
|
15
|
+
export class PermissionDenied extends Error {
|
|
16
|
+
origin;
|
|
17
|
+
capability;
|
|
18
|
+
resource;
|
|
19
|
+
constructor(origin, capability, resource, reason) {
|
|
20
|
+
super(`Permission denied for ${origin} (${capability} ${resource}): ${reason}`);
|
|
21
|
+
this.origin = origin;
|
|
22
|
+
this.capability = capability;
|
|
23
|
+
this.resource = resource;
|
|
24
|
+
this.name = 'PermissionDenied';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class CapabilityGate {
|
|
28
|
+
store;
|
|
29
|
+
baseDir;
|
|
30
|
+
bundled;
|
|
31
|
+
audit;
|
|
32
|
+
constructor(cfg) {
|
|
33
|
+
this.store = cfg.store;
|
|
34
|
+
this.baseDir = cfg.baseDir;
|
|
35
|
+
this.bundled = new Set(cfg.bundledBinaries);
|
|
36
|
+
this.audit = cfg.audit ?? new AuditLog();
|
|
37
|
+
}
|
|
38
|
+
/** The on-disk scope root for an origin (its sandboxed app folder). */
|
|
39
|
+
scopeRoot(origin) {
|
|
40
|
+
return originScopeDir(this.baseDir, origin);
|
|
41
|
+
}
|
|
42
|
+
deny(origin, capability, resource, reason) {
|
|
43
|
+
this.audit.record({ origin, capability, resource, allowed: false, reason });
|
|
44
|
+
throw new PermissionDenied(origin, capability, resource, reason);
|
|
45
|
+
}
|
|
46
|
+
allow(origin, capability, resource) {
|
|
47
|
+
this.audit.record({ origin, capability, resource, allowed: true });
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Authorize + resolve an `ugly.fs.*` path. 'scoped' confines to the
|
|
51
|
+
* origin's folder; 'full' (sudo) allows any absolute path; 'none'
|
|
52
|
+
* denies. Returns the absolute path to operate on.
|
|
53
|
+
*/
|
|
54
|
+
resolveFsPath(origin, requestedPath) {
|
|
55
|
+
const fs = this.store.query(origin).fs;
|
|
56
|
+
if (fs === 'none') {
|
|
57
|
+
this.deny(origin, 'fs', requestedPath, 'no filesystem permission');
|
|
58
|
+
}
|
|
59
|
+
if (fs === 'full') {
|
|
60
|
+
const abs = path.resolve(requestedPath);
|
|
61
|
+
this.allow(origin, 'fs:full', abs);
|
|
62
|
+
return abs;
|
|
63
|
+
}
|
|
64
|
+
// scoped
|
|
65
|
+
try {
|
|
66
|
+
const abs = resolveScopedPath(this.scopeRoot(origin), requestedPath);
|
|
67
|
+
this.allow(origin, 'fs:scoped', abs);
|
|
68
|
+
return abs;
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
const reason = e instanceof ScopeViolation ? 'path escapes scoped folder' : 'invalid path';
|
|
72
|
+
this.deny(origin, 'fs:scoped', requestedPath, reason);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolve + confine a process's working directory. A `process` grant
|
|
77
|
+
* alone is enough to run inside the origin's scope folder — this does
|
|
78
|
+
* NOT require a separate `fs` grant (that gates `ugly.fs.*`). A 'full'
|
|
79
|
+
* (sudo) origin may cwd anywhere; everyone else is confined to scope.
|
|
80
|
+
*/
|
|
81
|
+
resolveProcessCwd(origin, cwd) {
|
|
82
|
+
const root = this.scopeRoot(origin);
|
|
83
|
+
if (!cwd)
|
|
84
|
+
return root;
|
|
85
|
+
if (this.store.query(origin).fs === 'full') {
|
|
86
|
+
const abs = path.resolve(cwd);
|
|
87
|
+
this.allow(origin, 'process:cwd', abs);
|
|
88
|
+
return abs;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const abs = resolveScopedPath(root, cwd);
|
|
92
|
+
this.allow(origin, 'process:cwd', abs);
|
|
93
|
+
return abs;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
this.deny(origin, 'process:cwd', cwd, 'cwd escapes scoped folder');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Does this origin hold the github capability (authorizes gh + git)? */
|
|
100
|
+
holdsGithub(origin) {
|
|
101
|
+
return this.store.query(origin).github;
|
|
102
|
+
}
|
|
103
|
+
/** Authorize an `ugly.process` spawn of `binary`. Throws if not allowed. */
|
|
104
|
+
checkProcess(origin, binary) {
|
|
105
|
+
if (!this.bundled.has(binary)) {
|
|
106
|
+
this.deny(origin, 'process', binary, 'not a bundled tool');
|
|
107
|
+
}
|
|
108
|
+
const g = this.store.query(origin);
|
|
109
|
+
// The github capability authorizes exactly the GitHub CLIs.
|
|
110
|
+
const githubTool = g.github && (binary === 'gh' || binary === 'git');
|
|
111
|
+
if (!githubTool && !g.process.includes(binary)) {
|
|
112
|
+
this.deny(origin, 'process', binary, 'tool not granted');
|
|
113
|
+
}
|
|
114
|
+
this.allow(origin, 'process', binary);
|
|
115
|
+
}
|
|
116
|
+
checkServe(origin, port = 0) {
|
|
117
|
+
if (!this.store.query(origin).serve) {
|
|
118
|
+
this.deny(origin, 'serve', String(port), 'serve not granted');
|
|
119
|
+
}
|
|
120
|
+
this.allow(origin, 'serve', String(port));
|
|
121
|
+
}
|
|
122
|
+
checkNet(origin, target = '') {
|
|
123
|
+
if (!this.store.query(origin).net) {
|
|
124
|
+
this.deny(origin, 'net', target, 'net not granted');
|
|
125
|
+
}
|
|
126
|
+
this.allow(origin, 'net', target);
|
|
127
|
+
}
|
|
128
|
+
checkWorkers(origin, opts = {}) {
|
|
129
|
+
const g = this.store.query(origin);
|
|
130
|
+
if (!g.workers)
|
|
131
|
+
this.deny(origin, 'workers', '', 'workers not granted');
|
|
132
|
+
if (opts.boot && !g.workersBoot) {
|
|
133
|
+
this.deny(origin, 'workers:boot', '', 'boot-start not granted');
|
|
134
|
+
}
|
|
135
|
+
this.allow(origin, opts.boot ? 'workers:boot' : 'workers', '');
|
|
136
|
+
}
|
|
137
|
+
checkSecrets(origin, key = '') {
|
|
138
|
+
if (!this.store.query(origin).secrets) {
|
|
139
|
+
this.deny(origin, 'secrets', key, 'secrets not granted');
|
|
140
|
+
}
|
|
141
|
+
this.allow(origin, 'secrets', key);
|
|
142
|
+
}
|
|
143
|
+
checkBrowse(origin, target = '') {
|
|
144
|
+
if (!this.store.query(origin).browse) {
|
|
145
|
+
this.deny(origin, 'browse', target, 'browse not granted');
|
|
146
|
+
}
|
|
147
|
+
this.allow(origin, 'browse', target);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../../../src/native/server/permissions/gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-origin grant store + the incremental request decision logic.
|
|
3
|
+
*
|
|
4
|
+
* `evaluate()` is pure: it diffs a request against what the origin holds
|
|
5
|
+
* and reports the delta + whether a prompt / elevated confirm is needed,
|
|
6
|
+
* WITHOUT mutating. The Electron layer shows the prompt for the delta,
|
|
7
|
+
* then calls `grant()` to commit (or auto-commits for trusted origins).
|
|
8
|
+
* This keeps "decide" separate from "persist" and makes both testable.
|
|
9
|
+
*/
|
|
10
|
+
import { type CapabilityRequest, type OriginGrant, type PermissionDecision } from './types.js';
|
|
11
|
+
export declare class GrantStore {
|
|
12
|
+
private readonly grants;
|
|
13
|
+
private readonly trusted;
|
|
14
|
+
constructor(trustedOrigins?: readonly string[]);
|
|
15
|
+
isTrusted(origin: string): boolean;
|
|
16
|
+
/** Capabilities the origin currently holds (EMPTY_GRANT if none). */
|
|
17
|
+
query(origin: string): OriginGrant;
|
|
18
|
+
/**
|
|
19
|
+
* Decide what a request needs — the delta beyond what's held, and
|
|
20
|
+
* whether it must prompt / elevate. Pure: does not mutate.
|
|
21
|
+
*/
|
|
22
|
+
evaluate(origin: string, req: CapabilityRequest): PermissionDecision;
|
|
23
|
+
/**
|
|
24
|
+
* Commit a (delta) grant onto the origin's held set. Idempotent and
|
|
25
|
+
* monotonic — merges, never downgrades. Returns the new held grant.
|
|
26
|
+
*/
|
|
27
|
+
grant(origin: string, req: CapabilityRequest): OriginGrant;
|
|
28
|
+
/** Revoke everything for an origin (Subsystem B exposes a settings UI). */
|
|
29
|
+
revoke(origin: string): void;
|
|
30
|
+
/** Serializable copy of all held grants (for persistence). */
|
|
31
|
+
snapshot(): Record<string, OriginGrant>;
|
|
32
|
+
/** Replace all held grants from a snapshot (load on startup). */
|
|
33
|
+
restore(snapshot: Record<string, OriginGrant>): void;
|
|
34
|
+
}
|
|
35
|
+
export type GrantSnapshot = Record<string, OriginGrant>;
|
|
36
|
+
//# sourceMappingURL=grants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../../../../src/native/server/permissions/grants.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,KAAK,iBAAiB,EAEtB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACxB,MAAM,YAAY,CAAC;AAapB,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;gBAElC,cAAc,GAAE,SAAS,MAAM,EAAO;IAIlD,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIlC,qEAAqE;IACrE,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW;IAIlC;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,iBAAiB,GAAG,kBAAkB;IAyCpE;;;OAGG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,iBAAiB,GAAG,WAAW;IAmB1D,2EAA2E;IAC3E,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI5B,8DAA8D;IAC9D,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC;IAMvC,iEAAiE;IACjE,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,IAAI;CAMrD;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC"}
|